Package: plone.z3cform
plone.z3cform is a library that enables the use ofz3c.formin Zope.
It depends only on Zope and z3c.form.For Plone integration, there is alsoplone.app.z3cform, which can be
installed to make the default form templates more Ploneish. That package
pulls in this one as a dependency.In addition to pure interoperability support, a few patterns which are useful
in Zope 2 applications are implemented here.ContentsInstallationStandalone formsLayout form wrapperDefault templates and macrosTemplate factoriesThe widget traverserA caveat about securityFieldsets and form extendersCRUD (Create, Read, Update and Delete) formsSetupA simple formEditing items with our formDelete an item with our formAdd an item with our formRender some of the fields in view modeDon’t render one partRender only in view, and define own actionsCustomizing sub formsUsing batchingChangelog2.0.3 (2023-12-14)2.0.2 (2023-10-07)2.0.1 (2023-03-21)2.0.0 (2022-11-23)2.0.0b1 (2022-06-23)2.0.0a3 (2022-05-24)2.0.0a2 (2022-03-23)2.0.0a1 (2021-04-21)1.1.3 (2020-06-16)1.1.2 (2020-04-21)1.1.1 (2019-10-12)1.1.0 (2019-04-09)1.0.0 (2018-11-04)0.9.1 (2017-09-03)0.9.0 (2016-05-25)0.8.1 (2015-01-22)0.8.0 (2012-08-30)0.7.8 - 2011-09-240.7.7 - 2011-06-300.7.6 - 2011-05-170.7.5 - 2011-05-030.7.4 - 2011-05-030.7.3 - 2011-03-020.7.2 - 2011-02-170.7.1 - 2011-01-180.7.0 - 2010-08-040.6.0 - 2010-04-200.5.10 - 2010-02-010.5.9 - 2010-01-080.5.8 - 2009-11-240.5.7 - 2009-11-170.5.6 - 2009-09-250.5.5 - 2009-07-260.5.4 - 2009-04-170.5.3 - 2008-12-090.5.2 - 2008-08-280.5.1 - 2008-08-210.5.0 - 2008-07-300.4 - 2008-07-250.3 - 2008-07-240.2 - 2008-06-200.1 - 2008-05-21InstallationTo use this package, simply install it as a dependency of the package where
you are using forms, via theinstall_requiresline insetup.py. Then
loads its configuration via ZCML:<include package="plone.z3cform" />Standalone formsIf you are using Zope 2.12 or later, z3c.form forms willalmostwork
out of the box. However, two obstacles remain:The standard file upload data converter does not work with Zope 2, so
fields (likezope.schema.Bytes) using the file widget will not work
correctly.z3c.form expects request values to be decoded to unicode strings by the
publisher, which does not happen in Zope 2.To address the first problem, this package provides an override for the
standard data converter adapter (registered on thezope.schema.Bytesclass
directly, so as to override the default, which is registered for the less
generalIBytesinterface). To address the second, it applies a monkey
patch to theupdate()methods ofBaseFormandGroupFormfromz3c.formwhich performs the necessary decoding in a way that is consistent
with the Zope 3-style publisher.Note: If you overrideupdate()in your own form you must either call the
base class version or call the functionplone.z3cform.z2.processInputs()on the requestbeforeany values in the request are used. For example:from plone.z3cform.z2 import processInputs
from z3c.form import form

...

class MyForm(form.Form):

    ...

    def update(self):
        processInputs(self.request)
        ...Other than that, you can create a form using standardz3c.formconventions.
For example:from zope.interface import Interface
from zope import schema
from z3c.form import form, button

class IMyFormSchema(Interface):
    field1 = schema.TextLine(title=u"A field")
    field2 = schema.Int(title=u"Another field")

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do somethingYou can register this as a view in ZCML using the standard<browser:page />directive:<browser:page
    for="*"
    name="my-form"
    class=".forms.MyForm"
    permission="zope2.View"
    />A default template will be used to render the form. If you want to associate
a custom template, you should do so by setting thetemplateclass variable
instead of using thetemplateattribute of the ZCML directive:from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)
    template = ViewPageTemplateFile('mytemplate.pt')

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do somethingSee below for more details about standard form macros.Note that to render any of the standard widgets, you will also need to make
sure the request is marked withz3c.form.interfaces.IFormLayer, as is
the norm with z3c.form. If you installplone.app.z3cformin Plone, that
is already done for you, but in other scenarios, you will need to do this
in whichever way Zope browser layers are normally applied.Layout form wrapperIn versions of Zope prior to 2.12, z3c.form instances cannot be registered
as views directly, because they do not support Zope 2 security (via the
acquisition mechanism). Whilst it may be possible to support this via custom
mix-ins, the preferred approach is to use a wrapper view, which separates the
rendering of the form from the page layout.There are a few other reasons why you may want to use the wrapper view, even
in later versions of Zope:To support both an earlier version of Zope and Zope 2.12+To reuse the same form in multiple views or viewletsTo use theIPageTemplateadapter lookup semantics from z3c.form to
provide a different default or override template for the overall page
layout, while retaining (or indeed customising independently) the default
layout of the form.When using the wrapper view, you donotneed to ensure your requests are
marked withIFormLayer, as it is applied automatically during the
rendering of the wrapper view.The easiest way to create a wrapper view is to call thewrap_form()function:from zope.interface import Interface
from zope import schema
from z3c.form import form, button

from plone.z3cform import layout

class IMyFormSchema(Interface):
    field1 = schema.TextLine(title=u"A field")
    field2 = schema.Int(title=u"Another field")

class MyForm(form.Form):
    fields = field.Fields(IMyformSchema)

    @button.buttonAndHandler(u'Submit')
    def handleApply(self, action):
        data, errors = self.extractData()
        # do something

MyFormView = layout.wrap_form(MyForm)You can now register the (generated)MyFormViewclass as a browser view:<browser:page
    for="*"
    name="my-form"
    class=".forms.MyFormView"
    permission="zope2.View"
    />If you want to have more control, you can define the wrapper class manually.
You should derive from the default version to get the correct semantics. The
following is equivalent to thewrap_form()call above:class MyFormView(layout.FormWrapper):
    form = MyFormYou can of then add additional methods to the class, use a custom page
template, and so on.The defaultFormWrapperclass exposes a few methods and properties:update()is called to prepare the request and then update the wrapped
form.render()is called to render the wrapper view. If a template has
been set (normally via thetemplateattribute of the<browser:page />directive), it will be rendered here. Otherwise,
a default page template is found by adapting the view (self) and
the request tozope.pagetemplate.interfaces.IPageTemplate, in the
same way thatz3c.formdoes for its views. A default template is
supplied with this package (and customised inplone.app.z3cformto
achieve a standard Plone look and feel).formis a class variable referring to the form class, as set above.form_instanceis an instance variable set to the current form instance
once the view has been initialised.When a form is rendered in a wrapper view, the form instance is temporarily
marked withplone.z3cform.interfaces.IWrappedForm(unless the form is
a subform), to allow custom adapter registrations. Specifically, this is used
to ensure that a form rendered “standalone” gets a full-page template applied,
while a form rendered in a wrapper is rendered using a template that renders
the form elements only.Default templates and macrosSeveral standard templates are provided with this package. These are all
registered as adapters from(view, request)toIPageTemplate, as is
the convention in z3c.form. It therefore follows that these defaults can be
customised with an adapter override, e.g. for a specific browser layer. This
is useful if you want to override the standard rendering of all forms. If you
just want to provide a custom template for a particular form or wrapper view,
you can specify a template directly on the form or view, as shown above.templates/layout.ptis the default template for the layout wrapper view.
It uses the CMFDefaultmain_templateand fills theheaderslot.templates/wrappedform.ptis the default template for wrapped forms.
It renders thetitlelessformmacro from the@@ploneform-macrosview.templates/subform.ptis the default template for sub-forms.
It uses the macros in@@ploneform-macrosview to render a heading,
top/bottom content (verbatim) and the fields and actions of the subform (but
does not) render the<form />tag itself.templates/form.ptis the default template for a standalone form. It uses
the macrocontext/@@standard_macros/page(supplied by Five and normally
delegating to CMF’smain_template) to render a form where the form label
is the page heading.As hinted, this package also registers a view@@ploneform-macros, which
contains a set of macros that be used to construct forms with a standard
layout, error message display, and so on. It contains the following macros:formis a full page form, including the label (output as an<h3 />),
description, and all the elements oftitlelessform.  It defines two
slots:titlecontains the label, anddescriptioncontains the
description.titlelessformincludes the formstatusat the top, the<form />element, and the contents of thefieldsandactionsmacros. It also
defines four slots:formtopis just inside the opening<form>tag;formbottom`is just inside the closing</form>tag;fieldscontains thefieldsmacro; andactionscontains theactionsmacro.fieldsiterates over all widgets in the form and renders each, using the
contents of thefieldmacro.  It also defines one slot,fieldwhich
contains thefieldmacro.fieldrenders a single field. It expects the variablewidgetto be
defined in the TAL scope, referring to a z3c.form widget instance. It will
output an error message if there is a field validation error, a label,
a marker to say whether the field is required, the field description, and
the widget itself (normally just an<input />element).actionsrenders all actions (buttons) on the form. This normally results
in a row of<inputtype="submit"... />elements.Thus, to use thetitlelessformmacro, you could add something like the
following in a custom form template:<metal:use use-macro="context/@@ploneform-macros/titlelessform" />Note that all of the templates mentioned above are customised byplone.app.z3cformto use standard Plone markup (but still retain the same
macros), so if you are using that package to configure this one, you should
look for the Plone-specific versions there.Template factoriesIf you want to provide anIPageTemplateadapter to customise the default
page template used for wrapper views, forms or sub-forms, this package
provides helper classes to create an adapter factory for that purpose. You
should use these instead ofz3c.form.form.FormTemplateFactoryand
(possibly)z3c.form.widget.WidgetTemplateFactoryto get page templates
with Zope 2 semantics. These factories are alsoChameleonaware, if you
havefive.ptinstalled.The most commonly used factory isplone.z3cform.templates.ZopeTwoFormTemplateFactory, which can be used to
render a wrapper view or a standalone form.To render a wrapped form, you can useplone.z3cform.templates.FormTemplateFactory, which is closer to the
defaultz3c.formversion, but adds Chameleon-awareness.To render a widget, the defaultWidgetTemplateFactoryfrom z3c.form should
suffice, but if you need Zope 2 semantics for any reason, you can useplone.z3cform.templates.ZopeTwoWidgetTemplateFactory.As an example, here are the default registrations from this package:import z3c.form.interfaces
import plone.z3cform.interfaces

from plone.z3cform.templates import ZopeTwoFormTemplateFactory
from plone.z3cform.templates import FormTemplateFactory

path = lambda p: os.path.join(os.path.dirname(plone.z3cform.__file__), 'templates', p)

layout_factory = ZopeTwoFormTemplateFactory(path('layout.pt'),
    form=plone.z3cform.interfaces.IFormWrapper
)

wrapped_form_factory = FormTemplateFactory(path('wrappedform.pt'),
        form=plone.z3cform.interfaces.IWrappedForm,
    )

# Default templates for the standalone form use case

standalone_form_factory = ZopeTwoFormTemplateFactory(path('form.pt'),
        form=z3c.form.interfaces.IForm
    )

subform_factory = FormTemplateFactory(path('subform.pt'),
        form=z3c.form.interfaces.ISubForm
    )These are registered in ZCML like so:<!-- Form templates for wrapped layout use case -->
<adapter factory=".templates.layout_factory" />
<adapter factory=".templates.wrapped_form_factory" />

<!-- Form templates for standalone form use case -->
<adapter factory=".templates.standalone_form_factory" />
<adapter factory=".templates.subform_factory" />The widget traverserIt is sometimes useful to be able to register a view on awidgetand be
able to traverse to that view, for example during a background AJAX request.
As an example of widget doing this, seeplone.formwidget.autocomplete.This package provides a++widget++namespace traversal adapter which can
be used for this purpose. It is looked up on either the form wrapper view,
or the form itself (in the case of standalone) forms. Thus, if you have a
form view called@@my-form, with a field calledmyfield, you could
traverse to the widget for that view using:http://example.com/@@my-form/++widget++myfieldThe widget may be on the form itself, or in a group (fieldset). If it exists
in multiple groups, the first one found will be used.The example above will yield widget, but it is probably not publishable.
You would therefore commonly register a view on the widget itself and use
that. In this case,self.contextin the view is the widget instance. Such
a view could be looked up with:http://example.com/@@my-form/++widget++myfield/@@some-viewA caveat about securityIn Zope 2.12 and later, the security machinery is aware of__parent__pointers. Thus, traversal and authorisation on@@some-viewin the example
above will work just fine for a standard widget. In earlier versions of Zope,
you will need to mix acquisition into your widget (which rules out using any
of the standardz3c.formwidgets). For example:from Acquisition import Explicit
from z3c.form.widget import Widget

class MyWidget(Widget, Explicit):
    ...Unfortunately, in Zope 2.12, this will cause some problems during traversal
unless you also mix acquisition into the view you registered on the widget
(@@some-viewabove). Specifically, you will get an error as the publisher
tries to wrap the view in the widget.To stay compatible with both Zope 2.12+ and earlier versions, you have two
options:Ensure that you mix acquisition into the view on the widgetEnsure that the widget inherits fromExplicit, but doesnotprovide
theIAcquirerinterface. This tricks the publisiher into relying on__parent__pointers in Zope 2.12.To do the latter, you can useimplementsOnly(), e.g.:from zope.interface import implementsOnly
from Acquisition import Explicit
from z3c.form.widget import Widget

...

class MyWidget(Widget, Explicit):
    implementsOnly(IMyWidget) # or just IWdget from z3c.form
    ...Fieldsets and form extendersTheplone.z3cform.fieldsetspackage provides support for z3c.form groups
(fieldsets) and other modifications via “extender” adapters. The idea is that
a third party component can modify the fields in the form and the way that
they are grouped and ordered.This support relies on a mixin class, which is itself a subclass of
z3c.form’sGroupForm:>>> from plone.z3cform.fieldsets import group, extensibleTo use this, you have to mix it into another form as thefirstbase class:>>> from zope.annotation import IAttributeAnnotatable
>>> from z3c.form import form, field, tests, group
>>> from zope.interface import Interface
>>> from zope.interface import implementer
>>> from zope import schema

>>> class ITest(Interface):
...     title = schema.TextLine(title=u"Title")

>>> @implementer(ITest, IAttributeAnnotatable)
... class Test(object):
...     # avoid needing an acl_users for this test in Zope 2.10
...     __allow_access_to_unprotected_subobjects__ = 1
...
...     title = u""
...     def getPhysicalRoot(self): # needed for template to acquire REQUEST in Zope 2.10
...         return self

>>> class TestForm(extensible.ExtensibleForm, form.Form):
...     fields = field.Fields(ITest)Here, note the order of the base classes. Also note that we use an ordinary
set of fields directly on the form. This known as the default fieldset.This form should work as-is, i.e. we can update it. First we need to fake a
request:>>> from plone.z3cform.tests import TestRequest

>>> request = TestRequest()
>>> request.other = {}
>>> context = Test()
>>> context.REQUEST = request

>>> form = TestForm(context, request)
>>> form.update()
>>> list(form.fields.keys())
['title']Now let’s register an adapter that adds two new fields - one in the
default fieldset as the first field, and one in a new group. To do this,
we only need to register a named multi-adapter. However, we can use a
convenience base class to make it easier to manipulate the fields of the
form:>>> from plone.z3cform.fieldsets.interfaces import IFormExtender
>>> from zope.component import adapter
>>> from zope.component import provideAdapter

>>> class IExtraBehavior(Interface):
...     foo = schema.TextLine(title=u"Foo")
...     bar = schema.TextLine(title=u"Bar")
...     baz = schema.TextLine(title=u"Baz")
...     fub = schema.TextLine(title=u"Fub")
...     qux = schema.TextLine(title=u"Qux")One plausible implementation is to use an annotation to store this data.>>> from zope.annotation import factory
>>> from zope.annotation.attribute import AttributeAnnotations
>>> from persistent import Persistent
>>> @implementer(IExtraBehavior)
... @adapter(Test)
... class ExtraBehavior(Persistent):
...
...
...     foo = u""
...     bar = u""
...     baz = u""
...     fub = u""
...     qux = u""

>>> ExtraBehavior = factory(ExtraBehavior)
>>> provideAdapter(ExtraBehavior)
>>> provideAdapter(AttributeAnnotations)We can now write the extender. The base class gives us some helper methods
to add, remove and move fields. Here, we do a bit of unnecessary work just
to exercise these methods.>>> @adapter(Test, TestRequest, TestForm) # context, request, form
... class ExtraBehaviorExtender(extensible.FormExtender):
...
...     def __init__(self, context, request, form):
...         self.context = context
...         self.request = request
...         self.form = form
...
...     def update(self):
...         # Add all fields from an interface
...         self.add(IExtraBehavior, prefix="extra")
...
...         # Remove the fub field
...         self.remove('fub', prefix="extra")
...
...         all_fields = field.Fields(IExtraBehavior, prefix="extra")
...
...         # Insert fub again, this time at the top
...         self.add(all_fields.select("fub", prefix="extra"), index=0)
...
...         # Move 'baz' above 'fub'
...         self.move('baz', before='fub', prefix='extra', relative_prefix='extra')
...
...         # Move 'foo' after 'bar' - here we specify prefix manually
...         self.move('foo', after='extra.bar', prefix='extra')
...
...         # Remove 'bar' and re-insert into a new group
...         self.remove('bar', prefix='extra')
...         self.add(all_fields.select('bar', prefix='extra'), group='Second')
...
...         # Move 'baz' after 'bar'. This means it also moves group.
...         self.move('extra.baz', after='extra.bar')
...
...         # Remove 'qux' and re-insert into 'Second' group,
...         # then move it before 'baz'
...         self.remove('qux', prefix='extra')
...         self.add(all_fields.select('qux', prefix='extra'), group='Second')
...         self.move('qux', before='baz', prefix='extra', relative_prefix='extra')

>>> provideAdapter(factory=ExtraBehaviorExtender, name=u"test.extender")With this in place, let’s update the form once again:>>> form = TestForm(context, request)
>>> form.update()At this point, we should have a set of default fields that represent the
ones set in the adapter:>>> list(form.fields.keys())
['extra.fub', 'title', 'extra.foo']And we should have one group created by the group factory:>>> form.groups # doctest: +ELLIPSIS
(<plone.z3cform.fieldsets.group.Group object at ...>,)Note that the created group is of a subtype of the standard z3c.form group,
which has got support for a separate label and description as well as a
canonical name:>>> isinstance(form.groups[0], group.Group)
TrueThis should have the group fields provided by the adapter as well:>>> list(form.groups[0].fields.keys())
['extra.bar', 'extra.qux', 'extra.baz']CRUD (Create, Read, Update and Delete) formsThis module provides an abstract base class to create CRUD forms.
By default, such forms provide a tabular view of multiple objects, whose
attributes can be edited in-place.Please refer to theICrudForminterface for more details.>>> from plone.z3cform.crud import crudSetup>>> from plone.z3cform.tests import setup_defaults
>>> setup_defaults()A simple formFirst, let’s define an interface and a class to play with:>>> from zope import interface, schema
>>> class IPerson(interface.Interface) :
...     name = schema.TextLine()
...     age = schema.Int()>>> @interface.implementer(IPerson)
... class Person(object):
...     def __init__(self, name=None, age=None):
...         self.name, self.age = name, age
...     def __repr__(self):
...         return "<Person with name=%r, age=%r>" % (self.name, self.age)For this test, we take the the name of our persons as keys in our
storage:>>> storage = {'Peter': Person(u'Peter', 16),
...            'Martha': Person(u'Martha', 32)}Our simple form looks like this:>>> class MyForm(crud.CrudForm):
...     update_schema = IPerson
...
...     def get_items(self):
...         return sorted(storage.items(), key=lambda x: x[1].name)
...
...     def add(self, data):
...         person = Person(**data)
...         storage[str(person.name)] = person
...         return person
...
...     def remove(self, id_item):
...         id, item = id_item
...         del storage[id]This is all that we need to render a combined edit add form containing
all our items:>>> class FakeContext(object):
...     def absolute_url(self):
...         return 'http://nohost/context'
>>> fake_context = FakeContext()>>> from plone.z3cform.tests import TestRequest
>>> print(MyForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Martha...Peter...</div>Editing items with our formBefore we start with editing objects, let’s log all events that the
form fires for us:>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectModifiedEvent)>>> request = TestRequest()
>>> request.form['crud-edit.Martha.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Peter.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Martha.widgets.name'] = u'Martha'
>>> request.form['crud-edit.Martha.widgets.age'] = u'55'
>>> request.form['crud-edit.Peter.widgets.name'] = u'Franz'
>>> request.form['crud-edit.Peter.widgets.age'] = u'16'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply changes'
>>> html = MyForm(fake_context, request)()
>>> "Successfully updated" in html
TrueTwo modified events should have been fired:>>> event1, event2 = log.pop(), log.pop()
>>> storage['Peter'] in (event1.object, event2.object)
True
>>> storage['Martha'] in (event1.object, event2.object)
True
>>> log
[]If we don’t make any changes, we’ll get a message that says so:>>> html = MyForm(fake_context, request)()
>>> "No changes made" in html
True
>>> log
[]Now that we renamed Peter to Franz, it would be also nice to have
Franz use ‘Franz’ as the id in the storage, wouldn’t it?>>> storage['Peter']
<Person with name='Franz', age=16>We can override the CrudForm’sbefore_updatemethod to perform a
rename whenever the name of a person is changed:>>> class MyRenamingForm(MyForm):
...     def before_update(self, item, data):
...         if data['name'] != item.name:
...             del storage[item.name]
...             storage[str(data['name'])] = itemLet’s rename Martha to Maria.  This will give her another key in our
storage:>>> request.form['crud-edit.Martha.widgets.name'] = u'Maria'
>>> html = MyRenamingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]
>>> sorted(storage.keys())
['Maria', 'Peter']Next, we’ll submit the form for edit, but we’ll make no changes.
Instead, we’ll select one time.  This shouldn’t do anything, since we
clicked the ‘Apply changes’ button:>>> request.form['crud-edit.Maria.widgets.name'] = u'Maria'
>>> request.form['crud-edit.Maria.widgets.age'] = u'55'
>>> request.form['crud-edit.Maria.widgets.select'] = [u'selected']
>>> html = MyRenamingForm(fake_context, request)()
>>> "No changes" in html
True
>>> log
[]And what if we do have changesandclick the checkbox?>>> request.form['crud-edit.Maria.widgets.age'] = u'50'
>>> html = MyRenamingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]If we omit the name, we’ll get an error:>>> request.form['crud-edit.Maria.widgets.name'] = u''
>>> html = MyRenamingForm(fake_context, request)()
>>> "There were some errors" in html
True
>>> "Required input is missing" in html
TrueWe expect an error message in the title cell of Maria:>>> checkbox_pos = html.index('crud-edit.Maria.widgets.select-empty-marker')
>>> "Required input is missing" in html[checkbox_pos:]
TrueDelete an item with our formWe can delete an item by selecting the item we want to delete and
clicking the “Delete” button:>>> request = TestRequest()
>>> request.form['crud-edit.Peter.widgets.select'] = ['selected']
>>> request.form['crud-edit.form.buttons.delete'] = u'Delete'
>>> html = MyForm(fake_context, request)()
>>> "Successfully deleted items" in html
True
>>> 'Franz' in html
False
>>> storage
{'Maria': <Person with name='Maria', age=50>}Add an item with our form>>> from zope.lifecycleevent.interfaces import IObjectCreatedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectCreatedEvent)>>> request = TestRequest()
>>> request.form['crud-add.form.widgets.name'] = u'Daniel'
>>> request.form['crud-add.form.widgets.age'] = u'28'
>>> request.form['crud-add.form.buttons.add'] = u'Add'
>>> html = MyForm(fake_context, request)()
>>> "Item added successfully" in html
TrueAdded items should show up right away:>>> "Daniel" in html
True>>> storage['Daniel']
<Person with name='Daniel', age=28>
>>> log.pop().object == storage['Daniel']
True
>>> log
[]What if we try to add “Daniel” twice?  Our current implementation of
the add form will simply overwrite the data:>>> save_daniel = storage['Daniel']
>>> html = MyForm(fake_context, request)()
>>> "Item added successfully" in html
True
>>> save_daniel is storage['Daniel']
False
>>> log.pop().object is storage['Daniel']
TrueLet’s implement a class that prevents this:>>> class MyCarefulForm(MyForm):
...     def add(self, data):
...         name = data['name']
...         if name not in storage:
...             return super(MyCarefulForm, self).add(data)
...         else:
...             raise schema.ValidationError(
...                 u"There's already an item with the name '%s'" % name)>>> save_daniel = storage['Daniel']
>>> html = MyCarefulForm(fake_context, request)()
>>> "Item added successfully" in html
False
>>> "There's already an item with the name 'Daniel'" in html
True
>>> save_daniel is storage['Daniel']
True
>>> len(log) == 0
TrueRender some of the fields in view modeWe can implement in our form aview_schemaattribute, which will
then be used to view information in our form’s table.  Let’s say we
wanted the name of our persons to be viewable only in the table:>>> from z3c.form import field
>>> class MyAdvancedForm(MyForm):
...     update_schema = field.Fields(IPerson).select('age')
...     view_schema = field.Fields(IPerson).select('name')
...     add_schema = IPerson>>> print(MyAdvancedForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Daniel...Maria...</div>We can still edit the age of our Persons:>>> request = TestRequest()
>>> request.form['crud-edit.Maria.widgets.age'] = u'40'
>>> request.form['crud-edit.Daniel.widgets.age'] = u'35'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply Changes'
>>> html = MyAdvancedForm(fake_context, request)()
>>> "Successfully updated" in html
True>>> storage['Maria'].age
40
>>> storage['Daniel'].age
35We can still add a Person using both name and age:>>> request = TestRequest()
>>> request.form['crud-add.form.widgets.name'] = u'Thomas'
>>> request.form['crud-add.form.widgets.age'] = u'28'
>>> request.form['crud-add.form.buttons.add'] = u'Add'
>>> html = MyAdvancedForm(fake_context, request)()
>>> "Item added successfully" in html
True
>>> len(storage)
3
>>> storage['Thomas']
<Person with name='Thomas', age=28>Our form can also contain links to our items:>>> class MyAdvancedLinkingForm(MyAdvancedForm):
...     def link(self, item, field):
...         if field == 'name':
...             return 'http://en.wikipedia.org/wiki/%s' % item.name>>> print(MyAdvancedLinkingForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Daniel"...
...<a href="http://en.wikipedia.org/wiki/Maria"...
...<a href="http://en.wikipedia.org/wiki/Thomas"...
</div>What if we wanted the name to be both used for linking to the itemandfor edit?  We can just include the title field twice:>>> class MyAdvancedLinkingForm(MyAdvancedLinkingForm):
...     update_schema = IPerson
...     view_schema = field.Fields(IPerson).select('name')
...     add_schema = IPerson>>> print(MyAdvancedLinkingForm(fake_context, TestRequest())()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Thomas"...Thomas...</a>...
</div>We can now change Thomas’s name and see the change reflected in the
Wikipedia link immediately:>>> request = TestRequest()
>>> for name in 'Daniel', 'Maria', 'Thomas':
...     request.form['crud-edit.%s.widgets.name' % name] = storage[name].name
...     request.form['crud-edit.%s.widgets.age' % name] = storage[name].age
>>> request.form['crud-edit.Thomas.widgets.name'] = u'Dracula'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply Changes'>>> print(MyAdvancedLinkingForm(fake_context, request)()) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Dracula"...Dracula...</a>...
</div>
>>> storage['Thomas'].name = u'Thomas'Don’t render one partWhat if we wanted our form to display only one part, that is, only the
addorthe edit form.  Our CrudForm can implementeditform_factoryandaddform_factoryto override one or both
forms.  Setting one of these tocrud.NullFormwill make them
disappear:>>> class OnlyEditForm(MyForm):
...     addform_factory = crud.NullForm
>>> html = OnlyEditForm(fake_context, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(True, False)>>> class OnlyAddForm(MyForm):
...     editform_factory = crud.NullForm
>>> html = OnlyAddForm(fake_context, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(False, True)Render only in view, and define own actionsSometimes you want to present a list of items, possibly in view mode
only, and have the user select one or more of the items to perform
some action with them.  We’ll present a minimal example that does this
here.We can simply leave theupdate_schemaclass attribute out (it
defaults toNone).  Furthermore, we’ll need to override the
ediform_factory with our custom version that provides other buttons
than the ‘edit’ and ‘delete’ ones:>>> from pprint import pprint
>>> from z3c.form import button>>> class MyEditForm(crud.EditForm):
...     @button.buttonAndHandler(u'Capitalize', name='capitalize')
...     def handle_capitalize(self, action):
...         self.status = u"Please select items to capitalize first."
...         selected = self.selected_items()
...         if selected:
...             self.status = u"Capitalized items"
...             for id, item in selected:
...                 item.name = item.name.upper()>>> class MyCustomForm(crud.CrudForm):
...     view_schema = IPerson
...     editform_factory = MyEditForm
...     addform_factory = crud.NullForm     # We don't want an add part.
...
...     def get_items(self):
...         return sorted(storage.items(), key=lambda x: x[1].name)>>> request = TestRequest()
>>> html = MyCustomForm(fake_context, TestRequest())()
>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)
>>> pprint(storage)
{'Daniel': <Person with name='Daniel', age=35>,
 'Maria': <Person with name='Maria', age=40>,
 'Thomas': <Person with name='Thomas', age=28>}>>> request.form['crud-edit.Thomas.widgets.select'] = ['selected']
>>> request.form['crud-edit.form.buttons.capitalize'] = u'Capitalize'
>>> html = MyCustomForm(fake_context, request)()
>>> "Capitalized items" in html
True
>>> pprint(storage)
{'Daniel': <Person with name='Daniel', age=35>,
 'Maria': <Person with name='Maria', age=40>,
 'Thomas': <Person with name='THOMAS', age=28>}Wecannotuse any of the other buttons:>>> del request.form['crud-edit.form.buttons.capitalize']
>>> request.form['crud-edit.form.buttons.delete'] = u'Delete'
>>> html = MyCustomForm(fake_context, request)()
>>> "Successfully deleted items" in html
False
>>> 'Thomas' in storage
TrueCustomizing sub formsThe EditForm class allows you to specify an editsubform_factory-a class
inherits from EditSubForm.  This allows you to say, override the crud-row.pt
page template and customize the look of the fields.>>> import zope.schema
>>> class MyCustomEditSubForm(crud.EditSubForm):
...
...     def _select_field(self):
...         """I want to customize the field that it comes with..."""
...         select_field = field.Field(
...         zope.schema.TextLine(__name__='select',
...                              required=False,
...                              title=u'select'))
...         return select_field>>> class MyCustomEditForm(MyEditForm):
...     editsubform_factory = MyCustomEditSubForm>>> class MyCustomFormWithCustomSubForm(MyCustomForm):
...     editform_factory = MyCustomEditForm>>> request = TestRequest()
>>> html = MyCustomFormWithCustomSubForm(fake_context, TestRequest())()Still uses same form as before>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)Just changes the widget used for selecting…>>> 'type="checkbox"' in html
FalseUsing batchingThe CrudForm base class supports batching.  When setting thebatch_sizeattribute to a value greater than0, we’ll only get
as many items displayed per page.>>> class MyBatchingForm(MyForm):
...     batch_size = 2
>>> request = TestRequest()
>>> html = MyBatchingForm(fake_context, request)()
>>> "Daniel" in html, "Maria" in html
(True, True)
>>> "THOMAS" in html
FalseMake sure, the batch link to the next page is available>>> 'crud-edit.form.page=1' in html
TrueShow next page and check content>>> request = TestRequest(QUERY_STRING='crud-edit.form.page=1')
>>> request.form['crud-edit.form.page'] = '1'
>>> html = MyBatchingForm(fake_context, request)()
>>> "Daniel" in html, "Maria" in html
(False, False)
>>> "THOMAS" in html
TrueThe form action also includes the batch page information so
the correct set of subforms can be processed:>>> print(html) \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<BLANKLINE>
...
<form action="http://nohost/context?crud-edit.form.page=1"
          method="post"...Let’s change Thomas’ age on the second page:>>> request.form['crud-edit.Thomas.widgets.name'] = u'Thomas'
>>> request.form['crud-edit.Thomas.widgets.age'] = '911'
>>> request.form['crud-edit.form.buttons.edit'] = u'Apply changes'
>>> html = MyBatchingForm(fake_context, request)()
>>> "Successfully updated" in html
True
>>> "911" in html
TrueChangelog2.0.3 (2023-12-14)Bug fixes:Replace deprecatedcgi.FieldStorageclass with a simple one.
This is only used for converting aZPublisherFileUploadto azope.publisherone.
[maurits] (#1)2.0.2 (2023-10-07)Internal:Update configuration files.
[plone devs] (cfffba8c)2.0.1 (2023-03-21)Internal:Update configuration files.
[plone devs] (a533099d)2.0.0 (2022-11-23)Bug fixes:Final release for Plone 6.0.0.  [maurits] (#600)2.0.0b1 (2022-06-23)Bug fixes:Fix traversal toz3c.form.widget.MultiWidgetwidgets.
[petschki] (#22)2.0.0a3 (2022-05-24)Bug fixes:cleanup crud-table markup
[petschki] (#21)2.0.0a2 (2022-03-23)New features:compatibility with z3c.form >= 4.x
[petschki] (#20)2.0.0a1 (2021-04-21)Breaking changes:Update for Plone 6 with Bootstrap markup
Refs PLIPhttps://github.com/plone/Products.CMFPlone/issues/3061[petschki, ale-rt, agitator] (#16)1.1.3 (2020-06-16)Bug fixes:Copy the HTTPRequest._decode from Zope4 because it is going away in Zope5 (#13)1.1.2 (2020-04-21)Bug fixes:Buttons in crud-table should be list items.
[erral] (#9)1.1.1 (2019-10-12)Bug fixes:Fix edit/delete in paginated crud-forms [fRiSi]1.1.0 (2019-04-09)New features:Change license to GPLv2
[petschki]Enable:record[s]fields inz2.processInputs[petschki]Cosmetic tweaks.
[gforcada]1.0.0 (2018-11-04)New features:Support for Python 3
[pbauer, davilima6, ale-rt, jensens]Bug fixes:Test cleanup.
[jensens]Rename folder templates to pagetemplates, because of ZCML browser directive warnings.
[jensens]Removal of tuple parameter unpacking in method definition (Fixes #14)
[ale-rt]Provide an up-to-date bootstrap.py
[ale-rt]Use the adapter and implementer decorators
[ale-rt]0.9.1 (2017-09-03)Bug fixes:Remove deprecated __of__ calls on BrowserViews
[MrTango]0.9.0 (2016-05-25)New:Enable groups aka fieldsets to be orderable.
[jensens]Fixes:Fix batching navigation in  CRUD form.
[petschki]Added two missing German translations.
One of those fixeshttps://github.com/plone/Products.CMFPlone/issues/1580[jensens]QA: pep8.  [maurits, thet]0.8.1 (2015-01-22)Feature: take group class from parent-form if given. If not given it uses
the default class.
[jensens]Added Ukrainian translation
[kroman0]Added Italian translation
[giacomos]0.8.0 (2012-08-30)Remove backwards-compatibility code for Zope < 2.12
[davisagli]Use plone.testing for functional test layer support.
[hannosch]Use ViewPageTemplateFile from zope.browserpage.
[hannosch]Use form action URL as given by the view, instead of implementing it
in the template as a call to thegetURLmethod of the request.
[malthe]Use plone.batching for batches instead of z3c.batching
[tom_gross]0.7.8 - 2011-09-24Do not display h1 element if there is no label on view.
[thomasdesvenain]Add Chinese translation.
[jianaijun]0.7.7 - 2011-06-30Avoid rendering a wrapped form if a redirect has already occurred after
updating it.
[davisagli]Remove <a name=””/> elements from inside the CRUD table TBODY element
they were otherwise unused (and illegal in that location of the HTML content
model).
[mj]0.7.6 - 2011-05-17Add ability to find widgets with non-integer names in lists. This shouldn’t
generally be something that happens, and ideally should be removed if
DataGridField looses it’s ‘AA’ and ‘TT’ rows.
[lentinj]0.7.5 - 2011-05-03Fix traversal tests on Zope 2.10 to handle TraversalError instead of
LocationError.
[elro]Fix traversal.py syntax to be python2.4 compatible.Revert [120798] as it breaks on Zope2.10 / Plone 3.3. We can deal with Zope
2.14 in 0.8.x.
[elro]0.7.4 - 2011-05-03Define ‘hidden’ within field macro.
[elro]Ignore “form.widgets.” if ++widget++ path begins with it.
[lentinj]Rework traverser to handle lists and subforms
[lentinj]Only search a group’s widgets if they exist. collective.z3cform.wizard doesn’t
create widgets for pages/groups other than the current one
[lentinj, elro]Deal with forward compatibility with Zope 2.14.Adds Brazilian Portuguese translation.
[davilima6]0.7.3 - 2011-03-02Handle wrong fieldnames more cleanly in the ++widget++ traverser.
[elro]0.7.2 - 2011-02-17Make sure the CRUD add form doesn’t use a standalone template.
[davisagli]0.7.1 - 2011-01-18Add zope.app.testing to test dependencies so that it continues to work under
Zope 2.13.
[esteele]0.7.0 - 2010-08-04Add a marker interface which can be used by widgets to defer any security
checks they may be doing when they are set up during traversal with the
++widgets++ namespace
[dukebody]Fix re-ordering of fields not in the default fieldset. Thanks to Thomas
Buchberger for the patch.
[optilude]Added Norwegian translation.
[regebro]0.6.0 - 2010-04-20In the CRUD table, fix odd/even labels, which were reversed.
[limi]Added slots to thetitlelessformmacro. SeeREADME.txtfor details.
[optilude, davisagli]Remove the distinction between wrapped and unwrapped subforms. A subform is
always wrapped by the form that contains it, and can use a Zope 3 page
template.
[davisagli]Fixed tests in Plone 3.
[davisagli]Fixed tests in Plone 4
[optilude]Made it possible to distinguish wrapped and unwrapped forms via the
IWrappedForm marker interface.
[optilude]Made it possible to use z3c.form forms without a FormWrapper in Plone 4.
[optilude]0.5.10 - 2010-02-01A z3c.form.form.AddForm do a redirect in its render method.
So we have to render the form to see if we have a redirection.
In the case of redirection, we don’t render the layout at all.
This version remove the contents method on FormWrapper,
it’s now an attribute set during the FormWrapper.update.
This change fixes status message not shown because it was consumed by
the never shown rendered form.
[vincentfretin]0.5.9 - 2010-01-08Fix security problem with the ++widget++ namespace
[optilude]0.5.8 - 2009-11-24Don’t do the rendering if there is a redirection, use the update/render
pattern for that.
Seehttp://dev.plone.org/plone/ticket/10022for an example how
to adapt your code, in particular if you used FormWrapper with ViewletBase.
[vincentfretin]0.5.7 - 2009-11-17Fix silly doctests so that they don’t break in Python 2.6 / Zope 2.12
[optilude]0.5.6 - 2009-09-25Added title_required msgid in macros.pt to be the same as plone.app.z3cform
because macros.pt from plone.app.z3cform uses plone.z3cform translations.
Added French translation and fixed German and Dutch translations
for label_required and title_required messages.
[vincentfretin]0.5.5 - 2009-07-26Removed explicit <includeOverrides /> call from configure.zcml. This causes
race condition type errors in ZCML loading when overrides are included
later.
[optilude]0.5.4 - 2009-04-17Added monkey patch to fix a bug in z3c.form’s ChoiceTerms on z3c.form 1.9.0.
[optilude]Fix obvious bugs and dodgy naming in SingleCheckBoxWidget.
[optilude]Use chameleon-based page templates from five.pt if available.
[davisagli]Copied the basic textlines widget from z3c.form trunk for use until
it is released.
[davisagli]0.5.3 - 2008-12-09Add translation marker for batch, update translation files.
[thefunny42]Handle changed signature for widget extract method in z3c.form > 1.9.0
[davisagli]Added wildcard support to the ‘before’ and ‘after’ parameters of the
fieldset ‘move’ utility function.
[davisagli]Fixes for Zope 2.12 compatibility.
[davisagli]Don’t display an ‘Apply changes’ button if you don’t define an
update_schema.
[thefunny42]Declare xmlnamespace into ‘layout.pt’ and ‘subform.pt’ templatesAdded support for an editsubform_factory for an EditForm so you can
override the default behavior for a sub form now.Changed css in crud-table.pt for a table to “listing” so that tables
now look like plone tables.Copy translation files to an english folder, so if your browser
negotiate toen,nl, you will get english translations instead of
dutch ones (like expected).
[thefunny42]Send an event IAfterWidgetUpdateEvent after updating display widgets
manually in a CRUD form.
[thefunny42]0.5.2 - 2008-08-28Add a namespace traversal adapter that allows traversal to widgets. This
is useful for AJAX calls, for example.0.5.1 - 2008-08-21Add batching toplone.z3cform.crudCrudForm.Look up the layout template as an IPageTemplate adapter. This means that
it is possible for Plone to provide a “Ploneish” default template for forms
that don’t opt into this, without those forms having a direct Plone
dependency.Default to the titleless form template, since the layout template will
provide a title anyway.Inplone.z3cform.layout, allow labels to be defined per form
instance, and not only per form class.0.5.0 - 2008-07-30No longer depend on <3.5 of zope.component.0.4 - 2008-07-25Depend on zope.component<3.5 to avoidTypeError("Missing'provides' attribute")error.Allow ICrudForm.add to raise ValidationError, which allows for
displaying a user-friendly error message.Make the default layout template CMFDefault- compatible.0.3 - 2008-07-24Moved Plone layout wrapper toplone.app.z3cform.layout.  If you
were usingplone.z3cform.base.FormWrapperto get the Plone
layout before, you’ll have to useplone.app.z3cform.layout.FormWrapperinstead now.  (Also, make
sure you include plone.app.z3cform’s ZCML in this case.)Move out Plone-specific subpackages toplone.app.z3cform.  These
are:wysywig: Kupu/Plone integrationqueryselect: use z3c.formwidget.query with ArchetypesClean up testing code and developmentbuildout.cfgto not pull
in Plone anymore.
[nouri]Relicensed under the ZPL 2.1 and moved into the Zope repository.
[nouri]Add German translation.
[saily]0.2 - 2008-06-20Fix usage of NumberDataConverter with zope.i18n >= 3.4 as the
previous test setup was partial and did not register all adapters
from z3c.form (some of them depends on zope >= 3.4)
[gotcha, jfroche]More tests
[gotcha, jfroche]0.1 - 2008-05-21Provide andregisterdefault form and subform templates.  These
allow forms to be used with the style provided in this package
without having to declareform =ViewPageTemplateFile('form.pt').This does not hinder you from overriding with your ownformattribute like usual.  You can also still register a more
specialized IPageTemplate for your form.Add custom FileUploadDataConverter that converts a Zope 2 FileUpload
object to a Zope 3 one before handing it to the original
implementation.  Also add support for different enctypes.
[skatja, nouri]Added Archetypes reference selection widget (queryselect)
[malthe]Moved generic Zope 2 compatibility code for z3c.form and a few
goodies from Singing & Dancing into this new package.
[nouri]