z3c.form

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.

Examining current widgets

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

Setting form status message

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"

Rendering form manually

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

Emulating form HTTP POST in unit tests

  • 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.

  • You must emulate the ZPublisher behavior

    which automatically converts string input to Python primitives. For example, all choice/select values are Python lists.

  • Some z3c widgets, like <select>, need to have WIDGETNAME-empty-marker value set to

    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))

Fields

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).

Creating a field

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]

Adding a field to a form

Use overridden += operator. Fields instances can be added to the existing Fields instances.

Example:

self.form.fields += z3c.form.Fields(schema_field)

Modifying a field

Fields can be accessed by their name in form.fields. Example:

self.form.fields["myfieldname"].name = u"Foobar"

Accesing schema of the field

zope.schema Field is stored as a field attribute of a field. Example:

textline = self.form.fields["myfieldname"].field # zope.schema.TextLine

Widgets

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.

Accessing a widget

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

Modifying a widget

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"

Setting widget types

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.

Widget save

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

Widget value, either from form POST or previous context data, is available in widget.value after form.update() call.

Adding a CSS class

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.

Accesing schema of the field

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.

Getting selection widget vocabulary value as human readable text

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>

Setting widget templates

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"> &mdash; <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>

Customizing form frame

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 form

CRUD (Create, read, update, delete) forms manage list of objects.

CRUD form elements

  • Add form creates new objects and renders the form below the le
  • Edit sub-form edits existing object and renders one le row
  • Edit form lists all objects and allows deleting them ( le master)
  • CRUD form orchestrates the whole thing and renders add and edit forms
  • view_schema outputs read-only fields in CRUD le
  • update_schema outputs edi le fields in CRUD le. Usually you want either view_schema or update_schema
  • add_schema outputs add form

Notes: context attribute of add and edit form is the parent CRUD form. Context attribute of edit sub form is the edit form.