Skip to main content

A library that allows use of z3c.form with Zope 2 and Plone

Project description

=============
plone.z3cform
=============

plone.z3cform is a library that allows use of z3c.form with Zope 2 and
Plone.

Quick start
===========

Tons of examples of using ``z3c.form`` can be found online. This is a
simple example of a form for Plone:

>>> from zope import interface, schema
>>> from z3c.form import form, field, button
>>> from plone.z3cform import base

>>> class MySchema(interface.Interface):
... age = schema.Int(title=u"Age")

>>> class MyForm(form.Form):
... fields = field.Fields(MySchema)
... ignoreContext = True # don't try to get data from context
...
... @button.buttonAndHandler(u'Apply')
... def handleApply(self, action):
... data, errors = self.extractData()
... print data['age'] # ... or do stuff

>>> class MyView(base.FormWrapper):
... form = MyForm
... label = u"Please enter your age"

Note that we're using ``base.FormWrapper`` as a base class for our
browser view. We can register the ``MyView`` view just like any other
``browser:page``.

Only the ``MyView`` bit is specific to ``plone.z3cform``. The rest is
standard ``z3c.form`` stuff. For more details on the base FormWrapper
class, see the ``plone.z3cform.base`` module.

Please also refer to the `online documentation`_ for more details.

.. _online documentation: http://plone.org/documentation/how-to/easy-forms-with-plone3


WYSIWYG widget
==============

The ``wysiwyg`` package provides an implementation of the Plone
WYSIWYG widget compatible with ``z3c.form``. This will allow you to
use Kupu, FCKeditor and other editors compatible with the Plone
WYSIWYG interface in your ``z3c.form`` forms.

To use, simply set the widget factory for the widget you'd like to be
displayed with the WYSIWYG widget:

>>> from zope import interface, schema
>>> from z3c.form import form, field
>>> from z3c.form.interfaces import INPUT_MODE
>>> from plone.z3cform.wysiwyg.widget import WysiwygFieldWidget

>>> class IProfile(interface.Interface):
... name = schema.TextLine(title=u"Name")
... age = schema.Int(title=u"Age")
... bio = schema.Text(title=u"Bio")

>>> class MyForm(form.Form):
... fields = field.Fields(IProfile)
... fields['bio'].widgetFactory[INPUT_MODE] = WysiwygFieldWidget


Query select widget
===================

The ``queryselect`` module provides a query source compatible with
``z3c.formwidget.query`` which combines to a selection field that can
be queried.

The native value type for the widget is Archetypes UID collections.
The default implementation will simply search using the
``SearchableText`` index in the portal catalog.

This is how your form schema could look like:

>>> from zope import interface, schema
>>> from plone.z3cform.queryselect import ArchetypesContentSourceBinder

>>> class ISelection(interface.Interface):
... items = schema.Set(
... title=u"Selection",
... description=u"Search for content",
... value_type=schema.Choice(
... source=ArchetypesContentSourceBinder()))

Optionally, instead of storing Archetypes UIDs, you can choose to use
``persistent.wref``, i.e. weak references, instead of UIDs:

>>> from plone.z3cform.queryselect import uid2wref
>>> factory = uid2wref(ISelection['items'])

To store weak references instead of UIDs you would register such a
factory as a component adapting the context. The factory
automatically provides the interface which defines the field.
(XXX: Please rewrite this paragraph.)


Crud
====

This module gives you an abstract base class to make CRUD forms with.
These forms give you by default a tabular view of the objects, where
attributes of the object can be edited in-place. Please refer to the
``ICrudForm`` interface for more details.

>>> from plone.z3cform.crud import crud

Setup
-----

>>> from plone.z3cform.tests import setup_defaults
>>> setup_defaults()

A simple form
-------------

First, let's define an interface and a class to play with:

>>> from zope import interface, schema
>>> class IPerson(interface.Interface) :
... name = schema.TextLine()
... age = schema.Int()

>>> class Person(object):
... interface.implements(IPerson)
... def __init__(self, name=None, age=None):
... self.name, self.age = name, age
... def __repr__(self):
... return "<Person with name=%r, age=%r>" % (self.name, self.age)

For this test, we take the the name of our persons as keys in our
storage:

>>> storage = {'Peter': Person(u'Peter', 16),
... 'Martha': Person(u'Martha', 32)}

Our simple form looks like this:

>>> class MyForm(crud.CrudForm):
... update_schema = IPerson
...
... def get_items(self):
... return sorted(storage.items(), key=lambda x: x[1].name)
...
... def add(self, data):
... person = Person(**data)
... storage[str(person.name)] = person
... return person
...
... def remove(self, (id, item)):
... del storage[id]

This is all that we need to render a combined edit add form containing
all our items:

>>> from z3c.form.testing import TestRequest
>>> print MyForm(None, TestRequest())() \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Martha...Peter...</div>

Editing items with our form
---------------------------

Before we start with editing objects, let's log all events that the
form fires for us:

>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectModifiedEvent)

>>> request = TestRequest()
>>> request.form['crud-edit.Martha.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Peter.widgets.select-empty-marker'] = u'1'
>>> request.form['crud-edit.Martha.widgets.name'] = u'Martha'
>>> request.form['crud-edit.Martha.widgets.age'] = 55
>>> request.form['crud-edit.Peter.widgets.name'] = u'Franz'
>>> request.form['crud-edit.Peter.widgets.age'] = 16
>>> request.form['crud-edit.buttons.edit'] = u'Apply changes'
>>> html = MyForm(None, request)()
>>> "Successfully updated" in html
True

Two modified events should have been fired:

>>> event1, event2 = log.pop(), log.pop()
>>> storage['Peter'] in (event1.object, event2.object)
True
>>> storage['Martha'] in (event1.object, event2.object)
True
>>> log
[]

If we don't make any changes, we'll get a message that says so:

>>> html = MyForm(None, request)()
>>> "No changes made" in html
True
>>> log
[]

Now that we renamed Peter to Franz, it would be also nice to have
Franz use 'Franz' as the id in the storage, wouldn't it?

>>> storage['Peter']
<Person with name=u'Franz', age=16>

We can override the CrudForm's ``before_update`` method to perform a
rename whenever the name of a person is changed:

>>> class MyRenamingForm(MyForm):
... def before_update(self, item, data):
... if data['name'] != item.name:
... del storage[item.name]
... storage[str(data['name'])] = item

Let's rename Martha to Maria. This will give her another key in our
storage:

>>> request.form['crud-edit.Martha.widgets.name'] = u'Maria'
>>> html = MyRenamingForm(None, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]
>>> sorted(storage.keys())
['Maria', 'Peter']

Next, we'll submit the form for edit, but we'll make no changes.
Instead, we'll select one time. This shouldn't do anything, since we
clicked the 'Apply changes' button:

>>> request.form['crud-edit.Maria.widgets.name'] = u'Maria'
>>> request.form['crud-edit.Maria.widgets.age'] = 55
>>> request.form['crud-edit.Maria.widgets.select'] = [u'selected']
>>> html = MyRenamingForm(None, request)()
>>> "No changes" in html
True
>>> log
[]

And what if we do have changes *and* click the checkbox?

>>> request.form['crud-edit.Maria.widgets.age'] = 50
>>> html = MyRenamingForm(None, request)()
>>> "Successfully updated" in html
True
>>> log.pop().object == storage['Maria']
True
>>> log
[]

If we omit the name, we'll get an error:

>>> request.form['crud-edit.Maria.widgets.name'] = u''
>>> html = MyRenamingForm(None, request)()
>>> "There were some errors" in html
True
>>> "Required input is missing" in html
True

We expect an error message in the title cell of Maria:

>>> checkbox_pos = html.index('crud-edit.Maria.widgets.select-empty-marker')
>>> "Required input is missing" in html[checkbox_pos:]
True

Delete an item with our form
----------------------------

We can delete an item by selecting the item we want to delete and
clicking the "Delete" button:

>>> request = TestRequest()
>>> request.form['crud-edit.Peter.widgets.select'] = ['selected']
>>> request.form['crud-edit.buttons.delete'] = u'Delete'
>>> html = MyForm(None, request)()
>>> "Successfully deleted items" in html
True
>>> 'Franz' in html
False
>>> storage
{'Maria': <Person with name=u'Maria', age=50>}

Add an item with our form
-------------------------

>>> from zope.lifecycleevent.interfaces import IObjectCreatedEvent
>>> from plone.z3cform.tests import create_eventlog
>>> log = create_eventlog(IObjectCreatedEvent)

>>> request = TestRequest()
>>> request.form['crud-add.widgets.name'] = u'Daniel'
>>> request.form['crud-add.widgets.age'] = 28
>>> request.form['crud-add.buttons.add'] = u'Add'
>>> html = MyForm(None, request)()
>>> "Item added successfully" in html
True

Added items should show up right away:

>>> "Daniel" in html
True

>>> storage['Daniel']
<Person with name=u'Daniel', age=28>
>>> log.pop().object == storage['Daniel']
True
>>> log
[]

Render some of the fields in view mode
--------------------------------------

We can implement in our form a ``view_schema`` attribute, which will
then be used to view information in our form's table. Let's say we
wanted the name of our persons to be viewable only in the table:

>>> from z3c.form import field
>>> class MyAdvancedForm(MyForm):
... update_schema = field.Fields(IPerson).select('age')
... view_schema = field.Fields(IPerson).select('name')
... add_schema = IPerson

>>> print MyAdvancedForm(None, TestRequest())() \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...Daniel...Maria...</div>

We can still edit the age of our Persons:

>>> request = TestRequest()
>>> request.form['crud-edit.Maria.widgets.age'] = 40
>>> request.form['crud-edit.Daniel.widgets.age'] = 35
>>> request.form['crud-edit.buttons.edit'] = u'Apply Changes'
>>> html = MyAdvancedForm(None, request)()
>>> "Successfully updated" in html
True

>>> storage['Maria'].age
40
>>> storage['Daniel'].age
35

We can still add a Person using both name and age:

>>> request = TestRequest()
>>> request.form['crud-add.widgets.name'] = u'Thomas'
>>> request.form['crud-add.widgets.age'] = 28
>>> request.form['crud-add.buttons.add'] = u'Add'
>>> html = MyAdvancedForm(None, request)()
>>> "Item added successfully" in html
True
>>> len(storage)
3
>>> storage['Thomas']
<Person with name=u'Thomas', age=28>

Our form can also contain links to our items:

>>> class MyAdvancedLinkingForm(MyAdvancedForm):
... def link(self, item, field):
... if field == 'name':
... return 'http://en.wikipedia.org/wiki/%s' % item.name

>>> print MyAdvancedLinkingForm(None, TestRequest())() \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Daniel"...
...<a href="http://en.wikipedia.org/wiki/Maria"...
...<a href="http://en.wikipedia.org/wiki/Thomas"...
</div>

What if we wanted the name to be both used for linking to the item
*and* for edit? We can just include the title field twice:

>>> class MyAdvancedLinkingForm(MyAdvancedLinkingForm):
... update_schema = IPerson
... view_schema = field.Fields(IPerson).select('name')
... add_schema = IPerson

>>> print MyAdvancedLinkingForm(None, TestRequest())() \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Thomas"...Thomas...</a>...
</div>

We can now change Thomas's name and see the change reflected in the
Wikipedia link immediately:

>>> request = TestRequest()
>>> for name in 'Daniel', 'Maria', 'Thomas':
... request.form['crud-edit.%s.widgets.name' % name] = storage[name].name
... request.form['crud-edit.%s.widgets.age' % name] = storage[name].age
>>> request.form['crud-edit.Thomas.widgets.name'] = u'Dracula'
>>> request.form['crud-edit.buttons.edit'] = u'Apply Changes'

>>> print MyAdvancedLinkingForm(None, request)() \
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
<div class="crud-form">...
...<a href="http://en.wikipedia.org/wiki/Dracula"...Dracula...</a>...
</div>
>>> storage['Thomas'].name = u'Thomas'

Don't render one part
---------------------

What if we wanted our form to display only one part, that is, only the
add *or* the edit form. Our CrudForm can implement
``editform_factory`` and ``addform_factory`` to override one or both
forms. Seeting one of these to ``crud.NullForm`` will make them
disappear:

>>> class OnlyEditForm(MyForm):
... addform_factory = crud.NullForm
>>> html = OnlyEditForm(None, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(True, False)

>>> class OnlyAddForm(MyForm):
... editform_factory = crud.NullForm
>>> html = OnlyAddForm(None, TestRequest())()
>>> 'Edit' in html, 'Add' in html
(False, True)

Render only in view, and define own actions
-------------------------------------------

Sometimes you want to present a list of items, possibly in view mode
only, and have the user select one or more of the items to perform
some action with them. We'll present a minimal example that does this
here.

We can simply leave the ``update_schema`` class attribute out (it
defaults to ``None``). Furthermore, we'll need to override the
ediform_factory with our custom version that provides other buttons
than the 'edit' and 'delete' ones:

>>> from pprint import pprint
>>> from z3c.form import button

>>> class MyEditForm(crud.EditForm):
... @button.buttonAndHandler(u'Capitalize', name='capitalize')
... def handle_capitalize(self, action):
... self.status = u"Please select items to capitalize first."
... selected = self.selected_items()
... if selected:
... self.status = u"Capitalized items"
... for id, item in selected:
... item.name = item.name.upper()

>>> class MyCustomForm(crud.CrudForm):
... view_schema = IPerson
... editform_factory = MyEditForm
... addform_factory = crud.NullForm # We don't want an add part.
...
... def get_items(self):
... return sorted(storage.items(), key=lambda x: x[1].name)

>>> request = TestRequest()
>>> html = MyCustomForm(None, TestRequest())()
>>> "Delete" in html, "Apply changes" in html, "Capitalize" in html
(False, False, True)
>>> pprint(storage)
{'Daniel': <Person with name=u'Daniel', age=35>,
'Maria': <Person with name=u'Maria', age=40>,
'Thomas': <Person with name=u'Thomas', age=28>}

>>> request.form['crud-edit.Thomas.widgets.select'] = ['selected']
>>> request.form['crud-edit.buttons.capitalize'] = u'Capitalize'
>>> html = MyCustomForm(None, request)()
>>> "Capitalized items" in html
True
>>> pprint(storage)
{'Daniel': <Person with name=u'Daniel', age=35>,
'Maria': <Person with name=u'Maria', age=40>,
'Thomas': <Person with name=u'THOMAS', age=28>}

We *cannot* use any of the other buttons:

>>> del request.form['crud-edit.buttons.capitalize']
>>> request.form['crud-edit.buttons.delete'] = u'Delete'
>>> html = MyCustomForm(None, request)()
>>> "Successfully deleted items" in html
False
>>> 'Thomas' in storage
True

Changelog
=========

0.2 - 2008-06-20
----------------

- fix usage of NumberDataConverter with zope.i18n >= 3.4 as the previous test
setup was partial and did not register all adapters from z3c.form (some of
them depends on zope >= 3.4)
[gotcha, jfroche]

- more tests
[gotcha, jfroche]

0.1 - 2008-05-21
----------------

* Provide and *register* default form and subform templates. These
allow forms to be used with the style provided in this package
without having to declare ``form = ViewPageTemplateFile('form.pt')``.

This does not hinder you from overriding with your own ``form``
attribute like usual. You can also still register a more
specialized IPageTemplate for your form.

* Add custom FileUploadDataConverter that converts a Zope 2 FileUpload
object to a Zope 3 one before handing it to the original
implementation. Also add support for different enctypes.
[skatja, nouri]

* Added Archetypes reference selection widget (queryselect)
[malthe]

* Moved generic Zope 2 compatibility code for z3c.form and a few
goodies from Singing & Dancing into this new package.
[nouri]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plone.z3cform-0.2.tar.gz (33.6 kB view hashes)

Uploaded Source

Built Distribution

plone.z3cform-0.2-py2.4.egg (46.2 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page