Developer manual¶
Read the Configuration section first to understand which hooks both integrators and developers can use to customize and extend Kotti.
Contents
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
The add_view
parameter of the type_info
attribute is the name of a view that can be used to construct a Document
instance.
This view has to be available for all content types specified in addable_to
parameter.
See the section below and the Adding Forms and a View section in the tutorial on how to define a view restricted to a specific context.
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 = 'A new title'
Let us add three documents to our root:
>>> from kotti.resources import Document
>>> root['first'] = Document(title='First page')
>>> root['second'] = Document(title='Second page')
>>> root['third'] = Document(title='Third page')
Note how the keys in the dict correspond to the name
of child
nodes:
>>> first = root['first']
>>> first.name
'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()
['first', 'third']
>>> first.keys()
['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()
['first']
>>> first.keys()
['copy-of-second', '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.