ServerZen.Net

Technology news centering around Python

Entries tagged "zope.formlib"

Using zope.formlib With Plone: Part 3

The second part in this series focused on demonstrating how a search form could be implemented with minimal fuss. This third part will show how to setup a content type using a Zope 3 schema which will be used to setup formlib based view and edit forms.

Traditional Archetypes Schema’s

By now the entire Plone and Plone add-on developer communities generally use the Archetypes framework to construct their content types. The Archetypes framework introduced its own concepts of schema’s, widgets, and fields which suited most developers fine. The problem is that shortly thereafter (perhaps even simultaneously) the Zope 3 community was already building their own constructs of these same concepts. The result was zope.interface, zope.schema, and zope.app.form. Eventually came zope.formlib that would help glue more of this together in a web UI environment.

Zope 3 Schema’s

A Zope 3 schema functions very closely to an Archetypes schema from a conceptual perspective. They both share the basic principles of defining the fields of a content type. One glaring difference is that is that a Zope 3 schema field has no direct association (within the schema) with any sort of UI logic. That is, it does not know what widget will be used to display itself on a web form. This help keeps content and UI concerns separate. Content, after all, should never need to know or care what it will actually look like in a user interface. It only cares about, well, data.

Our Example

For purposes of keeping things clean, we will break up some of our previous example code into more common python modules. This means moving our ISearch interface from the browser module into a new interfaces module.

A Content Schema

In Zope 3, schema’s are defined by constructing Zope 3 interfaces, much like the interface we created in the second part. What wasn’t made clear in the second part was that we actually created a schema to represent the search fields.

Lets begin by creating a new interface in the interfaces module called IExampleContent. This interface will have various fields and should look like this:

class IExampleContent(interface.Interface):
    title = schema.TextLine(title=u'Title',
                            required=True)

    description = schema.Text(title=u'Description',
                              required=False)

    funny = schema.Bool(title=u'Am I Funny?',
                        default=False,
                        required=True)

    really_funny = schema.Bool(title=u'Am I Really and Truly Funny?',
                               default=False,
                               required=True)

You can look at the resulting interfaces module to see what this file should end up looking like.

As mentioned earlier this example demonstrates that no UI properties are made available on the schema itself. All that is described about the schema and its fields is what is required to define the type of data that it represents.

Formlib Views

Now that we have the schema that describes what fields our content type will eventually have, we can use the same basic approach that the second part used to build views for this content.

The schema we just defined sets up a couple fields as being of type Bool. The default widget for a Bool field displays true/false as the selectable options. For purposes of our example, we need these to show yes/no instead so we’ll setup our own tweaked custom widget for this.

def YesNoWidget(field, request, true=_('yes'), false=_('no')):
    vocabulary = schemavocab.SimpleVocabulary.fromItems(((true, True),
                                                         (false, False)))
    return form_browser.RadioWidget(field, vocabulary, request)

What we have created here is a type of factory that will give us the widget we need based on an existing widget, RadioWidget. RadioWidget takes a vocabulary which we setup as having yes and no as items. Next we will define our form fields based on our content schema.

example_content_fields = form.FormFields(interfaces.IExampleContent)
example_content_fields['really_funny'].custom_widget = YesNoWidget

As with our example in the second part we generate the form fields using form.FormFields(). This class takes an interface as an argument and generates the form fields (the UI complement of a schema’s fields) we will use for our views. The second line here sets up our really_funny schema field to use our own custom widget.

All that’s left now is to actually define the default view and edit form for our content type.

class ExampleContentView(formbase.DisplayForm):
    form_fields = example_content_fields

    def __init__(self, *args, **kwargs):
        formbase.DisplayForm.__init__(self, *args, **kwargs)

        # a hack to make the content tab work
        self.template.getId = lambda: 'index.html'

class ExampleContentEditForm(formbase.EditForm):
    form_fields = example_content_fields

    def __init__(self, *args, **kwargs):
        formbase.EditForm.__init__(self, *args, **kwargs)

        # a hack to make the content tab work
        self.template.getId = lambda: 'edit.html'

Pretty basic. We defined ExampleContentView to extend formbase.DisplayForm which means formlib will understand that this is a regular display view so all widgets should be displayed in view mode. The edit form, ExampleContentEditForm was defined to extend formbase.EditForm so that formlib would know to display the widgets in edit mode. But this isn’t the only thing that formlib knows to do with the edit form. By default, formbase.EditForm defines a single apply action for its form. When this form is submitted with the apply action, formlib knows that it must actually update the current object (ie context) with the submitted values. And for those adventurous types, it should also be observed that a successfully submitted apply action will fire an IObjectModifiedEvent upon saving the submitted data.

Of course once these views have been defined they need to be registered with the Zope 3 component architecture. This is done with configure.zcml.

<browser:page
    name="index.html"
    for=".interfaces.IExampleContent"
    class=".browser.ExampleContentView"
    permission="zope2.View"
    />

<browser:page
    name="edit.html"
    for=".interfaces.IExampleContent"
    class=".browser.ExampleContentEditForm"
    permission="cmf.ModifyPortalContent"
    />

This zcml snippet shows that we have given our default view the name, index.html and our edit form the name, edit.html.

You can look at the browser module and configure.zcml to see the end result.

Content Type

So now that the schema and views have all been defined for our content type, its time to build the actual content type class. As a matter of best practise, we define this class in the content module.

class FormlibExampleContent(atapi.BaseContent):
    interface.implements(interfaces.IExampleContent)

    title = fieldproperty.FieldProperty(interfaces.IExampleContent['title'])
    description = fieldproperty.FieldProperty(interfaces.IExampleContent['description'])
    funny = fieldproperty.FieldProperty(interfaces.IExampleContent['funny'])
    really_funny = fieldproperty.FieldProperty(interfaces.IExampleContent['really_funny'])

The first line after the class statement ensures that our new content class implements the IExampleContent interface which we defined in the interfaces module earlier (a schema is just an interface with zope.schema fields). The remaining lines setup python properties for each of the required fields. As an example, the funny line says to define a funny attribute that is modelled after the funny schema field in IExampleContent). This ensures some basic validation is setup on the content type itself. If someone where to have an instance of this content type with name, myobj and tried to do myobj.funny = ‘foo’ then a validation error would be raised because the value ‘foo’ is not of type Bool. Bool types expect true or false.

That’s it for the content type class itself. Take a look at the finished content module to see the end result.

Content Type Cataloguing

One neglected aspect of all of this is since we’re no longer using Archetypes auto-generated forms our content is no longer getting catalogued. In the world of Zope 3 such things would be accomplished by using events. Since formlib will fire off an IObjectModifiedEvent when a successful save has taken place, all that is left to us is to define a handler for that event.

def catalog_content(obj, event):
    obj.reindexObject()

The handler itself is quite simple, it takes as arguments the actual object and the event (in this case the IObjectModifiedEvent instance). Since we have the object in hand at this point, we merely call reindexObject().

Of course we still have to hook this up with the Zope 3 component architecture which takes us back to configure.zcml.

<subscriber
    for=".interfaces.IExampleContent
         zope.app.event.interfaces.IObjectModifiedEvent"
    handler=".content.catalog_content"
    />

This basically says we want to handle any IObjectModifiedEvent that has been fired with an instance of IExampleContent as the target object. In our case, FormlibExampleContent implements the IExampleContent interface so we know our handler will get called when it has been modified.

The only thing that remains now is to register our content type with CMF/GenericSetup.

Content Type Installation With GenericSetup

Since we’re using Plone 2.5 with these examples we will use the new GenericSetup tool to setup our new content type in a Plone site. More information specifically about GenericSetup and Plone can be read in Rob Miller’s excellent Understanding and Using GenericSetup in Plone tutorial.

Basic steps for setting up our content type with GenericSetup are:

  1. Create a profile directory structure that has profiles/default/types beneath our formlib directory.
  2. Construct a new types.xml file underneath the default directory.
  3. Create a file with the name FormlibExampleContent.xml underneath the types directory.
  4. Register a new extension profile that uses these files with GenericSetup in our main __init__.py file.

Actual construction of these files goes beyond the scope of this writing, but the contents of these files can be found starting at the base formlib directory.

Just remember to activate these content types in Plone 2.5 you would go to the portal_setup tool, select ploneexample.formlib sample content as the active site configuration, and then run all import steps.

Conclusion

Zope 3 schema’s in Plone has arrived. And thanks to zope.formlib we have auto-generated views and forms to boot. The widget and field sets to choose from in Zope 3 are a lot smaller than in the Archetypes world, but Zope 3 is quickly catching up.

The only main missing piece (from formlib’s perspective) here is using an auto-generated add form. While we can build those, they can’t easily be hooked into Plone as Plone feels it needs to create the content first before displaying any forms.

Jul 9, 2006 9:14:00 PM by rocky, 0 comments

Using zope.formlib With Plone: Part 2

The first part in this series focused on getting a good consistent package skeleton in place. This second part will attempt to do something useful with formlib in a Plone environment.

Understanding Formlib

zope.formlib is a Zope 3 package designed to ease the development of web-based forms in your Zope applications. In its simplest form you can compare what it does with the auto-generated displays Archetypes provides you for viewing a content type (base_view) and editing a content type (base_edit). For all practical sense formlib based components are really regular Zope view components with some convenient base classes for auto-generating output based on schema’s and other configuration info.

Thankfully beginning with Zope 2.9.3 zope.formlib is now being distributed with Zope 2. Of course Five >= 1.4 is required to make use of this Zope 3 package.

Defining Our First Form

For purposes of this writing we will construct a very simple search form for searching Plone content. This form will be similar to Plone’s built in advanced search form but much simpler.

You can view the working source code of these examples at the updated collective svn browser and updated collective svn repository locations.

The Form Class

We begin by creating a new file, browser.py, which will need to live in ploneexample.formlib/ploneexample/formlib/. The browser.py file will comprise the bulk of the necessary work. Lets start by adding the necessary imports.

from zope import interface, schema
from zope.formlib import form
from Products.CMFCore import utils as cmfutils
from Products.Five.browser import pagetemplatefile
from Products.Five.formlib import formbase

Next we’ll construct our first Zope 3 interface:

class ISearch(interface.Interface):
    text = schema.TextLine(title=u'Search Text',
                           description=u'The text to search for',
                           required=False)

    description = schema.TextLine(title=u'Description',
                                  required=False)

The purpose of the interface in this case is not to describe a particular content object but instead to define the fields that formlib will use. Later on we’ll discover how tradtional interfaces used to describe actual content classes can be used in combination with formlib to autogenerate proper add and edit forms for content.

And now for the form view class itself. We will start with the first part of the class definition.

class SearchForm(formbase.PageForm):
    form_fields = form.FormFields(ISearch)
    result_template = pagetemplatefile.ZopeTwoPageTemplateFile('search-results.pt')

We use the PageForm class as our super class to inherit functionality from formlib itself. By default, PageForm knows how to generate all the HTML that will make up of our finished form. But in order to do this, formlib needs to know what fields we want. We do this by providing the form_fields attribute. FormFields is a formlib helper class that generates the appropriate field items from any Zope 3 schema (in this case, the schema interface we just defined).

The result_template attribute defines a new page template that we will use to iterate over all of the results of our search.

Next we define an action for our form:

@form.action("search")
def action_search(self, action, data):
    catalog = cmfutils.getToolByName(self.context, 'portal_catalog')

    kwargs = {}
    if data['text']:
        kwargs['SearchableText'] = data['text']
    if data['description']:
        kwargs['description'] = data['description']

    self.search_results = catalog(**kwargs)
    self.search_results_count = len(self.search_results)
    return self.result_template()

This is where the real work takes place. A formlib action is generally a handler that will somehow get invoked by submitting an HTML form. In this case we create a new action labeled search, that will be used to handle when a user hits the search button. Our formlib-based class will automatically understand how to hook in an search button on the HTML form itself. This particular action handler will return our result template as a result.

The Result Page Template

In order to display the results of our search form we need to setup a simple page template. We will name this template, search-results.pt. Most the of template is pretty uninteresting. But for purposes of this writing we will demonstrate the result printing portion.

<tal:block tal:repeat="single view/search_results">
<div class="single-result">
  <h4>
    <span tal:replace="repeat/single/number"></span>.
    <a tal:content="single/Title" tal:attributes="href single/getURL" href=""></a>
  </h4>
  <p tal:content="single/Description"></p>
</div>
</tal:block>

Since our previous formlib based class was a regular view, it gets treated that way inside the page templates. And we are able to assign simple attributes to our view that can get picked up within the template.

Tying It All Together With ZCML

Now that we’ve defined the form class and the result page template to go along with it we need to glue this all into Zope. We do this in configure.zcml.

So we need to add the appropriate ZCML snippet:

<browser:page
    name="search.html"
    for="Products.CMFPlone.Portal.PloneSite"
    class=".browser.SearchForm"
    permission="zope.Public"
    />

Again, since formlib is all based on regular Zope 3 view components, we register them the same way in the ZCML.

Our First zope.formlib Example In Summary

The example demonstrated here shows the simplest form that could be created with formlib and how to hook in a simple action. It should be obvious from this example how you could use formlib to replace simple CMFFormController based logic. Of course formlib can do many other advanced things such as provide sub-form functionality and autogenerated add and edit forms for content classes.

The bottom line is that zope.formlib is ready for use inside Plone today. And since formlib is so easy to work with, the author recommends all Plone application developers give it a try.

Jun 13, 2006 7:13:00 AM by rocky, 2 comments

Using zope.formlib With Plone: Part 1

The primary purpose of this writing is to display the use of the Zope 3 technology, zope.formlib, in a Zope 2 + Plone based environment. A side goal is to help demonstrate some new practices with building new Plone based applications.

Part 1 specfically focuses on getting the initial skeleton product in place.

Required Stack Components

The example built by this writing is designed to run on the following stack:

  • Python >= 2.4.3
  • Zope >= 2.9.3
  • Plone >= 2.5
  • Five >= 1.4

The example described in this writing may work on different versions but is untested by the author so your mileage may vary.

Some Things To Note Before Starting

We will begin by working with a product by the name of ploneexample.formlib. The finished code for this example can be browsed using the collective svn browser or checked out from Subversion using at the collective svn repository.

The first thing the keen Plone application developer will notice is the unusual naming convention of the product. Traditionally Plone products use CamelCase as the convention for naming products with no additional periods (ie PloneExampleFormlib). But as one of the goals of this writing, best practices will be demonstrated where we try to be as pythonic as possible which means using all lowercase for package names.

The Importance Of Pythonic

Many of you may be asking yourself, “but why do we really care about keeping this pythonic?” The single biggest reason for keeping things pythonic is to keep things as simple and easy to understand for people who are already familiar with Python. When developing standard Zope 2 products and placing them into the Products directory of your Zope 2 instance, Zope magically places them within the Products namespace package (ie so the full package path of CMFPlone actually becomes Products.CMFPlone). Living in PYTHONPATH like any other package and being reusable in general is A Good Thing TM.

Also, by keeping products pythonic, we can now use generic Python tools such as easy_install and setuptools (both great package management components) to work with these products.

Creating The Product

For the initial creation of the product we will use a small component of the Python Paste project. The rest of this writing will assume the reader has installed Phillip J. Eby’s setuptools and easy_install products. As well as the necessary paster component along with the ZopeSkel template package. For instructions on how to setup paster and ZopeSkel, please see Daniel Nouri’s excellent article on ZopeSkel.

So lets get started:

$ paster create -t plone_core ploneexample.formlib
Selected and implied templates:
  ZopeSkel#plone_core  A Plone Core project

Variables:
  package:  ploneexampleformlib
  project:  ploneexample.formlib
Creating template plone_core
Enter namespace_package (Namespace package) ['plone']: ploneexample
Enter package (The package contained namespace package (like i18n)) ['']: formlib
Enter pythonproducts (Are you making a productsless Zope 2 Product?) [False]: True
Enter version (Version) ['0.1']:
Enter description (One-line description of the package) ['']: A Plone product for demonstrating zope.formlib usage
Enter long_description (Multi-line description (in reST)) ['']:
Enter author (Author name) ['Plone Foundation']: Rocky Burt
Enter author_email (Author email) ['plone-developers@lists.sourceforge.net']: rocky@serverzen.com
Enter keywords (Space-separated keywords/tags) ['']:
Enter url (URL of homepage) ['http://svn.plone.org/svn/plone/plone.i18n']: http://dev.plone.org/collective/browser/examples/ploneexample.formlib
Enter license_name (License name) ['GPL']:
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]:
  Recursing into +namespace_package+
    Creating ./ploneexample.formlib/ploneexample/
    Recursing into +package+
      Creating ./ploneexample.formlib/ploneexample/formlib/
      Copying HISTORY.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/HISTORY.txt
      Copying LICENSE.GPL to ./ploneexample.formlib/ploneexample/formlib/LICENSE.GPL
      Copying LICENSE.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/LICENSE.txt
      Copying README.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/README.txt
      Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/formlib/__init__.py
      Copying configure.zcml to ./ploneexample.formlib/ploneexample/formlib/configure.zcml
      Copying version.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/version.txt
    Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/__init__.py
  Copying setup.cfg to ./ploneexample.formlib/setup.cfg
  Copying setup.py_tmpl to ./ploneexample.formlib/setup.py
Running /usr/bin/python2.4 setup.py egg_info

Setting Up Development

After this has completed running you should now have a ploneexample.formlib directory in your current working directory. For purposes of development we will now use setuptools to setup the ploneexample.formlib project on the PYTHONPATH in order to make it available to Zope. So make sure you make the ploneexample.formlib directory your current directory and do the following (remember to run this with sudo if you’re in a UNIX environment and don’t have permission to write to the system python site-packages directory):

$ python2.4 setup.py develop

This will yield some output similar to the following:

[snip output...]
Installed /home/rocky/Documents/developing/projects/ploneexample.formlib
Processing dependencies for ploneexample.formlib==0.1dev

Hooking Up The New Product Into Zope 2

Now that we have a fully runnable Zope 2 product we will proceed with hooking this up to an existing Zope 2 instance. Until Zope and CMF/Plone catch up (Zope 2.10 already includes the necessary changes) we will need to use the pythonproducts product to enable Zope 2 products to exist outside of the instance Products directory.

So be sure to download and install the pythonproducts product into your Zope 2 instance by following the install instructions provided by pythonproducts. For those in a hurry, setting up pythonproducts is as simple as downloading the pythonproducts tarball, extracting the contents into some temporary directory, and running python setup.py install –home $INSTANCE_HOME.

Now you need to tell your Zope 2 instance to “activate” the new ploneexample.formlib package as a Zope 2 product. You do this by going to the etc/package-includes directory in your Zope 2 instance and create a new file there called ploneexample.formlib-configure.zcml with its only contents being:

<include package="ploneexample.formlib" />

You should now test your Zope 2 instance to confirm that the new ploneexample.formlib product is available. You can do this by starting your Zope 2 instance as you normally would and going to the Products section of the Control Panel via the ZMI. Towards the bottom of the list you should see:

ploneexample.formlib (Installed product ploneexample.formlib)

Stay tuned for the next part of this series!

Jun 8, 2006 9:15:00 PM by rocky, 2 comments