Edit me on GitHub

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:

class Document(Content):
    type_info = Content.type_info.copy(
        name=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

documents = Table('documents', metadata,
    Column('id', Integer, ForeignKey('contents.id'), primary_key=True),
    Column('body', UnicodeText()),
    Column('mime_type', String(30)),
)

mapper(Document, documents, inherits=Content, polymorphic_identity='document')

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

Configuring custom views, subscribers and more

kotti.includes allows you to hook includeme functions that configure your custom views, subscribers and more. An includeme function takes the Pyramid Configurator API object as its sole argument. An example that overrides the default view for all Document content types:

def my_view(request):
    from pyramid.response import Response
    return Response('OK')

def includeme(config):
    config.add_view(my_view)

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

kotti.includes = mypackage.views.includeme

The Node API

One of Kotti’s core abstractions is the Node API, which is a (ordered) dict-like API for manipulation of objects and their parent/child relationships.

An example:

# Get the root node and set its title:
from kotti.resources import get_root
root = get_root()
root.title = u'A new title'

# Add two pages:
from kotti.resources import Document
root['first'] = Document(title=u'First page')
root['second'] = Document(title=u'Second page')

# Make a copy of the second page and move it into the first page:
root['first']['copy-of-second'] = root['second'].copy()

# Delete the original second page:
del root['first']['second']

# List all children names and nodes:
root.keys()
['first']
root.values()
[<Document at /first>]

kotti.views.slots

This module allows add-ons to register renderers that add pieces of HTML to the overall page. In other systems, these are called portlets or viewlets.

A simple example that’ll render Hello, World! in in the left column of every page:

def render_hello(context, request):
    return u'Hello, World!'  

from kotti.views.slots import register
from kotti.views.slots import RenderLeftSlot
register(RenderLeftSlot, None, render_hello)

Slot renderers may also return None to indicate that they don’t want to include anything. We can change our render_hello function to include a message only when the context is the root object:

from kotti.resources import get_root
def render_hello(context, request):
    if context == get_root():
        return u'Hello, World!'  

The second argument to kotti.views.slots.register() allows you to filter on context. These two are equivalent:

from kotti.views.slots import RenderRightSlot
from mypackage.resources import Calendar

def render_agenda1(context, request):
    if isinstance(context, Calendar):
        return '<div>...</div>'
register(RenderRightSlot, None, render_agenda1)

def render_agenda2(context, request):
    return '<div>...</div>'
register(RenderRightSlot, Calendar, render_agenda2)

Usually you’ll want to call kotti.views.slots.register() inside an includeme function and not on a module level, to allow users of your package to include your slot renderers through the kotti.includes configuration setting.

kotti.events

This module includes a simple events system that allows users to subscribe to specific events, and more particularly to object events of specific object types.

To subscribe to any event, write:

def all_events_handler(event):
    print event  
kotti.events.listeners[object].append(all_events_handler)

To subscribe only to ObjectInsert events of Document types, write:

def document_insert_handler(event):
    print event.object, event.request
kotti.events.objectevent_listeners[(ObjectInsert, Document)].append(
    document_insert_handler)

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

Notifying listeners of an event is as simple as calling the listeners_notify function:

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

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

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 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'] = '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