Personal tools
You are here: Home Documentation Tutorials Grok Poller Tutorial Interfaces

Interfaces

In the previous section, we defined a schema for the ``Poll`` class manually, which is not a good solution, as we might need multiple forms for them, and thus will need to repeat a lot of code.
This tutorial shows how to implement a simple polling application using Grok.
Page 8 of 14.

We can fix this by defining interfaces for our models. Grok uses Zope interface framework [1] for this. In short, the interfaces define what attributes and methods a class implementing the interface should have, but it's not limited to classes, you can also define interfaces for functions or even modules too. The interface system is used extensively by the Zope component architecture [2], which too large to cover completely in here, instead we will just use the interface system so that you can see how it works, but for more information you should read other documents about it.

[1]Zope Interface <http://pypi.python.org/pypi/zope.interface>
[2]Zope Component <http://pypi.python.org/pypi/zope.component>

To define the interfaces for the PollOption model, add the following code to a new module called interfaces.py:

from zope.interface import Interface, Attribute

# By convention, interfaces are prefixes with an "I"
class IPollOption(Interface):
    """An option for ``Poll``"""
    label = Attribute(u'Label of a poll option')
    description = Attribute(u'Description of a poll option')

this is a simple interface definition, which defines an interface called IPollOption, which contains two attributes. The attributes can be of any type, and the string as the parameter is the documentation for that attribute. This is all there is to defining interfaces. Next we will define the interface for the Poll model, or more precisely an interface which the Poll model is an implementation of, as there could be multiple implementations of a single interface:

class IPoll(Interface):
    """A poll"""
    question = Attribute(u'Question')
    options = Attribute(u'Options')

    def get_response(option):
        """Get the response for an option"""

    def choose(option):
        """User chooses an option"""

simple as that. There a few things to note in this interface:

  • The methods do not define self. This is because it's an implementation detail, this interface could be a Python module, where these "methods" are actually module functions.
  • The methods don't have pass in them. Without the docstrings, this would be a SyntaxError in Python, but the docstrings is enough for Python, it's a common misconception that docstringed methods are "empty."

These interfaces would work for our models, but there is still something missing, the actual schema definition we typed in the form. The attributes do not define a type; thus the form system doesn't know their types nor any invariants applied to them. The schema fields we used in the forms are actually interface attributes, they are just specialized attributes; again, you should read more about them in another tutorial, but the actual interface definitions we need is below:

from zope.interface import Interface
from zope.schema import Object, Tuple, TextLine

class IPollOption(Interface):
    """An option for ``Poll``"""
    label = TextLine(title=u'Label', min_length=1)
    description = TextLine(title=u'Description', min_length=1)


class IPoll(Interface):
    """A poll"""
    question = TextLine(title=u'Question')

    # The tuple default must be defined (as with all sequences) because
    # of a bug in zope.app.formlib which is used in Grok form system.
    # (grok 1.0a4)
    options = Tuple(title=u'Options',
                    value_type=Object(IPollOption, title=u'Poll Option'),
                    default=tuple())

    def get_response(option):
        """Get the response for an option"""

    def choose(option):
        """User chooses an option"""

this version actually defines what is the type of the different attributes, and their invariants. Sequence fields such as Tuple can even define the type of the values contained in them, in this case we define that options is a tuple which contains only objects which implement IPollOption interface.

Now we must modify our models to inform the interface system that they implement these interfaces, this is done using grok.Implements directive in the models, and can be used in any classes, not just models. The following is the PollOption model code again, you should be able to change Poll by yourself:

from poller.interfaces import IPollOption

class PollOption(grok.Model):
    grok.implements(IPollOption)

    label = u''
    description = u''

Now we are ready to change the add form implementation to use the defined schemas, by replacing form_fields in app.py Add class with:

from poller.interfaces import IPoll
...
class Add(grok.AddForm):
    form_fields = grok.AutoFields(IPoll)
    ...

But if you try to display the form, it will raise an exception, because it doesn't know how to display IPollOption as a field. If the tuple value type would have been a "simple" schema field, like TextLine, then the form would work automatically, but in this case, we must define a custom widget for the object; for more information, see the documentation of zope.app.form which explains this problem in detail. Fortunately this is just a couple of lines of boilerplate code, but it breaks the DRY [3] concept a bit as the customization has to be done to all forms that use IPoll interface.

[3]DRY <http://en.wikipedia.org/wiki/Don't_repeat_yourself>

Add the following lines to poll.py module:

from zope.app.form import CustomWidgetFactory
from zope.app.form.browser import SequenceWidget, ObjectWidget

poll_objectwidget = CustomWidgetFactory(ObjectWidget, PollOption)
poll_seqwidget = CustomWidgetFactory(SequenceWidget,
                                     subwidget=poll_objectwidget)

This is what zope.app.form documentation says about this:

"Note the creation of the widget via a CustomWidgetFactory. So, whenever the options_widget is used, a new SequenceWidget(subwidget=CustomWidgetFactory(ObjectWidget, PollOption)) is created. The subwidget argument indicates that each item in the sequence should be represented by the indicated widget instead of their default. If the contents of the sequence were just Text fields, then the default would be just fine - the only odd cases are Sequence and Object Widgets because they need additional arguments when they're created.

Each item in the sequence will be represented by a CustomWidgetFactory(ObjectWidget, PollOption) - thus a new ObjectWidget(context, request, PollOption) is created for each one."

Next we must tell the form about the custom widgets by adding this line below the form_fields definition, after importing poller.poll.poll_seqwidget:

form_fields['options'].custom_widget = poll_seqwidget

this simply sets the custom widget for the form field for options. Now the form works fine, and is actually quite cool as it dynamically generates subforms for the poll options. Now the form works, but there is still a small detail that should be fixed.

 

Import change with zope.formlib 4

Posted by anil at Jan 21, 2011 02:49 AM
In the above code with zope.formlib 4, needed to change the import statements to:
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.objectwidget import ObjectWidget
from zope.formlib.sequencewidget import SequenceWidget