[Zope3-Users] browser:form

Jeff Shell eucci.group at gmail.com
Tue Feb 21 16:25:18 EST 2006


On 2/20/06, David Johnson <djohnson at jsatech.com> wrote:
> I've implemented the browser:form ZCML directive. The implementation works
> well until I add an __init__ method to my view class.  The following error
> is generated.
>   File
> "/home/myuser/Zope-3.2.0/build/lib.linux-i686-2.4/zope/app/form/browser/editview.py",
> line 76, in widgets
>     return [getattr(self, name+'_widget')
> AttributeError: 'SimpleViewClass from edit.pt' object has no attribute
> 'name_widget'
>
> Here is the __init__ that leads to the error
> ---:
>
> class Function(object):
>     """A transaction function."""
>     __used_for__ = ITPM
>     def __init__(self, context, request, base_url=''):
>         self.context = context
>         self.request = request
>         self.base_url = base_url

First - if you're running on Zope 3.2, I (and many others) would
recommend looking at zope.formlib. Later in this message, I go into a
little more detail about how it might help you.

But the direct answer is - you need to call ``super(...)``.

"Use super()?" you ask, "I'm just subclassing from ``object``, right?"
Wrong! The browser:form directive, like many view generating
directives for ZCML, is making a new class on the fly by mixing in a
few base classes and your class together. Those other classes that are
used as bases are likely to provide their own __init__, and you need
to ensure they get called because they could be doing something
important, like one of the zope.app.form.browser form classes is doing
in this case.

Try this:

class Function(object):
    ....
    def __init__(self, context, request, base_url=''):
        self.base_url = base_url
        super(Function, self).__init__(context, request)

That will set up context and request for you. More importantly, it
*should* call __init__ on the class that's created by the
<browser:form> directive, which calls something like
self.setUpWidgets. self.setUpWidgets (I'm not sure that's the method
name, but it's something like that) creates attributes on the view in
the form of ``fieldname_widget``: so ``name_widget``,
``title_widget``, and so on, based on the schema and options provided
by ZCML.

Since you're not calling super(), that setup step is not being run.

But I have a question: how is this form going to be used? Are you
instantiating it directly in other Python code? Because it's unlikely
that base_url will ever have a value applied to it otherwise. Most
views are instantiated in Zope by calling ``getMultiAdapter((context,
request)...)``, which calls __init__ on the view object found and
passes in context and request as the two arguments. Unless you are
explicitly calling ``Function(context, request, 'foo/bar')``, that
third argument is unlikely to be set. If you just want to ensure that
it's there as an attribute, you can put it in the class definition::

class Function(object):
    base_url = ""

And then you don't have to override __init__.

It's probably not a good idea to provide/expect extra __init__
arguments when writing an adapter (or view), since most of the time
it's the adapter lookup machinery that will be calling that __init__
code. It's not bad to write your own __init__ though. But I do
recommend trying to find out what classes you're inheriting from and
what they do in __init__ so you can either do it yourself or use
super().

Formlib
=======

If you're using forms and you're running Zope 3.2, look at
zope.formlib. With formlib, it's easier to see what classes you can
inherit from and what they're doing and when you might want to
override a method and would need to call super, and what you would
need to pass along to the superclass when you do. This is an example
of what that might look like:

from zope.formlib import form

class Function(form.FormBase):
    # form.Fields finds the fields that might have been provided by
    # browser:form schema="...ITPM".
    form_fields = form.Fields(ITPM)

    base_url = ''

    # if you have a custom template, you can provide it here
    # instead of ZCML
    template = ViewPageTemplateFile('functionform.pt')

And then register via <browser:view>. (You'd need to do a little bit
more work, of course, to load/bind custom data or respond to a 'Save'
action, depending on your requirements).

It really is a more flexible, adaptive, controllable, and documented
system than the form views available from zope.app.form via the
<browser:*form> directives. You should be able to print out the
documentation from apidoc in the 'book' menu, and look up the IFormAPI
interface too. All of our custom forms became so much easier to
control and understand after moving to formlib, and in many cases the
code to support a form actually shrank. There's a lot more plain old
Python code and a lot less ZCML magic, which makes it easier to trace
how a form and its widgets get built. It might take a little bit of
time to read and understand all of the options available, but it's
well worth it as I've found zope.formlib to be a wonderfully solid
foundation to build on.

--
Jeff Shell


More information about the Zope3-users mailing list