[Zope3-dev] Brainstorming about browser pages

Jeff Shell eucci.group at gmail.com
Fri Feb 17 16:08:50 EST 2006


This is another infamously long post of mine. I'll summarize my
thoughts on the brainstorming here, with details below::

Summary / Quick Thoughts
========================

I'm not fond of <browser:page>. I can't wait to revisit and remove a
few uses of <browser:pages> that I'm still supporting because it's
proven to be very unwieldy. I find <browser:view> to be very useful in
its current form. I would like to see something that made exposing
view attributes as URL traversable elements as easy (or easier) than
formlib actions. I'd like to see, perhaps, a 'pagelib' that does this.
Or maybe just a little bit more work in 'zope.formlib.page' to promote
the 'update/render' style (which all of the page.Page based classes in
formlib.form use anyways), with some equally usable base classes that
made writing a template-only view a two line affair, while allowing
that view to grow in capability by adding / providing more methods::

  class PageView(zope.formlib.page.Page):
      def update(self):
          """
          Subclasses may use this to respond to requests and get items
          ready for rendering
          """
          pass

      def render(self):
          raise NotImplementedError("Subclass should provide render")

      def __call__(self):
          self.update()
          return self.render()


  class TemplatedPageView(PageView):
      template = ViewPageTemplateFile('empty.pt')

      def render(self):
          return self.template()

Also see below for thoughts on exposing multiple attributes as
traversable named 'sub-pages'.


Actual Response / Very Long Thoughts
====================================

On 2/17/06, Lennart Regebro <regebro at gmail.com> wrote:
> Great page. Heres the first thing I don't like:
>
> Registering it would probably work like this::
>   <view
>
> You would register them using a ``browser:pages`` call:
>
> It is not immediately obvious to thew Zope3 newbie what the difference
> between a "page" and a "view" is  and why we would need to
> differentiate between them. So I think we shouldn't use both view and
> page statements, but probably only have "page" and "pages" statements.

I disagree, but it's a matter of semantics. Just like 'view' is a
matter of semantics when it comes down to it.

In the last two applications I've worked on in Zope 3, I haven't
really thought about things in terms of "pages". I'm using a lot of
AJAX. I'm using viewlets and content providers, and often also just
using views on views. I have a view for managing people based objects
in a container. There are three kinds of objects that can be in that
container - a Group, a Passenger, and a Crew Member. They have slight
variations in what information they need to present. So the view
object that lists these things for management then looks up another
view (by name) to render each of the three lists. Each of those lists,
in turn, look up a very small view object for how to display the
individual content items. Each of these items has actions (like edit
or delete) that can be rendered for them, and the edit action does
in-place editing using AJAX.

In a traditional "page" mentality, I would have just lumped all of
this together in one template and arm wrestled my way through it. But
these days, I break everything into separate components. And it
actually makes my life easier.

What makes a lot of this work is <browser:view>. One of the main
reasons why is because <browser:view> allows me to specify a
'provides' value, so I can make views for views. Why not viewlets for
this? Because I need to traverse to them by URL.

The other thing I like about <browser:view> over <browser:page> is
that I always know what it's going to call: __call__. I've also gotten
into the habit of doing the update/render pattern. Now writing new
views and having them work as full pages, viewlets (or viewlet like),
or small chunks of HTML to be loaded in by Javascript, it's all
uniform in how I approach writing it. Sometimes a view might be as
simple as this::

class FooDisplay(BrowserView):
    template = ViewPageTemplateFile('foodisplay.pt')

    def update(self):
        pass

    def render(self):
        return self.template()

    def __call__(self):
        self.update()
        return self.render()

Yeah, that might look like a lot of work for a simple 'page'. But if
you put update, render, and __call__ into a base class just like this,
and you have a very easy to use View that is easy to extend when it
starts needing any sort of sophisticated logic. The above code would
also work in a content provider / viewlet manager, or in any view
situation that used sub-views so that all update() calls happened in
one pass, and all render() calls happened separately, yet still in a
composed manner.

My applications have had terrifically increased sophistication since
adopting this pattern. Putting aside 'page' as referred to by
'<browser:page>' for a moment, I just want to say that I've found
terrific freedom away from 'page' oriented thinking.

> Second thing:
>
>       name="index.html"
> vs
>       @page(u'update.html')
>
> I think that unless we can move all naming into python (and I'm not
> sure we can) we shouldn't have any naming in Python. Consistency is
> the enemy of confusion! :-)

I'm mixed on this. I think that "views as named adapters" should stay
named in ZCML, or however one registers the view adapter with the
component architecture. Further - not all views / multi-adapters have
or need names.

On the other hand, I think the <browser:pages> directive with
sub-pages is a terrible thing, based on my own experiences with it.
It's my understanding that::

    <browser:pages class=".Updater" name="contents"...>
      <browser:page name="update.html" attribute="update"/>
      <browser:page name="delete.html" attribute="killThoseSuckers"/>
      <browser:page name="index.html" template="contents.pt"/>
    </browser:pages>

Registers named multi-adapters for each of those specified things.
Although, I could be wrong, since I'm not entirely sure how
<browser:pages> works. But making a separate multi-adapter for each
little attribute seems overkill. And, personally, I think it's bad
design. I should know - I have a very important view written in our
first Zope 3 app that's like this. And making variations on it is
p-a-i-n-f-u-l. The surrounding 'Updater' class is what's important
anyways. That's the one that should be registered with the system as a
named component (through ZCML, etc). Dispatching to 'update.html',
'delete.html', and 'index.html' should just be a case of
publishTraverse. And here you could take some design from formlib's
actions, fields, widgets, etc.

class Updater(PageProvider):
    pages = Pages(
        Page(name='index.html', template='content.pt'),
        Page(name='delete.html', attribute='killThoseSuckers', condition=..),
        Page(name='update.html', attribute='update'),
        )
    pages.setDefault('index.html')

    def publishTraverse(self, request, name):
        if name in self.pages:
            page = self.pages[name]
            if page.available():
                return page.providedFor(self, request)
        return super(PageProvider, self).publishTraverse(request, name)

And, like with formlib's actions, 'pages' could be provided with
@page(name) as a decorator for the attribute/method they publish,
which would add them to the ``pages`` collection.

The use case for this? For when you do have URL publishable 'pages'
provided as attributes/templates bound to a view class that don't need
to be stand alone views.

Why is this better than <browser:pages> in ZCML? Because all of these
'pages' are regular Python objects in their own right. They can be
queried and manipulated. I *LOVE LOVE LOVE LOVE LOVE* this about
formlib. And, I've been able to read formlib's documentation and
understand it, and also follow its code and understand it. I cannot
say the same thing about most of the ZCML directives.

Can it really be that powerful yet easy? Oh hell yes. I've done
amazingly fun and funky things with zope.formlib in recent weeks that
would have been a pain otherwise with the form views in zope.app.form.
Things like inline editing (via ajax) of individual fields while
retaining custom widgets and validation options. Things like embedding
add forms inside of a complex multi-vocabulary widget that work via
AJAX and tell the outer form nothing of themselves...

A coworker used <browser:form>, with a class, to make a custom search
form using some date widgets we had made (the custom widgets were also
declared in ZCML as sub-directives). I wanted to make the search form
have default computed values for the date fields (one week back for
one, three weeks ahead for the other). Couldn't do it easily with a
<browser:form> style view. Once I changed it into a formlib based one,
so many new possibilities opened up. But after just translating it to
formlib, *the combined number of Python and ZCML lines involved were
less than they were before*. Six lines of Python later, I had
functions to compute the default values and they were being applied to
the widget automatically. (two lines each for the functions, one line
each to bind it to the form field... And if I wanted, I could subclass
and change just one of them in the subclass).

The point is - doing something smart to wrap up multiple
templates/attributes as "sub-pages" (cough) of a view class could and
should be done. And it could probably be done in a more powerful and
flexible way than you might imagine while providing the right base
classes (like zope.formlib does) to make it easy to work with, but
also easy to expand on when  you need more power. Most importantly,
the same techniques you learned for doing the simple thing should
still apply as complexity grows. I found this to be the case with
zope.formlib. The earlier zope.app.form editforms (which preferred to
be built all in ZCML, including custom widgets) were much more
frustrating when you stepped above a certain threshold. They made it
easy to generate forms without need for any "boilerplate" Python class
- all you needed was ZCML. But when you needed to step up and do just
a little bit more, or just a little bit different, or just run the
status message through your own page notification API, things got a
*lot* harder. (This is a common meme of mine, it seems - if you go out
of the way and make things really easy for certain use cases and take
care of too much behind the scenes, you may get a happy user for the
first two days. But on the third when he needs to do something more
than the automatic tools provide and tries to find answers and finds
brick walls or mazes of ZCML metadirective handlers, THAT's when you
lose them; THAT's when the learning curve complaints start; THAT's
when you see strange hacks in other peoples code that make you go "why
did you do that?" and they go "I couldn't figure out any other way to
do it so I copied this from the root and changed some things but I
still don't know why it does what it does...").

> Third thing:
> "What if someone wants to customize this view by changing the template
> but not the helper method? You either have to subclass this view class
> and add a custom __call__ to your subclass, or you use *named
> templates* from zope.formlib:"
>
> Naaaah. I don't like this, but I can't tell you why. Maybe just
> because it's too complicated. Same goes with your usecase 1. I
> actually don't want to create a boilerplate class just to hold page
> templates. And sure, if we could get rid of the automated class
> creation in Usecase 3b, then I'd might opt for getting rid of this
> class creation too, but now I just don't see why.
>
> On the other hand, in most cases it means I'll create one view class
> per object and tuck a lot of templates on it, but still, it doesn't
> feel quite right...

I'd say it feels very wrong, in fact, had I not done something sortof
similar with head resources recently. But those were just ways of
compressing five lines of viewlet registration per javascript/css
resource that were meant to be used on every-single-page-in-the-system
into a single line of Python each. But those items aren't providing or
requiring the sort of intelligence that a view might.

Maybe I feel that way because I use page templates less and less and
have broken things out into small work units that generate HTML
through a simple generator based (loosely) on Nevow's STAN. About the
only full Templates I register any more are macro collections -
standard template, form templates, etc. I find that with almost
everything else, at least based on recent work, that I do want a view
class living along side my template. Very few objects in my system
really have the guts to format their data on their own properly, so I
always need at least *some* kind of support.

If done properly, having this::

    class ArticleDisplay(PageView):
        template = ViewPageTemplateFile('article.pt')

    [ZCML]
    <browser:view for="..interfaces.IArticle" name="article.html"
        class=".pages.ArticleDisplay"/>

seems pleasant enough. Then when you need to format the 'description'
field through something like newline_to_br, or present computed dates
('updated 4 and a half hours ago'), you already have a place to hang
them. Whereas with this::

    <browser:page for="..interfaces.IArticle" name="article.html"
        template="article.pt"/>

You don't. And then you won't want to put the nice formatter on their
because your brain hits the "oh, I need to make that into a class...
Ugh, not right now" moment. And you find yourself staring at a page
that you want to make prettier, but there's _just_ enough annoying
effort involved to keep you away from it. At least, that's been my
personal experience, but I'm also a bit of a crank.

So this (what I show immediately above with the false 'PageView'
subclass) is effectively the pattern I've started enforcing for all of
our new work, and it can be done now in what ships with Zope 3.{1,2}.
A two line "boilerplate" Python class now saves headaches later.

--
Jeff Shell


More information about the Zope3-dev mailing list