Quantcast
Channel: Planet Plone - Where Developers And Integrators Write
Viewing all articles
Browse latest Browse all 3535

Bo Simonsen: IFormExtender – the magic of z3c.form

$
0
0

Approximately in the spring of 2012, we left Archetypes-based content types in favour of Dexterity-based content types. To us Dexterity was mature enough for production environments and just some of the many advantages that made us change did include:

  • More light-weight objects that will easier scale on bigger sites.
  • The advantage of z3c.form and all the extensions provided.
  • Elegant extension of already existing types by Dexterity behaviors (compared to Archetypes Schema Extender).

We found the existing work done for Dexterity/z3c.form of a high quality and good design. For example, with plone.directives.form, it was easy to change widgets, set default values of fields etc.; but it did not take long before we got stuck doing just simple things. Just to mention one example, you have a schema defined by a regular z3c.form interface, you desire to change the widget out side the class level. Here plone.directives.form cannot help you, instead you need to set the tagged value manually. It is not to criticize plone.directives.form in any way, but at least we saw it was problematic to use for general purposes. We found tagged values to be a sufficient solution in this case, i.e. plone.directives.form is just setting a tagged value on the particular field when changing widget.

Further troubles were ahead. Dexterity behaviours are very powerful, and it gets hard to get back to old Archetypes-based sites, and do something elegant, as you can do with behaviours. But consider you have a behaviour there is shared over many content types. You want just for one content type a field in the behaviour to be required. If you are unaware of IFormExtender here you are really lost. I found this interface by discussing missing adapter lookups with David Glick (unfortunately I lost the github issue reference).

When you have a factory adapting Interface, IDefaultBrowserLayer, IForm
and implementing IFormExtender, you can ensure the update method is run every time a form is rendered. You could of course implement custom forms for your specific dexterity content types but this would be very time consuming. Instead of adapting on Interface you can also adapt on the specific behavior/schema interface.

Example

We have a content type, lets call it publication, this content type has the behavior IDublinCore (providing Dublin Core metadata for Dexterity content types). We desire the effective date field (effective) is required just for this content type. We create the following factory (lets store it in factory.py).

# -*- coding: utf-8 -*-
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.interface import Interface, implements
from zope.component import adapts
from plone.z3cform.fieldsets.extensible import FormExtender
from plone.z3cform.fieldsets.interfaces import IFormExtender

from z3c.form.field import Fields
from z3c.form.interfaces import IEditForm, IAddForm, IForm

class EffectiveDateRequired(FormExtender):
    implements(IFormExtender)
    adapts(Interface, IDefaultBrowserLayer, IForm)

    def __init__(self, context, request, form):
        self.context = context
        self.request = request
        self.form = form

    def update(self):
        is_form = IEditForm.providedBy(self.form) or \
            IAddForm.providedBy(self.form)

        if hasattr(self.form, 'portal_type') and \
           self.form.portal_type == 'Publication' and \
           is_form:

            field = effective_group.fields['IDublinCore.effective']
            field.field.required = True
        else:
            field = effective_group.fields['IDublinCore.effective']
            field.field.required = False

The update method will be called everytime a form is rendered so it is important for us to check if it is either an edit- or add form. Further on we check that we only handle the Publication type.

The adapter needs to be registered, which we do trivially via configure.zcml

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

  <adapter factory=".factory.EffectiveDateRequired" />

</configure>

 Concluding remarks

IFormExtender is in my opinion the universal fix where you get stuck with plone.directives.form or any other “convenience library”. It is nice to have such a fix but I would rather see plone.directives.form improved, to support more general use-cases.

 


Viewing all articles
Browse latest Browse all 3535

Trending Articles