z3c.form is generic, very flexible and very complex form library for Python. plone.app.z3cform and plone.z3cform provide Plone adaptions of this library.
Read each package tutorials before proceeding.
Example:
from z3c.form import form
class MyForm(form.Form):
def updateWidgets(self):
""" Customize widget options before rendering the form. """
form.Form.updateWidgets(self)
# Dump out all widgets - note that each <fieldset> is a subform and this function only
# concerns the current fieldset
for i in self.widgets.items():
print i
Form global status message tells whether the form action succeeded or not.
Form status message will be rendered only on the form. If you want to set a message which will be visible even if the user renders other page after form, you need to use Products.statusmessage.
To set the form status message:
form.status = u"My message"
Usually a a wrapper is used to render the form which takes care of setting up rendering requirements. If you want to render your from inside viewlet or part of the other view, you can do the following.
TODO
HTTP request must have field at least one of buttons filled
Form widget naming must match HTTP post values. Usually widgets have form.widgets prefix.
which automatically converts string input to Python primitives. For example, all choice/select values are Python lists.
interger 1 to be processed
Usually you can get the dummy HTTP request object via acquisition self.portal.REQUEST
Example (incomplete):
layout = "accommondationsummary_view"
# Zope publisher uses Python list to mark <select> values
self.portal.REQUEST["form.widgets.area"] = [SAMPLE_AREA]
self.portal.REQUEST["form.buttons.search"] = u"Search"
view = self.portal.cards.restrictedTraverse(layout)
# Call update() for form
view.process_form()
print view.form.render()
# Always check form errors after update()
errors = view.errors
self.assertEqual(len(errors), 0, "Got errors:" + str(errors))
Field is responsible for 1) prepopulating form values from context 2) storing data to context after succesful POST.
Form fields are stored in form.fields variable which is instance of Fields class (ordered dictionary like).
Fields are created by adapting one or more zope.schema fields for z3c.form using Fields() constructor.
Example of creating one field:
schema_field = zope.schema.TextLine()
form_fields = z3c.form.field.Fields(schema_field)
one_form_field = zfields.values()[0]
Use overridden += operator. Fields instances can be added to the existing Fields instances.
Example:
self.form.fields += z3c.form.Fields(schema_field)
Fields can be accessed by their name in form.fields. Example:
self.form.fields["myfieldname"].name = u"Foobar"
zope.schema Field is stored as a field attribute of a field. Example:
textline = self.form.fields["myfieldname"].field # zope.schema.TextLine
Widget is responsible for 1) rendering HTML code for input 2) parsing HTTP post input.
Widgets are stored as widgets attribute of a form. It is presented by ordered dict like Widgets class.
Widgets are not available until form’s update() and updateWidgets() methods have been called. updateWidgets() will bind() widgets to the form context. For example, vocabularies defined by name are resolved in this point.
Widget has two names:
- widget.__name__ is the name of the corresponding field. Look ups from form.widgets[] can be done using this name.
- widget.name is the decorated name used in HTML code. It is in format ${form name}.${field set name}.${widget.__name__}.
Zope publisher will also mangle widget names based on what kind of input the widget takes. When HTTP POST request comes in, Zope publisher automatically converts <select> dropdowns to lists and so on.
Widget can be accessed by its field’s name. Example:
class MyForm(z3c.form.Form):
def update(self):
z3c.form.Form.update(self)
widget = form.widgets["myfieldname"] # Get one wiget
for w in wiget.items(): print w # Dump all widgets
Widgets are stored in form.widgets dictionary. Mapping is field name -> widget. Widget label can be different than field name.
Example:
from z3c.form import form
class MyForm(form.Form):
def updateWidgets(self):
""" Customize widget options before rendering the form. """
self.widgets["myfield"].label = u"Foobar"
By default, widgets for form fields are determined by FieldWidget adapters (defined in ZCML). You can override adapters per field using field’s widgetFactory property.
Below is an example which creates a custom widget, its FieldWidget factory and uses it for one field in one form:
from zope.component import adapter, getMultiAdapter
from zope.interface import implementer, implements, implementsOnly
from z3c.form.interfaces import IFieldWidget
from z3c.form.widget import FieldWidget
from plone.formwidget.namedfile.widget import NamedFileWidget, NamedImageWidget
class HeaderFileWidget(HeaderWidgetMixin, NamedFileWidget):
# Get download url for HeaderAnimation object's file.
# Download URL is set externally by edit sub form and
download_url = None
class HeaderImageWidget(HeaderWidgetMixin, NamedImageWidget):
pass
@implementer(IFieldWidget)
def HeaderFileFieldWidget(field, request):
""" Factory for creating HeaderFileWidget which is bound to one field """
return FieldWidget(field, HeaderFileWidget(request))
class EditHeaderAnimationSubForm(crud.EditSubForm):
"""
"""
def updateWidgets(self):
""" Enforce custom widget types which get file/image attachment URL right """
# Custom widget types are provided by FieldWidget factories
# before updateWidgets() is called
self.fields["animation"].widgetFactory = HeaderFileFieldWidget
crud.EditSubForm.updateWidgets(self)
# Make edit form aware of correct image download URLs
self.widgets["animation"].download_url = "http://mymagicalurl.com"
Alternatively, you can use plone.directives.form to add widget hints to form schema.
After form.update() if the request was save request and all data was valid form applyChanges(data) is called.
By default widgets use datamanger.AttributeField and tries to store its value as a member attribute of the object returned by form.getContent().
TODO: How do add custom DataManager
Widget value, either from form POST or previous context data, is available in widget.value after form.update() call.
Widgets have a method addClass() to add extra CSS classes. This is useful if you have Javascript/JQuery associated with your special form:
widget.addClass("myspecialwidgetclass")
Note that these classes are directly applied to <input>, <select> etc. itself and not the wrapping <div> element.
zope.schema Field is stored as a field attribute of a widget. Example:
textline = form.widgets["myfieldname"].field # zope.schema.TextLine
widget.field is not z3c.form.field.Field object.
Example:
widget = self.widgets["myselectionlist"]
token = widget.value[0] # widget.value is list of unicode strings, each is token for the vocabulary
user_readable = widget.terms.getTermByToken(token).title
Example (page template):
<td tal:define="widget view/widgets/myselectionlist">
<span tal:define="token python:widget.value[0]" tal:content="python:widget.terms.getTermByToken(token).title" />
</td>
Usually the correct way to set the widget template is using <z3c:widgetTemplate> ZCML:
<z3c:widgetTemplate
mode="display"
widget=".interfaces.INamedFileWidget"
layer="z3c.form.interfaces.IFormLayer"
template="file_display.pt"
/>
You can also enforce widget template in the render() method of the widget class:
from zope.component import adapter, getMultiAdapter
from zope.interface import implementer, implements, implementsOnly
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from z3c.form.interfaces import IFieldWidget, INPUT_MODE, DISPLAY_MODE, HIDDEN_MODE
from z3c.form.widget import FieldWidget
from plone.formwidget.namedfile.widget import NamedFileWidget, NamedImageWidget
class HeaderFileWidget(NamedFileWidget):
""" Subclass widget a use a custom template """
display_template = ViewPageTemplateFile("header_file_display.pt")
def render(self):
"""See z3c.form.interfaces.IWidget."""
if self.mode == DISPLAY_MODE:
# Enforce template and do not query it from the widget template factory
template = self.display_template
return NamedFileWidget.render(self)
Widget template example:
<span id="" class="" i18n:domain="plone.formwidget.namedfile"
tal:attributes="id view/id;
class view/klass;
style view/style;
title view/title;
lang view/lang;
onclick view/onclick;
ondblclick view/ondblclick;
onmousedown view/onmousedown;
onmouseup view/onmouseup;
onmouseover view/onmouseover;
onmousemove view/onmousemove;
onmouseout view/onmouseout;
onkeypress view/onkeypress;
onkeydown view/onkeydown;
onkeyup view/onkeyup"
tal:define="value view/value;
exists python:value is not None">
<span tal:define="fieldname view/field/__name__ | nothing;
filename view/filename;
filename_encoded view/filename_encoded;"
tal:condition="python: exists and fieldname">
<a tal:content="filename"
tal:attributes="href string:${view/download_url}">Filename</a>
<span class="discreet"> — <span tal:define="sizekb view/file_size" tal:replace="sizekb">100</span> KB</span>
</span>
<span tal:condition="not:exists" class="discreet" i18n:translate="no_file">
No file
</span>
</span>
If you want to change the surroundings around the z3c.form form, like Plone main template, text above and below the form, you can do as in the following example:
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile as FiveViewPageTemplateFile
from plone.directives import form
from plone.z3cform.layout import FormWrapper, wrap_form
class EditHeaderBehaviorForm(form.EditForm):
""" Form which displays options to edit header animation.
"""
...
class EditHeaderBehaviorView(FormWrapper):
""" Render Plone frame around our form with little modifications """
# We need to define form and index attributes for custom FormWrapper
# form points to our Form class
form = EditHeaderBehaviorForm
# Index is Zope 2 page template file which renders the frame around the form
index = FiveViewPageTemplateFile("edit_header.pt")
def __init__(self, context, request):
# We can optionally set some variables in the constructor
FormWrapper.__init__(self, context, request)
self.header_animation_helper = self.context.restrictedTraverse("@@header_animation_helper")
# Our view exposes two custom functions to the template
def getAnimationCount(self):
""" Return how many animations are availabe in the context """
return len(self.header_animation_helper.header.alternatives)
def getHeadeDefiner(self):
""" Return the parent object defining animations in this context """
return self.header_animation_helper.defining_context
And corresponding template edit_header.pt:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
lang="en"
metal:use-macro="here/main_template/macros/master"
i18n:domain="plone.app.headeranimation">
<body>
<metal:main fill-slot="main">
<tal:main-macro metal:define-macro="main">
<h1 class="documentFirstHeading" tal:content="view/label">Title</h1>
<div id="skel-contents">
<span tal:replace="structure view/contents" />
</div>
<!-- Custom section goes here below the form -->
<h2>Available animations</h2>
<div id="animations">
<span>
We have <b tal:content="view/getAnimationCount"> animations or images</b>
defined by <a tal:attributes="href view/getHeaderDefiner/absolute_url" tal:content="view/getHeadeDefiner/title_or_id" />
</span>
</div>
</tal:main-macro>
</metal:main>
CRUD (Create, read, update, delete) forms manage list of objects.
CRUD form elements
Notes: context attribute of add and edit form is the parent CRUD form. Context attribute of edit sub form is the edit form.