Skip to content

Latest commit

 

History

History
243 lines (195 loc) · 7.47 KB

widgets.rst

File metadata and controls

243 lines (195 loc) · 7.47 KB

Simple example showing ObjectWidget and SequenceWidget usage

The following implements a Poll product (add it as zope/app/demo/poll) which has poll options defined as:

label

A TextLine holding the label of the option

description

Another TextLine holding the description of the option

Simple stuff.

Our Poll product holds an editable list of the PollOption instances.

First the Schemas are defined in the interfaces.py file below:

>>> from zope.interface import Interface
>>> from zope.schema import Object, Tuple, TextLine
>>> from zope.schema.interfaces import ITextLine
>>> from zope.i18nmessageid import MessageFactory

>>> _ = MessageFactory("poll")

>>> class IPollOption(Interface):
...    label = TextLine(title=u'Label', min_length=1)
...    description = TextLine(title=u'Description', min_length=1)

>>> class IPoll(Interface):
...    options = Tuple(title=u'Options',
...        value_type=Object(IPollOption, title=u'Poll Option'))
...
...    def getResponse(option): "get the response for an option"
...
...    def choose(option): 'user chooses an option'

The implementation is shown in the poll.py source below:

>>> from persistent import Persistent
>>> from zope.interface import implementer, classImplements

>>> @implementer(IPollOption)
... class PollOption(Persistent, object):
...       pass

>>> @implementer(IPoll)
... class Poll(Persistent, object):
...
...    def getResponse(self, option):
...        return self._responses[option]
...
...    def choose(self, option):
...        self._responses[option] += 1
...        self._p_changed = 1
...
...    def get_options(self):
...        return self._options
...
...    def set_options(self, options):
...        self._options = options
...        self._responses = {}
...        for option in self._options:
...            self._responses[option.label] = 0
...
...    options = property(get_options, set_options, None, 'fiddle options')

Note the use of the Tuple and Object schema fields above. The Tuple could optionally have restrictions on the min or max number of items - these will be enforced by the SequenceWidget form handling code. The Object must specify the schema that is used to generate its data.

Now we have to specify the actual add and edit views. We use the existing AddView and EditView, but we pre-define the widget for the sequence because we need to pass in additional information. This is given in the browser.py file:

from zope.app.form.browser.editview import EditView
from zope.app.form.browser.add import AddView
from zope.formlib.widget import CustomWidgetFactory
from zope.app.form.browser import SequenceWidget, ObjectWidget

from interfaces import IPoll
from poll import Poll, PollOption

class PollVoteView:
    __used_for__ = IPoll

    def choose(self, option):
        self.context.choose(option)
        self.request.response.redirect('.')

ow = CustomWidgetFactory(ObjectWidget, PollOption)
sw = CustomWidgetFactory(SequenceWidget, subwidget=ow)

class PollEditView(EditView):
    __used_for__ = IPoll

    options_widget = sw

class PollAddView(AddView):
    __used_for__ = IPoll

    options_widget = sw

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. The PollOption class ("factory") is used to create new instances when new data is created in add forms (or edit forms when we're adding new items to a Sequence).

Tying all this together is the configure.zcml:

<configure xmlns='http://namespaces.zope.org/zope'
    xmlns:browser='http://namespaces.zope.org/browser'>

<content class=".poll.Poll">
<factory id="zope.app.demo.poll"
         permission="zope.ManageContent" />

<implements
    interface="zope.annotation.interfaces.IAttributeAnnotatable"
    />

<require
    permission="zope.View"
    interface=".interfaces.IPoll"
    />

<require
    permission="zope.ManageContent"
    set_schema=".interfaces.IPoll"
    />
</content>

<content class=".poll.PollOption">
<require
    permission="zope.View"
    interface=".interfaces.IPollOption"
    />
</content>

<browser:page for=".interfaces.IPoll"
    name="index.html"
    template="results.zpt"
    permission="zope.View"
    />

<browser:pages
    for=".interfaces.IPoll"
    class=".browser.PollVoteView"
    permission="zope.ManageContent">
    <browser:page name="vote.html" template="vote.zpt" />
    <browser:page name="choose" attribute="choose" />
</browser:pages>

<browser:addform
    schema=".interfaces.IPoll"
    label="Add a Poll"
    content_factory=".poll.Poll"
    name="AddPoll.html"
    class=".browser.PollAddView"
    permission="zope.ManageContent" />

<browser:addMenuItem
    title="Poll Demo"
    description="Poll Demo"
    content_factory=".poll.Poll"
    view="AddPoll.html"
    permission="zope.ManageContent"
    />


<browser:editform
    schema=".interfaces.IPoll"
    class=".browser.PollEditView"
    label="Change a Poll"
    name="edit.html"
    permission="zope.ManageContent" />

</configure>

Note the use of the class attribute on the addform and editform elements. Otherwise, nothing much exciting here.

Finally, we have some additional views...

results.zpt:

<html metal:use-macro="context/@@standard_macros/page">
<title metal:fill-slot="title">Poll results</title>
<div metal:fill-slot="body">
<table border="1">
<caption>Poll results</caption>
<thead>
    <tr><th>Option</th><th>Results</th><th>Description</th></tr>
</thead>
<tbody>
    <tr tal:repeat="option context/options">
    <td tal:content="option/label">Option</td>
    <td tal:content="python:context.getResponse(option.label)">Result</td>
    <td tal:content="option/description">Option</td>
    </tr>
</tbody>
</table>
</div>
</html>

vote.zpt:

<html metal:use-macro="context/@@standard_macros/page">
<title metal:fill-slot="title">Poll voting</title>
<div metal:fill-slot="body">
<form action="choose">
<table border="1">
<caption>Poll voting</caption>
<tbody>
    <tr tal:repeat="option context/options">
    <td><input type="radio" name="option"
                tal:attributes="value option/label"></td>
    <td tal:content="option/label">Option</td>
    <td tal:content="option/description">Option</td>
    </tr>
</tbody>
</table>
<input type="submit">
</form>
</div>
</html>