Friday, April 07, 2006

Zope3 Viewlets and Formlib

I recently came upon a situation where I needed to embed a number of forms inside viewlets to be displayed "dashboard" style from a primary application page in zope3. I've been trying to migrate away from 3.0/3.1's's view libraries and toward zope.formlib.form's form builders. I figured I'd document what I put together to make it work, in case anyone else ends up trying the same thing. It didn't take long, but still... more documentation is better ;)

I started with a simple container class that I wanted to be able to be able to create a form for that would allow me to edit the values of a child object (with a form of it's own) in a viewlet. the class creation and zcml for it were simple enough, I won't relate them here, as they're pretty standard.

I then created a child object that, for our purposes, I'll call "Silly." The class and interface were as follows:

class ISilly(Interface):
    silly = Bool(title = _(u'This is Silly!'), description = _(u'I bet it is'), )
    someText = TextLine(title = _(u'Silly Text'),description = _(u'I am happy text'), )

class Silly(Persistent):
    """silly object"""
    silly = True
    someText = u''

next i created a viewlet manager, and the necessary zcml

class ISillyViewletManager(IViewletManager):
    """silly viewlet manager"""

from zope.viewlet.manager import ViewletManager
SillyViewletManager = ViewletManager(ISillyViewletManager)
<!--silly viewlet manager -->

next we actually build the form that we want to embed in the viewlet. as srichter mentioned to me, both formlib and viewlet use the same render/update patterns, so making a form inside a viewlet really flows very naturally.

the only thing i want to point out in the code below (explicitly) is the change in context. if you want to derive from editform, you need to change the context at the outset. if you want to derive from form.Form, you don't have to change it in __init__, but before you build any functionality that uses self.context, you have to make sure that it's actually pointing at the objects you want to affect. in this case, 'silly1' is an ISilly object contained in the current context.

the reason for this is that setUpWidgets in EditForm pulls in the current zodb stored values to populate the fields with by running form.setUpEditWidgets. this is the behavior i wanted, but it does that at render. form.Form doesn't pull those values in, so you can theoretically wait to change self.context until later, if you override setUpWidgets out of form.Form.

class SillyViewletForm(form.EditForm):


    def __init__(self, context, request, view, manager):
        super(SillyViewletForm, self).__init__(context, request, view, manager)
        self.__parent__ = view
        self.context = context['silly1']
        self.request = request
        self.manager = manager

    prefix = u'silly'
    form_fields = form.Fields(ISilly, omit_readonly=True)
    template = ViewPageTemplateFile('sillyviewlet.zpt')

notice also that setting "prefix" explicitly ensures that you don't have field name overlap between viewlets and primary form fields (or other viewlets, even). one other piece that may look innocuous from a quick glance is the template line. while the viewlet zcml will accept a template parameter, it will not act on it in any meaningful way, and will instead assume that it should use a namedtemplate of 'default' since that's what all of formlib-derived forms use unless otherwise instructed. in our case, we set template to's ViewPageTemplateFile, with a filename of the template we want to frame our viewlet in. a final bit to notice is that our SillyViewletForm implements IViewlet. necessary piece.

this is the zcml for this viewlet:


the remaining configuration and use is simple, and is outlined in formlib more than adequately, but i'll include it here just for reference. to use a viewletmanager (you only use viewletmanagers, not viewlets themselves, at least as of 3.2) in tal markup on a page template, it's included like this:

<tal:block replace="structure provider:petetest.browser.silly.ISillyViewletManager" />

the actual page template markup to display the viewlets in a viewletmanager. in this example, this was silly.zpt:

<div class="box">
    <div class="entry"
      tal:repeat="viewlet options/viewlets"
      tal:content="structure viewlet/render" />

the final piece of the whole bit is what the form page template that makes up the viewlet form itself should look like. this is obviously up to the individual designer/developer, but for our example, i literally just took from zope.formlib and modified it slightly, such that it doesn't extend the view macro, nor define itself as main. if you don't extend or use view or page, you'll need to get rid of the fill-slot="body" div. i also redefined the define-macro defined at the <form level from "master" to "silly" so that the form itself wouldn't be in conflict with any other more generically derived combinations of tal on the page.

this is a pretty quick-and-dirty runthrough, but i think it has enough info that if i'd seen it when i started, i would have understood what was going on more quickly. if anyone has any comments, critiques, or suggestions, please let me know. baldtrol_at_gmail_dot_com. Thanks to Stephan Richter, Gary Poster, and Alen Stanisic for their help while I was figuring this out :)


Blogger Kevin Gill said...

Posting a form from a viewlet turned out to be far simpler than I expected. Thanks for this excellent post. It will be really useful.

Tue Jan 22, 06:56:00 AM GMT-5  

Post a Comment

Links to this post:

Create a Link

<< Home