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.
Form model consist of
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 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"
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 of Fields instance. 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
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
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
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
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>
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.
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 (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.
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
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.")
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)
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.
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.
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.
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"])
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
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
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)
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
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…
</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")
This example code was taken from mfabrik.plonezohointegration product which is in Plone collective SVN.
Another tutorial