Kotti Documentation

Kotti is a user-friendly, nimble and extensible web content management system, based on Pyramid and SQLAlchemy.

If you are a user of a Kotti system, and either found this page through browsing or searching, or were referred here, you will likely want to go directly to the Kotti User Manual.

The documentation below is for developers of Kotti.

First Steps

Get an overview of what you can do with Kotti, how to install it and how to create your first Kotti project / add on.

Overview

Kotti is most useful when you are developing CMS-like applications that

  • have complex security requirements,
  • use workflows, and/or
  • work with hierarchical data.

Built on top of a number of best-of-breed software components, most notably Pyramid and SQLAlchemy, Kotti introduces only a few concepts of its own, thus hopefully keeping the learning curve flat for the developer.

Features

You can try out the default installation on Kotti’s demo page.

The Kotti CMS is a content management system that’s heavily inspired by Plone. Its main features are:

  • User-friendliness: editors can edit content where it appears; thus the edit interface is contextual and intuitive
  • WYSIWYG editor: includes a rich text editor
  • Responsive design: Kotti builds on Twitter Bootstrap, which looks good both on desktop and mobile
  • Templating: easily extend the CMS with your own look & feel with little programming required (see Static resource management)
  • Add-ons: install a variety of add-ons and customize them as well as many aspects of the built-in CMS by use of an INI configuration file (see Configuration)
  • Security: the advanced user and permissions management is intuitive and scales to fit the requirements of large organizations
  • Internationalized: the user interface is fully translatable, Unicode is used everywhere to store data (see Translations)

For developers

For developers, Kotti delivers a strong foundation for building different types of web applications that either extend or replace the built-in CMS.

Developers can add and modify through a well-defined API:

Kotti has a down-to-earth API. Developers working with Kotti will most of the time make direct use of the Pyramid and SQLAlchemy libraries. Other notable components used but not enforced by Kotti are Colander and Deform for forms, and Chameleon for templating.

Kotti itself is developed on Github. You can check out Kotti’s source code via its GitHub repository. Use this command:

git clone git@github.com:Kotti/Kotti

Continuous testing against different versions of Python and with PostgreSQL, MySQL and SQLite and a complete test coverage make Kotti a stable platform to work with. build status

Support

Installation

Requirements

  • Python 2.6 or 2.7
  • virtualenv
  • build_essential and python-dev (on Debian or Ubuntu)
  • or Xcode (on OSX)

Installation using virtualenv

It is recommended to install Kotti inside a virtualenv:

virtualenv mysite
cd mysite
bin/pip install -r https://raw.github.com/Kotti/Kotti/2.0.1/requirements.txt
bin/pip install Kotti==|version|

This will install Kotti 2.0.1 and all its requirements into your virtualenv.

Kotti uses Paste Deploy for configuration and deployment. An example configuration file is included with Kotti’s source distribution. Download it to your virtualenv directory (mysite):

wget https://github.com/Kotti/Kotti/raw/2.0.1/app.ini

See the list of Kotti tags, perhaps to find the latest released version. You can search the Kotti listing on PyPI also, for the latest Kotti release (Kotti with a capital K is Kotti itself, kotti_this and kotti_that are add-ons in the list on PyPI).

To run Kotti using the app.ini example configuration file:

bin/pserve app.ini

This command runs Waitress, Pyramid’s WSGI server, which Kotti uses as a default server. You will see console output giving the local URL to view the site in a browser.

As you learn more, install other servers, with WSGI enabled, as needed. For instance, for Apache, you may install the optional mod_wsgi module, or for Nginx, you may use choose to use uWSGI. See the Pyramid documentation for a variety of server and server configuration options.

The pserve command above uses SQLite as the default database. On first run, Kotti will create a SQLite database called Kotti.db in your mysite directory.

Kotti includes support for PostgreSQL, MySQL and SQLite (tested regularly), and other SQL databases. The default use of SQLite makes initial development easy. Although SQLite may prove to be adequate for some deployments, Kotti is flexible for installation of your choice of database during development or at deployment.

Tutorial

Let’s learn by example. We’ll create an add-on package that will:

  • change the look and feel of Kotti by registering an additional CSS file
  • add content types and forms

Note

If you have questions going through this tutorial, please post a message to the mailing list or join the #kotti channel on irc.freenode.net to chat with other Kotti users who might be able to help.

The Tutorial is splitted into three parts:

Tutorial Part 1: Creating an add-on with a custom Look and Feel

In this part of the tutorial, we’ll concentrate on how to create the new add-on package, how to install and register it with our site, and how to manage static resources in Kotti.

Kotti add-ons are proper Python packages. A number of them are available on PyPI. They include kotti_media, for adding a set of video and audio content types to a site, kotti_gallery, for adding a photo album content type, kotti_blog, for blog and blog entry content types, etc.

The add-on we will make, kotti_mysite, will be just like those, in that it will be a proper Python package created with the same command line tools used to make kotti_media, kotti_blog, and the others. We will set up kotti_mysite for our Kotti site, in the same way that we might wish later to install, for example, kotti_media.

So, we are working in the mysite directory, a virtualenv. We will create the add-on as mysite/kotti_mysite. kotti_mysite will be a proper Python package, installable into our virtualenv.

Creating the Add-On Package

To create our add-on, we’ll use a starter template from kotti_paster. For this, we’ll need to first install the kotti_paster package into our virtualenv (the one that was created during the Installation).

bin/pip install kotti_paster

With kotti_paster installed, we can now create the skeleton for the add-on package:

bin/paster create -t kotti_addon kotti_mysite

Running this command, it will ask us a number of questions. Hit enter for every question to accept the defaults. When finished, observe that a new directory called kotti_mysite was added to the current working directory, as mysite/kotti_mysite.

Installing Our New Add-On

To install the add-on (or any add-on, as discussed above) into our Kotti site, we’ll need to do two things:

  • install the package into our virtualenv
  • include the package inside our site’s app.ini

Note

Why two steps? Installation of our add-on as a Python package is different from activating the add-on in our site. Consider that you might have multiple add-ons installed in a virtualenv, but you could elect to activate a subset of them, as you experiment or develop add-ons.

To install the package into the virtualenv, we’ll change into the new kotti_mysite directory, and issue a python setup.py develop. This will install the package in development mode:

cd kotti_mysite
../bin/python setup.py develop

Note

python setup.py install is for normal installation of a finished package, but here, for kotti_mysite, we will be developing it for some time, so we use python setup.py develop. Using this mode, a special link file is created in the site-packages directory of your virtualenv. This link points to the add-on directory, so that any changes you make to the software will be reflected immediately without having to do an install again.

Step two is configuring our Kotti site to include our new kotti_mysite package. To do this, open the app.ini file, which you downloaded during Installation. Find the line that says:

kotti.configurators = kotti_tinymce.kotti_configure

And add kotti_mysite.kotti_configure to it:

kotti.configurators =
    kotti_tinymce.kotti_configure
    kotti_mysite.kotti_configure

Now you’re ready to fire up the Kotti site again:

cd ..
bin/pserve app.ini

Visit the site in your browser and notice how the the title now has a shadow.

Adding CSS Files

How was the color for the shadow changed? Take a look into the directory kotti_mysite/kotti_mysite/static/. This is where the CSS file lives.

How is it hooked up with Kotti? Kotti uses fanstatic for managing its static resources. fanstatic has a number of cool features – you may want to check out their homepage to find out more.

Take a look at kotti_mysite/kotti_mysite/fanstatic.py to see how the creation of the necessary fanstatic components is done:

from __future__ import absolute_import

from fanstatic import Group
from fanstatic import Library
from fanstatic import Resource

library = Library("kotti_mysite", "static")
kotti_mysite_css = Resource(library, "style.css")
kotti_mysite_group = Group([kotti_mysite_css])

If you wanted to add a JavaScript file, you would do this very similarly. To add a JavaScript file called script.js, you would add a fanstatic resource for it in kotti_mysite/kotti_mysite/fanstatic.py like so:

kotti_mysite_js = Resource(library, "script.js")

And change the last line to:

kotti_mysite_group = Group([kotti_mysite_css, kotti_mysite_js])
Configuring the Package with kotti.configurators

Remember when we added kotti_mysite.kotti_configure to the kotti.configurators setting in the app.ini configuration file? This is how we told Kotti to call additional code on start-up, so that add-ons have a chance to configure themselves. The function in kotti_mysite that is called on application start-up lives in kotti_mysite/kotti_mysite/__init__.py. Let’s take a look:

def kotti_configure(settings):
   settings['kotti.fanstatic.view_needed'] += ' kotti_mysite.fanstatic.kotti_mysite_group'

Here, settings is a Python dictionary with all configuration variables in the [app:kotti] section of our app.ini, plus the defaults. The values of this dictionary are merely strings. Notice how we add to the string kotti.fanstatic.view_needed.

Note

Note the initial space in ‘ kotti_mysite.static.kotti_mysite_group’. This allows a handy use of += on different lines. After concatenation of the string parts, blanks will delimit them.

This kotti.fanstatic.view_needed setting, in turn, controls which resources are loaded in the public interface (as compared to the edit interface).

As you might have guessed, we could have also completely replaced Kotti’s resources for the public interface by overriding the kotti.fanstatic.view_needed setting instead of adding to it, like this:

def kotti_configure(settings):
    settings['kotti.fanstatic.view_needed'] = ' kotti_mysite.fanstatic.kotti_mysite_group'

This is useful if you’ve built your own custom theme. Alternatively, you can completely override the master template for even more control (e.g. if you don’t want to use Bootstrap).

See also Configuration for a full list of Kotti’s configuration variables, and Static resource management for a more complete discussion of how Kotti handles static resources through fanstatic.

In the next part of the tutorial, we’ll add our first content types, and add forms for them.

Tutorial Part 2: A Content Type

Kotti’s default content types include Document, Image and File. In this part of the tutorial, we’ll add add to these built-in content types by making a Poll content type which will allow visitors to view polls and vote on them.

Adding Models

Let’s create a new file at kotti_mysite/kotti_mysite/resources.py and add the definition of the Poll content type:

import sqlalchemy as sqla

from kotti.resources import Content


class Poll(Content):
    id = sqla.Column(
        sqla.Integer(), sqla.ForeignKey('contents.id'), primary_key=True)

    type_info = Content.type_info.copy(
        name=u'Poll',
        title=u'Poll',
        add_view=u'add_poll',
        addable_to=[u'Document'],
        )

Things to note here:

  • Kotti’s content types use SQLAlchemy for definition of persistence.
  • Poll derives from kotti.resources.Content, which is the common base class for all content types.
  • Poll declares a sqla.Column id, which is required to hook it up with SQLAlchemy’s inheritance.
  • The type_info class attribute does essential configuration. We refer to name and title, two properties already defined as part of Content, our base class. The add_view defines the name of the add view, which we’ll come to in a second. Finally, addable_to defines which content types we can add Poll items to.
  • We do not need to define any additional sqlaColumn() properties, as the title is the only property we need for this content type.

We’ll add another content class to hold the choices for the poll. Add this into the same resources.py file:

class Choice(Content):
    id = sqla.Column(
        sqla.Integer(), sqla.ForeignKey('contents.id'), primary_key=True)
    votes = sqla.Column(sqla.Integer())

    type_info = Content.type_info.copy(
        name=u'Choice',
        title=u'Choice',
        add_view=u'add_choice',
        addable_to=[u'Poll'],
        )

    def __init__(self, votes=0, **kwargs):
        super(Choice, self).__init__(**kwargs)
        self.votes = votes

The Choice class looks very similar to Poll. Notable differences are:

  • It has an additional sqla.Column property called votes. We’ll use this to store how many votes were given for the particular choice. We’ll again use the inherited title column to store the title of our choice.
  • The type_info defines the title, the add_view view, and that choices may only be added into Poll items, with the line addable_to=[u'Poll'].
Adding Forms and a View

Views (including forms) are typically put into a module called views. Let’s create a new file for this module at kotti_mysite/kotti_mysite/views.py and add the following code:

import colander


class PollSchema(colander.MappingSchema):
    title = colander.SchemaNode(
        colander.String(),
        title=u'Question',
        )


class ChoiceSchema(colander.MappingSchema):
    title = colander.SchemaNode(
        colander.String(),
        title=u'Choice',
        )

Colander is the library that we use to define our schemas. Colander allows us to validate schemas against form data.

The two classes define the schemas for our add and edit forms. That is, they specify which fields we want to display in the forms.

Let’s move on to building the actual forms. Add this to views.py:

from kotti.views.form import AddFormView
from kotti.views.form import EditFormView

from kotti_mysite.resources import Choice
from kotti_mysite.resources import Poll


class PollEditForm(EditFormView):
    schema_factory = PollSchema


class PollAddForm(AddFormView):
    schema_factory = PollSchema
    add = Poll
    item_type = u"Poll"


class ChoiceEditForm(EditFormView):
    schema_factory = ChoiceSchema


class ChoiceAddForm(AddFormView):
    schema_factory = ChoiceSchema
    add = Choice
    item_type = u"Choice"

Using the AddFormView and EditFormView base classes from Kotti, these forms are simple to define. We associate the schemas defined above, setting them as the schema_factory for each form, and we specify the content types to be added by each.

Wiring up the Content Types and Forms

It’s time for us to see things in action. For that, some configuration of the types and forms is in order.

Find kotti_mysite/kotti_mysite/__init__.py and add configuration that registers our new code in the Kotti site.

We change the kotti_configure function to look like:

def kotti_configure(settings):
    settings['kotti.fanstatic.view_needed'] += (
        ' kotti_mysite.fanstatic.kotti_mysite_group')
    settings['kotti.available_types'] += (
        ' kotti_mysite.resources.Poll kotti_mysite.resources.Choice')
    settings['pyramid.includes'] += ' kotti_mysite'

Here, we’ve added our two content types to the site’s available_types, a global registry.

Now add a function called includeme to the same file:

def includeme(config):
    from kotti_mysite.resources import Poll
    from kotti_mysite.resources import Choice
    from kotti_mysite.views import PollAddForm
    from kotti_mysite.views import PollEditForm
    from kotti_mysite.views import ChoiceAddForm
    from kotti_mysite.views import ChoiceEditForm

    config.add_view(
        PollAddForm,
        name='add_poll',
        permission='add',
        renderer='kotti:templates/edit/node.pt',
        )
    config.add_view(
        PollEditForm,
        context=Poll,
        name='edit',
        permission='edit',
        renderer='kotti:templates/edit/node.pt',
        )
    config.add_view(
        ChoiceAddForm,
        name='add_choice',
        permission='add',
        renderer='kotti:templates/edit/node.pt',
        )
    config.add_view(
        ChoiceEditForm,
        context=Choice,
        name='edit',
        permission='edit',
        renderer='kotti:templates/edit/node.pt',
        )

Here, we call config.add_view once for each form. The first argument of each call is the form class. The second argument gives the name of the view. The names of each add view, add_poll and add_choice, match the names set in the type_info class attribute of the types (Compare to the classes where Poll() and Choice() are defined). The names of the edit views are simply edit, the names of add views are simply add. We can, of course, add our own view names, but add and edit should be used for adding and editing respectively, as Kotti uses those names for its base functionality.

Adding a Poll and Choices to the site

Let’s try adding a Poll and some choices to the site. Start the site up with the command

bin/pserve app.ini

Login with the username admin and password qwerty and click on the Add menu button. You should see a few choices, namely the base Kotti classes Document, File and Image and the Content Type we added, Poll.

Lets go ahead and click on Poll. For the question, let’s write What is your favourite color?. Now let’s add three choices, Red, Green and Blue in the same way we added the poll.

If we now go to the poll we added, we can see the question, but not our choices, which is definitely not what we wanted. Let us fix this, shall we?

Adding a custom View to the Poll

Since there are plenty tutorials on how to write TAL templates, we will not write a complete one here, but just a basic one, to show off the general idea.

First, we need to write a view that will send the needed data (in our case, the choices we added to our poll). Here is the code, added to views.py.

from kotti_mysite.fanstatic import kotti_mysite_group


def poll_view(context, request):
    kotti_mysite_group.need()
    choices = context.values()
    return {
        'choices': choices
    }

To find out if a Choice was added to the Poll we are currently viewing, we compare it’s parent_id attribute with the id of the Poll - if they are the same, the Choice is a child of the Poll. To get all the appropriate choices, we do a simple database query, filtered as specified above. Finally, we return a dictionary of all choices under the keyword choices.

Next on, we need a template to actually show our data. It could look something like this. Create a folder named templates and put the file poll.pt into it.

<!DOCTYPE html>
<html xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      metal:use-macro="api.macro('kotti:templates/view/master.pt')">

  <article metal:fill-slot="content" class="poll-view content">
    <h1>${context.title}</h1>
    <ul>
        <li tal:repeat="choice choices">
          <a href="${request.resource_url(choice)}">${choice.title}</a>
        </li>
    </ul>
  </article>

</html>

The first 6 lines are needed so our template plays nicely with the master template (so we keep the add/edit bar, base site structure etc.). The next line prints out the context.title (our question) inside the <h1> tag and then prints all choices (with links to the choice) as an unordered list.

Now all that remains is linking the two together. We do this in the __init__.py file, like this.

from kotti_mysite.views import poll_view

config.add_view(
    poll_view,
    context=Poll,
    name='view',
    permission='view',
    renderer='kotti_mysite:templates/poll.pt',
)

With this, we are done with the second tutorial. Restart the server instance, take a look at the new Poll view and play around with the template until you are completely satisfied with how our data is presented. If you will work with templates for a while (or anytime you’re developing basically) I’d recommend you use the pyramid reload_templates and debug_templates options as they save you a lot of time lost on server restarts.

pyramid.reload_templates = true
pyramid.debug_templates = true

In the next tutorial, we will learn how to enable our users to actually vote for one of the Poll options.

Tutorial Part 3: User interaction

In this part of the tutorial, we will change the site we made in the previous one so our users can actually vote on our polls.

Enabling voting on Poll Choices

We will enable users to vote using a new view. When the user goes to that link, his or her vote will be saved and they will be redirected back to the Poll.

First, let’s construct a new view inside views.py.

from pyramid.httpexceptions import HTTPFound


def vote_view(context, request):
    context.votes += 1
    return HTTPFound(location=request.resource_url(context.parent))

The view will be called on the Choice content type, so the context is the Choice itself. We add 1 to the current votes of the Choice, then we do a redirect using HTTPFound. The location is the parent of our context - the Poll in which our Choice resides.

The view needs to be wired to our site. Add this to the __init__.py file, inside the existing inlcudeme function.

def includeme(config):
    from kotti_mysite.views import vote_view

    config.add_view(
        vote_view,
        context=Choice,
        name="vote",
        permission="edit",
        )

With this, we can now vote on a Choice by appending /vote at the end of the Choice URL.

Changing the Poll view so we see the votes

First, we will add some extra content into our poll_view so we are able to show current votes of a Choice.

def poll_view(context, request):
    kotti_mysite_group.need()
    choices = context.values()
    all_votes = sum(choice.votes for choice in choices)
    return {
        'choices': choices,
        'all_votes': all_votes,
        }

Our view will now be able to get the sum of all votes in the poll via the all_votes variable. We will also want to change the link to go to our new vote view. Open poll.pt and change the link into

<a href="${request.resource_url(choice)}/vote">
  ${choice.title}
</a> (${choice.votes}/${all_votes})

This will add the number of votes/all_votes after each choice and enable us to vote by clicking on the Choice. Fire up the server and go test it now.

Adding an info block about voting on the view

As you can see, the voting now works, but it doesn’t look particulary good. Let us at least add a nice information bubble when we vote alright? The easiest way to go about that is to use request.session.flash, which allows us to flash different messages (success, error, info etc.). Change the vote_view to include the the flash message before redirecting.

def vote_view(context, request):
  context.votes += 1
  request.session.flash(u'You have just voted for the choice "{0}"'.format(
      context.title), 'info')
  return HTTPFound(location=request.resource_url(context.parent))

As before, I encourage you to play around a bit more, as you learn much by trying our new things. A few ideas on what you could work on are:

  • Change the Choice content type so it has an extra description field that is not required (if you change database content, you will need to delete the database or do a migration). Then make a new Choice view that will list the extra information.
  • Make sure only authenticated users can vote, anonymous users should see the results but when trying to vote, it should move them to the login page. Also make sure that each user can vote only once, and list all users who voted for the Choice on the Choice’s view.

Narrative Documentation

The narrative documentation contains various topics that explain how to use Kotti.

Basic Topics

Developer manual

Read the Configuration section first to understand which hooks both integrators and developers can use to customize and extend Kotti.

Screencast tutorial

Here’s a screencast that guides you through the process of creating a simple Kotti add-on for visitor comments:

Content types

Defining your own content types is easy. The implementation of the Document content type serves as an example here:

from kotti.resources import Content

class Document(Content):
    id = Column(Integer(), ForeignKey('contents.id'), primary_key=True)
    body = Column(UnicodeText())
    mime_type = Column(String(30))

    type_info = Content.type_info.copy(
        name=u'Document',
        title=_(u'Document'),
        add_view=u'add_document',
        addable_to=[u'Document'],
        )

    def __init__(self, body=u"", mime_type='text/html', **kwargs):
        super(Document, self).__init__(**kwargs)
        self.body = body
        self.mime_type = mime_type

You can configure the list of active content types in Kotti by modifying the kotti.available_types setting.

Note that when adding a relationship from your content type to another Node, you will need to add a primaryjoin parameter to your relationship. An example:

from sqlalchemy.orm import relationship

class DocumentWithRelation(Document):
  id = Column(Integer, ForeignKey('documents.id'), primary_key=True)
  related_item_id = Column(Integer, ForeignKey('nodes.id'))
  related_item = relationship(
      'Node', primaryjoin='Node.id==DocumentWithRelation.related_item_id')
Add views, subscribers and more

pyramid.includes allows you to hook includeme functions that you can use to add views, subscribers, and more aspects of Kotti. An includeme function takes the Pyramid Configurator API object as its sole argument.

Here’s an example that’ll override the default view for Files:

def my_file_view(request):
    return {...}

def includeme(config):
    config.add_view(
        my_file_view,
        name='view',
        permission='view',
        context=File,
        )

To find out more about views and view registrations, please refer to the Pyramid documentation.

By adding the dotted name string of your includeme function to the pyramid.includes setting, you ask Kotti to call it on application start-up. An example:

pyramid.includes = mypackage.views.includeme
Working with content objects

Every content node in the database (be it a document, a file…) is also a container for other nodes. You can access, add and delete child nodes of a node through a dict-like interface. A node’s parent may be accessed through the node.__parent__ property.

kotti.resources.get_root gives us the root node:

>>> from kotti.resources import get_root
>>> root = get_root()
>>> root.__parent__ is None
True
>>> root.title = u'A new title'

Let us add three documents to our root:

>>> from kotti.resources import Document
>>> root['first'] = Document(title=u'First page')
>>> root['second'] = Document(title=u'Second page')
>>> root['third'] = Document(title=u'Third page')

Note how the keys in the dict correspond to the name of child nodes:

>>> first = root['first']
>>> first.name
u'first'
>>> first.__parent__ == root
True
>>> third = root['third']

We can make a copy of a node by using the node.copy() method. We can delete child nodes from the database using the del operator:

>>> first['copy-of-second'] = root['second'].copy()
>>> del root['second']

The lists of keys and values are ordered:

>>> root.keys()
[u'first', u'third']
>>> first.keys()
[u'copy-of-second']
>>> root.values()
[<Document ... at /first>, <Document ... at /third>]

There’s the node.children attribute should you ever need to change the order of the child nodes. node.children is a SQLAlchmey ordered_list which keeps track of the order of child nodes for us:

>>> root.children
[<Document ... at /first>, <Document ... at /third>]
>>> root.children[:] = [root.values()[-1], root.values()[0]]
>>> root.values()
[<Document ... at /third>, <Document ... at /first>]

Note

Removing an element from the nodes.children list will not delete the child node from the database. Use del node[child_name] as above for that.

You can move a node by setting its __parent__:

>>> third.__parent__
<Document ... at />
>>> third.__parent__ = first
>>> root.keys()
[u'first']
>>> first.keys()
[u'copy-of-second', u'third']

Also see:

kotti.configurators

Requiring users of your package to set all the configuration settings by hand in the Paste Deploy INI file is not ideal. That’s why Kotti includes a configuration variable through which extending packages can set all other INI settings through Python. Here’s an example of a function that programmatically modified kotti.base_includes and kotti.principals_factory which would otherwise be configured by hand in the INI file:

# in mypackage/__init__.py
def kotti_configure(config):
    config['kotti.base_includes'] += ' mypackage.views'
    config['kotti.principals_factory'] = 'mypackage.security.principals'

And this is how your users would hook it up in their INI file:

kotti.configurators = mypackage.kotti_configure
Security

Kotti uses Pyramid’s security API, most notably its support inherited access control lists support. On top of that, Kotti defines roles and groups support: Users may be collected in groups, and groups may be given roles, which in turn define permissions.

The site root’s ACL defines the default mapping of roles to their permissions:

root.__acl__ == [
    ['Allow', 'system.Everyone', ['view']],
    ['Allow', 'role:viewer', ['view']],
    ['Allow', 'role:editor', ['view', 'add', 'edit']],
    ['Allow', 'role:owner', ['view', 'add', 'edit', 'manage']],
    ]

Every Node object has an __acl__ attribute, allowing the definition of localized row-level security.

The kotti.security.set_groups() function allows assigning roles and groups to users in a given context. kotti.security.list_groups() allows one to list the groups of a given user. You may also set the list of groups globally on principal objects, which are of type kotti.security.Principal.

Kotti delegates adding, deleting and search of user objects to an interface it calls kotti.security.AbstractPrincipals. You can configure Kotti to use a different Principals implementation by pointing the kotti.principals_factory configuration setting to a different factory. The default setting here is:

kotti.principals_factory = kotti.security.principals_factory

There are views that you might want to override when you override the principal factory. That is, if you use different columns in the database, then you will probably want to make changes to the deform schema as well.

These views are kotti.views.users.UsersManage, kotti.views.users.UserManage and kotti.views.users.Preferences. Notice that you should override them using the standard way, that is, by overriding setup_users, setup_user or prefs views. Then you can override any sub-view used inside them as well as include any logic for your usecase when it is called, if needed.

Security

Kotti security is based on the concepts of users, groups, roles and permissions.

_images/user-group-role-permission.svg
User
A user is an entity that can authenticate himself.
Group
A group is a collection of users or other groups.
Permission

A permission describes what is allowed on an object.

Permissions are never directly assigned to users or groups but always aggregated in roles.

Role

A Role is a collection of permissions.

Users or groups can have global or local roles.

Global Roles
Global roles are assigned to a user or group via Kotti’s user management screens. They apply to every object in a site. You should use them very rarely, maybe only assign the “Adminsitrator” role to the “Administrator” group. This assignment is present by default in a fresh Kotti site.
Local Roles
Local roles are assigned to a user or group via the “Sharing” screen of a content object. They apply only to this object and its children.

Configuration

INI File

Kotti is configured using an INI configuration file. The Installation section explains how to get hold of a sample configuration file. The [app:kotti] section in it might look like this:

[app:kotti]
use = egg:Kotti
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.debug_templates = true
pyramid.default_locale_name = en
pyramid.includes = pyramid_debugtoolbar
                   pyramid_tm
mail.default_sender = yourname@yourhost
sqlalchemy.url = sqlite:///%(here)s/Kotti.db
kotti.site_title = Kotti
kotti.secret = changethis1

Various aspects of your site can be changed right here.

Overview of settings

This table provides an overview of available settings. All these settings must go into the [app:kotti] section of your Paste Deploy configuration file.

Setting Description
kotti.site_title The title of your site
kotti.secret Secret token used for the initial admin password
kotti.secret2 Secret token used for email password reset token
sqlalchemy.url SQLAlchemy database URL
mail.default_sender Sender address for outgoing email
mail.host Email host to send from
pyramid.includes List of Python configuration hooks
kotti.available_types List of active content types
kotti.base_includes List of base Python configuration hooks
kotti.zcml_includes List of packages to include the ZCML from
kotti.configurators List of advanced functions for config
kotti.root_factory Override Kotti’s default Pyramid root factory
kotti.populators List of functions to fill initial database
kotti.search_content Override Kotti’s default search function
kotti.asset_overrides Override Kotti’s templates
kotti.templates.api Override api object available in templates
kotti.fanstatic.view_needed List of static resources used for public interface
kotti.fanstatic.edit_needed List of static resources used for edit interface
kotti.authn_policy_factory Component used for authentication
kotti.authz_policy_factory Component used for authorization
kotti.session_factory Component used for sessions
kotti.caching_policy_chooser Component for choosing the cache header policy
kotti.url_normalizer Component used for url normalization
kotti.date_format Date format to use, default: medium
kotti.datetime_format Datetime format to use, default: medium
kotti.time_format Time format to use, default: medium
kotti.max_file_size Max size for file uploads, default: `10 (MB)
pyramid.default_locale_name Set the user interface language, default en

Only the settings in bold letters required. The rest has defaults.

Do take a look at the required settings (in bold) and adjust them in your site’s configuration. A few of the settings are less important, and sometimes only used by developers, not integrators.

kotti.secret and kotti.secret2

The value of kotti.secret will define the initial password of the admin user. Thus, if you define kotti.secret = mysecret, the admin password will be mysecret. Log in and change the password at any time through the web interface.

The kotti.secret token is also used for signing browser session cookies. The kotti.secret2 token is used for signing the password reset token.

Here’s an example:

kotti.secret = myadminspassword
kotti.secret2 = $2a$12$VVpW/i1MA2wUUIUHwY6v8O

Note

Do not use these values in your site

Override templates (kotti.asset_overrides)

In your settings file, set kotti.asset_overrides to a list of asset specifications. This allows you to set up a directory in your package that will mirror Kotti’s own and that allows you to override Kotti’s templates on a case by case basis.

As an example, image that we wanted to override Kotti’s master layout template. Inside the Kotti source, the layout template is located at kotti/templates/view/master.pt. To override this, we would add a directory to our own package called kotti-overrides and therein put our own version of the template so that the full path to our own custom template is mypackage/kotti-overrides/templates/view/master.pt.

We can then register our kotti-overrides directory by use of the kotti.asset_overrides setting, like so:

kotti.asset_overrides = mypackage:kotti-overrides/
Use add-ons

Add-ons will usually include in their installation instructions which settings one should modify to activate them. Configuration settings that are used to activate add-ons are:

  • pyramid.includes
  • kotti.available_types
  • kotti.base_includes
  • kotti.configurators
pyramid.includes

pyramid.includes defines a list of hooks that will be called when your Kotti app starts up. This gives the opportunity to third party packages to add registrations to the Pyramid Configurator API in order to configure views and more.

Here’s an example. Let’s install the kotti_twitter extension and add a Twitter profile widget to the right column of all pages. First we install the package from PyPI:

bin/pip install kotti_twitter

Then we activate the add-on in our site by editing the pyramid.includes setting in the [app:kotti] section of our INI file. (If a line with pyramid.includes does not exist, add it.)

pyramid.includes = kotti_twitter.include_profile_widget

kotti_twitter also asks us to configure the Twitter widget itself, so we add some more lines right where we were:

kotti_twitter.profile_widget.user = dnouri
kotti_twitter.profile_widget.loop = true

The order in which the includes are listed matters. For example, when you add two slots on the right hand side, the order in which you list them in pyramid.includes will control the order in which they will appear. As an example, here’s a configuration with which the search widget will be displayed above the profile widget:

pyramid.includes =
    kotti_twitter.include_search_widget
    kotti_twitter.include_profile_widget

Read more about including packages using ‘pyramid.includes’ in the Pyramid documentation.

kotti.available_types

The kotti.available_types setting defines the list of content types available. The default configuration here is:

kotti.available_types = kotti.resources.Document kotti.resources.File

An example that removes File and adds two content types:

kotti.available_types =
    kotti.resources.Document
    kotti_calendar.resources.Calendar
    kotti_calendar.resources.Event
kotti.populators

The default configuration here is:

kotti.populators = kotti.populate.populate

Populators are functions with no arguments that get called on system startup. They may then make automatic changes to the database (before calling transaction.commit()).

kotti.search_content

Kotti provides a simple search over the content types based on kotti.resources.Content. The default configuration here is:

kotti.search_content = kotti.views.util.default_search_content

You can provide an own search function in an add-on and register this in your INI file. The return value of the search function is a list of dictionaries, each representing a search result:

[{'title': 'Title of search result 1',
  'description': 'Description of search result 1',
  'path': '/path/to/search-result-1'},
 {'title': 'Title of search result 2',
  'description': 'Description of search result 2',
  'path': '/path/to/search-result-2'},
 ...
 ]

An add-on that defines an alternative search function is kotti_solr, which provides an integration with the Solr search engine.

Configure the user interface language

By default, Kotti will display its user interface in English. The default configuration is:

pyramid.default_locale_name = en

You can configure Kotti to serve a German user interface by saying:

pyramid.default_locale_name = de_DE

The list of available languages is here.

Configure authentication and authorization

You can override the authentication and authorization policy that Kotti uses. By default, Kotti uses these factories:

kotti.authn_policy_factory = kotti.authtkt_factory
kotti.authz_policy_factory = kotti.acl_factory

These settings correspond to pyramid.authentication.AuthTktAuthenticationPolicy and pyramid.authorization.ACLAuthorizationPolicy being used.

Sessions

The kotti.session_factory configuration variable allows the overriding of the default session factory. By default, Kotti uses pyramid_beaker for sessions.

Caching

You can override Kotti’s default set of cache headers by changing the kotti.views.cache.caching_policies dictionary, which maps policies to headers. E.g. the Cache Resource entry there caches all static resources for 32 days. You can also choose which responses match to which caching policy by overriding Kotti’s default cache policy chooser through the use of the kotti.caching_policy_chooser configuration variable. The default is:

kotti.caching_policy_chooser = kotti.views.cache.default_caching_policy_chooser
Url normalization

Kotti normalizes document titles to URLs by replacing language specific characters like umlauts or accented characters with its ascii equivalents. You can change this default behavour by setting kotti.url_normalizer.map_non_ascii_characters configuration variable to False. If you do, Kotti will leave national characters in URLs.

You may also replace default component used for url normalization by setting kotti.url_normalizer configuation variable.

The default configuration here is:

kotti.url_normalzier = kotti.url_normalizer.url_normalizer
kotti.url_normalizer.map_non_ascii_characters = True
Local navigation

Kotti provides a build in navigation widget, which is disabled by default. To enable the navigation widget add the following to the pyramid.includes setting:

pyramid.includes = kotti.views.slots.includeme_local_navigation

The add-on kotti_navigation provides also a navigation widget with more features. With this add-on included your configuration looks like:

pyramid.includes = kotti_navigation.include_navigation_widget

Check the documentation of kotti_navigation for more options.

Automated tests

Kotti uses pytest, zope.testbrowser and WebTest for automated testing.

Before you can run the tests, you must install Kotti’s ‘testing’ extras. Inside your Kotti checkout directory, do:

bin/python setup.py dev

To then run Kotti’s test suite, do:

bin/py.test
Using Kotti’s test fixtures/funcargs in third party add-ons’ tests

To be able to use all of Kotti’s fixtures and funcargs in your own package’s tests, you only need to “include” them with a line like this in your conftest.py file:

pytest_plugins = "kotti"
Continuous Integration

Kotti itself is tested against Python versions 2.6 and 2.7 as well as SQLite, mySQL and PostgreSQL (in every possible combination of those) on every commit (and pull request) via the excellent GitHub / Travis CI hook.

If you want your add-on packages’ to be tested the same way with additional testing against multiple versions of Kotti (including the current master), you can add a .travis.yml file to your repo that looks similar to this: https://raw.github.com/Kotti/kotti_media/master/.travis.yml.

The packages under http://kottipackages.xo7.de/ include all Kotti versions released on PyPI (synced every night at 00:15 CET) and a package built from the current master on GitHub (created every 15 minutes).

Translations

You can find the list of Kotti’s translations here. Kotti uses GNU gettext and .po files for internationalization.

You can set the pyramid.default_locale_name in your configuration file to choose which language Kotti should serve the user interface (see Configure the user interface language).

Extraction of new messages into the .pot file, updating the existing .po files and compiling them to .mo files is all done with subsequent runs of the included i18n.sh script:

./i18n.sh

To add a new translations run:

./i18n.sh <2 letter code of the new language>

Deployment

Kotti deployment is not different from deploying any other WSGI app. You have a bunch of options on multiple layers: OS, RDBMS, Webserver, etc.

This document assumes the following Stack:

OS
Ubuntu 12.04
Webserver
Nginx
RDBMS
PostgreSQL
Kotti
latest version available on PyPI
installed in its own virtualenv
deployed in an uWSGI application container
Manual installation

Install OS packages:

apt-get install build-essential libpq-dev python python-dev python-virtualenv

Install PostgreSQL:

apt-get install postgresql-9.1

Create a DB user:

sudo -u postgres createuser -P

Enter name of role to add: kotti
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n

Create a DB:

sudo -u postgres createdb -O kotti kotti

Install Nginx:

apt-get install nginx-full

Create a config file in /etc/nginx/sites-available/<your_domain>.conf:

server {
    listen 80;
    server_name <your_domain>;
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/home/kotti/<your_domain>.sock;
    }
}

Create a user for your Kotti application:

useradd -m kotti

Create a virtualenv in the new user’s home directory:

sudo -u kotti virtualenv --no-site-packages /home/kotti

Install Kotti and its dependencies in the virtualenv:

sudo -u kotti /home/kotti/bin/pip install -r https://raw.github.com/Kotti/Kotti/0.8a1/requirements.txt
sudo -u kotti /home/kotti/bin/pip install Kotti==0.8a1

Create an ini file in /home/kotti/kotti.ini:

[app:main]
use = egg:kotti
pyramid.includes = pyramid_tm
sqlalchemy.url = postgresql://kotti:<db_password>@127.0.0.1:5432/kotti
kotti.configurators = kotti_tinymce.kotti_configure
kotti.site_title = Kotti deployed with fabric
kotti.secret = qwerty
filter-with = fanstatic

[filter:fanstatic]
use = egg:fanstatic#fanstatic

[alembic]
script_location = kotti:alembic

[uwsgi]
socket = /home/kotti/<your_domain>.sock
master = true
chmod-socket = 666
processes = 1

Install Supervisor:

apt-get install supervisor

Create a supervisor config for Kotti / uWSGI in /etc/supervisor/conf.d/kotti.conf:

[program:kotti]
autorestart=true
command=uwsgi_python --ini-paste /home/kotti/kotti.ini
directory=/home/kotti
redirect_stderr=true

Reload the supervisor config:

supervisorctl reload

That’s all. Your Kotti deployment should now happily serve pages.

Fabfile

WARNING: this is only an example. Do not run this unmodified against a host that is intended to do anything else or things WILL break!

For your convenience there is a fabric file that automates all of the above. If you don’t know what fabric is and how it works read their documentation first.

On your local machine make a separate virtualenv first and install the fabric and fabtools packages into that virtualenv:

mkvirtualenv kotti_deployment && cdvirtualenv
pip install fabric fabtools

Get the fabfile:

wget https://gist.github.com/gists/4079191/download

Read and modify the file to fit your needs. Then run it against your server:

fab install_all

You’re done. Everything is installed and configured to serve Kotti under http://kotti.yourdomain.com/

Advanced Topics

Using Kotti as a library

Instead of taking control of your application, and delegating to your extension, you may use Kotti in applications where you define the main entry point yourself.

You’ll anyway still need to call kotti.base_configure from your code to set up essential parts of Kotti:

default_settings = {
    'pyramid.includes': 'myapp myapp.views',
    'kotti.authn_policy_factory': 'myapp.authn_policy_factory',
    'kotti.base_includes': (
        'kotti kotti.views kotti.views.login kotti.views.users'),
    'kotti.use_tables': 'orders principals',
    'kotti.populators': 'myapp.resources.populate',
    'kotti.principals_factory': 'myapp.security.Principals',
    'kotti.root_factory': 'myapp.resources.Root',
    'kotti.site_title': 'Myapp',
    }

def main(global_config, **settings):
    settings2 = default_settings.copy()
    settings2.update(settings)
    config = kotti.base_configure(global_config, **settings2)
    engine = sqlalchemy.engine_from_config(config.registry.settings, 'sqlalchemy.')
    kotti.resources.initialize_sql(engine)
    return config.make_wsgi_app()

The above example configures Kotti so that its user database and security subsystem are set up. Only a handful of tables (kotti.use_tables) and a handful of Kotti’s views (kotti.base_includes) are activated. Furthermore, our application is configured to use a custom root factory (root node) and a custom populator.

In your PasteDeploy configuration you’d then wire up your app directly, maybe like this:

[app:myapp]
use = egg:myapp
pyramid.includes = pyramid_tm
mail.default_sender = yourname@yourhost
sqlalchemy.url = sqlite:///%(here)s/myapp.db
kotti.secret = secret

[filter:fanstatic]
use = egg:fanstatic#fanstatic

[pipeline:main]
pipeline =
    fanstatic
    myapp

Close your site for anonymous users

This recipe describes how to configure Kotti to require users to log in before they can view any of your site’s pages.

To achieve this, we’ll have to set our site’s ACL. A custom populator will help us do that (see kotti.populators).

Remember that the default site ACL gives view privileges to every user, including anonymous (see Security). We’ll thus have to restrict the view permission to the viewer role:

from kotti.resources import get_root

SITE_ACL = [
 (u'Allow', u'role:viewer', [u'view']),
 (u'Allow', u'role:editor', [u'view', u'add', u'edit']),
]

def populate():
    site = get_root()
    site.__acl__ = SITE_ACL

Default views in Kotti

In Kotti every Content node has a default_view attribute. This allows to have different views for any instance of a content type without having to append the view name to the URL.

You can also provide additional views for the default content types in your third party add on. To make them show up in the default view selector in the UI you have to append a (view_name, view_title) tuple to the type_info attribute of the respective content class via its class method add_selectable_default_view(name, title).

E.g. the kotti_media add on provides a media_folder_view for the Document content type that lists all ‘media type’ children of a Document with their title and a media player.

Registration is done like this:

from kotti.resources import Document
from kotti_media import _

def includeme(config):

    Document.type_info.add_selectable_default_view("media_folder_view",
                                                   _("Media Folder"))

Events

Kotti has a builtin event system that is based on the Publish-subscribe pattern.

The basic concept is that whenever a specific event occurs, all handler functions that have subscribed to that event will be executed.

There are two different types of events:

  • Object events…

    …relate to a specific object. In most cases this object will be a node from the content tree (i.e. the same as context in view callables).

    Events of type ObjectEvent have object and request attributes. event.request may be None when no request is available.

  • Generic events…

    …don’t have that kind of context.

    Kotti supports such events but doesn’t use them anywhere.

The event types provided by Kotti (see API docs for kotti.events) may be extended with your own event types. Subclass ObjectEvent (for object events) or object (for generic events) and follow the subscription instructions below, as you would for Kotti-provided events.

Subscribing to Events

To add a handler for a specific event type, you must implement a function which takes a single argument event and associate that to the appropriate event type by decorating it with the subscribe decorator.

That decorator takes up to two arguments that restrict the handler execution to specific events only. When called without arguments the handler is subscribed to all events:

from kotti.events import subscribe

@subscribe()
def all_events_handler(event):
    print event

To subscribe to a specific event type, supply the desired type as the first argument to subscribe:

from kotti.events import ObjectInsert
from kotti.events import subscribe

@subscribe(ObjectInsert)
def document_insert_handler(event):
    print event.object, event.request

You can further narrow the subscription by adding a second argument that limits the subscription to specific object types. For example, to subscribe to ObjectDelete events of Document types, write:

from kotti.events import ObjectDelete
from kotti.events import subscribe
from kotti.resources import Document

@subscribe(ObjectDelete, Document)
def document_delete_handler(event):
    print event.object, event.request
Triggering Event Handler Execution

Notifying listeners of an event is as simple as calling notify():

from kotti.events import notify
notify(MyFunnyEvent())

Listeners are generally called in the order in which they are registered.

Use a different template for the front page (or any other page)

This recipe describes a way to override the template used for a specific object in your database. Imagine you want your front page to stand out from the rest of your site and use a unique layout.

We can set the default view for any content object by settings its default_view attribute, which is usually None. Inside our own populator (see kotti.populators), we write this:

from kotti.resources import get_root

def populate():
    site = get_root()
    site.default_view = 'front-page'

What’s left is to register the front-page view:

def includeme(config):
    config.add_view(
        name='front-page',
        renderer='myapp:templates/front-page.pt',
    )

Note

If you want to override instead the template of all pages, not only that of a particluar page, you should look at the kotti.override_assets setting (Override templates (kotti.asset_overrides)).

Image URLs

Kotti provides on-the-fly image scaling by utilizing plone.scale.

Images can be referenced by this URL schema: /path/to/image_content_object/image[/<image_scale>]/download] where <image_scale> is a predefined image scale (see below).

If the last URL path segment is download, the image will be served with Content-disposition: attachment otherwise it will be served with Content-disposition: inline.

Predefined image scale sizes

You may define image scale sizes in your .ini file by setting values for kotti.image_scales.<scale_name> to values of the form <max_width>x<max_height> (e.g. kotti.image_scales.thumb = 160x120 with the resulting scale name thumb).

span1 (60x120) to span12 (1160x2320) are always defined (with values corresponding to the Twitter Bootstrap default grid sizes), but their values can be overwritten by setting kotti.image_scales.span<N> to different values in your .ini file.

Static resource management

In the default settings Kotti uses Fanstatic to manage its static resources (i.e. CSS, JS, etc.). This is accomplished by a WSGI pipeline:

[app:kotti]
use = egg:kotti

[filter:fanstatic]
use = egg:fanstatic#fanstatic

[pipeline:main]
pipeline =
    fanstatic
    kotti

[server:main]
use = egg:waitress#main
host = 127.0.0.1
port = 5000
Defining resources in third party addons

Defining your own resources and have them rendered in the pages produced by Kotti is also easy. You just need to define resource objects (as described in the corresponding Fanstatic documentation) and add them to either edit_needed or view_needed in kotti.fanstatic:

from fanstatic import Library
from fanstatic import Resource
from kotti.fanstatic import edit_needed
from kotti.fanstatic import view_needed

my_library = Library('my_package', 'resources')
my_resource = Resource(my_library, "my.js")

def includeme(config):
    # add to edit_needed if the resource is needed in edit views
    edit_needed.add(my_resource)
    # add to view_needed if the resource is needed in edit views
    view_needed.add(my_resource)

Don’t forget to add an entry_point to your package’s setup.py:

entry_points={
    'fanstatic.libraries': [
        'foo = my_package:my_library',
        ],
    },

Fanstatic has many more useful options, such as being able to define additional minified resources for deployment. Please consult Fanstatic’s documentation for a complete list of options.

Overriding Kotti’s default definitions

You can override the resources to be included in the configuration file.

The defaults are

[app:kotti]

kotti.fanstatic.edit_needed = kotti.fanstatic.edit_needed
kotti.fanstatic.view_needed = kotti.fanstatic.view_needed

which ist actually a shortcut for

[app:kotti]

kotti.fanstatic.edit_needed =
    kotti.fanstatic.edit_needed_js
    kotti.fanstatic.edit_needed_css

kotti.fanstatic.view_needed =
    kotti.fanstatic.view_needed_js
    kotti.fanstatic.view_needed_css

You may add as many kotti.fanstatic.NeededGroup, fanstatic.Group or fanstatic.Resource (or actually anything that provides a .need() method) objects in dotted notation as you want.

Say you want to completely abandon Kotti’s CSS resources (and use your own for both view and edit views) but use Kotti’s JS resources plus an additional JS resource defined within your app (only in edit views). Your configuration file might look like this:

[app:kotti]

kotti.fanstatic.edit_needed =
    kotti.fanstatic.edit_needed_js
    myapp.fanstatic.js_resource
    myapp.fanstatic.css_resource

kotti.fanstatic.view_needed =
    kotti.fanstatic.view_needed_js
    myapp.fanstatic.css_resource
Using Kotti without Fanstatic

To handle resources yourself, you can easily and completely turn off fanstatic:

[app:main]
use = egg:kotti

[server:main]
use = egg:waitress#main
host = 127.0.0.1
port = 5000

Understanding Kotti’s startup phase

  1. When a Kotti application is started the kotti.main() function is called by the WSGI server and is passed a settings dictionary that contains all key / value pairs from the [app:kotti] section of the *.ini file.
  2. The settings dictionary is passed to kotti.base_configure(). This is where the main work happens:
    1. Every key in kotti.conf_defaults that is not in the settings dictionary (i.e. that is not in the .ini file) is copied to the settings dictionary, together with the default value for that key.
    2. Add-on initializations: all functions that are listed in the kotti.configurators parameter are resolved and called.
    3. pyramid.includes are removed from the settings dictionary for later processing, i.e. after kotti.base_includes.
    4. A class:pyramid.config.Configurator is instanciated with the remaining settings.
    5. The kotti.base_includes (containing various Kotti subsystems, such as kotti.events, kotti.views, etc.) are passed to config.include.
    6. The pyramid.includes that were removed from the settings dictionary in step 2.3 are processed.
    7. The kotti.zcml_includes are processed.
  3. The SQLAlchemy engine is created with the connection URL that is defined in the sqlalchemy.url parameter in the .ini file.
  4. The fully configured WSGI application is returned to the WSGI server and is ready to process requests.

API

API Documentation

kotti.events

kotti.fanstatic

kotti.interfaces

kotti.message

kotti.migrate

kotti.populate

kotti.resources

kotti.security

kotti.sqla

kotti.testing

kotti.util

kotti.views

kotti.views.cache
kotti.views.edit
kotti.views.edit.actions
kotti.views.edit.content
kotti.views.edit.default_views
kotti.views.file
kotti.views.form
kotti.views.image
kotti.views.login
kotti.views.site_setup
kotti.views.slots
kotti.views.users
kotti.views.util
kotti.views.view

kotti.workflow

Getting Help / Contributing

Getting Help

Contributing

The Kotti project can use your help in developing the software, requesting features, reporting bugs, writing developer and end-user documentation – the usual assortment for an open source project.

Please devote some of your time to the project.

Contributing to the Code Base

To contribute to Kotti itself, and to test and run against the master branch (the current development code base), first create an account on GitHub if you don’t have one. Fork Kotti to your github account, and follow the usual steps to get a local clone, with origin as your fork, and with upstream as the Kotti/Kotti repo. Then, you will be able to make branches for contributing, etc. Please read the docs on GitHub if you are new to development, but the steps, after you have your own fork, would be something like this:

git clone https://github.com/your_github/Kotti.git

cd Kotti

git remote add upstream git://github.com/Kotti/Kotti.git

Now you should be set up to make branches for this and that, doing a pull request from a branch, and the usual git procedures. You may wish to read the GitHub fork-a-repo help.

To run and develop within your clone, do these steps:

virtualenv . --no-site-packages

bin/python setup.py develop

This will create a new virtualenv “in place” and do the python develop steps to use the Kotti code in the repo.

Run bin/pip install kotti_someaddon, and add a kotti_someaddon entry to app.ini, as you would do normally, to use add-ons.

You may wish to learn about the virtualenvwrapper system if you have several add-ons you develop or contribute to. For example, you could have a development area devoted to Kotti work, ~/kotti, and in there you could have clones of repos for various add-ons. And for each, or in some combination, you would use virtualenvwrapper to create virtualenvs for working with individual add-ons or Kotti-based projects. virtualenvwrapper will set these virtualenvs up, by default, in a directory within your home directory. With this setup, you can do workon kotti_this and workon kotti_that to switch between different virtualenvs. This is handy for maintaining different sets of dependencies and customizations, and for staying organized.

Contributing to Developer Docs

Kotti uses the Sphinx tool, using reStructuredText to write documents, stored in docs/ in a directory structure with .rst files. Use the normal git procedures for first making a branch, e.g., navigation_docs, then after making changes, commit, push to this branch on your fork, and do a pull request from there, just as you would for contributing to the code base.

In your Kotti clone you can install the requirements for building and viewing the documents locally:

python setup.py docs

cd docs/

make html

Then you can check the .html files in the _build/ directory locally, before you do an actual pull request.

The rendered docs are built and hosted on readthedocs.org.

Contributing to User Docs

The Kotti User Manual also uses Sphinx and reStructuredText, but there is a bit more to the procedure, because several additional tools are used. Selenium is used for making screen captures, and thereby helps to actually test Kotti in the process. blockdiag is used to make flow charts and diagrams interjected into the docs.

Please follow the readme instructions in the Kotti User Manual repo to get set up for contributing to the user manual. Of course, you can do pull requests that change only the text, but please get set up for working with graphics also, because this is a way to do the important task of keeping Kotti user docs up-to-date, guaranteed to have graphics in sync with the latest Kotti version.

The rendered docs are built and hosted on readthedocs.org.

Future and Past

Change History

0.10 - Unreleased

  • Add a new scaffold based on Pyramid’s pcreate. To run the tests for the scaffold, you must invoke py.test with the --runslow option. This is enabled by default on Travis.
  • kotti._resolve_dotted now return a resolved copy of the settings (instead of in place resolving as before).
  • Factor out DBMS specific patches and make them available to the test fixtures.
  • Add new fixtures that can also be used in add on tests:
    • custom_settings does nothing and is meant to be overridden in add on test suites. It allows injection of arbitrary key / values into the settings dict used in tests.
    • unresolved_settings is guaranteed to only contain unresolved string values (or lists therof).
    • settings is now guaranteed to be fully resolved.
    • webtest returns a webtest.TestApp instance with support for the @user marker. This should be used instead of browser doctests for functional tests.
  • Use RTD theme for documentation.
  • Use latest versions of all requirements. The only upgrade with notable differences is lingua (from 1.4 to 3.6.1). This completely changes lingua’s API. See docs/developing/basic/translations.rst for details on the greatly simplified new usage.
  • Remove code (incl. tests) that has been marked as deprecated since (at least) Kotti 0.8.
  • Revise UI to make better use of Bootstrap 3.
  • Allow parameters for move-child-position views to either be in request.POST or request.json_body.
  • Deprecate kotti_js, view_css and edit_css from kotti.fanstatic.
  • Bundle Bootstrap on our own. js.bootstrap is incomplete, has a bug and is updated only irregularly.

0.10b1 - 2014-07-11

  • Add a __json__ method to MutationList and MutationDict.

    This is to allow Pyramid’s serializer to just work.

0.10a4 - 2014-06-19

  • Upgrade Pyramid to version 1.5.1.

0.10a3 - 2014-06-11

  • Upgrade SQLAlchemy and alembic dependencies from 0.8.2 and 0.5.0 to 0.9.4 and 0.6.5 respectively.
  • Do not flush within Node.path event handlers. We would otherwise trigger object handlers with funny object states.
  • Fix bug with Node.path where we attach a Node instance to a parent that has been loaded from the database, but its parents have not been loaded yet.
  • Fix deprecation warnings with regard to Pyramid’s custom_view_predicates and set_request_property. Also deprecate kotti.views.util.is_root.

0.10a2 - 2014-06-05

  • Add Node.path column. This allows queries based on path, so it’s much easier just to find all children, grandchildren etc. of a given node:

    DBSession.query(Node).filter(Node.path.startswith(mynode.path))
    
  • Adds session attribute to the request attributes to copy to the slot view request.

Migrations
  • Upgrading from 0.9.2 to 0.10 requires you to run a migration script on your database. To run the migration, call:

    $ bin/kotti-migrate <myconfig.ini> upgrade
    

    Make sure you backup your database before running the migration!

0.10a1 - 2014-05-19

  • Kotti is now based on Bootstrap 3 (and therefore Deform 2).

    THIS IS A BACKWARD INCOMPATIBLE CHANGE W.R.T. MOST TEMPLATES, INCLUDING FORM TEMPLATES! IF YOUR PROJECT EITHER HAS TEMPLATE CUSTOMIZATIONS OR DEPENDS ON ADD-ONS THINGS WILL LOOK BROKEN!

    If you only use Kotti’s default UI, chances are good that your application will continue to work well unchanged. Kotti’s API is mostly unchanged and fully backward compatible though.

  • Rework implementation of ‘kotti.util.Link’ (‘ViewLink’) to be more flexible.

    There’s now proper support for nesting ‘edit_links’, so that the special ‘action_links’ list is no longer necessary. Links now also make better use of templates for rendering, and are probably easier to customize overall.

  • Added compatiblity for and now require Pyramid>=1.5. #273

  • In tests, turned settings and setup_app into fixtures to ease overriding.

  • Add kotti_context_url JS global variable. For more details on why this is needed see:

  • Adds delete permission needed for ‘delete’ and ‘delete_nodes’ views. The default workflow was updated in consequence. It allows to elaborate more fine grained workflows : for instance, create a role which can edit a content but not delete it.

    To make existent Kotti’s instances using default workflow compatibles and avoid users that have ‘editor’ role (and so far, whom have the possibility to edit and delete the content) to not be able to delete contents, it’s needed to reset workflow with “kotti-reset-workflow <application ini file>” command.

  • Fix #308: Unique name constraint issue during paste of a cut node.

0.9.2 - 2013-10-15

  • Fix #268: Convert None to colander.null in get_appstruct so that serialization doesn’t fail (needed due to recent changes in colander).

0.9.1 - 2013-09-25

  • Allow user admins to modify user passwords.
  • Require newer kotti_tinymce (source code editing was broken in 0.4).

0.9 - 2013-09-17

  • Add multi file content upload. You can now select several files from your local storage that you want to upload and chose what content nodes shall be created in your Kotti site. Currently files with MIME types of image/* can be uploaded and be created as either Image or File nodes, all other MIME types will be created as File. In future releases (or add-on products) this can be extended with additional converters allowing for example to upload HTML files and create Document nodes with the content of the title tag becoming the node’s title, the content of the body tag becoming the node’s body and so on.

  • Fix #253: Many translations weren’t included in the last release.

    ‘–use-fuzzy’ translations when running ‘compile_catalog’ adds back translations that were recently marked as fuzzy. (All translations that were marked as fuzzy in German were still accurate.)

  • Fix #252: Wrap templates where extract_messages failed with <tal:block>

  • Fix #249: TinyMCE translations work again.

0.9b2 - 2013-08-20

  • Fix #251: Broken comparison of NestedMutationDict and NestedMutationList.
  • Update kotti_tinymce to version 4.0.2.
  • Fix bug in kotti.views.content.FileEditForm to preserve file content while editing it.

0.9b1 - 2013-06-26

  • Add kotti.util.ViewLink.visible method for better control over whether a view link should be visible or not. This allows us to move formerly hardcoded action links defined in kotti.views.edit.actions into TypeInfo.action_links and thus make them configurable either globally or per content type.

  • kotti.security.view_permitted will now check for pyramid.security.view_execution_permitted with a request method set to ‘GET’ by default. It used to check for a view that matches the current request’s method.

    This fixes an issue where kotti.util.ViewLink.permitted would by mistake check for a ‘POST’ view when the current request was ‘POST’.

  • Add INavigationRoot interface and TemplateAPI.navigation_root property. The latter returns the first content node in the lineage that implements INavigationRoot or the root node if INavigationRoot is not implemented by any node in the lineage. Make the nav.pt template use api.navigation_root instead of api.root. This allows third party add-ons to define content types that can reside somewhere in the content tree while still being the root for the navigation.

  • Move navigation related view code to new module kotti.views.navigation. Deprecate imports from the old locations.

  • Remove some code that has been deprecated in 0.6 or 0.7.

  • A view assigned to a slot can access the slot name where its rendered.

  • Add missing transaction.commit() in kotti-reset-workflow.

  • Fix bug in kotti.views.util.render_view where local roles weren’t respected correctly.

  • Add helper method kotti.message.send_email for sending general emails. These emails must follow a particular structure. Look at kotti:templates/email-set-password.pt as an example.

0.9a2 - 2013-05-04

  • Fix #222: Use SQLAlchemy’s before_flush event for object events.

    We were using the wrong events previously. The problem with before_insert, before_update, and before_delete was that event handlers could not reliably call Session.add, Session.delete, and change mapped relationships. But only SQLAlchemy 0.8 started emitting a warning when that was done.

    Also deprecated ObjectAfterDelete because I don’t think it’s useful.

  • Remove the html5shim from the master templates and use the fanstatic package js.html5shiv instead.

  • A temporary fix for #187. Basically suppresses DetachedInstanceError.

  • Add kotti.events.subscribe decorator. See the also updated docs on that topic / module for details.

0.9a1 - 2013-03-12

  • Fix ordering on how include_me functions are loaded. This puts Kotti’s own and Kotti add-on search paths in front of deform_bootstrap’s.
  • Add image thumbs with preview popovers to @@contents view.
  • Add drag’n’drop ordering support to @@contents view.
  • Add “toggle all” checkbox to @@contents view.
  • Add contents path bar to @@contents view.

0.8 - 2013-03-12

  • No changes.

0.8b2 - 2013-02-08

  • Fix Kotti’s tests to no longer trigger deprecation warnings. Kotti’s funcargs need to be better documented still, see #141.
  • Add a fanstatic.Group ‘tagit’ and need() it in the defered widget. This is needed to make the tags widget render correctly with a theme package enabled until the defered widget is replaced by a widget class that declares its requirements in the usual deform style.
  • Transform setup_users, setup_user and prefs views into class-based views. Add a little text at subsection Security on developer manual mentioning those views.

0.8b1 - 2012-12-30

  • No changes

0.8a2 - 2012-12-15

  • Remove test related dependencies on requirements.txt. So now we need to run python setup.py dev to get testing dependencies.
  • Update packages versions on requirements.txt for latest working versions.
  • Added a tags display in views for documents, files, folders, and images, where they show up as a horizontal list between description and body.
  • Modified general search to include simple tags searching. The default search in Kotti works on a simple search term matching basis. Tags searching is added here in a simple fashion also, such that you can only search for one tag at a time, but partial matches work: searching for ‘foo’ finds content tagged ‘foo bar’. You can also search on single tags by clicking an individual tag in the tags display of an item. More sophisticated tags searching, just as for general search, is left to dedicated add-ons.

0.8a1 - 2012-11-13

  • Make language-dependent URL normalization the default. (How to do this used to be a cookbook entry.)
  • Cleanup node edit actions and use decorated view classes.
  • Add contents view with actions for multiple items.
  • Add children_with_permission method to ContainerMixin.
  • Add UI for default_view selection.
  • Deprecate ‘kotti.views.edit.generic_add’ and ‘generic_edit’. Just use class-based forms instead.

0.7.2 - 2012-10-02

  • Improve installation instructions. Now uses tagged requirements.txt file.
  • Added event request POST vars to the request for the slot viewlet.
  • Added IFile and IImage interfaces to allow for file and image subclasses to reuse the same view (registrations).

0.7.1 - 2012-08-30

  • Add deletion of users to the users management.
  • Fix tag support for files and images.
  • Upgrade to Twitter Bootstrap 2.1
    • remove lots of CSS that is no longer needed
    • fix responsive layout that was broken on some phone size screen resolutions
  • Add “Site Setup” submenu / remove @@setup view.

0.7 - 2012-08-16

  • Fix critical issue with migrations where version number would not be persisted in the Alembic versions table.

0.7rc1 - 2012-08-14

  • No changes.

0.7a6 - 2012-08-07

  • Fix a bug with connections in the migration script. This would previously cause Postgres to deadlock when calling kotti-migrate.

0.7a5 - 2012-08-07

  • Add workflow support based on repoze.workflow. A simple workflow is included in workflow.zcml and is active by default. Use kotti.use_workflow = 0 to deactivate. The workflow support adds a drop-down that allows users with state_change permission to modify the workflow state.

  • Change the default layout

    Kotti’s new default look is now even closer to the Bootstrap documentation, with the main nav bar at the very top and the edit bar right below.

    Upgrade note: if you have a customized main_template and want to use the recent changes in that template, you need to swap positions of nav.pt and editor-bar.pt api.render_template calls and remove the search.pt call from the main_template (it’s now called from within nav.pt). Everything else is completely optional.

  • Add migrations via Alembic. A new script kotti-migrate helps with managing database upgrades of Kotti and Kotti add-ons. Run kotti-migrate <your.ini> upgrade to upgrade the Kotti database to the latest version.

    Add-on authors should see the kotti.migrate module’s docstring for more details.

  • Make Document.body searchable (and therefore the search feature actually useful for the first time).

  • Add a “minify” command to compress CSS and JS resources.

    To use it run:

    python setup.py dev
    python setup.py minify
    

    The minify command assumes, that all resources are in kotti/static/. YUI compressor is used for compression and will be automatically installed when running python setup.py dev. However, you still need a JVM on your development machine to be able to use the minify command.

  • Fix settings: only values for kotti* keys should be converted to unicode strings.

  • Fix #89: Validate email address for uniqueness when user changes it.

  • Fix #91: Styling of search box.

  • Fix #104: Make fanstatic resources completely overridable.

  • Enabled deferred loading on File.data column.

Migrations
  • Upgrading from 0.6 to 0.7 requires you to run a migration script on your database. To run the migration, call:

    $ bin/kotti-migrate <myconfig.ini> upgrade
    

    Make sure you backup your database before running the migration!

  • Upgrading to 0.7 will initialize workfow state and permissions for all your content objects, unless you’ve overwritten kotti-use_workflow to not use a workflow (use 0) or a custom one.

    It is important that sites that have custom permissions, e.g. custom modifications to SITE_ACL, turn off workflow support prior to running the upgrade script.

0.7a4 - 2012-06-25

  • Add minified versions JS/CSS files.
  • Fix #88: logging in with email.
  • Update translations.

0.7a3 - 2012-06-15

  • Include kotti.tinymce which adds plug-ins for image and file upload and content linking to the TinyMCE rich text editor.

  • Slot renderers have been replaced by normal views (or viewlets). kotti.views.slots.register has been deprecated in favour of kotti.views.slots.assign_slot, which works similarly, but takes a view name of a registered view instead of a function for registration.

  • Switch to fanstatic for static resource management.

    Upgrade note: This requires changes to existing *.ini application configuration files. Concretely, you’ll need to add a filter:fanstatic section and a pipeline:main section and rename an existing app:main section to app:Kotti or the like. Take a look at Kotti’s own development.ini for an example.

  • Retire the undocumented kotti.resources.Setting class and table. kotti.get_settings will now return registry.settings straight, without looking for persistent overrides in the database.

  • Drop support for Pyramid<1.3, since we use pyramid.response.FileResponse, and kotti_tinymce uses pyramid.view.view_defaults.

  • Fix encoding error with non-ascii passwords.

0.7a2 - 2012-06-07

  • Do not allow inactive users to reset their password.

0.7a1 - 2012-06-01

Features
  • Add a new ‘Image’ content type and image scaling, originally from the kotti_image_gallery add-on. See kotti.image_scales.* settings.
  • Add search and related setting kotti.search_content.
  • Add subscriber to set cache headers based on caching rules. See also related setting kotti.caching_policy_chooser.
  • Remove TinyMCE from the core.
  • Move email templates into page templates in kotti:templates/email-set-password.pt and kotti:templates/email-reset-password.pt. This is to make them easier to translate and customize. This deprecates kotti.message.send_set_password.
  • Add a ‘edit_inhead’ slot for stuff that goes into the edit interface’s head. ‘inhead’ is no longer be used in ‘edit/master.pt’.
  • For more details, see also http://danielnouri.org/notes/2012/05/28/kotti-werkpalast-sprint-wrap-up/
Bugs

0.6.3 - 2012-05-08

  • Add tag support. All content objects now have tags. They can be added in the UI using the “jQuery UI Tag-it!” widget. See https://github.com/Pylons/Kotti/pull/55 .
  • Fix a bug with file download performance.

0.6.2 - 2012-04-21

  • Links in Navigation view lead to node view. Added edit links to view the node’s edit form.
  • Hitting ‘Cancel’ now returns to the context node for add/edit views

0.6.1 - 2012-03-30

  • Added button to show/hide nodes from navigation in the order screen.
  • The ‘rename’ action now strips slashes out of names. Fixes #53.
  • Add Dutch translation.
  • Allow translation of TinyMCE’s UI (starting with deform 0.9.5)
  • Separated out testing dependencies. Run bin/python setup.py dev to install Kotti with extra dependencies for testing.
  • Deprecate ‘kotti.includes’ setting. Use the standard ‘pyramid.includes’ instead.
  • Setting ‘Node.__acl__’ to the empty list will now persist the empty list instead of setting ‘None’.
  • Let ‘pyramid_deform’ take care of configuring deform with translation dirs and search paths.

0.6.0 - 2012-03-22

  • Add Japanese translation.
  • Enforce lowercase user names and email with registration and login.
  • Moved SQLAlchemy related stuff from kotti.util into kotti.sqla.
  • You can also append to ‘Node.__acl__’ now in addition to setting the attribute.

0.6.0b3 - 2012-03-17

  • Have the automatic __tablename__ and polymorphic_identity for CamelCase class names use underscores, so a class ‘MyFancyDocument’ gets a table name of ‘my_fancy_documents’ and a type of ‘my_fancy_document’.

0.6.0b2 - 2012-03-16

  • Make the ‘item_type’ attribute of AddForm optional. Fixes #41.
  • kotti.util.title_to_name will now return a name with a maximum length of 40. Fixes #31.

0.6.0b1 - 2012-03-15

0.5.2 - 2012-03-10

  • A new ‘Actions’ menu makes copy, paste, delete and rename of items more accessible.
  • Add German translation.
  • Populators no longer need to call transaction.commit() themselves.

0.5.1 - 2012-02-27

  • Internationalize user interface. Add Portuguese as the first translation.
  • A new ‘Add’ menu in the editor toolbar allows for a more intuitive adding of items in the CMS.
  • Refine Node.copy. No longer copy over local roles per default.

0.5.0 - 2012-02-15

  • Move Kotti’s default user interface to use Twitter Bootstrap 2.
  • Add a new ‘File’ content type.
  • Add CSRF protection to some forms.
  • Remove Kotti’s FormController in favor of using pyramid_deform.
  • Use plone.i18n to normalize titles to URL parts.
  • Add a separate navigation screen that replaces the former intelligent breadcrumbs menu.
  • Use pyramid_beaker as the default session factory.
  • Make kotti.messages.send_set_password a bit more flexible.

0.4.5 - 2012-01-19

  • Add ‘kotti.security.has_permission’ which may be used instead of ‘pyramid.security.has_permission’.

    The difference is that Kotti’s version will set the “authorization context” to be the context that you pass to ‘has_permission’. The effect is that ‘list_groups’ will return a more correct list of local roles, i.e. the groups in the given context instead of ‘request.context’.

  • Add a template (‘forbidden.pt’) for when user is logged in but still getting HTTPForbidden.

0.4.4 - 2012-01-05

  • The “Forbidden View” will no longer redirect clients that don’t accept ‘text/html’ to the login form.
  • Fix bug with ‘kotti.site_title’ setting.

0.4.3 - 2011-12-22

  • Add ‘kotti.root_factory’ setting which allows the override Kotti’s default Pyramid root factory. Also, make master templates more robust so that a minimal root with ‘__parent__’ and ‘__name__’ can be rendered.
  • The ‘kotti.tests’ was factored out. Tests should now import from ‘kotti.testing’.

0.4.2 - 2011-12-20

  • More convenient overrides for add-on packages by better use of ‘config.commit()’.

0.4.1 - 2011-12-20

  • Modularize Kotti’s Paste App Factory ‘kotti.main’.
  • Allow explicit setting of tables that Kotti creates (‘kotti.use_tables’).

0.4.0 - 2011-12-14

  • Remove configuration variables ‘kotti.templates.*’ in favour of ‘kotti.asset_overrides’, which uses Pyramid asset specs and their overrides.
  • Remove ‘TemplateAPI.__getitem__’ and instead add ‘TemplateAPI.macro’ which has a similar but less ‘special’ API.
  • Factor snippets in ‘kotti/templates/snippets.pt’ out into their own templates. Use ‘api.render_template’ to render them instead of macros.

0.3.1 - 2011-12-09

  • Add ‘keys’ method to mutation dicts (see 0.3.0).

0.3.0 - 2011-11-30

  • Replace Node.__annotations__ in favor of an extended Node.annotations.

    Node.annotations will attempt to not only recognize changes to subobjects of type dict, it will also handle list objects transparently. That is, changing arbitrary JSON structures should just work with regard to calling node.annotations.changed() when the structure was changed.

0.2.10 - 2011-11-22

  • ‘api.format_datetime’ now also accepts a timestamp in addition to datetime.

0.2.9 - 2011-11-21

  • Remove MANIFEST.in in favour of using ‘setuptools-git’.

0.2.8 - 2011-11-21

0.2.7 - 2011-11-20

  • Add ‘PasteScript’ dependency.
  • Fix #11 where ‘python setup.py test’ would look into a hard-coded ‘bin’ directory.
  • Structural analysis documentation. (Unfinished; in ‘analysis’ directory during development. Will be moved to main docs when finished.)

0.2.6 - 2011-11-17

  • Add Node.__annotations__ convenience attribute.

    Node.__annotations__ will wrap the annotations dict in such a way that both item and attribute access are possible. It’ll also record changes to dicts inside dicts and mark the parent __annotations__ attribute as dirty.

  • Add a welcome page.

  • Delete the demo added in version 0.2.4.

0.2.5 - 2011-11-14

  • Add ‘TemplateAPI.render_template’; allow templates to be rendered conveniently from templates.

0.2.4 - 2011-11-13

  • Adjust for Pyramid 1.2: INI file, pyramid_tm, Wsgiref server, pcreate and pserve. (MO)
  • Add Kotti Demo source and documentation.

0.2.3 - 2011-10-28

  • Node.__getitem__ will now also accept a tuple as key.

    folder['1', '2'] is the same as folder['1']['2'], just more efficient.

  • Added a new cache decorator based on repoze.lru.

0.2.2 - 2011-10-10

  • Change the function signature of kotti.authn_policy_factory, kotti.authz_policy_factory and kotti.session_factory to include all settings from the configuration file.

0.2.1 - 2011-09-29

  • Minor changes to events setup code to ease usage in tests.

0.2 - 2011-09-16

  • No changes.

0.2a2 - 2011-09-05

  • Fix templates to be compatible with Chameleon 2. Also, require Chameleon>=2.
  • Require pyramid>=1.2. Also, enable pyramid_debugtoolbar for development.ini profile.

0.2a1 - 2011-08-29

  • Improve database schema for Nodes. Split Node class into Node and Content.

    This change is backward incompatible in that existing content types in your code will need to subclass Content instead of Node. The example in the docs has been updated. Also, the underlying database schema has changed.

  • Improve user database hashing and local roles storage.

  • Compatibility fix for Pyramid 1.2.