[Zope3-checkins] CVS: Zope3/src/zope/app/form/browser -
__init__.py:1.1 add.pt:1.1 add.py:1.1 addwizard.pt:1.1
addwizard.py:1.1 configure.zcml:1.1 display.pt:1.1
edit.pt:1.1 editview.py:1.1 editwizard.pt:1.1
editwizard.py:1.1 enumerated.py:1.1 interfaces.py:1.1
meta.zcml:1.1 metaconfigure.py:1.1 metadirectives.py:1.1
schemadisplay.py:1.1 subedit.pt:1.1 submit.py:1.1
vocabularywidget.py:1.1 vocabularywidget.zcml:1.1
widget.py:1.1 widgets.txt:1.1
Stephan Richter
srichter at cosmos.phy.tufts.edu
Sat Mar 13 20:11:36 EST 2004
Update of /cvs-repository/Zope3/src/zope/app/form/browser
In directory cvs.zope.org:/tmp/cvs-serv23846/src/zope/app/form/browser
Added Files:
__init__.py add.pt add.py addwizard.pt addwizard.py
configure.zcml display.pt edit.pt editview.py editwizard.pt
editwizard.py enumerated.py interfaces.py meta.zcml
metaconfigure.py metadirectives.py schemadisplay.py subedit.pt
submit.py vocabularywidget.py vocabularywidget.zcml widget.py
widgets.txt
Log Message:
Moved zope.app.browser.form to zope.app.form.browser
=== Added File Zope3/src/zope/app/form/browser/__init__.py ===
#
# This file is necessary to make this directory a package.
=== Added File Zope3/src/zope/app/form/browser/add.pt ===
<html metal:use-macro="context/@@standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row" metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="label"><input type="text" style="width:100%" /></div>
</div>
<div class="row"
metal:define-macro="widget_rows" tal:repeat="widget view/widgets">
<div class="label" tal:content="structure widget/label">Name</div>
<div class="field" tal:content="structure widget">
<input type="text" style="width:100%"/>
</div>
<div class="error" tal:define="error widget/error"
tal:condition="error" tal:content="structure error">
The Error
</div>
</div>
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<br/><br/>
<div class="row">
<div class="controls"><hr />
<input type='submit' value='Refresh'
i18n:attributes='value refresh-button'>
<input type="submit" name="UPDATE_SUBMIT" value=" Add "
tal:replace="structure context/renderAddButton|default"/>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/form/browser/add.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Add Form View class
$Id: add.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import sys
from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import ValidationError
from zope.security.checker import defineChecker, NamesChecker
from zope.app.publisher.browser.globalbrowsermenuservice import \
globalBrowserMenuService
from editview import EditView
from submit import Update
class AddView(EditView):
"""Simple edit-view base class.
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
def _setUpWidgets(self):
setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
def update(self):
if self.update_status is not None:
# We've been called before. Just return the previous result.
return self.update_status
if Update in self.request:
self.update_status = ''
try:
data = getWidgetsData(self, self.schema, names=self.fieldNames)
self.createAndAdd(data)
except WidgetsError, errors:
self.errors = errors
self.update_status = _("An error occured.")
return self.update_status
self.request.response.redirect(self.nextURL())
return self.update_status
def create(self, *args, **kw):
"""Do the actual instantiation."""
return self._factory(*args, **kw)
def createAndAdd(self, data):
"""Add the desired object using the data in the data argument.
The data argument is a dictionary with the data entered in the form.
"""
args = []
if self._arguments:
for name in self._arguments:
args.append(data[name])
kw = {}
if self._keyword_arguments:
for name in self._keyword_arguments:
if name in data:
kw[str(name)] = data[name]
content = self.create(*args, **kw)
adapted = getAdapter(content, self.schema, context=self.context)
errors = []
if self._set_before_add:
for name in self._set_before_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
publish(self.context, ObjectCreatedEvent(content))
content = self.add(content)
adapted = getAdapter(content, self.schema)
if self._set_after_add:
for name in self._set_after_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
return content
def add(self, content):
return self.context.add(content)
def nextURL(self):
return self.context.nextURL()
def AddViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u'', usage=u''):
s = zapi.getService(None, zapi.servicenames.Presentation)
class_ = SimpleViewClass(
template,
used_for = schema, bases = bases
)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments
class_._keyword_arguments = keyword_arguments
class_._set_before_add = set_before_add
class_._set_after_add = set_after_add
class_.generated_form = ViewPageTemplateFile(default_template)
if not usage and menu:
usage = globalBrowserMenuService.getMenuUsage(menu)
if not usage:
# usage could be None
usage = u''
s.useUsage(usage)
class_.usage = usage
defineChecker(class_,
NamesChecker(
("__call__", "__getitem__",
"browserDefault", "publishTraverse"),
permission,
)
)
s.provideView(for_, name, IBrowserRequest, class_, layer)
=== Added File Zope3/src/zope/app/form/browser/addwizard.pt ===
<tal:block condition="view/update"/><html
metal:use-macro="context/@@standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:condition="view/feedback" tal:content="view/feedback">
A feedback message to the user
</p>
<div tal:condition="view/errors">
<ul>
<li tal:repeat="error view/errors">
<strong tal:content="error/__class__">
Error Type</strong>:
<span tal:content="error">Error text</span>
</li>
</ul>
</div>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row" metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="label"><input type="text" style="width:100%" /></div>
</div>
<div class="row"
metal:define-macro="widget_rows" tal:repeat="widget view/widgets"
tal:content="structure widget/row">
<div class="label">Name</div>
<div class="field"><input type="text" style="width:100%" /></div>
<div class="error">Error message</div>
</div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
</div>
<div class="row">
<div class="controls">
<!-- <input type="submit" value="Refresh"
i18n:attributes="value refresh-button" /> -->
<input tal:condition="view/show_previous"
type="submit" name="PREVIOUS_SUBMIT" value="Previous"
i18n:attributes="value previous-button" />
<input tal:condition="view/show_submit"
type="submit" name="UPDATE_SUBMIT" value="Submit"
i18n:attributes="value submit-button"/>
<input tal:condition="view/show_next"
type="submit" name="NEXT_SUBMIT" value="Next"
i18n:attributes="value next-button" />
</div>
</div>
<div tal:replace="structure view/renderHidden">
<!-- type=hidden input controls for passing state without session -->
<input type="hidden" name="example" value="foo" />
</div>
</form>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/form/browser/addwizard.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Add Wizard View Classes
$Id: addwizard.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import sys
from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.utility import setUpWidgets
from zope.app.form.interfaces import WidgetsError, IInputWidget
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import ValidationError
from zope.security.checker import defineChecker, NamesChecker
from editwizard import EditWizardView, WizardStorage
class AddWizardView(EditWizardView):
"""Multi-page add-view base class.
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
def _setUpWidgets(self):
if self.use_session:
# Need session for File upload fields
raise NotImplementedError, 'Need a working ISessionDataManager'
else:
self.storage = WizardStorage(self.fieldNames, None)
setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
def create(self, *args, **kw):
"""Do the actual instantiation."""
return self._factory(*args, **kw)
def apply_update(self, data):
"""Add the desired object using the data in the data argument.
The data argument is a dictionary with the data entered in the form.
Issues a redirect to context.nextURL()
Returns False, as per editview.apply_update
"""
# This code originally from add.py's createAndAdd method
args = []
for name in self._arguments:
args.append(data[name])
kw = {}
for name in self._keyword_arguments:
if name in data:
kw[str(name)] = data[name]
content = self.create(*args, **kw)
adapted = getAdapter(content, self.schema, context=self.context)
errors = []
for name in self._set_before_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
publish(self.context, ObjectCreatedEvent(content))
content = self.context.add(content)
adapted = getAdapter(content, self.schema)
for name in self._set_after_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
self.request.response.redirect(self.context.nextURL())
return False
def AddWizardViewFactory(
name, schema, permission, layer, panes, fields,
template, default_template, bases, for_, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add, use_session=True):
class_ = SimpleViewClass(template, used_for = schema, bases = bases)
class_.schema = schema
class_.panes = panes
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments or []
class_._keyword_arguments = keyword_arguments or []
class_._set_before_add = set_before_add or []
class_._set_after_add = set_after_add or []
class_.use_session = use_session
class_.generated_form = ViewPageTemplateFile(default_template)
defineChecker(class_,
NamesChecker(
("__call__", "__getitem__", "browserDefault"),
permission,
)
)
s = zapi.getService(None, zapi.servicenames.Presentation)
s.provideView(for_, name, IBrowserRequest, class_, layer)
=== Added File Zope3/src/zope/app/form/browser/configure.zcml ===
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Core display widgets -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".widget.DisplayWidget"
permission="zope.Public"
/>
<!-- Core edit widgets -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.ITextLine"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.TextWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IText"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.TextAreaWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.ISourceText"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.TextAreaWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IBytesLine"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.BytesWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IBytes"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.FileWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IASCII"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.BytesAreaWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IInt"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.IntWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IFloat"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.FloatWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IDatetime"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.DatetimeWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IDate"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.DateWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IBool"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.CheckBoxWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.ITuple"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.TupleSequenceWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IList"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.ListSequenceWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IPassword"
provides="zope.app.form.interfaces.IInputWidget"
factory=".widget.PasswordWidget"
permission="zope.Public"
/>
<!-- Widgets for enumerated field flavours -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IEnumeratedTextLine"
provides="zope.app.form.interfaces.IInputWidget"
factory=".enumerated.EnumeratedTextWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IEnumeratedInt"
provides="zope.app.form.interfaces.IInputWidget"
factory=".enumerated.EnumeratedIntWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IEnumeratedFloat"
provides="zope.app.form.interfaces.IInputWidget"
factory=".enumerated.EnumeratedFloatWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IEnumeratedDatetime"
provides="zope.app.form.interfaces.IInputWidget"
factory=".enumerated.EnumeratedDatetimeWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IEnumeratedDate"
provides="zope.app.form.interfaces.IInputWidget"
factory=".enumerated.EnumeratedDateWidget"
permission="zope.Public"
/>
<!-- Vocabulary fields share special widget factories that redirect
to the vocabularies they reference. -->
<!-- Single selection -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".vocabularywidget.VocabularyFieldDisplayWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyField"
provides="zope.app.form.interfaces.IInputWidget"
factory=".vocabularywidget.VocabularyFieldEditWidget"
permission="zope.Public"
/>
<!-- Bags -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyBagField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".vocabularywidget.VocabularyBagFieldDisplayWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyBagField"
provides="zope.app.form.interfaces.IInputWidget"
factory=".vocabularywidget.VocabularyBagFieldEditWidget"
permission="zope.Public"
/>
<!-- Lists -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyListField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".vocabularywidget.VocabularyListFieldDisplayWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyListField"
provides="zope.app.form.interfaces.IInputWidget"
factory=".vocabularywidget.VocabularyListFieldEditWidget"
permission="zope.Public"
/>
<!-- Sets -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularySetField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".vocabularywidget.VocabularySetFieldDisplayWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularySetField"
provides="zope.app.form.interfaces.IInputWidget"
factory=".vocabularywidget.VocabularySetFieldEditWidget"
permission="zope.Public"
/>
<!-- Unique lists -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyUniqueListField"
provides="zope.app.form.interfaces.IDisplayWidget"
factory=".vocabularywidget.VocabularyUniqueListFieldDisplayWidget"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IVocabularyUniqueListField"
provides="zope.app.form.interfaces.IInputWidget"
factory=".vocabularywidget.VocabularyUniqueListFieldEditWidget"
permission="zope.Public"
/>
<!-- implementation support for vocabulary field widgets -->
<include file="vocabularywidget.zcml" />
</configure>
=== Added File Zope3/src/zope/app/form/browser/display.pt ===
<html metal:use-macro="views/standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Display something</h3>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row" metal:define-macro="widget_rows"
tal:repeat="widget view/widgets">
<div class="label" tal:content="structure widget/label">Name</div>
<div class="field" tal:content="structure widget">
<input type="text" style="width:100%"/>
</div>
<div class="error" tal:define="error widget/error"
tal:condition="error" tal:content="structure error">
The Error
</div>
</div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
</div>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/form/browser/edit.pt ===
<tal:tag condition="view/update"/>
<html metal:use-macro="views/standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row" metal:define-macro="widget_rows"
tal:repeat="widget view/widgets">
<div class="label" tal:content="structure widget/label">Name</div>
<div class="field" tal:content="structure widget">
<input type="text" style="width:100%"/>
</div>
<div class="error" tal:define="error widget/error"
tal:condition="error" tal:content="structure error">
The Error
</div>
</div>
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="UPDATE_SUBMIT" value="Change"
i18n:attributes="value submit-button"/>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/form/browser/editview.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Edit View Classes
$Id: editview.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from datetime import datetime
from zope.schema import getFieldNamesInOrder
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.security.checker import defineChecker, NamesChecker
from zope.component import getAdapter
from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.interfaces import WidgetsError
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.publisher.browser import BrowserView
from zope.app.publisher.browser.globalbrowsermenuservice import \
globalBrowserMenuService
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.browser.submit import Update
class EditView(BrowserView):
"""Simple edit-view base class
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
errors = ()
update_status = None
label = ''
# Fall-back field names computes from schema
fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
# Fall-back template
generated_form = ViewPageTemplateFile('edit.pt')
def __init__(self, context, request):
super(EditView, self).__init__(context, request)
self._setUpWidgets()
def _setUpWidgets(self):
adapted = getAdapter(self.context, self.schema)
if adapted is not self.context:
if not ILocation.providedBy(adapted):
adapted = LocationProxy(adapted)
adapted.__parent__ = self.context
self.adapted = adapted
setUpEditWidgets(self, self.schema, source=self.adapted,
names=self.fieldNames)
def setPrefix(self, prefix):
for widget in self.widgets():
widget.setPrefix(prefix)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.fieldNames]
def changed(self):
# This method is overridden to execute logic *after* changes
# have been made.
pass
def update(self):
if self.update_status is not None:
# We've been called before. Just return the status we previously
# computed.
return self.update_status
status = ''
content = self.adapted
if Update in self.request:
changed = False
try:
changed = applyWidgetsChanges(self, self.schema,
target=content, names=self.fieldNames)
# We should not generate events when an adapter is used.
# That's the adapter's job.
if changed and self.context is self.adapted:
publish(content, ObjectModifiedEvent(content))
except WidgetsError, errors:
self.errors = errors
status = _("An error occured.")
else:
setUpEditWidgets(self, self.schema, source=self.adapted,
ignoreStickyValues=True,
names=self.fieldNames)
if changed:
self.changed()
formatter = self.request.locale.dates.getFormatter(
'dateTime', 'medium')
status = _("Updated on ${date_time}")
status.mapping = {'date_time': formatter.format(
datetime.utcnow())}
self.update_status = status
return status
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u'',
usage=u''):
s = zapi.getService(None, zapi.servicenames.Presentation)
class_ = SimpleViewClass(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full edit"
class_.fulledit_label = fulledit_label
class_.generated_form = ViewPageTemplateFile(default_template)
if not usage and menu:
usage = globalBrowserMenuService.getMenuUsage(menu)
if not usage:
# usage could be None
usage = u''
s.useUsage(usage)
class_.usage = usage
defineChecker(class_,
NamesChecker(("__call__", "__getitem__",
"browserDefault", "publishTraverse"),
permission))
s.provideView(for_, name, IBrowserRequest, class_, layer)
=== Added File Zope3/src/zope/app/form/browser/editwizard.pt ===
<tal:tag condition="view/update"
/><html metal:use-macro="views/standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data" >
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:condition="view/feedback" tal:content="view/feedback" />
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row" metal:define-macro="widget_rows"
tal:repeat="widget view/widgets">
<div class="label" tal:content="structure widget/label">Name</div>
<div class="field" tal:content="structure widget">
<input type="text" style="width:100%"/>
</div>
<div class="error" tal:define="error widget/error"
tal:condition="error" tal:content="structure error">
The Error
</div>
</div>
<div tal:replace="structure view/renderHidden">
<!-- type=hidden input controls for passing state without session -->
<input type="hidden" name="example" value="foo" />
</div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
</div>
<div class="row">
<div class="controls">
<!-- <input type="submit" value="Refresh"
i18n:attributes="value refresh-button" /> -->
<input tal:condition="view/show_previous"
type="submit" name="PREVIOUS_SUBMIT" value="Previous"
i18n:attributes="value previous-button" />
<input tal:condition="view/show_submit"
type="submit" name="UPDATE_SUBMIT" value="Submit"
i18n:attributes="value submit-button"/>
<input tal:condition="view/show_next"
type="submit" name="NEXT_SUBMIT" value="Next"
i18n:attributes="value next-button" />
</div>
</div>
</form>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/form/browser/editwizard.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Edit Wizard View Classes
$Id: editwizard.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.component import getAdapter
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.security.checker import defineChecker, NamesChecker
from zope.app import zapi
from zope.app.event import publish
from zope.app.event.objectevent import ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.publisher.browser.globalbrowsermenuservice import \
globalBrowserMenuService
from editview import EditView
from submit import Next, Previous, Update
from zope.app.form.interfaces import WidgetInputError, WidgetsError
from zope.app.form.utility \
import setUpEditWidgets, getWidgetsData, applyWidgetsChanges
PaneNumber = 'CURRENT_PANE_IDX'
# TODO: Needs to be persistent aware for session (?)
class WizardStorage(dict):
def __init__(self, fields, content):
super(WizardStorage, self).__init__(self)
if content:
for k in fields:
self[k] = getattr(content,k)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError, key
def __setattr__(self, key, value):
self[key] = value
class EditWizardView(EditView):
def _setUpWidgets(self):
adapted = getAdapter(self.context, self.schema)
if adapted is not self.context:
if not ILocation.providedBy(adapted):
adapted = LocationProxy(adapted)
adapted.__parent__ = self.context
self.adapted = adapted
if self.use_session:
# Need session for File upload fields
raise NotImplementedError, \
'Cannot be implemented until we have an ISessionDataManager'
else:
self.storage = WizardStorage(self.fieldNames, adapted)
# Add all our widgets as attributes on this view
setUpEditWidgets(self, self.schema, source=self.storage,
names=self.fieldNames)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.currentPane().names
]
_current_pane_idx = 0
def currentPane(self):
return self.panes[self._current_pane_idx]
_update_called = 0
# Message rendered at the top of the form, probably set by update()
feedback = u''
def update(self):
'''Called before rendering each pane. It is responsible
for extracting data into temporary storage, and selecting
which pane should be rendered.
'''
# Calling twice does nothing
if self._update_called:
return
self._update_called = 1
# Determine the current pane
if PaneNumber in self.request:
self._current_pane_idx = int(self.request[PaneNumber])
assert self._current_pane_idx >= 0
assert self._current_pane_idx < len(self.panes)
else:
# First page
self._current_pane_idx = 0
self.errors = {}
self.label = self.currentPane().label
self._choose_buttons()
return
# Validate the current pane, and set self.errors
try:
names = self.currentPane().names
data = getWidgetsData(self, self.schema, names=names)
self.errors = {}
except WidgetsError, errors:
x = {}
for k, label, msg in errors:
x[k] = msg
self.errors = x
else:
self.storage.update(data)
if Next in self.request:
self._current_pane_idx += 1
assert self._current_pane_idx < len(self.panes)
elif Previous in self.request:
self._current_pane_idx -= 1
assert self._current_pane_idx >= 0
elif Update in self.request:
if not self.use_session:
# Data from panes other than the current one is still
# stuck in request
self.storage.update(getWidgetsData(
self, self.schema, names=self.fieldNames))
if self.apply_update(self.storage):
self.feedback = _(u'No changes to save')
else:
self.feedback = _(u'Changes saved')
# Set the current label
self.label = self.currentPane().label
self._choose_buttons()
def _choose_buttons(self):
'''Determine what buttons appear when we render the current pane'''
# The submit button appears if every field on every pane except the
# current one has valid input or a valid default value.
# This is almost always the case for edit forms.
try:
for k in self.fieldNames:
if k not in self.currentPane().names:
getattr(self, k).getInputValue()
self.show_submit = 1
except WidgetInputError:
self.show_submit = 0
self.show_next = (self._current_pane_idx < len(self.panes) - 1)
self.show_previous = self._current_pane_idx > 0
def apply_update(self, storage):
''' Save changes to our content object '''
for k,v in storage.items():
getattr(self,k).setRenderedValue(v)
content = self.adapted
changed = applyWidgetsChanges(self, self.schema, target=content,
names=self.fieldNames)
# We should not generate events when an adapter is used.
# That's the adapter's job
if changed and self.context is self.adapted:
publish(content, ObjectModifiedEvent(content))
return not changed
def renderHidden(self):
''' Render state as hidden fields. Also render hidden fields to
propagate self.storage if we are not using the session to do this.
'''
olist = []
out = olist.append
# the index of the pane being rendered needs to be propagated
out('<input class="hiddenType" type="hidden" name="%s" value="%d" />'%(
PaneNumber, self._current_pane_idx
))
if self.use_session:
# Need to output a unique key as a hidden field to identity this
# particular wizard. We use this to ensure data for this view
# doesn't conflict with other wizards in progress in other
# browser windows.
# Otherwise, no more state to propagate
raise NotImplementedError, 'use_session'
else:
current_fields = self.currentPane().names
for k in self.fieldNames:
if k not in current_fields:
widget = getattr(self, k)
out(widget.hidden())
return ''.join(olist)
def EditWizardViewFactory(name, schema, permission, layer,
panes, fields, template, default_template, bases, for_,
menu=u'', usage=u'', use_session=True):
# XXX What about the __implements__ of the bases?
class_ = SimpleViewClass(template, used_for=schema, bases=bases)
class_.schema = schema
class_.panes = panes
class_.fieldNames = fields
class_.use_session = use_session
class_.generated_form = ViewPageTemplateFile(default_template)
class_.usage = usage or (
menu and globalBrowserMenuService.getMenuUsage(menu))
defineChecker(
class_,
NamesChecker(("__call__", "__getitem__", "browserDefault"), permission)
)
s = zapi.getService(None, zapi.servicenames.Presentation)
s.provideView(for_, name, IBrowserRequest, class_, layer)
=== Added File Zope3/src/zope/app/form/browser/enumerated.py ===
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Widgets for enumerated field flavours.
$Id: enumerated.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from widget import TextWidget, IntWidget, FloatWidget, \
DatetimeWidget, DateWidget
__metaclass__ = type
class Enumerated:
"""Mixin for enumerated field widgets
"""
def __init__(self, *args):
super(Enumerated, self).__init__(*args)
field = self.context
if field.allowed_values is not None:
values = []
# if field is optional and missing_value isn't in
# allowed_values, add an additional option at top to represent
# field.missing_value
if not field.required and \
field.missing_value not in field.allowed_values:
values.append(field.missing_value)
values += list(field.allowed_values)
self.__values = values
def __call__(self):
selected = self._showData()
result = ['<select id="%s" name="%s">' % (self.name, self.name)]
for value in self.__values:
unconverted = self._unconvert(value)
selectedStr = unconverted == selected and ' selected' or ''
result.append('<option value="%s"%s>%s</option>' % \
(unconverted, selectedStr, unconverted))
result.append('</select>')
return '\n\t'.join(result)
class EnumeratedTextWidget(Enumerated, TextWidget):
"""EnumeratedText widget (for TextLines)
"""
class EnumeratedIntWidget(Enumerated, IntWidget):
"""EnumeratedInt widget
"""
class EnumeratedFloatWidget(Enumerated, FloatWidget):
"""EnumeratedFloat widget
"""
class EnumeratedDatetimeWidget(Enumerated, DatetimeWidget):
"""EnumeratedDatetime widget
"""
class EnumeratedDateWidget(Enumerated, DateWidget):
"""EnumeratedDate widget
"""
=== Added File Zope3/src/zope/app/form/browser/interfaces.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: interfaces.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.interface import Interface
from zope.app.form.interfaces import IWidget
class IAddFormCustomization(Interface):
"""This interface defined methods of add forms that can be overridden
Classes supplied when defining add forms may need to override some
of these methods.
In particular, when the context of an add form is not an IAdding,
a subclass needs to override ``nextURL`` and one of ``add`` or
``createAndAdd``.
To see how all this fits together, here's pseudo code for the
update() method of the form:
def update(self):
data = <get data from widgets> # a dict
self.createAndAdd(data)
self.request.response.redirect(self.nextURL())
def createAndAdd(self, data):
content = <create the content from the data>
content = self.add(content) # content wrapped in some context
<set after-add attributes on content>
"""
def createAndAdd(data):
"""Create a new object from the given data and the resulting object.
The data argument is a dictionary with values supplied by the form.
If any user errors occur, they should be collected into a list
and raised as a WidgetsError.
(For the default implementation, see pseudo-code in class docs.)
"""
def add(content):
"""Add the given content
This method is overridden when the context of the add form is
not an IAdding. In this case, the class that customizes the
form must take over adding the object.
The content should be returned wrapped in the context of the
object that it was added to.
The default implementation returns self.context.add(content),
i.e. it delegates to the IAdding view.
"""
def nextURL():
"""Return the URL to be displayed after the add operation.
This can be relative to the view's context.
The default implementation returns self.context.nextURL(),
i.e. it delegates to the IAdding view.
"""
class IBrowserWidget(IWidget):
"""A field widget contains all the properties that are required
to represent a field. Properties include css_sheet,
default value and so on.
"""
def __call__(): # XXX promote to IWidget?
"""Render the widget
"""
def hidden():
"""Render the widget as a hidden field
"""
def label():
"""Render a label tag"""
def error(): # XXX promote to IWidget?
"""Render the validation error for the widget, or return
an empty string if no error"""
def row():
"""Render the widget as two or three div elements,
for the label, the field and possibly the validation error
For example:
<div class="label">label</div><div class="field">field</div>
<div class="error">Validation error message</div>
"""
class IFormCollaborationView(Interface):
"""Views that collaborate to create a single form
When a form is applied, the changes in the form need to
be applied to individual views, which update objects as
necessary.
"""
def __call__():
"""Render the view as a part of a larger form.
Form input elements should be included, prefixed with the
prefix given to setPrefix.
'form' and 'submit' elements should not be included. They
will be provided for the larger form.
"""
def setPrefix(prefix):
"""Set the prefix used for names of input elements
Element names should begin with the given prefix,
followed by a dot.
"""
def update():
"""Update the form with data from the request.
"""
class IVocabularyQueryView(Interface):
"""View support for IVocabularyQuery objects.
Implementations of this interface are used by vocabulary field
edit widgets to support query and result presentations.
"""
def setName(name):
"""Set the name used to compute the form field names.
Form field names should be the given name, or additional name
components separated by dots may be appended if multiple form
fields are needed.
This method will be called after the IVocabularyQueryView has
been created and before performAction() is called.
"""
def setWidget(widget):
"""Set the widget using this query view.
This allows the query view to take advantage of rendering
helper methods made available by the widget.
This method will be called after the IVocabularyQueryView has
been created and before performAction() is called.
"""
def performAction(value):
"""Perform any action indicated by any submit buttons in the
sub-widget.
'value' is the current value of the field. Submit actions may
cause the value to be modified. If so, the new value should
be returned; otherwise the old value should be returned.
Actions should only be performed if a submit button provided
by the view was selected.
This method will be called after setName() and setWidget() and
before renderInput() or renderResults().
"""
def renderInput():
"""Return a rendering of the input portion of the widget."""
def renderResults(value):
"""Return a rendering of the results portion of the widget.
'value' is the current value represented by the widget.
"""
=== Added File Zope3/src/zope/app/form/browser/meta.zcml ===
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
>
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:complexDirective
name="addwizard"
schema=".metadirectives.IAddWizardDirective"
handler=".metaconfigure.AddWizardDirective"
>
<meta:subdirective
name="pane"
schema=".metadirectives.IPaneSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="editwizard"
schema=".metadirectives.IEditWizardDirective"
handler=".metaconfigure.EditWizardDirective"
>
<meta:subdirective
name="pane"
schema=".metadirectives.IPaneSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="editform"
schema=".metadirectives.IEditFormDirective"
handler=".metaconfigure.EditFormDirective"
/>
<meta:complexDirective
name="subeditform"
schema=".metadirectives.ISubeditFormDirective"
handler=".metaconfigure.SubeditFormDirective"
/>
<meta:complexDirective
name="addform"
schema=".metadirectives.IAddFormDirective"
handler=".metaconfigure.AddFormDirective"
/>
<meta:complexDirective
name="schemadisplay"
schema=".metadirectives.ISchemaDisplayDirective"
handler=".metaconfigure.SchemaDisplayDirective"
/>
</meta:directives>
</configure>
=== Added File Zope3/src/zope/app/form/browser/metaconfigure.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: metaconfigure.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
__metaclass__ = type
import os
from zope.configuration.exceptions import ConfigurationError
from zope.schema import getFieldNamesInOrder
from zope.app.container.interfaces import IAdding
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.globalbrowsermenuservice import \
menuItemDirective
from add import AddView, AddViewFactory
from editview import EditView, EditViewFactory
from addwizard import AddWizardView, AddWizardViewFactory
from editwizard import EditWizardView, EditWizardViewFactory
from schemadisplay import DisplayView, DisplayViewFactory
class BaseFormDirective:
# to be overriden by the subclasses
view = None
default_template = None
# default basic information
for_ = None
layer = 'default'
permission = 'zope.Public'
template = None
class_ = None
# default form information
title = None
label = None
menu = None
fields = None
def __init__(self, _context, **kwargs):
self._context = _context
for key, value in kwargs.items():
if not (value is None and hasattr(self, key)):
setattr(self, key, value)
self._normalize()
def _normalize(self):
if self.for_ is None:
self.for_ = self.schema
if self.class_ is None:
self.bases = (self.view,)
else:
self.bases = (self.class_, self.view)
if self.template is not None:
self.template = os.path.abspath(str(self.template))
if not os.path.isfile(self.template):
raise ConfigurationError("No such file", self.template)
else:
self.template = self.default_template
self.names = getFieldNamesInOrder(self.schema)
if self.fields:
for name in self.fields:
if name not in self.names:
raise ValueError("Field name is not in schema",
name, self.schema)
else:
self.fields = self.names
def _args(self):
return (self.name, self.schema, self.label, self.permission,
self.layer, self.template, self.default_template,
self.bases, self.for_, self.fields)
def _discriminator(self):
return ('view', self.for_, self.name, IBrowserRequest,
self.layer)
class Pane:
''' Holder for information about a Pane of a wizard '''
# TODO: Add more funky stuff to each pane, such as a validator
def __init__(self, field_names, label):
self.names = field_names
self.label = label
class BaseWizardDirective(BaseFormDirective):
# default wizard information
description = None
use_sessions = True
def __init__(self, _context, **kwargs):
super(BaseWizardDirective, self).__init__(_context, **kwargs)
self.panes = []
def _args(self):
return (self.name, self.schema, self.permission, self.layer,
self.panes, self.fields, self.template, self.default_template,
self.bases, self.for_)
def pane(self, _context, fields, label=''):
for f in fields:
if f not in self.fields:
raise ValueError(
'Field name is not in schema',
f, self.schema
)
self.panes.append(Pane(fields, label))
class AddFormDirective(BaseFormDirective):
view = AddView
default_template = 'add.pt'
usage = None
for_ = IAdding
# default add form information
description = None
content_factory = None
arguments = None
keyword_arguments = None
set_before_add = None
set_after_add = None
def _handle_menu(self):
if self.menu or self.title:
if (not self.menu) or (not self.title):
raise ValueError("If either menu or title are specified, "
"they must both be specified")
# XXX why no self.schema in for as in EditFormDirective
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission,
description=self.description)
def _handle_arguments(self, leftover=None):
schema = self.schema
fields = self.fields
arguments = self.arguments
keyword_arguments = self.keyword_arguments
set_before_add = self.set_before_add
set_after_add = self.set_after_add
if leftover is None:
leftover = fields
if arguments:
missing = [n for n in arguments if n not in fields]
if missing:
raise ValueError("Some arguments are not included in the form",
missing)
optional = [n for n in arguments if not schema[n].required]
if optional:
raise ValueError("Some arguments are optional, use"
" keyword_arguments for them",
optional)
leftover = [n for n in leftover if n not in arguments]
if keyword_arguments:
missing = [n for n in keyword_arguments if n not in fields]
if missing:
raise ValueError(
"Some keyword_arguments are not included in the form",
missing)
leftover = [n for n in leftover if n not in keyword_arguments]
if set_before_add:
missing = [n for n in set_before_add if n not in fields]
if missing:
raise ValueError(
"Some set_before_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_before_add]
if set_after_add:
missing = [n for n in set_after_add if n not in fields]
if missing:
raise ValueError(
"Some set_after_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_after_add]
self.set_after_add += leftover
else:
self.set_after_add = leftover
def __call__(self):
self._handle_menu()
self._handle_arguments()
self._context.action(
discriminator=self._discriminator(),
callable=AddViewFactory,
args=self._args()+(self.content_factory, self.arguments,
self.keyword_arguments,
self.set_before_add, self.set_after_add),
kw={'menu': self.menu, 'usage': self.usage},
)
class EditFormDirective(BaseFormDirective):
view = EditView
default_template = 'edit.pt'
usage = None
title = 'Edit'
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission)
def __call__(self):
self._handle_menu()
self._context.action(
discriminator=self._discriminator(),
callable=EditViewFactory,
args=self._args(),
kw={'menu': self.menu, 'usage': self.usage},
)
class SubeditFormDirective(BaseFormDirective):
view = EditView
default_template = 'subedit.pt'
# default subedit form directive
fulledit_path = None
fulledit_label = None
def __call__(self):
self._context.action(
discriminator = self._discriminator(),
callable = EditViewFactory,
args = self._args()+(self.fulledit_path, self.fulledit_label),
)
class AddWizardDirective(BaseWizardDirective, AddFormDirective):
view = AddWizardView
default_template = 'addwizard.pt'
use_session = False
def __call__(self):
self._handle_menu()
all_fields = self.fields
leftover = []
for pane in self.panes:
leftover.extend(pane.names)
self.fields = leftover[:]
self._handle_arguments(leftover)
self.fields = all_fields
self._context.action(
discriminator = self._discriminator(),
callable = AddWizardViewFactory,
args = self._args()+(self.content_factory, self.arguments,
self.keyword_arguments, self.set_before_add,
self.set_after_add, self.use_session)
)
class EditWizardDirective(BaseWizardDirective, EditFormDirective):
view = EditWizardView
default_template = 'editwizard.pt'
def __call__(self):
self._handle_menu()
self._context.action(
discriminator = self._discriminator(),
callable = EditWizardViewFactory,
args = self._args()+(self.menu, self.usage, self.use_session)
)
class SchemaDisplayDirective(EditFormDirective):
view = DisplayView
default_template = 'display.pt'
def __call__(self):
self._handle_menu()
self._context.action(
discriminator = self._discriminator(),
callable = DisplayViewFactory,
args = self._args()+(self.menu, self.usage)
)
=== Added File Zope3/src/zope/app/form/browser/metadirectives.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: metadirectives.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.interface import Interface
from zope.configuration.fields import GlobalObject, Tokens, Path, \
Bool, PythonIdentifier, MessageID
from zope.schema import Text, TextLine, Id
from zope.app.publisher.interfaces.browser import IUsage
class ICommonInformation(IUsage):
"""
Common information for all successive directives
"""
name = TextLine(
title=u"Name",
description=u"The name of the generated view.",
required=True
)
schema = GlobalObject(
title=u"Schema",
description=u"The schema from which the form is generated.",
required=True
)
for_ = GlobalObject(
title=u"Interface",
description=u"""
The interface this page (view) applies to.
The view will be for all objects that implement this
interface. The schema is used if the for attribute is not
specified.
If the for attribute is specified, then the objects views must
implement or be adaptable to the schema.""",
required=False
)
permission = Id(
title=u"Permission",
description=u"The permission needed to use the view.",
required=True
)
layer = TextLine(
title=u"Layer",
description=u"The later the view is in. Default: 'default'",
required=False
)
template = Path(
title=u"Template",
description=u"An alternate template to use for the form.",
required=False
)
class_ = GlobalObject(
title=u"Class",
description=u"""
A class to provide custom widget definitions or methods to be
used by a custom template.
This class is used as a mix-in class. As a result, it needn't
subclass any special classes, such as BrowserView.""",
required=False
)
class ICommonFormInformation(ICommonInformation):
"""
Common information for browser forms
"""
label = MessageID(
title=u"Label",
description=u"A label to be used as the heading for the form.",
required=False
)
menu = TextLine(
title=u"The browser menu to include the form in.",
description=u"""
Many views are included in menus. It's convenient to name the
menu in the page directive, rather than having to give a
separate menuItem directive.""",
required=False
)
title = MessageID(
title=u"Menu title",
description=u"The browser menu label for the form.",
required=False
)
fields = Tokens(
title=u"Fields",
description=u"""
The fields and the order in which to display them. If this is
not specified, all schema fields will be displayed in the
order specified in the schema itself.""",
required=False,
value_type=PythonIdentifier()
)
class ICommonWizardInformation(ICommonInformation):
"""
Common information for browser wizards
"""
menu = TextLine(
title=u"The browser menu to include the form in.",
description=u"""
Many views are included in menus. It's convenient to name the
menu in the page directive, rather than having to give a
separate menuItem directive.""",
required=False
)
title = MessageID(
title=u"Menu title",
description=u"The browser menu label for the form.",
required=False
)
description = MessageID(
title=u"A longer description of the add form.",
description=u"""
A UI may display this with the item or display it when the
user requests more assistance.""",
required=False
)
use_session = Bool(
title=u"Use session",
description=u"""
If 'no', hidden input controls are used to maintain state
between panes in the wizard. Only simple data types can
be propagated with this method.
Defaults to 'yes'.""",
required=False
)
class ICommonAddInformation(Interface):
"""
Common information for add forms/wizards
"""
content_factory = GlobalObject(
title=u"Content factory",
description=u"""
An object to call to create new content objects.
This attribute isn't used if a class is specified that
implements createAndAdd.""",
required=False
)
arguments = Tokens(
title=u"Arguments",
description=u"""
A list of field names to supply as positional arguments to the
factory.""",
required=False,
value_type=PythonIdentifier()
)
keyword_arguments = Tokens(
title=u"Keyword arguments",
description=u"""
A list of field names to supply as keyword arguments to the
factory.""",
required=False,
value_type=PythonIdentifier()
)
set_before_add = Tokens(
title=u"Set before add",
description=u"""
A list of fields to be assigned to the newly created object
before it is added.""",
required=False,
value_type=PythonIdentifier(),
)
set_after_add = Tokens(
title=u"Set after add",
description=u"""
A list of fields to be assigned to the newly created object
after it is added.""",
required=False,
value_type=PythonIdentifier()
)
class IAddWizardDirective(ICommonWizardInformation, ICommonAddInformation):
"""
Define an automatically generated add wizard (multi-page form)
The addwizard directive creates and registers a view for adding an
object based on a schema.
Adding an object is a bit trickier than editing an object, because
the object the schema applies to isn't available when forms are
being rendered. The addwizard directive provides an customization
interface to overcome this difficulty.
See zope.app.browser.form.interfaces.IAddFormCustomization.
"""
class IEditWizardDirective(ICommonWizardInformation):
"""
Define an automatically generated edit wizard (multi-page form).
The editwizard directive creates and register's a view for editing
an object based on a schema.
"""
title = MessageID(
title=u"The browser menu label for the edit form",
description=u"This attribute defaults to 'Edit'.",
required=False
)
class IPaneSubdirective(Interface):
"""
Define a Pane (page) of the wizard
"""
label = MessageID(
title=u"Label",
description=u"The label used as the heading on this pane",
required=False,
)
fields = Tokens(
title=u"Fields",
description=u"The fields to display on this pane of the wizard",
required=True,
value_type=PythonIdentifier()
)
class IEditFormDirective(ICommonFormInformation):
"""
Define an automatically generated edit form
The editform directive creates and register's a view for editing
an object based on a schema.
"""
class ISubeditFormDirective(ICommonInformation):
"""
Define a subedit form
"""
label = TextLine(
title=u"Label",
description=u"A label to be used as the heading for the form.",
required=False
)
fulledit_path = TextLine(
title=u"Path (relative URL) to the full edit form",
required=False
)
fulledit_label = MessageID(
title=u"Label of the full edit form",
required=False
)
class IAddFormDirective(ICommonFormInformation, ICommonAddInformation):
"""
Define an automatically generated add form
The addform directive creates and registers a view for adding an
object based on a schema.
Adding an object is a bit trickier than editing an object, because
the object the schema applies to isn't available when forms are
being rendered. The addform directive provides an customization
interface to overcome this difficulty.
See zope.app.browser.form.interfaces.IAddFormCustomization.
"""
description = Text(
title=u"A longer description of the add form.",
description=u"""
A UI may display this with the item or display it when the
user requests more assistance.""",
required=False
)
class ISchemaDisplayDirective(ICommonFormInformation):
"""
Define an automatically generated display form.
The schemadisplay directive creates and register's a view for
displaying an object based on a schema.
"""
title = MessageID(
title=u"The browser menu label for the edit form",
description=u"This attribute defaults to 'Edit'.",
required=False
)
=== Added File Zope3/src/zope/app/form/browser/schemadisplay.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Support for display-only pages based on schema.
$Id: schemadisplay.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from zope.app import zapi
from zope.schema import getFieldNamesInOrder
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.publisher.browser import BrowserView
from zope.security.checker import defineChecker, NamesChecker
from zope.component import getAdapter
from zope.app.form.utility import setUpDisplayWidgets
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
class DisplayView(BrowserView):
"""Simple display-view base class.
Subclasses should provide a schema attribute defining the schema
to be displayed.
"""
errors = ()
update_status = ''
label = ''
# Fall-back field names computes from schema
fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
def __init__(self, context, request):
super(DisplayView, self).__init__(context, request)
self._setUpWidgets()
def _setUpWidgets(self):
adapted = getAdapter(self.context, self.schema)
if adapted is not self.context:
if not ILocation.providedBy(adapted):
adapted = LocationProxy(adapted)
adapted.__parent__ = self.context
self.adapted = adapted
setUpDisplayWidgets(self, self.schema, source=adapted,
names=self.fieldNames)
def setPrefix(self, prefix):
for widget in self.widgets():
widget.setPrefix(prefix)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.fieldNames]
def DisplayViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u'',
usage=u''):
# XXX What about the __implements__ of the bases?
class_ = SimpleViewClass(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full display"
class_.fulledit_label = fulledit_label
class_.generated_form = ViewPageTemplateFile(default_template)
class_.usage = usage or (
menu and globalBrowserMenuService.getMenuUsage(menu)
)
defineChecker(class_,
NamesChecker(("__call__", "__getitem__", "browserDefault"),
permission))
s = zapi.getService(None, zapi.servicenames.Presentation)
s.provideView(for_, name, IBrowserRequest, class_, layer)
=== Added File Zope3/src/zope/app/form/browser/subedit.pt ===
<div metal:define-macro="formbody">
<h5 metal:define-slot="heading">
<a tal:attributes="href context/@@absolute_url"
tal:condition="view/label"
tal:content="view/label">
Edit something
</a>
</h5>
<p tal:condition="view/fulledit_label">
<a tal:attributes="href
string:${context/@@absolute_url}/${view/fulledit_path}"
tal:content="view/fulledit_label">Full edit</a>
</p>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<div tal:condition="view/errors">
<ul>
<li tal:repeat="error view/errors">
<strong tal:content="error/__class__">
Error Type</strong>:
<span tal:content="error">Error text</span>
</li>
</ul>
</div>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row" metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row" metal:define-macro="widget_rows"
tal:repeat="widget view/widgets">
<div class="label" tal:content="structure widget/label">Name</div>
<div class="field" tal:content="structure widget">
<input type="text" style="width:100%"/>
</div>
<div class="error" tal:define="error widget/error"
tal:condition="error" tal:content="structure error">
The Error
</div>
</div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
</div>
=== Added File Zope3/src/zope/app/form/browser/submit.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Standard submit button names
Update -- Name of the standard update submit button
$Id: submit.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
Next = "NEXT_SUBMIT"
Previous = "PREVIOUS_SUBMIT"
Update = "UPDATE_SUBMIT"
=== Added File Zope3/src/zope/app/form/browser/vocabularywidget.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Vocabulary widget support.
This includes support for vocabulary fields' use of the vocabulary to
determine the actual widget to display, and support for supplemental
query objects and helper views.
$Id: vocabularywidget.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
from xml.sax.saxutils import quoteattr
from zope.interface import implements, implementedBy
from zope.interface.declarations import directlyProvides
from zope.publisher.browser import BrowserView
from zope.security.proxy import trustedRemoveSecurityProxy
from zope.schema.interfaces import IIterableVocabularyQuery
from zope.schema.interfaces import ValidationError
from zope.i18n import translate
from zope.app import zapi
from zope.app.form.browser import widget
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.form.browser.interfaces import IVocabularyQueryView
from zope.app.form.interfaces import WidgetInputError
# These widget factories delegate to the vocabulary on the field.
# Display
def VocabularyFieldDisplayWidget(field, request):
"""Return a display widget based on a vocabulary field."""
return _get_vocabulary_widget(field, request, "display")
def VocabularyBagFieldDisplayWidget(field, request):
"""Return a display widget based on a vocabulary field."""
return _get_vocabulary_widget(field, request, "display-bag")
def VocabularyListFieldDisplayWidget(field, request):
"""Return a display widget based on a vocabulary field."""
return _get_vocabulary_widget(field, request, "display-list")
def VocabularySetFieldDisplayWidget(field, request):
"""Return a display widget based on a vocabulary field."""
return _get_vocabulary_widget(field, request, "display-set")
def VocabularyUniqueListFieldDisplayWidget(field, request):
"""Return a display widget based on a vocabulary field."""
return _get_vocabulary_widget(field, request, "display-unique-list")
# Edit
def VocabularyFieldEditWidget(field, request):
"""Return a value-selection widget based on a vocabulary field."""
return _get_vocabulary_edit_widget(field, request)
def VocabularyBagFieldEditWidget(field, request):
"""Return a value-selection widget based on a vocabulary field."""
return _get_vocabulary_edit_widget(field, request, "bag")
def VocabularyListFieldEditWidget(field, request):
"""Return a value-selection widget based on a vocabulary field."""
return _get_vocabulary_edit_widget(field, request, "list")
def VocabularySetFieldEditWidget(field, request):
"""Return a value-selection widget based on a vocabulary field."""
return _get_vocabulary_edit_widget(field, request, "set")
def VocabularyUniqueListFieldEditWidget(field, request):
"""Return a value-selection widget based on a vocabulary field."""
return _get_vocabulary_edit_widget(field, request, "unique-list")
# Helper functions for the factories:
def _get_vocabulary_widget(field, request, viewname):
view = zapi.getView(field.vocabulary,
"field-%s-widget" % viewname, request)
view = trustedRemoveSecurityProxy(view)
view.setField(field)
return view
def _get_vocabulary_edit_widget(field, request, modifier=''):
if modifier:
modifier = "-" + modifier
viewname = "edit" + modifier
view = _get_vocabulary_widget(field, request, viewname)
query = field.vocabulary.getQuery()
if query is not None:
queryname = "widget-query%s-helper" % modifier
queryview = zapi.getView(query, queryname, request)
view.setQuery(query, queryview)
queryview.setWidget(view)
return view
class IterableVocabularyQuery(object):
"""Simple query object used to invoke the simple selection mechanism."""
implements(IIterableVocabularyQuery)
def __init__(self, vocabulary, *interfaces):
self.vocabulary = vocabulary
if interfaces:
directlyProvides(self, *interfaces)
class TranslationHook:
def translate(self, msgid):
return translate(self.context, msgid, context=self.request,
default=msgid)
def message(msgid, default):
msgid.default = default
return msgid
# Widget implementation:
class ViewSupport(object, TranslationHook):
# This is mixed into the vocabulary widget base classes.
"""Helper class for vocabulary and vocabulary-query widgets."""
def textForValue(self, term):
# Extract a string from the term. This can be overridden to
# support more complex term objects. The token is returned
# here since it's the only thing known to be a string, or
# str()able.
return term.token
def mkselectionlist(self, type, info, name):
L = [self.mkselectionitem(type, name, *item) for item in info]
return widget.renderElement("table",
contents="\n%s\n" % "\n".join(L))
def mkselectionitem(self, type, name, term, selected, disabled):
flag = ""
if selected:
flag = " checked='checked'"
if disabled:
flag += " disabled='disabled'"
if flag:
flag = "\n " + flag
return ("<tr><td>"
"<input type='%s' value='%s' name='%s'%s />"
"</td>\n <td>%s</td></tr>"
% (type, term.token, name, flag, self.textForValue(term)))
class VocabularyWidgetBase(ViewSupport, widget.BrowserWidget):
"""Convenience base class for vocabulary-based widgets."""
extra = ""
type = "vocabulary"
context = None
def __init__(self, context, request):
self.request = request
self.vocabulary = context
# self.context is set to the field in setField below
def setField(self, field):
assert self.context is None
# only allow this to happen for a bound field
assert field.context is not None
self.context = field
self.setPrefix(self._prefix)
assert self.name
def setPrefix(self, prefix):
super(VocabularyWidgetBase, self).setPrefix(prefix)
# names for other information from the form
self.empty_marker_name = self.name + "-empty-marker"
def __call__(self):
if self._data is self._data_marker:
if self.hasInput():
try:
value = self.getInputValue()
except WidgetInputError:
value = self.request.form.get(self.name, self._missing)
else:
value = self._getDefault()
else:
value = self._data
return self.render(value)
def render(self, value):
raise NotImplementedError(
"render() must be implemented by a subclass")
def convertTokensToValues(self, tokens):
L = []
for token in tokens:
try:
term = self.context.vocabulary.getTermByToken(token)
except LookupError:
raise WidgetInputError(
self.context.__name__,
self.context.title,
"token %r not found in vocabulary" % token)
else:
L.append(term.value)
return L
_have_field_data = False
def getInputValue(self):
value = self._compute_data()
field = self.context
# missing value is okay if field is not required
if value == field.missing_value and not field.required:
return value
# all other values must be valid
try:
field.validate(value)
except ValidationError, v:
self._error = WidgetInputError(
self.context.__name__, self.title, v)
raise self._error
return value
def _emptyMarker(self):
return "<input name='%s' type='hidden' value='1' />" % (
self.empty_marker_name)
def hasInput(self):
return (self.name in self.request.form or
self.empty_marker_name in self.request.form)
def setRenderedValue(self, value):
self._data = value
def _compute_data(self):
raise NotImplementedError(
"_compute_data() must be implemented by a subclass\n"
"It may be inherited from the mix-in classes SingleDataHelper\n"
"or MultiDataHelper (from zope.app.form.browser.vocabularywidget)")
def _showData(self):
raise NotImplementedError(
"vocabulary-based widgets don't use the _showData() method")
def _convert(self, value):
raise NotImplementedError(
"vocabulary-based widgets don't use the _convert() method")
def _unconvert(self, value):
raise NotImplementedError(
"vocabulary-based widgets don't use the _unconvert() method")
_msg_missing_single_value_display = message(
_("vocabulary-missing-single-value-for-display"), "")
_msg_missing_multiple_value_display = message(
_("vocabulary-missing-multiple-value-for-display"), "")
_msg_missing_single_value_edit = message(
_("vocabulary-missing-single-value-for-edit"), "(no value)")
_msg_missing_multiple_value_edit = message(
_("vocabulary-missing-multiple-value-for-edit"), "(no values)")
class SingleDataHelper(object):
def _compute_data(self):
token = self.request.form.get(self.name)
if token:
return self.convertTokensToValues([token])[0]
else:
return None
class MultiDataHelper(object):
def _compute_data(self):
if self.name in self.request.form:
tokens = self.request.form[self.name]
if not isinstance(tokens, list):
tokens = [tokens]
return self.convertTokensToValues(tokens)
return []
def _getDefault(self):
# Return the default value for this widget;
# may be overridden by subclasses.
val = self.context.default
if val is None:
val = []
return val
class VocabularyDisplayWidget(SingleDataHelper, VocabularyWidgetBase):
"""Simple single-selection display that can be used in many cases."""
_msg_no_value = _msg_missing_single_value_display
def render(self, value):
if value is None:
return self.translate(self._msg_no_value)
else:
term = self.context.vocabulary.getTerm(value)
return self.textForValue(term)
class VocabularyMultiDisplayWidget(MultiDataHelper, VocabularyWidgetBase):
itemTag = 'li'
tag = 'ol'
def render(self, value):
if value:
rendered_items = self.renderItems(value)
return widget.renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
cssClass=self.cssClass,
contents="\n".join(rendered_items),
extra=self.extra)
else:
return self.translate(_msg_missing_multiple_value_display)
def renderItems(self, value):
L = []
vocabulary = self.context.vocabulary
cssClass = self.cssClass or ''
if cssClass:
cssClass += "-item"
tag = self.itemTag
for v in value:
term = vocabulary.getTerm(v)
L.append(widget.renderElement(tag,
cssClass=cssClass,
contents=self.textForValue(term)))
return L
class VocabularyListDisplayWidget(VocabularyMultiDisplayWidget):
"""Display widget for ordered multi-selection fields.
This can be used for both VocabularyListField and
VocabularyUniqueListField fields.
"""
tag = 'ol'
class VocabularyBagDisplayWidget(VocabularyMultiDisplayWidget):
"""Display widget for unordered multi-selection fields.
This can be used for both VocabularyBagField and
VocabularySetField fields.
"""
tag = 'ul'
class ActionHelper(object, TranslationHook):
__actions = None
def addAction(self, action, msgid):
if self.__actions is None:
self.__actions = {}
assert action not in self.__actions
self.__actions[action] = msgid
def getAction(self):
assert self.__actions is not None, \
"getAction() called on %r with no actions defined" % self
get = self.request.form.get
for action in self.__actions.iterkeys():
name = "%s.action-%s" % (self.name, action)
if get(name):
return action
return None
def renderAction(self, action, disabled=False):
msgid = self.__actions[action]
return ("<input type='submit' name='%s.action-%s' value=%s %s />"
% (self.name, action, quoteattr(self.translate(msgid)),
disabled and "\n disabled='disabled' " or ""))
class VocabularyEditWidgetBase(VocabularyWidgetBase):
size = 5
tag = 'select'
query = None
queryview = None
def setQuery(self, query, queryview):
assert self.query is None
assert self.queryview is None
if query is None:
assert queryview is None
else:
assert queryview is not None
self.query = query
self.queryview = queryview
# Use of a hyphen to form the name for the query widget
# ensures that it won't clash with anything else, since
# field names are normally Python identifiers.
queryview.setName(self.name + "-query")
def setPrefix(self, prefix):
super(VocabularyEditWidgetBase, self).setPrefix(prefix)
if self.queryview is not None:
self.queryview.setName(self.name + "-query")
def render(self, value):
contents = []
have_results = False
if self.queryview is not None:
s = self.queryview.renderResults(value)
if s:
contents.append(self._div('queryresults', s))
s = self.queryview.renderInput()
contents.append(self._div('queryinput', s))
have_results = True
contents.append(self._div('value', self.renderValue(value)))
contents.append(self._emptyMarker())
if self.queryview is not None and not have_results:
s = self.queryview.renderInput()
if s:
contents.append(self._div('queryinput', s))
return self._div(self.cssClass, "\n".join(contents),
id=self.name)
def _div(self, cssClass, contents, **kw):
if contents:
return widget.renderElement('div',
cssClass=cssClass,
contents="\n%s\n" % contents,
**kw)
return ""
def renderItemsWithValues(self, values):
"""Render the list of possible values, with those found in
'values' being marked as selected."""
cssClass = self.cssClass
# multiple items with the same value are not allowed from a
# vocabulary, so that need not be considered here
rendered_items = []
count = 0
for term in self.context.vocabulary:
item_text = self.textForValue(term)
if term.value in values:
rendered_item = self.renderSelectedItem(count,
item_text,
term.token,
self.name,
cssClass)
else:
rendered_item = self.renderItem(count,
item_text,
term.token,
self.name,
cssClass)
rendered_items.append(rendered_item)
count += 1
return rendered_items
def renderItem(self, index, text, value, name, cssClass):
return widget.renderElement('option',
contents=text,
value=value,
cssClass=cssClass)
def renderSelectedItem(self, index, text, value, name, cssClass):
return widget.renderElement('option',
contents=text,
value=value,
cssClass=cssClass,
selected='selected')
class RadioWidget(SingleDataHelper, VocabularyEditWidgetBase):
"""Vocabulary-backed single-selection edit widget.
This widget can be used when the number of selections is going
to be small.
"""
implements(implementedBy(widget.SingleItemsWidget))
firstItem = False
_msg_no_value = _msg_missing_single_value_edit
_join_button_to_message_template = u"%s %s"
_join_messages_template = u"<br />\n"
def renderItem(self, index, text, value, name, cssClass):
elem = widget.renderElement('input',
value=value,
name=name,
cssClass=cssClass,
type='radio')
return self._join_button_to_message_template % (elem, text)
def renderSelectedItem(self, index, text, value, name, cssClass):
elem = widget.renderElement('input',
value=value,
name=name,
cssClass=cssClass,
checked=None,
type='radio')
return self._join_button_to_message_template % (elem, text)
def renderValue(self, value):
return "\n%s\n" % self._join_messages_template.join(
self.renderItems(value))
def renderItems(self, value):
# XXX this should be rolled into renderValue; separate only
# for the convenience of leveraging the already existing test
# framework
vocabulary = self.context.vocabulary
# check if we want to select first item
no_value = None
if (value == self.context.missing_value
and getattr(self.context, 'firstItem', False)
and len(vocabulary) > 0):
if self.context.required:
# Grab the first item from the iterator:
values = [iter(vocabulary).next().value]
else:
# the "no value" option will be checked
no_value = 'checked'
elif value != self.context.missing_value:
values = [value]
else:
values = ()
L = self.renderItemsWithValues(values)
if not self.context.required:
cssClass = self.cssClass
kwargs = {
'value':'',
'name':self.name,
'cssClass':cssClass,
'type':'radio'}
if no_value:
kwargs['checked']=None
option = widget.renderElement('input', **kwargs)
option = self._join_button_to_message_template % (
option, self.translate(self._msg_no_value))
L.insert(0, option)
return L
class SelectListWidget(SingleDataHelper, VocabularyEditWidgetBase):
"""Vocabulary-backed single-selection edit widget.
This widget can be used when the number of selections isn't going
to be very large.
"""
implements(implementedBy(widget.SingleItemsWidget))
firstItem = False
_msg_no_value = _msg_missing_single_value_edit
def renderValue(self, value):
rendered_items = self.renderItems(value)
contents = "\n%s\n" % "\n".join(rendered_items)
return widget.renderElement('select',
name=self.name,
contents=contents,
size=self.size,
extra=self.extra)
def renderItems(self, value):
vocabulary = self.context.vocabulary
# check if we want to select first item
if (value == self.context.missing_value
and getattr(self.context, 'firstItem', False)
and len(vocabulary) > 0):
# Grab the first item from the iterator:
values = [iter(vocabulary).next().value]
elif value != self.context.missing_value:
values = [value]
else:
values = ()
L = self.renderItemsWithValues(values)
if not self.context.required:
option = ("<option name='%s' value=''>%s</option>"
% (self.name, self.translate(self._msg_no_value)))
L.insert(0, option)
return L
# more general alias
VocabularyEditWidget = SelectListWidget
class DropdownListWidget(SelectListWidget):
"""Variation of the SelectListWidget that uses a drop-down list."""
size = 1
class VocabularyMultiEditWidget(MultiDataHelper, VocabularyEditWidgetBase):
"""Vocabulary-backed widget supporting multiple selections."""
_msg_no_value = _msg_missing_multiple_value_edit
def renderItems(self, value):
if value == self.context.missing_value:
values = ()
else:
values = list(value)
return self.renderItemsWithValues(values)
def renderValue(self, value):
# All we really add here is the ':list' in the name argument
# to widget.renderElement().
rendered_items = self.renderItems(value)
return widget.renderElement(self.tag,
name=self.name + ':list',
multiple=None,
size=self.size,
contents="\n".join(rendered_items),
extra=self.extra)
class VocabularyQueryViewBase(ActionHelper, ViewSupport, BrowserView):
"""Vocabulary query support base class."""
implements(IVocabularyQueryView)
# This specifically isn't a widget in it's own right, but is a
# form of BrowserView (at least conceptually).
widget = None
def __init__(self, context, request):
self.vocabulary = context.vocabulary
self.context = context
self.request = request
super(VocabularyQueryViewBase, self).__init__(context, request)
def setName(self, name):
assert not name.endswith(".")
self.name = name
def setWidget(self, widget):
assert self.widget is None
assert widget is not None
self.widget = widget
def renderInput(self):
return self.renderQueryInput()
def renderResults(self, value):
results = self.getResults()
if results is not None:
return self.renderQueryResults(results, value)
else:
return ""
def renderQueryResults(self, results, value):
raise NotImplementedError(
"renderQueryResults() must be implemented by a subclass")
def renderQueryInput(self):
raise NotImplementedError(
"renderQueryInput() must be implemented by a subclass")
def getResults(self):
# This is responsible for running the query against the query
# object (self.context), and returning a results object. If
# there isn't a query in the form, returns None.
return None
def performAction(self, value):
return value
ADD_DONE = "adddone"
ADD_MORE = "addmore"
MORE = "more"
class IterableVocabularyQueryViewBase(VocabularyQueryViewBase):
"""Query view for IIterableVocabulary objects without more
specific query views.
This should only be used (directly) for vocabularies for which
getQuery() returns None.
"""
implements(IVocabularyQueryView)
queryResultBatchSize = 8
_msg_add_done = message(_("vocabulary-query-button-add-done"),
"Add+Done")
_msg_add_more = message(_("vocabulary-query-button-add-more"),
"Add+More")
_msg_more = message(_("vocabulary-query-button-more"),
"More")
_msg_no_results = message(_("vocabulary-query-message-no-results"),
"No Results")
_msg_results_header = message(_("vocabulary-query-header-results"),
"Search results")
def __init__(self, *args, **kw):
super(IterableVocabularyQueryViewBase, self).__init__(*args, **kw)
self.addAction(ADD_DONE, self._msg_add_done)
self.addAction(ADD_MORE, self._msg_add_more)
self.addAction(MORE, self._msg_more)
def setName(self, name):
super(IterableVocabularyQueryViewBase, self).setName(name)
name = self.name
self.query_index_name = name + ".start"
self.query_selections_name = name + ".picks"
#
get = self.request.form.get
self.action = self.getAction()
self.query_index = None
if self.query_index_name in self.request.form:
try:
index = int(self.request.form[self.query_index_name])
except ValueError:
pass
else:
if index >= 0:
self.query_index = index
QS = get(self.query_selections_name, [])
if not isinstance(QS, list):
QS = [QS]
self.query_selections = []
for token in QS:
try:
term = self.vocabulary.getTermByToken(token)
except LookupError:
# XXX unsure what to pass to exception constructor
raise WidgetInputError(
"(query view for %s)" % self.context,
"(query view for %s)" % self.context,
"token %r not in vocabulary" % token)
else:
self.query_selections.append(term.value)
def renderQueryInput(self):
# There's no query support, so we can't actually have input.
return ""
def getResults(self):
if self.query_index is not None:
return self.vocabulary
else:
return None
def renderQueryResults(self, results, value):
# display query results batch
it = iter(results)
qi = self.query_index
have_more = True
try:
for xxx in range(qi):
it.next()
except StopIteration:
# we should only get here with a botched request; ADD_MORE
# and MORE will normally be disabled if there are no results
# (see below)
have_more = False
items = []
QS = []
try:
for i in range(qi, qi + self.queryResultBatchSize):
term = it.next()
disabled = term.value in value
selected = disabled
if term.value in self.query_selections:
QS.append(term.value)
selected = True
items.append((term, selected, disabled))
else:
# see if there's anything else:
it.next()
except StopIteration:
if not items:
return "<div class='results'>%s</div>" % (
self.translate(self._msg_no_results))
have_more = False
self.query_selections = QS
return ''.join(
["<div class='results'>\n",
"<h4>%s</h4>\n" % (
self.translate(self._msg_results_header)),
self.makeSelectionList(items, self.query_selections_name),
"\n",
self.renderAction(ADD_DONE), "\n",
self.renderAction(ADD_MORE, not have_more), "\n",
self.renderAction(MORE, not have_more), "\n"
"<input type='hidden' name='%s' value='%d' />\n"
% (self.query_index_name, qi),
"</div>"])
def performAction(self, value):
if self.action == ADD_DONE:
value = self.addSelections(value)
self.query_index = None
self.query_selections = []
elif self.action == ADD_MORE:
value = self.addSelections(value)
self.query_index += self.queryResultBatchSize
elif self.action == MORE:
self.query_index += self.queryResultBatchSize
elif self.action:
raise ValueError("unknown action in request: %r" % self.action)
return value
def addSelections(self, value):
for item in self.query_selections:
if item not in value and item in self.context.vocabulary:
value.append(item)
return value
class IterableVocabularyQueryView(IterableVocabularyQueryViewBase):
def makeSelectionList(self, items, name):
return self.mkselectionlist("radio", items, name)
def renderQueryResults(self, results, value):
return super(IterableVocabularyQueryView, self).renderQueryResults(
results, [value])
class IterableVocabularyQueryMultiView(IterableVocabularyQueryViewBase):
def makeSelectionList(self, items, name):
return self.mkselectionlist("checkbox", items, name)
=== Added File Zope3/src/zope/app/form/browser/vocabularywidget.zcml ===
<configure xmlns="http://namespaces.zope.org/zope">
<!-- Query view helpers -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
allowed_interface="zope.app.form.browser.interfaces.IVocabularyQueryView"
for="zope.schema.interfaces.IIterableVocabularyQuery"
name="widget-query-helper"
factory=".vocabularywidget.IterableVocabularyQueryView"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
allowed_interface="zope.app.form.browser.interfaces.IVocabularyQueryView"
for="zope.schema.interfaces.IIterableVocabularyQuery"
name="widget-query-list-helper"
factory=".vocabularywidget.IterableVocabularyQueryMultiView"
permission="zope.Public"
/>
<!-- Vocabulary field display widgets -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyDisplayWidget"
name="field-display-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyBagDisplayWidget"
name="field-display-bag-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyListDisplayWidget"
name="field-display-list-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyBagDisplayWidget"
name="field-display-set-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyListDisplayWidget"
name="field-display-unique-list-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<!-- Vocabulary edit widgets -->
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyEditWidget"
name="field-edit-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".vocabularywidget.VocabularyMultiEditWidget"
name="field-edit-list-widget"
for="zope.schema.interfaces.IVocabulary"
permission="zope.Public"
/>
</configure>
=== Added File Zope3/src/zope/app/form/browser/widget.py ===
##############################################################################
#
# Copyright (c) 2001-2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser Widget Definitions
$Id: widget.py,v 1.1 2004/03/14 01:11:34 srichter Exp $
"""
import re, cgi
import traceback
from warnings import warn
from xml.sax.saxutils import quoteattr
from zope.interface import implements
from zope.proxy import removeAllProxies
from zope.schema import getFieldNamesInOrder
from zope.schema.interfaces import ValidationError
from zope.publisher.browser import BrowserView
from zope.i18n import translate
from zope.app import zapi
from zope.app.tests import ztapi
from zope.app.form.interfaces import IInputWidget
from zope.app.form.browser.interfaces import IBrowserWidget
from zope.app.form.widget import Widget
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.interfaces import ConversionError, WidgetInputError
from zope.app.form.interfaces import MissingInputError
from zope.app.datetimeutils import parseDatetimetz
from zope.app.datetimeutils import DateTimeError
from zope.app.i18n import ZopeMessageIDFactory as _
ListTypes = list, tuple
class BrowserWidget(Widget, BrowserView):
"""A field widget that knows how to display itself as HTML.
When we generate labels, titles, descriptions, and errors, the
labels, titles, and descriptions are translated and the
errors are rendered with the view machinery, so we need to set up
a lot of machinery to support translation and views:
>>> setUp() # now we have to set up an error view...
>>> from zope.app.form.interfaces import IWidgetInputError
>>> from zope.app.publisher.browser import BrowserView
>>> from cgi import escape
>>> class SnippetErrorView(BrowserView):
... def __call__(self):
... return escape(self.context.errors[0])
...
>>> ztapi.browserView(IWidgetInputError, 'snippet', SnippetErrorView)
>>> from zope.publisher.browser import TestRequest
And now the tests proper...
>>> from zope.schema import Field
>>> import re
>>> isFriendly=re.compile(".*hello.*").match
>>> field = Field(__name__='foo', title=u'Foo', constraint=isFriendly)
>>> request = TestRequest(form={
... 'field.foo': u'hello\\r\\nworld',
... 'baz.foo': u'bye world'})
>>> widget = BrowserWidget(field, request)
>>> widget.name
'field.foo'
>>> widget.title
u'Foo'
>>> widget.hasInput()
True
>>> widget.getInputValue()
u'hello\\r\\nworld'
>>> widget.required
True
>>> widget._error is None
True
>>> widget.error()
''
>>> widget.setRenderedValue('Hey\\nfolks')
>>> widget.getInputValue()
u'hello\\r\\nworld'
>>> widget._error is None
True
>>> widget.error()
''
>>> widget.setPrefix('baz')
>>> widget.name
'baz.foo'
>>> widget.error()
''
>>> try:
... widget.getInputValue()
... except WidgetInputError:
... print widget._error.errors
(u'Constraint not satisfied', u'bye world')
>>> widget.error()
u'Constraint not satisfied'
>>> widget._error = None # clean up for next round of tests
>>> widget.setPrefix('test')
>>> widget.name
'test.foo'
>>> widget._error is None
True
>>> widget.error()
''
>>> widget.hasInput()
False
>>> widget.getInputValue()
Traceback (most recent call last):
...
MissingInputError: ('test.foo', u'Foo', None)
>>> field.required = False
>>> widget.request.form['test.foo'] = u''
>>> widget.required
False
>>> widget.getInputValue() == field.missing_value
True
>>> widget._error is None
True
>>> widget.error()
''
>>> print widget.label()
<label for="test.foo">Foo</label>
Now we clean up.
>>> tearDown()
"""
implements(IBrowserWidget)
tag = 'input'
type = 'text'
cssClass = ''
extra = ''
_missing = ''
_error = None
required = property(lambda self: self.context.required)
def hasInput(self):
"""See IWidget.hasInput.
Returns True if the submitted request form contains a value for
the widget, otherwise returns False.
Some browser widgets may need to implement a more sophisticated test
for input. E.g. checkbox values are not supplied in submitted
forms when their value is 'off' -- in this case the widget will
need to add a hidden element to signal its presence in the form.
"""
return self.name in self.request.form
def hasValidInput(self):
try:
self.getInputValue()
return True
except WidgetInputError:
return False
def getInputValue(self):
self._error = None
field = self.context
# form input is required, otherwise raise an error
input = self.request.form.get(self.name)
if input is None:
raise MissingInputError(self.name, self.title, None)
# convert input to suitable value - may raise conversion error
value = self._convert(input)
# allow missing values only for non-required fields
if value == field.missing_value and not field.required:
return value
# value must be valid per the field constraints
try:
field.validate(value)
except ValidationError, v:
self._error = WidgetInputError(
self.context.__name__, self.title, v)
raise self._error
return value
def validate(self):
self.getInputValue()
def applyChanges(self, content):
field = self.context
value = self.getInputValue()
if field.query(content, self) != value:
field.set(content, value)
return True
else:
return False
def _convert(self, input):
"""Converts input to a value appropriate for the field type.
Widgets for non-string fields should override this method to
perform an appropriate conversion.
This method is used by getInputValue to perform the conversion
of a form input value (keyed by the widget's name) to an appropriate
field value. Widgets that require a more complex conversion process
(e.g. utilize more than one form field) should override getInputValue
and disregard this method.
"""
if input == self._missing:
return self.context.missing_value
else:
return input
def _unconvert(self, value):
"""Converts a field value to a string used as an HTML form value.
This method is used in the default rendering of widgets that can
represent their values in a single HTML form value. Widgets whose
fields have more complex data structures should disregard this
method and override the default rendering method (__call__).
"""
if value == self.context.missing_value:
return self._missing
else:
return value
def _showData(self):
"""Returns a value suitable for use as an HTML form value."""
if not self._renderedValueSet():
if self.hasInput():
try:
value = self.getInputValue()
except WidgetInputError:
return self.request.form.get(self.name, self._missing)
else:
value = self._getDefault()
else:
value = self._data
return self._unconvert(value)
def _getDefault(self):
# Return the default value for this widget;
# may be overridden by subclasses.
return self.context.default
def __call__(self):
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
value=self._showData(),
cssClass=self.cssClass,
extra=self.extra)
def hidden(self):
return renderElement(self.tag,
type='hidden',
name=self.name,
id=self.name,
value=self._showData(),
cssClass=self.cssClass,
extra=self.extra)
def render(self, value):
warn("The widget render method is deprecated",
DeprecationWarning, 2)
self.setRenderedValue(value)
return self()
def renderHidden(self, value):
warn("The widget render method is deprecated",
DeprecationWarning, 2)
self.setRenderedValue(value)
return self.hidden()
def label(self):
kw = {"for": self.name,
"contents": cgi.escape(self.title)}
if self.context.description:
kw["title"] = self.context.description
return renderElement("label", **kw)
def error(self):
if self._error:
return zapi.getView(self._error, 'snippet', self.request)()
return ""
def labelClass(self):
return self.context.required and "label required" or "label"
def row(self):
if self._error:
return '<div class="%s">%s</div><div class="field">%s</div>' \
'<div class="error">%s</div>' % (self.labelClass(),
self.label(), self(),
self.error())
else:
return '<div class="%s">%s</div><div class="field">%s</div>' % (
self.labelClass(), self.label(), self())
class DisplayWidget(BrowserWidget):
def __call__(self):
return self._showData()
class CheckBoxWidget(BrowserWidget):
"""A checkbox widget used to display Bool fields.
For more detailed documentation, including sample code, see
tests/test_checkboxwidget.py.
"""
implements(IInputWidget)
type = 'checkbox'
default = 0
extra = ''
def __call__(self):
data = self._showData()
if data:
kw = {'checked': None}
else:
kw = {}
return "%s %s" % (
renderElement(self.tag,
type='hidden',
name=self.name+".used",
id=self.name+".used",
value=""
),
renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
cssClass=self.cssClass,
extra=self.extra,
**kw),
)
def _convert(self, value):
return value == 'on'
def _unconvert(self, value):
return value and "on" or ""
return value == 'on'
def hasInput(self):
return self.name + ".used" in self.request.form or \
super(CheckBoxWidget, self).hasInput()
def getInputValue(self):
# When it's checked, its value is 'on'.
# When a checkbox is unchecked, it does not appear in the form data.
value = self.request.form.get(self.name, 'off')
return value == 'on'
class TextWidget(BrowserWidget):
"""Text widget.
Single-line text (unicode) input
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import TextLine
>>> field = TextLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> widget = TextWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
u'Bob'
>>> def normalize(s):
... return '\\n '.join(filter(None, s.split(' ')))
>>> print normalize( widget() )
<input
class="textType"
id="field.foo"
name="field.foo"
size="20"
type="text"
value="Bob"
/>
>>> print normalize( widget.hidden() )
<input
class="hiddenType"
id="field.foo"
name="field.foo"
type="hidden"
value="Bob"
/>
Calling setRenderedValue will change what gets output:
>>> widget.setRenderedValue("Barry")
>>> print normalize( widget() )
<input
class="textType"
id="field.foo"
name="field.foo"
size="20"
type="text"
value="Barry"
/>
"""
implements(IInputWidget)
default = ''
displayWidth = 20
displayMaxWidth = ""
extra = ''
# XXX Alex Limi doesn't like this!
# style = "width:100%"
style = ''
__values = None
def __init__(self, *args):
super(TextWidget, self).__init__(*args)
def __call__(self):
displayMaxWidth = self.displayMaxWidth or 0
if displayMaxWidth > 0:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
value=self._showData(),
cssClass=self.cssClass,
style=self.style,
size=self.displayWidth,
maxlength=displayMaxWidth,
extra=self.extra)
else:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
value=self._showData(),
cssClass=self.cssClass,
style=self.style,
size=self.displayWidth,
extra=self.extra)
class Bytes(BrowserWidget):
def _convert(self, value):
value = super(Bytes, self)._convert(value)
if type(value) is unicode:
try:
value = value.encode('ascii')
except UnicodeError, v:
raise ConversionError("Invalid textual data", v)
return value
class BytesWidget(Bytes, TextWidget):
"""Bytes widget.
Single-line data (string) input
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import BytesLine
>>> field = BytesLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> widget = BytesWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
'Bob'
"""
class ASCII(Bytes):
"""ASCII"""
class ASCIIWidget(BytesWidget):
"""ASCII widget.
Single-line data (string) input
"""
class IntWidget(TextWidget):
displayWidth = 10
def _convert(self, value):
if value == self._missing:
return self.context.missing_value
else:
try:
return int(value)
except ValueError, v:
raise ConversionError("Invalid integer data", v)
class FloatWidget(TextWidget):
implements(IInputWidget)
displayWidth = 10
def _convert(self, value):
if value == self._missing:
return self.context.missing_value
else:
try:
return float(value)
except ValueError, v:
raise ConversionError("Invalid floating point data", v)
class DatetimeWidget(TextWidget):
"""Datetime entry widget."""
displayWidth = 20
def _convert(self, value):
if value == self._missing:
return self.context.missing_value
else:
try:
return parseDatetimetz(value)
except (DateTimeError, ValueError, IndexError), v:
raise ConversionError("Invalid datetime data", v)
class DateWidget(TextWidget):
"""Date entry widget.
"""
displayWidth = 20
def _convert(self, value):
if value == self._missing:
return self.context.missing_value
else:
try:
return parseDatetimetz(value).date()
except (DateTimeError, ValueError, IndexError), v:
raise ConversionError("Invalid datetime data", v)
class TextAreaWidget(BrowserWidget):
"""TextArea widget.
Multi-line text (unicode) input.
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import Text
>>> field = Text(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
>>> widget = TextAreaWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
u'Hello\\nworld!'
>>> def normalize(s):
... return '\\n '.join(filter(None, s.split(' ')))
>>> print normalize( widget() )
<textarea
cols="60"
id="field.foo"
name="field.foo"
rows="15"
>Hello\r
world!</textarea>
>>> print normalize( widget.hidden() )
<input
class="hiddenType"
id="field.foo"
name="field.foo"
type="hidden"
value="Hello\r
world!"
/>
Calling setRenderedValue will change what gets output:
>>> widget.setRenderedValue("Hey\\ndude!")
>>> print normalize( widget() )
<textarea
cols="60"
id="field.foo"
name="field.foo"
rows="15"
>Hey\r
dude!</textarea>
"""
implements(IInputWidget)
default = ""
width = 60
height = 15
extra = ""
style = ''
def _convert(self, value):
value = super(TextAreaWidget, self)._convert(value)
if value:
value = value.replace("\r\n", "\n")
return value
def _unconvert(self, value):
value = super(TextAreaWidget, self)._unconvert(value)
if value:
value = value.replace("\n", "\r\n")
return value
def __call__(self):
return renderElement("textarea",
name=self.name,
id=self.name,
cssClass=self.cssClass,
rows=self.height,
cols=self.width,
style=self.style,
contents=self._showData(),
extra=self.extra)
class BytesAreaWidget(Bytes, TextAreaWidget):
"""BytesArea widget.
Multi-line string input.
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import Bytes
>>> field = Bytes(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
>>> widget = BytesAreaWidget(field, request)
>>> widget.hasInput()
True
>>> widget.getInputValue()
'Hello\\nworld!'
"""
class PasswordWidget(TextWidget):
"""Password Widget"""
type = 'password'
def __call__(self):
displayMaxWidth = self.displayMaxWidth or 0
if displayMaxWidth > 0:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
value='',
cssClass=self.cssClass,
style=self.style,
size=self.displayWidth,
maxlength=displayMaxWidth,
extra=self.extra)
else:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
value='',
cssClass=self.cssClass,
style=self.style,
size=self.displayWidth,
extra=self.extra)
def hidden(self):
raise NotImplementedError(
'Cannot get a hidden tag for a password field')
class FileWidget(TextWidget):
"""File Widget"""
type = 'file'
def __call__(self):
displayMaxWidth = self.displayMaxWidth or 0
if displayMaxWidth > 0:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
cssClass=self.cssClass,
size=self.displayWidth,
maxlength=displayMaxWidth,
extra=self.extra)
else:
return renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
cssClass=self.cssClass,
size=self.displayWidth,
extra=self.extra)
def hasInput(self):
file = self.request.form.get(self.name)
if file is None:
return False
if getattr(file, 'filename', ''):
return True
try:
seek = file.seek
read = file.read
except AttributeError:
return False
seek(0)
if read(1):
return True
return False
def _convert(self, value):
try:
seek = value.seek
read = value.read
except AttributeError, e:
raise ConversionError('Value is not a file object', e)
else:
seek(0)
data = read()
if data or getattr(value, 'filename', ''):
return data
else:
return self.context.missing_value
class ItemsWidget(BrowserWidget):
"""A widget that has a number of items in it."""
# What the heck is this for?
implements(IInputWidget)
class SingleItemsWidget(ItemsWidget):
"""A widget with a number of items that has only a single
selectable item."""
default = ""
firstItem = False
def textForValue(self, value):
'''Returns the text for the given value.
Override this in subclasses.'''
# The text could be a MessageID, in which case we should try to
# translate it.
return translate(self.context, value, context=self.request,
default=value)
def renderItems(self, value):
name = self.name
# get items
items = self.context.allowed_values
# check if we want to select first item
if (not value and getattr(self.context, 'firstItem', False)
and len(items) > 0):
value = items[0]
cssClass = self.cssClass
# FIXME: what if we run into multiple items with same value?
rendered_items = []
count = 0
for item_value in items:
item_text = self.textForValue(item_value)
if item_value == value:
rendered_item = self.renderSelectedItem(count,
item_text,
item_value,
name,
cssClass)
else:
rendered_item = self.renderItem(count,
item_text,
item_value,
name,
cssClass)
rendered_items.append(rendered_item)
count += 1
return rendered_items
class ListWidget(SingleItemsWidget):
"""List widget."""
size = 5
def __call__(self):
renderedItems = self.renderItems(self._showData())
return renderElement('select',
name=self.name,
id=self.name,
cssClass=self.cssClass,
size=self.size,
contents="\n".join(renderedItems),
extra=self.extra)
def renderItem(self, index, text, value, name, cssClass):
return renderElement('option', contents=text, value=value,
cssClass=cssClass)
def renderSelectedItem(self, index, text, value, name, cssClass):
return renderElement('option', contents=text, value=value,
cssClass=cssClass, selected=None)
class RadioWidget(SingleItemsWidget):
"""Radio buttons widget."""
orientation = "vertical"
def __call__(self):
rendered_items = self.renderItems(self._showData())
orientation = self.orientation
if orientation == 'horizontal':
return " ".join(rendered_items)
else:
return '<br />'.join(rendered_items)
def _renderItem(self, index, text, value, name, cssClass, checked):
id = '%s.%s' % (name, index)
if checked:
element = renderElement('input',
type="radio",
cssClass=cssClass,
name=name,
id=id,
value=value,
checked=None)
else:
element = renderElement('input',
type="radio",
cssClass=cssClass,
name=name,
id=id,
value=value)
return '%s<label for="%s">%s</label>' % (element, id, text)
def renderItem(self, index, text, value, name, cssClass):
return self._renderItem(index, text, value, name, cssClass, False)
def renderSelectedItem(self, index, text, value, name, cssClass):
return self._renderItem(index, text, value, name, cssClass, True)
def label(self):
return translate(self.context, self.title, context=self.request,
default=self.title)
def row(self):
return ('<div class="%s"><label for="%s">%s</label></div>'
'<div class="field" id="%s">%s</div>' % (
self.labelClass(), self.name, self.label(), self.name, self()))
class MultiItemsWidget(ItemsWidget):
"""A widget with a number of items that has multiple selectable items."""
default = []
def _convert(self, value):
if not value:
return []
if isinstance(value, ListTypes):
return value
return [value]
def renderItems(self, value):
# need to deal with single item selects
value = removeAllProxies(value)
if not isinstance(value, ListTypes):
value = [value]
name = self.name
items = self.context.allowed_values
cssClass = self.cssClass
rendered_items = []
count = 0
for item in items:
try:
item_value, item_text = item
except ValueError:
item_value = item
item_text = item
if item_value in value:
rendered_item = self.renderSelectedItem(count,
item_text,
item_value,
name,
cssClass)
else:
rendered_item = self.renderItem(count,
item_text,
item_value,
name,
cssClass)
rendered_items.append(rendered_item)
count += 1
return rendered_items
class MultiListWidget(MultiItemsWidget):
"""List widget with multiple select."""
size = 5
def __call__(self):
rendered_items = self.renderItems(self._showData())
return renderElement('select',
name=self.name,
id=self.name,
multiple=None,
cssClass=self.cssClass,
size=self.size,
contents="\n".join(rendered_items),
extra=self.extra)
def renderItem(self, index, text, value, name, cssClass):
return renderElement('option', contents=text, value=value)
def renderSelectedItem(self, index, text, value, name, cssClass):
return renderElement('option', contents=text, value=value,
selected=None)
class MultiCheckBoxWidget(MultiItemsWidget):
"""Multiple checkbox widget."""
orientation = "vertical"
def __call__(self):
rendered_items = self.renderItems(self._showData())
orientation = self.orientation
if orientation == 'horizontal':
return " ".join(rendered_items)
else:
return "<br />".join(rendered_items)
def renderItem(self, index, text, value, name, cssClass):
return renderElement('input',
type="checkbox",
cssClass=cssClass,
name=name,
id=name,
value=value) + text
def renderSelectedItem(self, index, text, value, name, cssClass):
return renderElement('input',
type="checkbox",
cssClass=cssClass,
name=name,
id=name,
value=value,
checked=None) + text
class SequenceWidget(BrowserWidget):
"""A widget baseclass for a sequence of fields.
subwidget - Optional CustomWidget used to generate widgets for the
items in the sequence
"""
implements(IInputWidget)
_type = tuple
_data = () # pre-existing sequence items (from setRenderedValue)
def __init__(self, context, request, subwidget=None):
super(SequenceWidget, self).__init__(context, request)
self.subwidget = None
def __call__(self):
"""Render the widget
"""
# XXX we really shouldn't allow value_type of None
if self.context.value_type is None:
return ''
render = []
# length of sequence info
sequence = list(self._generateSequence())
num_items = len(sequence)
min_length = self.context.min_length
max_length = self.context.max_length
# ensure minimum number of items in the form
if num_items < min_length:
for i in range(min_length - num_items):
sequence.append(None)
num_items = len(sequence)
# generate each widget from items in the sequence - adding a
# "remove" button for each one
for i in range(num_items):
value = sequence[i]
render.append('<tr><td>')
if num_items > min_length:
render.append(
'<input type="checkbox" name="%s.remove_%d" />' % (
self.name, i)
)
widget = self._getWidget(i)
widget.setRenderedValue(value)
render.append(widget() + '</td></tr>')
# possibly generate the "remove" and "add" buttons
buttons = ''
if render and num_items > min_length:
button_label = _('remove-selected-items', "Remove selected items")
button_label = translate(self.context, button_label,
context=self.request, default=button_label)
buttons += '<input type="submit" value="%s" />' % button_label
if max_length is None or num_items < max_length:
field = self.context.value_type
button_label = _('Add %s')
button_label = translate(self.context, button_label,
context=self.request, default=button_label)
button_label = button_label % (field.title or field.__name__)
buttons += '<input type="submit" name="%s.add" value="%s" />' % (
self.name, button_label)
if buttons:
render.append('<tr><td>%s</td></tr>' % buttons)
return '<table border="0">' + ''.join(render) + '</table>'
def _getWidget(self, i):
field = self.context.value_type
if self.subwidget:
widget = self.subwidget(field, self.request)
else:
widget = zapi.getViewProviding(field, IInputWidget, self.request,
context=self.context)
widget.setPrefix('%s.%d.'%(self.name, i))
return widget
def hidden(self):
''' Render the list as hidden fields '''
# length of sequence info
sequence = self._generateSequence()
num_items = len(sequence)
min_length = self.context.min_length
# ensure minimum number of items in the form
if num_items < min_length:
for i in range(min_length - num_items):
sequence.append(None)
num_items = len(sequence)
# generate hidden fields for each value
s = ''
for i in range(num_items):
value = sequence[i]
widget = self._getWidget(i)
widget.setRenderedValue(value)
s += widget.hidden()
return s
def getInputValue(self):
"""Return converted and validated widget data.
If there is no user input and the field is required, then a
MissingInputError will be raised.
If there is no user input and the field is not required, then
the field default value will be returned.
A WidgetInputError is returned in the case of one or more
errors encountered, inputting, converting, or validating the data.
"""
sequence = self._generateSequence()
# validate the input values
for value in sequence:
self.context.value_type.validate(value)
return self._type(sequence)
# XXX applyChanges isn't reporting "change" correctly (we're
# re-generating the sequence with every edit, and need to be smarter)
def applyChanges(self, content):
field = self.context
value = self.getInputValue()
change = field.query(content, self) != value
if change:
field.set(content, value)
return change
def hasInput(self):
"""Is there input data for the field
Return True if there is data and False otherwise.
"""
return len(self._generateSequence()) != 0
def setRenderedValue(self, value):
"""Set the default data for the widget.
The given value should be used even if the user has entered
data.
"""
# the current list of values derived from the "value" parameter
self._data = value
def _generateSequence(self):
"""Take sequence info in the self.request and _data.
"""
len_prefix = len(self.name)
adding = False
removing = []
subprefix = re.compile(r'(\d+)\.(.*)$')
if self.context.value_type is None:
return []
# pre-populate
found = {}
if self._data is not None:
found = dict(enumerate(self._data))
# now look through the request for interesting values
for key in self.request.keys():
if not key.startswith(self.name):
continue
token = key[len_prefix+1:] # skip the '.'
if token == 'add':
# append a new blank field to the sequence
adding = True
elif token.startswith('remove_'):
# remove the index indicated
removing.append(int(token[7:]))
else:
match = subprefix.match(token)
if match is None:
continue
# key refers to a sub field
i = int(match.group(1))
# find a widget for the sub-field and use that to parse the
# request data
widget = self._getWidget(i)
value = widget.getInputValue()
found[i] = value
# remove the indicated indexes
for i in removing:
del found[i]
# generate the list, sorting the dict's contents by key
items = found.items()
items.sort()
sequence = [value for key, value in items]
# add an entry to the list if the add button has been pressed
if adding:
sequence.append(None)
return sequence
class TupleSequenceWidget(SequenceWidget):
pass
class ListSequenceWidget(SequenceWidget):
_type = list
class ObjectWidget(BrowserWidget):
"""A widget over an Interface that contains Fields.
"factory" - factory used to create content that this widget (field)
represents
*_widget - Optional CustomWidgets used to generate widgets for the
fields in this widget
"""
implements(IInputWidget)
_object = None # the object value (from setRenderedValue & request)
_request_parsed = False
def __init__(self, context, request, factory, **kw):
super(ObjectWidget, self).__init__(context, request)
# factory used to create content that this widget (field)
# represents
self.factory = factory
# handle foo_widget specs being passed in
self.names = getFieldNamesInOrder(self.context.schema)
for k, v in kw.items():
if k.endswith('_widget'):
setattr(self, k, v)
# set up my subwidgets
self._setUpEditWidgets()
def setPrefix(self, prefix):
super(ObjectWidget, self).setPrefix(prefix)
self._setUpEditWidgets()
def _setUpEditWidgets(self):
# subwidgets need a new name
setUpEditWidgets(self, self.context.schema, source=self.context,
prefix=self.name, names=self.names,
context=self.context)
def __call__(self):
"""Render the widget
"""
render = []
# XXX see if there's some widget layout already
# generate each widget from fields in the schema
field = self.context
title = field.title or field.__name__
render.append('<fieldset><legend>%s</legend>'%title)
for name, widget in self.getSubWidgets():
render.append(widget.row())
render.append('</fieldset>')
return '\n'.join(render)
def getSubWidgets(self):
l = []
for name in self.names:
l.append((name, getattr(self, '%s_widget'%name)))
return l
def hidden(self):
''' Render the list as hidden fields '''
for name, widget in self.getSubWidgets():
s += widget.hidden()
return s
def getInputValue(self):
"""Return converted and validated widget data.
The value for this field will be represented as an ObjectStorage
instance which holds the subfield values as attributes. It will
need to be converted by higher-level code into some more useful
object (note that the default EditView calls applyChanges, which
does this).
"""
content = self.factory()
for name, widget in self.getSubWidgets():
setattr(content, name, widget.getInputValue())
return content
def applyChanges(self, content):
field = self.context
# create our new object value
value = field.query(content, None)
if value is None:
# XXX ObjectCreatedEvent here would be nice
value = self.factory()
# apply sub changes, see if there *are* any changes
# XXX ObjectModifiedEvent here would be nice
changes = applyWidgetsChanges(self, field.schema, target=value,
names=self.names)
# if there's changes, then store the new value on the content
if changes:
field.set(content, value)
return changes
def hasInput(self):
"""Is there input data for the field
Return True if there is data and False otherwise.
"""
for name, widget in self.getSubWidgets():
if widget.hasInput():
return True
return False
def setRenderedValue(self, value):
"""Set the default data for the widget.
The given value should be used even if the user has entered
data.
"""
# re-call setupwidgets with the content
self._setUpEditWidgets()
for name, widget in self.getSubWidgets():
widget.setRenderedValue(getattr(value, name, None))
# XXX Note, some HTML quoting is needed in renderTag and renderElement.
def renderTag(tag, **kw):
"""Render the tag. Well, not all of it, as we may want to / it."""
attr_list = []
# special case handling for cssClass
cssClass = ''
if 'cssClass' in kw:
if kw['cssClass']:
cssClass = kw['cssClass']
del kw['cssClass']
# If the 'type' attribute is given, append this plus 'Type' as a
# css class. This allows us to do subselector stuff in css without
# necessarily having a browser that supports css subselectors.
# This is important if you want to style radio inputs differently than
# text inputs.
cssWidgetType = kw.get('type')
if cssWidgetType:
cssWidgetType += 'Type'
else:
cssWidgetType = ''
if cssWidgetType or cssClass:
names = filter(None, (cssClass, cssWidgetType))
attr_list.append('class="%s"' % ' '.join(names))
if 'style' in kw:
if kw['style'] != '':
attr_list.append('style=%s' % quoteattr(kw['style']))
del kw['style']
# special case handling for extra 'raw' code
if 'extra' in kw:
extra = " " + kw['extra'] # could be empty string but we don't care
del kw['extra']
else:
extra = ""
# handle other attributes
if kw:
items = kw.items()
items.sort()
for key, value in items:
if value == None:
value = key
attr_list.append('%s=%s' % (key, quoteattr(unicode(value))))
if attr_list:
attr_str = " ".join(attr_list)
return "<%s %s%s" % (tag, attr_str, extra)
else:
return "<%s%s" % (tag, extra)
def renderElement(tag, **kw):
if 'contents' in kw:
contents = kw['contents']
del kw['contents']
return "%s>%s</%s>" % (renderTag(tag, **kw), contents, tag)
else:
return renderTag(tag, **kw) + " />"
def setUp():
import zope.app.tests.placelesssetup
global setUp
setUp = zope.app.tests.placelesssetup.setUp
setUp()
def tearDown():
import zope.app.tests.placelesssetup
global tearDown
tearDown = zope.app.tests.placelesssetup.tearDown
tearDown()
=== Added File Zope3/src/zope/app/form/browser/widgets.txt ===
Simple example showing ObjectWidget and SequenceWidget usage
============================================================
The following implements a Poll product (add it as
zope/products/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. This
is shown in the ``poll.py`` source below::
from persistence import Persistent
from interfaces import IPoll, IPollOption
from zope.interface import implements, classImplements
class PollOption(Persistent, object):
implements(IPollOption)
class Poll(Persistent, object):
implements(IPoll)
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')
And the Schemas are define 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.i18n import MessageIDFactory
_ = MessageIDFactory("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'
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 enforce 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.container.interfaces import IAdding
from zope.app.event import publish
from zope.app.event.objectevent import ObjectModifiedEvent
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.browser.editview import EditView
from zope.app.form.browser.add import AddView
from zope.app.form.widget import CustomWidgetFactory
from zope.app.form.browser.widget 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.products.demo.poll"
permission="zope.ManageContent" />
<implements
interface="zope.app.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"
menu="zmi_views" title="View"
/>
<browser:pages
for=".interfaces.IPoll"
class=".browser.PollVoteView"
permission="zope.ManageContent">
<browser:page name="vote.html" template="vote.zpt"
menu="zmi_views" title="Vote" />
<browser:page name="choose" attribute="choose" />
</browser:pages>
<browser:addform
schema=".interfaces.IPoll"
label="Add a Poll"
content_factory=".poll.Poll"
name="zope.products.demo.poll"
class=".browser.PollAddView"
menu="add_content" title="Poll"
permission="zope.ManageContent" />
<browser:editform
schema=".interfaces.IPoll"
class=".browser.PollEditView"
label="Change a Poll"
name="edit.html"
menu="zmi_views" title="Edit"
permission="zope.ManageContent" />
</configure>
Note the use of the "class" argument to the addform and editform
directives. Otherwise, nothing much exciting here.
Finally, we have some additiona 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>
More information about the Zope3-Checkins
mailing list