Using sources in your forms

Author:jmichiel

This How To explains how one can use the zc.sourcefactory package to fill out lists and choices with dynamic content.

Prerequisities

You need to know how to work with zope.schema and grok.Form or its derivatives.

Step by step

Imagine you are writing an Issue Tracker in Grok. Your definition of an ‘issue’ might look like this:

class IIssue(Interface):
        title = schema.TextLine(title=u'Title', description=u'The issue in short')
        severity = schema.Choice(values=(u'Minor', u'Major', u'Blocker'), title=u'Severity',
                                                     description=u'How severe is this issue')
        description = schema.Text(title=u'Description', description=u'The complete description of the issue')

(Of course an issue should also have a state, but that is besides the focus of this howto, so I left it out to keep things simple). The severity attribute is the one of interest here. We used a hard-coded tuple to define the possible options. Hard-coded generally is not such a bright idea. Imagine you have a user who wants 5 levels of severity? He would be forced to edit code! This is where sources come in!

Installing zc.sourcefactory

Edit your setyp.py and add the zc.sourcefactory to install_requires:

install_requires=['setuptools',
                  'grok',
                  'grokui.admin',
                  'z3c.testsetup',
                  'zc.sourcefactory',
                  # Add extra requirements here
                  ],
Note:
for Grok 1.0a1, at the time of writing, if you would not run buildout, it would get you zc.sourcefactory version 0.4.0, which is, alas, incompatible with some packages Grok 1.0a1 installs by default. To counter this add this line in the [versions] section of your versions.cfg: zc.sourcefactory = 0.3.5

Now run bin/buildout.

Using Sources

Now we’re ready to start using Sources! Add this to your code:

from zc.sourcefactory.basic import BasicSourceFactory

class SeveritySource(BasicSourceFactory):
    def getValues(self):
        return (u'Minor', u'Major', u'Blocker')

And change your schema-definition of severity to this:

severity = schema.Choice(source=SeveritySource(), title=u'Severity', description=u'How severe is this issue')

This tells the Choice field to get its values from the SeveritySource. The getValues function is needed to get at the actual values this source is all about and should return something iterable. Of course this is still hard-coded, but it already separates the definition of severity away from the definition of Issue. This is also a great way to do some agile coding: first make a hard-coded source so you can implement Issue, and implement the whole severity part later, without needing to change your Issue definition again.

Making the Source truly Dynamic

Imagine you implemented Severity like this:

class ISeverity(Interface):
    label = schema.TextLine(title=u'Label')
    description = schema.Text(title=u'Description')

and that you have a SeverityContainer called ‘severities’ under your application, with the above mentioned severities. Now you can change your SeveritySource to this:

class SeveritySource(BasicSourceFactory):
    def getValues(self):
        return grok.getSite()['severities'].values()

However if you try your issue add form now, you would see a dropdown list with 3 empty values. What went wrong? The getValues function now returns objects of class Severity, but the droplist doesn’t know how to display these! To solve this you can add a getTitle function to your SeveritySource:

def getTitle(self, value):
    return value.label

This function accepts a value from the list of values that is returned by getValues, and allows you to return an appropriate title for it. Checking the add form now gets us back where we started. But if you now add a Severity to your SeverityContainer, it will show up in this list, without having you changing any code!

Further information

The docs of the zc.sourcefactory package can be found in the PyPI: http://pypi.python.org/pypi/zc.sourcefactory/0.3.5 .