You are here: Home / What do you need? / Help and documentation / Plone Info / Plone form example with z3c.form

Plone form example with z3c.form

by Darrell Kingsley last modified Mar 13, 2014 01:05 PM
Theres a lot of misinformation on how to do this. Somethings work and somethings don't. Here's what worked for me using Plone 4.0.5.

First off there was a load of talk of plone.directives but I didn't use this as I had buildout version issues and no time to figure out a KGS.

This is a simple form that will (when completed) email its data. This is not
creating any objects.

In the theme's browser folder, a python module called alertforms.py

from Products.CMFPlone import PloneMessageFactory as _
from zope.interface import Interface
from zope import schema
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
from z3c.form import form, field, button
from plone.z3cform.layout import wrap_form
from z3c.form.browser.radio import RadioFieldWidget
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

import logging
logger = logging.getLogger("alert form")

# define vocabularies for singleselection fields

deal_types = SimpleVocabulary(
    [SimpleTerm(value=u'E', title=_(u'Early booking deals')),
     SimpleTerm(value=u'L', title=_(u'Last minute deals')),
     SimpleTerm(value=u'S', title=_(u'All season deals')),
     SimpleTerm(value=u'A', title=_(u'All deals'))]
    )
who_is_going = SimpleVocabulary(
    [SimpleTerm(value=u'F', title=_(u'Family - require childcare and/or kids programmes')),
     SimpleTerm(value=u'A', title=_(u'Adults - no childcare or kids programmes necessary'))]
    )

# the fields to be displayed

class IDealAlert(Interface):
    tour_deals = schema.Choice(
        vocabulary=deal_types,
        title=_(u'label_tour_op_deals', default=u'Tour operator deals'),
        description=_(u'help_tour_op_deals',
                      default=u"Tour operator deal info "
                        "..."),
        required=True,
        )
    other_deals = schema.Choice(
        vocabulary=deal_types,
        title=_(u'other_deals', default=u'Other supplier deals'),
        description=_(u'help_other_deals',
                      default=u"e.g. Lift passes etc. "
                        "..."),
        required=True,
        )            
    country = schema.TextLine(
        title=_(u'label_country', default=u'Country'),
        description=_(u'help_country', default=u"Leave blank for any."),
        required=False)
    resort = schema.TextLine(
        title=_(u'label_resort', default=u'Resort'),
        description=_(u'help_resort', default=u"Leave blank for any."),
        required=False)
    flexible = schema.Bool(title=_(u'label_flexible', 
        default=u'I am flexible'),
        required=False,
        default=False)
    who = schema.Choice(
        vocabulary=who_is_going,
        title=_(u'label_who', default=u'Who is going?'),
        required=True)
    adults_no = schema.Int(
        title=_(u'label_adultsno', default=u'No. of adults'),
        required=True)
    children_no = schema.Int(
        title=_(u'label_childno', default=u'No. of children under 11'),
        required=False)
    departuredate = schema.Date(
        title=_(u'label_departuredate', default=u'When would you like to go on holiday?'),
        description=_(u'help_departuredate',
                      default=u"Departure date +/- 2days. Leave blank if flexible."),        
        required=False)
    alertsfrom = schema.Date(
        title=_(u'label_alertsfrom', default=u'When would you like to start receiving alerts?'),
        description=_(u'help_alertsfrom',
                      default=u"Leave blank for as soon as possible."),
        required=False)        

# the form view 
# override the drop-downs with radiobutton widgets.
# some docs used updateFields() method for this but...

class DealAlertForm(form.Form):
    fields = field.Fields(IDealAlert)
    fields['tour_deals'].widgetFactory = RadioFieldWidget
    fields['other_deals'].widgetFactory = RadioFieldWidget
    fields['who'].widgetFactory = RadioFieldWidget
    ignoreContext = True     # don't use context to get widget data
    label = _(u"Send a deal alert")
    description = _(u"At Pistebook we want to make sure you find the ski holiday deal that you are looking for. So we've tried not to over complicate the information we need from you, this means you don't miss out on a great deal by being too specific on your requirements.")

# the form will be generated automatically but I wanted more control
# for styling so a defined a template
# some docs used 'index =...' and also hinted that auto placement
# of a dealalertform.pt in alertforms_templates/ would do it 
# but only 'template = ...' seemed to work

    template = ViewPageTemplateFile('templates/dealalertform.pt')

# some minor widget adjustment

    def updateWidgets(self):
        super(DealAlertForm, self).updateWidgets()
        # Widgets
        self.widgets['adults_no'].size = 2
        self.widgets['children_no'].size = 2

# the submit buitton handler    
    @button.buttonAndHandler(u'Send deals')
    def handleApply(self, action):
        data, errors = self.extractData()
        if errors:
            return

# form data - do what you need to with it 
# ie it will be emailed...

#        if data.has_key('text'):
#            print data['text'] # ... or do stuff    
        logger.info('handleApply form data:%s', data)

# if you want form wrapped in a new page        
#DealAlertView = wrap_form(DealAlertForm)
# but I'm using a template so I don't
DealAlertView = DealAlertForm

In browser/templates I created the form template dealalertform.pt

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      i18n:domain="example.dexterityforms"
      metal:use-macro="context/main_template/macros/master">
      
    <metal:block fill-slot="main">
        
        <h1 class="documentFirstHeading" tal:content="view/label | nothing" />
        
        <p>Welcome to this form.</p>
        
<tal:x replace="nothing">
this diplays the whole form if the customisation is minimal.
</tal:x
        <div id="content-core">
            <metal:block use-macro="context/@@ploneform-macros/titlelessform" />
        </div>

<tal:x replace="nothing">
 Couldn't get the /field macro to work. Used various
values for widget??
</tal:x>
        <div tal:define="widget string:formfield-form-widgets-tour_deals">
            <metal:block use-macro="context/@@ploneform-macros/field" />
        </div>

<tal:x replace="nothing">
a field renderer that worked for me </tal:x>
<tal:field tal:replace="structure view/widgets/tour_deals/@@ploneform-render-widget" />        
    </metal:block>
    
</html>

Also defined the <FORM> in the template using method="POST" which is the default for auto-forms (no template). Comment out the template attribute on the form and see what the autoform form tags look like.

Use an Finally the plumbing in browser/configure.zcml

 

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    i18n_domain="bb.mrg">

...
<!-- form views -->      
  <browser:page
      for="*"
      name="deal-alerts"
      class=".alertforms.DealAlertView"
      layer=".interfaces.IThemeSpecific"
      permission="zope2.View"
   />
...   
</configure>

http://my-site/deal-alerts

Adios