Monday, April 23, 2012

Form in Plone: a simple approach using collective.wtforms

If you follow the Plone-Developers mailing list, you probably already know about a recent thread called "Rewrite old cpt forms to new technology like z3cform". If not: let simply me say that it's talking about removing old Plone stuff, replacing it with the new-way of doing form in Plone: z3c.form.

Although this is a very interesting discussion (that can also help you understand how things works inside the community) the argument of my article came from a single comment of Nathan Van Gheem, that introduced to me collective.wtforms.
This is a Plone integration for a Python, framework independent, library that generate forms: WTForms.

WTForms in general
Using WTForms in Python seems really easy, as introduced in the Getting started section of the documentation. The concept behind are the same we already know from Zope and Plone libraries:
  • a schema definition
  • a set of field types
  • a set of widgets
As it is a Python only framework we don't find ZCA around us.

Using collective.wtforms
The collective.wtforms package is simple. The Plone integration seems a simple work. It only gives you a base WTFormView class (a Zope 3 view that easily integrated you form in the Plone layout) and a WTFormControlPanelView class (if you ever need a Plone control panel form).

That's it.

Inside the view definition you must then use the basical WTForm features. Let's see an example:
from wtforms import Form
from wtforms import TextField
from wtforms import validators
from collective.wtforms.views import WTFormView

class Form1(Form):
    one = TextField("Field One", [validators.required()])
    two = TextField("Field Two")
    three = TextField("Field Three")

class Form1View(WTFormView):
    formClass = Form1
    buttons = ('Create', _(u'Cancel'))
    #label = _(u'Form 1')

    def submit(self, button):
        if button == 'Create' and self.validate():
            # do fun stuff here
            self.context.value = self.form.one.data
Then you need a zcml registration:
  <browser:page
      name="form1"
      for="*"
      class=".forms.Form1View"
      permission="zope2.View"
  />
You can find the example above, and other discussed later, in the package example.wtforms.
Problems
Of course, as always, is simple making simple things.
I found mainly two problems: the widget layout and internationalization.

WTForms widget layout
The template definition done in collective.wtforms is enough to display a form in the Plone way, however when displaying the "real widget" code we are using the WTForms core features. In that case we sometimes see some strange HTML (I mean: strange for Plone users).

One example: when using RadioField fields, the form radio set in wrapped in a UL/LI HTML structure.

This is not a big problem, just I want to say that this is uncommon in Plone forms.
Obviously WTForms can be extended and supports custom widgets.

A bigger task: I18N
A bigger problem was internationalization. The current alpha version of collective.wtforms (1.0a3) doesn't support internationalization of the UI, however fixing this is simple (you can find my changes in a fork of the original project)

With small changes you can see a fully translated of:
  • form title
  • form general description
  • submit buttons
  • fields label
  • fields description
The main problem is that WTForms doesn't support any internationalization.

Recently they added a new i18n module that helps users to translate the internal label (like: the error message after you didn't provided a required field). However this is not usable out of the box in Plone, because Plone translation mechanism is not the basic Python ones.
I tested it adding an italian translation to WTForms (it was missing in the core, so I also provided it to authors and they quickly integrate it. Man: I really love open source!) and I see no difference.

So what I did is to integrate the native ".pot" translation file into the Plone environment and leave this translations to the Zope Page Template engine... and obviously it worked!

Then: I needed some other simple fixes (like: we can't directly render the WTForms label, but we need to use manually render it using TAL).

Again: the the fork for see some code.

Vocabularies
Translating the vocabulary labels for select, multiselect and radio fields was not so simple. WTForms simply want an iterable argument named choices.

What I was forced to do (better patterns are welcome) is to provide a VocabularyWrapper class where vocabulary labels are translated accessing directly the translation machinery.

Conclusions
I'm sure that we can find also other form library outside Zope (Deform can be another valid choice and also YAFOWIL), however I find the use of WTForms really simple and easy to learn.

1 comment: