(z3c.form)

Description

z3c.form is modern, flexible, form library for web frameworks. A Plone specific implementation, plone.app.z3cform exists. This document will give you pointers how to use z3c.form to build your web forms programmaticaly.

はじめに

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.

z3c.form big picture

Form model consist of

  • self.request - HTTP request coming in
  • self.context - The Plone content item which was associated with the form view when URL traversing was done
  • self.getContent() - The actual object extracted from context and manipulated by the form if ignoreContext is not False
  • self.status - A message displayed at the top of the form to the user when the form is rendered. Usually it will be “Please correct the errors below”.

Form call chain goes like

  • Form.update() is called

    • [plone.autoform based forms only] Calls Form.updateFields() - this will set widget factory methods for fields. If you want to customize the type of the widget associated with the field do it here. If your form is not plone.autoform based you need to edit form.schema widget factories on the module level code after the class has been constructed. The logic mapping widget hints to widgets is in plone.autoform.utils.

    • Calls Form.updateWidgets() - you can customize widgets in this point if you override this method. self.widgets instance is created based on self.fields property.

    • Calls Form.updateActions()

      • Calls the action handler (button handler which was pressed)
      • If it’s edit form, action handler calls applyChanges() to store new values on the object and return True if any value was changed.
  • Form.render() is called

    • Outputs form HTML based on widgets and their templates

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"

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 of Fields instance. 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

Introspecting form 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

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"

If you want to have a complete different Python class for widget you need to override field’s widget factory in module body code after fields have been constructed in the class or in update() for dynamically constructed fields:

def update(self):

     self.fields["animation"].widgetFactory = HeaderFileFieldWidget

Making widgets required conditionally

If you want to avoid hardwired required on fields and toggle then conditionally you need to supplied dynamically modified schema field to z3c.form.field.Fields instance of the form.

Example:

class ShippingAddressForm(CheckoutSubform):
    ignoreContext = True
    label = _(u"Shipping address")

    # Distinct fields on same <form> HTML element
    prefix = "shipping"

    def __init__(self, optional, content, request, parentForm):
        """
        @param optional: Whether shipping address should be validated or not.
        """
        subform.EditSubForm.__init__(self, content, request, parentForm)
        self.optional = optional

    @property
    def fields(self):
        """ Get the field definition for this form.

        Form class's fields attribute does not have to
        be fixed, it can be property also.
        """

        # Construct the Fields instance as we would
        # normally do in more static way
        fields = z3c.form.field.Fields(ICheckoutAddress)

        # We need to override the actual required from the
        # schema field which is litte tricky.
        # Schema fields are shared between instances
        # by default, so we need to create a copy of it
        if self.optional:
            for f in fields.values():
                # Create copy of a schema field
                # and force it unrequired
                schema_field = copy.copy(f.field) # shallow copy of an instance
                schema_field.required = False
                f.field = schema_field

        return fields

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>

Subforms

Subforms are independed z3c.form.Form objects for which you need to call update() manually. But they do not have own buttons or status line.

Adding an action to parent and subform

Parent and subform actions must be linked.

Example:

class CheckoutForm(z3c.form.form.EditForm):


    @button.buttonAndHandler(_('Continue'), name='continue')
    def handleContinue(self, action):
        """ Extract the checkout data to session and redirect to payment processer checkout screen.

        Note:

        """

        # Following has been copied from z3c.form.form.EditForm
        data, errors = self.extractData()
        if errors:
            self.status = self.formErrorsMessage
            return

        changes = self.applyChanges(data)

        if changes:
            self.status = self.successMessage
        else:
            self.status = self.noChangesMessage


class CheckoutSubform(subform.EditSubForm):
    """ Add support for continue action. """


    def execute(self):
        """
        Make sure that the form is refreshed when parent
        form Continue is pressed.
        """

        data, errors = self.extractData()
        if errors:
            self.errors = errors
            self.status = self.formErrorsMessage
            return errors

        content = self.getContent()
        z3c.form.form.applyChanges(self, content, data)

        return None

    @button.handler(CheckoutForm.buttons['continue'])
    def handleContinue(self, action):
        """ What happens when the parent form button is pressed """
        self.execute()

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.

Displaying the status message in a non-standard location

By default, the status message is rendered inside plone.app.z3cform macros.pt above the form:

<metal:define define-macro="titlelessform">

    <tal:status define="status view/status" condition="status">
        <dl class="portalMessage error" tal:condition="view/widgets/errors">
            <dt i18n:domain="plone" i18n:translate="">
                Error
            </dt>
            <dd tal:content="status" />
        </dl>
        <dl class="portalMessage info" tal:condition="not: view/widgets/errors">
            <dt i18n:domain="plone" i18n:translate="">
                Info
            </dt>
            <dd tal:content="status" />
        </dl>
    </tal:status>

We can decouple the status message from the form, without overriding all the templates, by copying status message variable to another variable and then playing around with it in our wrapper view template.

Form class:

class HolidayServiceSearchForm(form.Form):
    """

    """


    @button.buttonAndHandler(_(u"Search"))
    def searchHandler(self, action):
        """ Search form submit handler for product card search.
        """

        data, errors = self.extractData()
        if len(self.search_results) == 0:
            self.status = _(u"No holiday services found.")
        else:
            msgid = _("found_results", default=u"Found ${results} holiday services.", mapping={u"results" : len(self.search_results)})
            self.status = self.context.translate(msgid)

        ...

        # Use non-standard location to display the status
        # for success messages
        if len(self.widgets.errors) == 0:
            self.result_message = self.status
            self.status = None

class HolidayServiceSearchView(FormWrapper):
    """
    HolidayService browser view
    """

    form = HolidayServiceSearchForm


    def result_message(self):
        """ Display result message in non-standard location """

        if len(self.form_instance.widgets.errors) == 0:
            # Do not display form highlight errors here
            return self.form_instance.result_message

... and then we can use a special result_message view accessor in our view template code

Button handlers

Easiest way is in-form class button declarations:

from z3c.form import button


class Form(...):

    button.buttonAndHandler(_('Add'), name='add')
    def handle_add(self, action):
        data, errors = self.extractData()
        if errors:
            self.status = "Please correct errors"
            return

        self.applyChanges(data)
         self.status = _(u"Item added successfully.")

Dynamic buttons

You can dynamically modify buttons and handlers properties of the form instance.

ノート

If you are doing dynamic modifications make sure that you work with a deep copied form instance (import copy ; copy.deepcopy). If the form instance is singleton, all changes are cumulative and produce funny results when the form is modified next time.

Exampe code:

def setActions(self):
    """ Add button to the form based on dynamic conditions. """


    if self.isSaveEnabled():

        but = button.Button("save", title=u"Save")
        self.form.buttons += button.Buttons(but)

        self.form.buttons._data_keys.reverse() # Fix Save button to left


        # Note that here we use unbound method (no self) TimepointFullForm.confirm
        # since handler manually forces in the form  parameter
        handler = button.Handler(but, self.form.__class__.handleSave)
        self.form.handlers.addHandler(but, handler)

Read-only fields

There is field.readonly flag.

Example code:

class AREditForm(crud.EditForm):
    """ Form whose fields are dynamically constructed """

    def ar_editable(self):
        """ Arbitary condition deciding whether fields on this form are
        patient=self.__parent__.__parent__
        if patient.getConfirmedAR()  in (None,'','EDITABLE_AR'):
            return True
        return False


    @property
    def fields(self):
        """
        Dynamically create field data based on run-time constructed schema.

        Instead using static ``fields`` attribute, we use Python property
        which allows us to generate z3c.form.fields.Fields instance for the
        for run-time.
        """


        constructor = ARFormConstructor(self.context, self.context.context, self.request)

        # Create z3c.form.field.Fields object instance
        fields = constructor.getFields()

        if not self.ar_editable():
            # Disable all fields in edit mode if this form is locked out
            for f in fields.values():
                f.mode = z3c.form.interfaces.DISPLAY_MODE

        return fields

You might also want to disable edit button if none if the fields are editable:

# Make edit button conditional AREditSubForm.buttons[“apply”].condition = lambda form: form.has_edit_button()

ノート

You can also set = z3c.form.interfaces.DISPLAY_MODE in updateWidgets() if you are not dynamically poking form fields themselves.

警告

Do not modify fields on singleton instances (form or fields objects are shared between all forms). This causes problems on concurrent access.

ノート

zope.schema.Field has readonly propertly. z3c.form.field.Field does not have this property, but has mode property. Do not confuse these two.

Storage format and data managers

By default, z3c.form reads incoming context values as the object attributes. This behavior can be customized using data managers.

You can, for example, use Python dictionaries to read and store form data.

Custom content objects

The following hack can be used if you have an object which does not conform your form interface and you want to explose only certain object attribute to the form to be edited.

Example:

class ISettings(zope.interface.Interface):

    # This maps to Archetypes field confirmedAR on SitsPatient
    confirmedAR = zope.schema.Choice(title=_(u"Confirm adherse reactions"),
                                       description=_(u"Confirm that all adherse reactions regarding the patient life cycle have been entered here and there will be no longer adherse reaction data"),
                                       vocabulary=make_zope_schema_vocabulary(ADVERSE_STATUS_VOCABULARY))


class ARSettingsForm(form.Form):
    """ General settings for all adherse reactions """

    fields = Fields(ISettings)

    def getContent(self):
        """ """

        # Create a temporary object holding the settings values out of the patient

        class TemporarySettingsContext(object):
            zope.interface.implements(ISettings)

        obj = TemporarySettingsContext()

        # Copy values we want to expose to the form from Plone context item to the temporary object
        obj.confirmedAR = self.context.confirmedAR

        return obj

ノート

Since getContent() is also used in applyChanges() you need to override applyChanges() too to save values correctly back to non-temporary object.

Custom change applying

The default behavior of z3c.form edit form is to write incoming data as the attributes of the object returned by getContent().

You can override this behavior by overriding applyChanges() method.

Example:

def applyChanges(self, data):
    """
    Reflect confirmed status to Archetypes schema.

    @param data: Dictionary of cleaned form data, keyed by field
    """


    # This is the context given to the form when the form object was constructed
    patient = self.context

    assert ISitsPatient.providedBy(patient) # safety check

    # Call archetypes field mutator to store the value on the patient object
    patient.setConfirmedAR(data["confirmedAR"])

WYSIWYG widgets

By using plone.directives.form and plone.app.z3cform packages you can do:

from plone.app.z3cform.wysiwyg import WysiwygFieldWidget

from mfabrik.plonezohointegration import _

class ISettings(form.Schema):
    """ Define schema for settings of the add-on product """

    form.widget(contact_form_prefix=WysiwygFieldWidget)
    contact_form_prefix = schema.Text(title=_(u"Contact form top text"),
                                      description=_(u"Custom text for the long contact form upper part"),
                                      required=False,
                                      default=u"")

More information

Wrapped and non-wrapped forms

z3c.form.form.Form object is “wrapped” when it is rendered inside Plone page frame and having acquisition chain in intact.

Since plone.app.z3cform 0.5.0 the behavior goes like this

  • Plone 3 forms are automatically wrapped
  • Plone 4 forms are unwrapped

Wrapper is a plone.z3cform.interfaces.IWrappedForm marker interface on the form object, applied it after the form instance has been constructed. If this marker interface is not applied, plone.z3cform.ZopeTwoFormTemplateFactory tries to embed form into Plone page frame. If the form is indended not be rendered as full page form, this usually leads to the following exception:

*** ContentProviderLookupError: plone.htmlhead

The form tries to render the full Plone page. Rendering this page needs an acquisition chain set-up for the view and the template. Embedded forms do not have this, or it would lead to recursion error.

If you are constructing form instances manually and want to render them without Plone page decoration, you must make sure that automatic form wrapping does not take place:

import zope.interface
from plone.z3cform.interfaces import IWrappedForm

class SomeView(BrowserView):

    def init(self):
        """ Constructor embedded sub forms """


        # Construct few embedded forms
        self.mobile_form_instance = MobileForm(self.context, self.request)
        zope.interface.alsoProvides(self.mobile_form_instance, IWrappedForm)

        self.publishing_form_instance = PublishingForm(self.context, self.request)
        zope.interface.alsoProvides(self.publishing_form_instance, IWrappedForm)

        self.override_form_instance = getMultiAdapter((self.context, self.request), IOverrideForm)
        zope.interface.alsoProvides(self.override_form_instance, IWrappedForm)

Embedding z3c.form forms in portlets, viewlets and views

By default, when plone.app.z3cform is installed through the add-on installer, all forms have full Plone page frame. If you are rendering forms inside non-full-page objects, you need to change the default template.

Below is an example how to put z3c.form based form into a portlet.

ノート

plone.app.z3cform version 0.5.1 or later is needed, as older versions do not support overriding form.action property.

You need following

  • z3c.form class
  • viewlet/portlet class
  • A form wrapper template which renders the frame around the form. The default version renders the whole Plone page frame - you don’t want this when the form is embedded, otherwise you get infinite recursion (plone page having a form having a plone page...)
  • Portlet/viewlet template which refers to the form
  • ZCML to register all components

Portlet code:

from plone.z3cform.layout import FormWrapper

class PortletFormView(FormWrapper):
     """ Form view which renders z3c.forms embedded in a portlet.

     Subclass FormWrapper so that we can use custom frame template. """

     index = ViewPageTemplateFile("formwrapper.pt")

class Renderer(base.Renderer):
    """ z3c.form portlet renderer.

    Instiate form and wrap it to a special layout template
    which will give the form suitable frame to be used in the portlet.

    We also set a form action attribute, so that
    the browser goes to another page after the form has been submitted
    (we really don't know what kind of page the portlet is displayed
    and is it safe to submit forms there, so we do this to make sure).
    The action page points to a browser:page view where the same
    form is displayed as full-page form, giving the user to better
    user experience to fix validation errors.
    """

    render = ViewPageTemplateFile('zohocrmcontact.pt')

    def __init__(self, context, request, view, manager, data):
        base.Renderer.__init__(self, context, request, view, manager, data)
        self.form_wrapper = self.createForm()

    def createForm(self):
        """ Create a form instance.

        @return: z3c.form wrapped for Plone 3 view
        """

        context = self.context.aq_inner

        returnURL = self.context.absolute_url()

        # Create a compact version of the contact form
        # (not all fields visible)
        form = ZohoContactForm(context, self.request, returnURLHint=returnURL, full=False)

        # Wrap a form in Plone view
        view = PortletFormView(context, self.request)
        view = view.__of__(context) # Make sure acquisition chain is respected
        view.form_instance = form

        return view

    def getContactFormURL(self):
        """ For rendering the form link at the bottom of the portlet.

        @return: URL leading to the full contact form
        """
        return self.form_wrapper.form_instance.action

formwrapper.pt is just a dummy form view template which wraps the form. This differs from standard form wrapper by not rendering Plone main layout around the form.

<div class="portlet-form">
   <div tal:replace="structure view/contents" />
</div>

Then the portlet template itself (zohoportlet.pt) renders the portlet. Form is referred by syntax <form tal:replace="structure view/form_wrapper" />.

<dl class="portlet portletZohoCRMContact"
    i18n:domain="mfabrik.plonezohointegration">

    <dt class="portletHeader">
        <span class="portletTopLeft"></span>
        <span i18n:translate="portlet_title">
           Contact Us
        </span>
        <span class="portletTopRight"></span>
    </dt>

    <dd class="portletItem odd">
        <form tal:replace="structure view/form_wrapper" />
    </dd>

    <dd class="portletFooter">
        <span class="portletBottomLeft"></span>
        <a href=""
           tal:attributes="href view/getContactFormURL"
           i18n:translate="box_more_news_link">
          Longer contact form&hellip;
        </a>
        <span class="portletBottomRight"></span>
    </dd>

</dl>

ノート

Viewlet behave little different, since they do automatically some acquisition chain mangling when you assign variables to self. Thus you should never have self.view = view or self.form = form in viewlet.

Template example for viewlet (don’t do sel.form_wrapper)

<div id="my-viewlet">
  <form tal:replace="structure python:view.createForm()()" />
</div>

Then the necessary parts of form itself:

class IZohoContactForm(zope.interface.Interface):
    """ Form field definitions for Zoho contact forms """

    first_name = schema.TextLine(title=_(u"First name"))

    last_name = schema.TextLine(title=_(u"Last name"))

    company = schema.TextLine(title=_(u"Company / organization"), description=_(u"The organization which you represent"))

    email = schema.TextLine(title=_(u"Email address"), description=_(u"Email address we will use to contact you"))

    phone_number = schema.TextLine(title=_(u"Phone number"),
                                   description=_(u"Your phone number in international format. E.g. +44 12 123 1234"),
                                   required=False,
                                   default=u"")


    returnURL = schema.TextLine(title=_(u"Return URL"),
                                description=_(u"Where the user is taken after the form is succesfully submitted"),
                                required=False,
                                default=u"")

class ZohoContactForm(Form):
    """ z3c.form used to handle the new lead submission.

    This form can be rendered

    * standalone (@@zoho-contact-form view)

    * embedded into the portlet

    ..note::

        It is recommended to use a CSS rule
        to hide form descriptions when rendered in the portlet to save
        some screen estate.

    Example CSS::

        .portletZohoCRMContact .formHelp {
           display: none;
        }
    """

    fields = Fields(IZohoContactForm)

    label = _(u"Contact Us")

    description = _(u"If you are interested our services leave your contact information below and our sales representatives will contact you.")

    ignoreContext = True

    def __init__(self, context, request, returnURLHint=None, full=True):
        """

        @param returnURLHint: Should we enforce return URL for this form

        @param full: Show all available fields or just required ones.
        """
        Form.__init__(self, context, request)
        self.all_fields = full

        self.returnURLHint = returnURLHint

    @property
    def action(self):
        """ Rewrite HTTP POST action.

        If the form is rendered embedded on the others pages we
        make sure the form is posted through the same view always,
        instead of making HTTP POST to the page where the form was rendered.
        """
        return self.context.portal_url() + "/@@zoho-contact-form"

    def updateWidgets(self):
        """ Make sure that return URL is not visible to the user.
        """
        Form.updateWidgets(self)

        # Use the return URL suggested by the creator of this form
        # (if not acting standalone)
        self.widgets["returnURL"].mode = z3c.form.interfaces.HIDDEN_MODE
        if self.returnURLHint:
            self.widgets["returnURL"].value = self.returnURLHint

        # Prepare compact version of this formw
        if not self.all_fields:
            # Hide fields which we don't want to bother user with
            self.widgets["phone_number"].mode = z3c.form.interfaces.HIDDEN_MODE


    @button.buttonAndHandler(_('Send contact request'), name='ok')
    def send(self, action):
        """ Form button hander. """

        data, errors = self.extractData()

        if not errors:

            settings = self.getZohoSettings()
            if settings is None:
                self.status = _(u"Zoho is not configured in Site Setup. Please contact the site administration.")
                return

            crm = CRM(settings.username, settings.password, settings.apikey)

            # Fill in data going to Zoho CRM
            lead = {
                "First Name" : data["first_name"],
                "Last Name" : data["last_name"],
                "Company" : data["company"],
                "Email" : data["email"],
            }

            phone = data.get("phone_number", "")
            if phone != "":
                # Only pass phone number to Zoho if it's set
                lead["Phone"] = phone

            # Pass in all prefilled lead fields configured in the site setup
            lead.update(self.parseExtraFields(settings.crm_lead_extra_data))

            # Open Zoho API connection
            try:
                # This will raise ZohoException and nuke the request
                # if Zoho credentials are wrong
                crm.open()

                # Make sure that wfTrigger is true
                # and Zoho does workflow actions for the new leads
                # (like informing sales about the availability of the lead)
                crm.insert_records([lead], {"wfTrigger" : "true"})
            except IOError:
                # Network down?
                self.status = _(u"Cannot connect to Zoho servers. Please contact web site administration")
                return

            ok_message = _(u"Thank you for contacting us. Our sales representatives will come back to you in few days")


            # Check whether this form was submitted from another page
            returnURL = data.get("returnURL", "")

            if returnURL != "" and returnURL is not None:

                # Go to page where we were sent and
                # pass the confirmation message as status message (in session)
                # as we are not in the control of the destination page
                from Products.statusmessages.interfaces import IStatusMessage
                messages = IStatusMessage(self.request)
                messages.addStatusMessage(ok_message, type="info")
                self.request.response.redirect(returnURL)
            else:
                # Act standalone
                self.status = ok_message
        else:
            # errors on the form
            self.status = _(u"Please fill in all the fields")

Further reading

This example code was taken from mfabrik.plonezohointegration product which is in Plone collective SVN.

Another tutorial