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:
- views,
- templates and layout (both via Pyramid),
- Content types,
- portlets (see
kotti.views.slots
), - access control and the user database (see Security),
- workflows (via repoze.workflow),
- and much more.
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.
Support¶
- Python 2.6 or 2.7
- Support for PostgreSQL, MySQL and SQLite (tested regularly), and a list of other SQL databases
- Support for WSGI and a variety of web servers, including Apache
Installation¶
Requirements¶
- Python 2.6 or 2.7
- virtualenv
build_essential
andpython-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 fromkotti.resources.Content
, which is the common base class for all content types.Poll
declares a sqla.Columnid
, 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. Theadd_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 addPoll
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 inheritedtitle
column to store the title of our choice. - The
type_info
defines the title, theadd_view
view, and that choices may only be added intoPoll
items, with the lineaddable_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.
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
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.
- 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¶
Contents
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
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 PyPIinstalled in its own virtualenvdeployed 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
haveobject
andrequest
attributes.event.request
may beNone
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¶
- When a Kotti application is started the
kotti.main()
function is called by the WSGI server and is passed asettings
dictionary that contains all key / value pairs from the[app:kotti]
section of the*.ini
file. - The
settings
dictionary is passed tokotti.base_configure()
. This is where the main work happens:- 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 thesettings
dictionary, together with the default value for that key. - Add-on initializations: all functions that are listed in the
kotti.configurators
parameter are resolved and called. pyramid.includes
are removed from thesettings
dictionary for later processing, i.e. afterkotti.base_includes
.- A class:pyramid.config.Configurator is instanciated with the remaining
settings
. - The
kotti.base_includes
(containing various Kotti subsystems, such askotti.events
,kotti.views
, etc.) are passed toconfig.include
. - The
pyramid.includes
that were removed from thesettings
dictionary in step 2.3 are processed. - The
kotti.zcml_includes
are processed.
- Every key in kotti.conf_defaults that is not in the
- The SQLAlchemy engine is created with the connection URL that is defined
in the sqlalchemy.url parameter in the
.ini
file. - The fully configured WSGI application is returned to the WSGI server and is ready to process requests.
API¶
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 invokepy.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 awebtest.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
orrequest.json_body
. - Deprecate
kotti_js
,view_css
andedit_css
fromkotti.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
andset_request_property
. Also deprecatekotti.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 eitherImage
orFile
nodes, all other MIME types will be created asFile
. In future releases (or add-on products) this can be extended with additional converters allowing for example to upload HTML files and createDocument
nodes with the content of thetitle
tag becoming the node’s title, the content of thebody
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 inkotti.views.edit.actions
intoTypeInfo.action_links
and thus make them configurable either globally or per content type.kotti.security.view_permitted
will now check forpyramid.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 andTemplateAPI.navigation_root
property. The latter returns the first content node in the lineage that implementsINavigationRoot
or the root node ifINavigationRoot
is not implemented by any node in the lineage. Make thenav.pt
template useapi.navigation_root
instead ofapi.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
andprefs
views into class-based views. Add a little text at subsectionSecurity
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 inworkflow.zcml
and is active by default. Usekotti.use_workflow = 0
to deactivate. The workflow support adds a drop-down that allows users withstate_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
andeditor-bar.pt
api.render_template
calls and remove thesearch.pt
call from the main_template (it’s now called from withinnav.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. Runkotti-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 inkotti/static/
.YUI compressor
is used for compression and will be automatically installed when runningpython setup.py dev
. However, you still need a JVM on your development machine to be able to use theminify
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 (use0
) 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 ofkotti.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 apipeline:main
section and rename an existingapp:main
section toapp:Kotti
or the like. Take a look at Kotti’s owndevelopment.ini
for an example.Retire the undocumented
kotti.resources.Setting
class and table.kotti.get_settings
will now returnregistry.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 usespyramid.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
andkotti:templates/email-reset-password.pt
. This is to make them easier to translate and customize. This deprecateskotti.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¶
- Fix bug with group edit views. See https://github.com/Pylons/Kotti/pull/61
- Fix bug where
user.last_login_date
was not set during automic login after the set password screen.
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__
andpolymorphic_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¶
Use declarative style instead of class mapper for SQLAlchemy resources.
Unfortunately, this change is backwards incompatible with existing content types (not with existing databases however). Updating your types to use Declarative is simple. See kotti_calendar for an example: https://github.com/dnouri/kotti_calendar/commit/509d46bd596ff338e0a88f481339882de72e49e0#diff-1
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 usingpyramid_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¶
- Remove ‘PasteScript’ dependency since that would result in spurious errors when installing Kotti. See http://jenkins.danielnouri.org/job/Kotti/42/TOXENV=py27/console
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 asfolder['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
andkotti.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
. SplitNode
class intoNode
andContent
.This change is backward incompatible in that existing content types in your code will need to subclass
Content
instead ofNode
. 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.