By Ingeniweb. A Django site.
Juillet 26, 2008
» Updating your form code to latest version of plone.z3cform


I am glad to see more work is happening for using z3c.form in CMF and Plone, and Daniel Nouri updated us this week with the latest changes. I just updated my buildout to use the new packages and got plone.z3cform 0.4 and plone.app.z3cform 0.3.2.

With the small API changes that happened, you can see below that there is less to write to expose the form in the Plone site (our ContactForm example from my
previous post):

# my.example/my/example/browser.py import datetime from zope import schema import zope.component import z3c.form import plone.app.z3cform from plone.app.z3cform import layout from plone.i18n.normalizer.interfaces import IIDNormalizer from my.example import interfaces from my.example.contact import MyContact class ContactForm(z3c.form.form.Form): """ Contact Form """ fields = z3c.form.field.Fields(interfaces.IContactData) message_field = z3c.form.field.Fields(schema.TextLine(__name__ = 'message', title=u"Message", required=False) ) fields += message_field ignoreContext = True @z3c.form.button.buttonAndHandler(u'Send', name='send') def handle_send(self, action): data, errors = self.extractData() if errors: self.status = z3c.form.form.EditForm.formErrorsMessage return # Add the contact data, if not in yet id = data['firstname'] + data['lastname'] id = zope.component.queryUtility(IIDNormalizer).normalize(id) if id not in self.context.objectIds(): self._name = id contact = MyContact(self._name, **data) self.context[self._name] = contact # Complete the code so it sends the message to the site admin... message = data['message'] print "Message from %s %s (%s):\n%s" % (data['firstname'], data['lastname'], data['email'], data['message']) self.request.RESPONSE.redirect(self.context.absolute_url()) # New way to provide the wrapping View ContactFormView = layout.wrap_form(ContactForm, label="Contact Form")

The related ZCML code does not change.
By the way, on my TODO list, is moving from ZCML to grokcore.component registration ;)

Juillet 19, 2008
» A variation on my previous z3c.form example


Another example of form logic, adding to the one discussed in my previous post.

Here is the idea: If you need a more flexible solution for your use case, you can inherit directly from z3c.form.form.Form.
Let’s say you want the form to be used for both adding contact information (making sure later that this only works when the user is authenticated) and sending a mail message to the site administrator. A site feedback form that will at the same time be used to populate your database of contacts.

Your form class could be defined as follows (I’m skipping the imports part):

# my.example/my/example/browser.py class ContactForm(z3c.form.form.Form): """ Contact Form """ fields = z3c.form.field.Fields(interfaces.IContactData) message_field = z3c.form.field.Fields(schema.TextLine(__name__ = 'message', title=u"Message", required=False) ) fields += message_field ignoreContext = True @z3c.form.button.buttonAndHandler(u'Send', name='send') def handle_send(self, action): data, errors = self.extractData() if errors: self.status = z3c.form.form.EditForm.formErrorsMessage return # Add the contact data, if not in yet id = data['firstname'] + data['lastname'] id = zope.component.queryUtility(IIDNormalizer).normalize(id) if id not in self.context.objectIds(): self._name = id contact = MyContact(self._name, **data) self.context[self._name] = contact # Do something else, e.g. send the message to the site admin. # Complete the code as needed... message = data['message'] print "Message from %s %s (%s):\n%s" % (data['firstname'], data['lastname'], data['email'], data['message']) # Redirect self.request.RESPONSE.redirect(self.context.absolute_url())

The main points of the used pattern:

  • You define the fields for the form ; new fields can be added to the already defined list (based on the IContactData schema in interfaces.py) using the “+” operator.
  • You define your specific form button(s) and handler method(s). In the case of a standard AddForm, there is already a handle_add() defined for the “add” button for you.
  • Also, you need to set the form attribute ‘ignoreContext’ to True, so that the form has the same behaviour as an AddForm, i.e. it does not have to get/set data on attributes on the context. Note that by default, an AddForm has this attribute set to True, and an EditForm has it set to False.

Now, the final touch with the wrapping view…

class ContactFormView(base.FormWrapper): form = ContactForm label= "Contact Form"

… and its configuration:

<configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:five="http://namespaces.zope.org/five" i18n_domain="my.example" > <browser:page for="Products.CMFPlone.Portal.PloneSite" name="contact_form" class=".browser.ContactFormView" permission="zope2.View" /> </configure>

Juillet 16, 2008
» Using z3c.form for our forms in Plone


I’ve been developing complex forms for a Plone project these days, and I get the job done with z3c.form, the form framework that was first used in the Plone world by the Singing & Dancing project.
Here is the first episode of a small serie to show interesting tips & tricks and patterns that I’ve been learning in the process.

To get started, install your buildout using fake eggs and the required dependencies (plone.z3cform, z3c.form…) You might want to follow the howto contributed by Daniel Nouri on plone.org.

(Note: The code snippets are simplified for easier reading.)

First things first !

z3c.form’s first concept, as you guess, is the “form”, which basically has the list of fields defined for it using an attribute called ‘fields’. From the form, it is also possible to access the list of field widgets using the ‘widgets’ attribute.
The framework is smartly engineered using Zope Component Architecture, so you have “separation of concerns” in every bit, and works with zope.schema for the fields definition and validation.

Note that, at least with the core, you can define a basic Form or a specific Add/Edit/DisplayForm, and other cases such as Group (a group of fields part of a Form) and Subform.

To define the list of fields for a form class, we must provide a schema (for example, IContactData), which I like to think of as the “data model” specification.

# my.example/my/example/interfaces.py from zope.interface import Interface, Invalid, invariant from zope import schema class IContactData(Interface): """Contact data interface """ firstname = schema.TextLine(title=u"Firstname", required=True) lastname =schema.TextLine(title=u"Lastname", required=True) email =schema.TextLine(title=u"Email", required=False) @invariant def email_format(obj): if obj.email.find('@') == -1: raise Invalid(u"Not a valid email")

Defining a storage… if you need to

Since, we generally need to store something, let’s choose the data storage. Though you could choose to do that later.
There are many options (including RDB-based), but the immediate one for us is using the ZODB.
The most simple way to do that, in most real-world apps, is by defining an object class that brings persistency, traversal, security and all the goodies we get in Zope for “free”, i.e. by inheriting from OFS.SimpleItem.SimpleItem (or OFS.Folder.Folder if we want containment) and defining the attributes it needs.

# my.example/my/example/contact.py from zope import interface from zope.schema.fieldproperty import FieldProperty import OFS from my.example.interfaces import IContactData class MyContact(OFS.SimpleItem.SimpleItem): """Contact model class, with ZODB-based storage. """ interface.implements(IContactData) firstname = FieldProperty(IContactData['firstname']) lastname = FieldProperty(IContactData['lastname']) email = FieldProperty(IContactData['email']) def __init__(self, id, **kw): self.id = id for key, value in kw.items(): setattr(self, key, value) super(MyContact, self).__init__(id) @property def title(self): return "%s %s" % (self.firstname, self.lastname)

What I like in this pattern: It’s simple, it’s pythonic ! And it does the job for most cases where we don’t need a full-featured Plone content type.
Of course, if we need to manage a full-featured content type, we can inherit from plone.app.content base classes and bring the required Plone mechanisms on the table.

For those who are new to zope.schema, the Field Property mechanism, is very handy too. It does the job of providing data validation based on the information found in the schema.

Defining an “add” form for our Contact objects

Now the really new stuff starts !

An AddForm (and EditForm) could be used by a Content Manager to maintain a list of contacts in a folder within the site. You do that by inheriting from z3c.form.form.AddForm and providing your create(), add() and nextURL() methods.

# my.example/my/example/browser.py import datetime from zope import schema import zope.component import z3c.form from plone.z3cform import base from plone.i18n.normalizer.interfaces import IIDNormalizer from my.example import interfaces from my.example.contact import MyContact class ContactAddForm(z3c.form.form.AddForm): """ Contact Add Form """ fields = z3c.form.field.Fields(interfaces.IContactData) def create(self, data): id = data['firstname'] + data['lastname'] id = zope.component.queryUtility(IIDNormalizer).normalize(id) self._name = id contact = MyContact(self._name, **data) return contact def add(self, obj): # Add the object within context, and persist it ! context = self.context context[self._name] = obj def nextURL(self): return "%s/%s" % (self.context.absolute_url(), self._name)

There is one last thing to do, to make sure that our form can be rendered via Plone’s presentation machinery like any other page ; we define a special View by inheriting from the FormWrapper class provided by the plone.z3cform package, as follows :

# my.example/my/example/browser.py class ContactAddFormView(base.FormWrapper): form = ContactAddForm label= "Contact Add Form"

And don’t forget to add the configuration for this in the right ZCML file, something along the lines of:

<configure xmlns="http://namespaces.zope.org/zope" xmlns:browser="http://namespaces.zope.org/browser" xmlns:five="http://namespaces.zope.org/five" i18n_domain="my.example" > <browser:page for="Products.CMFPlone.Portal.PloneSite" name="contact_add_form" class=".browser.ContactAddFormView" permission="cmf.AddPortalContent" /> </configure>

Update: Of course, we render our form after restarting Zope, via the URL http://plonesite/@@contact_add_form.

Of course, in a real case you would use another context for the container, by affecting the right interface to the ‘for’ attribute in the configure.zcml, instead of the ‘Products.CMFPlone.Portal.PloneSite’ value.

Mars 6, 2008
» Updating an old AT-based product for Plone 3 - Step 1: Using GenericSetup


I am spending some time to update the Archetypes Developer Manual on plone.org/documentation, so I can move forward in contributing on other stuff in the Doc Team tasks list.

If you are also in the situation of updating some old AT-based product for Plone 3, this might be useful to you.

The current version of our manual, in the part discussing a sample AT product called InstantMessage, is missing:

  • GenericSetup profile for setting types and subskins. This has been missing for a long time, so I decided to update it immediately.
  • ZCML-based registration for things such as FS Directory Views or class permissions and factory, which the current version of CMF permits.

I started updating the “InstantMessage” package’s code in this branch.

What was needed: Mainly replacing the old ‘Extensions/Install.py’ code by the needed GenericSetup profile XML files:

  • for content types:
  • profiles/default/types.xml
  • profiles/default/types/InstantMessage.xml
  • profiles/default/factorytool.xml
  • for the sub-skin: profiles/default/skins.xml

Then, you need a piece of code for loading the GenericSetup profile. You do that, nowadays, by adding a configure.zcml file at the root of the package with the following code:

<configure xmlns="http://namespaces.zope.org/zope"

       xmlns:genericsetup="http://namespaces.zope.org/genericsetup"

       i18n_domain="instantmessage" >

<genericsetup:registerProfile

       name="InstantMessage"

       title="InstantMessage"

       directory="profiles/default"

       description="Extension profile for InstantMessage sample AT content type."

       provides="Products.GenericSetup.interfaces.EXTENSION"

      />

</configure>

I think I can now update the manual to reflect the new setup code - pointing to a couple of GS-related tutorials that already exist on plone.org, and merge the updated branch of the code with the trunk so people can get this new version.

Next step: Introduce other ZCML-based registrations in the CMF/AT context. This update could wait for the “versions” feature support on plone.org so we add these details without loosing the text related to the old way of doing.

Février 5, 2008
» If I had a single item in my wishlist for Plone 4…


… it would be: “Rethink the Document/Page”.

First, the right term for the right feature ! The Plone/AT Document is not for Documents. It is for “simple presentation pages”. That confusion was fixed in Plone 3 (IIRC) by renaming the feature to “Page”. I would argue “Page” is too vague, since the CMS always serves a page, you see what I mean. Some of you probably already have it in mind, why not “Article”.

“Article” described as in “Generic content item where you can add text, images, and links”. This is actually what Ingeniweb and others have been adding to Plone for years via the PloneArticle product. Another way of doing is with RichDocument, though I don’t have experience with it. It’s funny, everytime we build a Plone site with editorial features (most of them), we disable the “Document” content type (in the types registry) and replace it with the “Article”.

You get me ! I would like to see an implementation of Article support in Plone “à la” PloneArticle, the main use case being “easy for a content manager to add images, files, links, and maybe other pieces of content, in an article”. Presented like this, it looks like a Folder actually, but a folder with a predefined structure where the content manager do not need to think about that structure. Note that there may be other pieces of content that a content manager would like to add within an article, such as links to arbitrary Plone content (using references - PloneArticle does that), references to Documents within an exernal DMS, some photos from a Flickr account, etc…

This would probably be done in an extensible way, using a Python/Zope 3 library that allows handling references to diverse objects, decoupled of where these objects are stored. Reminds me of Alec Mitchell’s talk on plone.app.relations, something I still need to investigate !

Octobre 12, 2007
» “Acceptance testing in Plone” talk today, by Maik Roeder, Plone conf 2007


Requirements he wants to address

  • Use case centered
  • Hight level
  • Readable
  • Reusable
  • Extensibility
  • Modularization

Acceptance Testing for Plone with Funittest

  • Write test first
  • Guarantie the quality of your Plone sites
  • Run in-browser acceptance tests: Selenium Remote Control (or Real TestBrowser).
  • Extensive library of reusable scripts, verbs, scenario, and tests

Development with Funittest

  • Documentation driven development
  • Start with the Use Cases
  • Write new use case scenarios or extensions
  • Write high-level, domain specific, vocabulary for each new user action
  • Functional tests reusing the scenarios

Presets

  • Prepare a site for tests. Define users, etc…
  • Custom presets

Data Providers (Test fixtures)

  • Collection of example data for the tests (an example exists for a standard Plone site)
  • Simple dictionary of dictionaries

Tests

  • Manual tests are acceptable. What’s important is to test !
  • Tests depend on the whole functional stack. You are testing that whole stack.

Verification steps

  • Catch state before
  • Change expected state
  • Execute verb
  • Compare expected state and real state

Other details about writing tests

  • Verbs
  • Scenarios, scenarios steps
  • Extension Points. Extend scenarios.
  • Scenarios can be used from Python code (interactively, can be useful to try scenarios/steps when coding)
  • Physical Model: The low-level part. The way you attack Selenium Remote Control.

If interested

  • Funittest is in the Collective
  • There is sprint planned at the end of the conference
  • Article on the subject by Andrea Jennitta: Brushing Up On Functional Test Effectivenes.
  • Book by Alistair Cockburn: Writing Effective Use Cases.

Octobre 5, 2007
» Preparing for the Plone conference


Since a few days, I have started focusing on my talks material.

Some details about the talks I will be giving:

  • Anti-patterns, patterns and rules of thumb for successful Plone projects. My subtitle for this talk is, well, “Things to avoid and things to do !”. It is difficult to give advices in this area, and any advice needs to be confronted to the real context. But it would be interesting to share ideas and feelings about how we (meaning User, Project Manager, Developer) better cooperate and get the job done for our projets. Hope to see you there !
  • The plateform, the framework and the coach. I will be advocating a “coaching” approach for helping technical teams, within the enterprise, to get on board and be able to develop/maintain their in-house applications.
  • Let the machine work for you. Using Plone 3 content rules… I will try to discuss interesting use case examples, and how you set up the content rules for them.

It’s the second Plone conference I will be attending ; I was in Vienna two years ago, and I know it’s a very special atmosphere ! I am coming with Encolpe, a colleague at Ingeniweb. See you all there !.

Septembre 1, 2007
» Hourah, Plone 3 is out !


The much awaited Plone 3 is out ! You can see the announcement here. For those interested, I made a news about it on the french community site Zopera.org.

Alse, we are all excited about the coming Plone conference in Naples. (Stay tuned for more about this event !)