Creating forms with megrok.z3cform

Author:timo

Purpose

There are different ways to create forms in Grok. The z3c.form form building framework provides a flexible and powerful way to create forms. You can find the code for this tutorial here: http://svn.zope.org/repos/main/grokapps/tutorialmegrokz3cform/trunk/

Prerequisities

Before we can start to create forms, we need to create a grok project with the necessary dependencies.

Creating a Grok Project:

grokproject tutorialmegrokz3cform

Adding megrok.z3cform package Dependencies to our Buildout

Since the megrok.z3cform.* packages are not officially released yet, we use mr.developer to automatically check out the packages from the Zope Subversion repository. We do this by adding the following lines to our buildout.cfg, right after the “versions=versions” line:

buildout.cfg:

[buildout]
...
versions = versions
extensions += mr.developer
auto-checkout =
    megrok.z3cform.base
    megrok.z3cform.layout

[versions]
z3c.pagelet = 1.0.3

[sources]
megrok.z3cform.base      = svn http://svn.zope.org/repos/main/megrok.z3cform.base/trunk
megrok.z3cform.layout    = svn http://svn.zope.org/repos/main/megrok.z3cform.layout/trunk
# do "bin/develop up -v" to update all the checkouts
...

We also want to use megrok.layout in order for our layout, so we add it to the package dependecies of our setup.py:

setup.py:

install_requires=[...
                  # Add extra requirements here
                  ...
                  'megrok.layout',
                  'megrok.z3cform.base',
                  'megrok.z3cform.layout',
                  ],

Run buildout to fetch the dependencies:

$ bin/buildout

Creating the Form

After creating the initial package, we can start to build our application by defining an interface. We will build a very simple database application to store contacts.

Creating an Interface

We define an interface for a contact with a subject and a text field.

interfaces.py:

import os
import zope.interface
import zope.schema

class IContact(zope.interface.Interface):
    """A z3c.form contact form."""

    name = zope.schema.TextLine(
        title=u'Name',
        description=u'Name of the person.',
        required=True)

    description = zope.schema.TextLine(
        title=u'Description',
        description=u'Description of the person',
        required=True)

Creating a Contact Class

Now we create a contact class that implements the interface we just defined.

contact.py:

import grok
import persistent
import zope.interface
from zope.schema import fieldproperty
from examplemegrokz3cform import interfaces

class Contact(grok.Model):
    grok.implements(interfaces.IContact)

    name = fieldproperty.FieldProperty(interfaces.IContact['name'])
    description = fieldproperty.FieldProperty(interfaces.IContact['description'])

    def __init__(self, name, description):
        self.name = name
        self.description = description

Creating Forms

Now we can create add, edit, and display forms for our contact class.

browser.py:

import grok
import megrok.z3cform.base

import contact
import interfaces
import datetime

from z3c.form import field, form, button, widget
from examplemegrokz3cform.app import Examplemegrokz3cform
from grokcore.component import global_adapter
from z3c.form.interfaces import IAddForm

class ContactAddForm(megrok.z3cform.base.PageAddForm):
    """ A sample add form."""
    grok.context(Examplemegrokz3cform)

    label = u'Contact Add Form'
    fields = field.Fields(interfaces.IContact)

    def create(self, data):
        return contact.Contact(**data)

    def add(self, object):
        count = 0
        while 'contact-%i' %count in self.context:
            count += 1;
        self._name = 'contact-%i' %count
        self.context[self._name] = object
        return object

    def nextURL(self):
        return self.redirect(self.url(self.context[self._name]))


class ContactEditForm(megrok.z3cform.base.PageEditForm):
    grok.context(contact.Contact)
    grok.name('edit.html')
    form.extends(form.EditForm)
    label = u'Contact Edit Form'
    fields = field.Fields(interfaces.IContact)

    @button.buttonAndHandler(u'Apply and View', name='applyView')
    def handleApplyView(self, action):
        self.handleApply(self, action)
        if not self.widgets.errors:
            self.redirect(self.url(self.context, name='index'))


class ContactDisplayForm(megrok.z3cform.base.PageDisplayForm):
    """ A simple Display Form"""
    grok.context(contact.Contact)
    grok.name('index')
    template = grok.PageTemplateFile('display.pt')
    fields = field.Fields(interfaces.IContact)

We have to add a default form layer, otherwise you will get a ComponentLookupError. (Why is this? Anyone?)

configure.zcml:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:grok="http://namespaces.zope.org/grok">
  <include package="grok" />
  <include package="megrok.z3cform.base" file="default_form_layer.zcml" />
  <includeDependencies package="." />
  <grok:grok package="." />
</configure>