Random notes from mg

a blog by Marius Gedminas

Marius is a Python hacker. He works for Programmers of Vilnius, a small Python/Zope 3 startup. He has a personal home page at http://gedmin.as. His email is marius@gedmin.as. He does not like spam, but is not afraid of it.

Sun, 04 Apr 2010

Review: Grok 1.0 Web Development

Disclaimer: I received a free review copy of this book. The book links are affiliate links; I get a small amount from any purchase you make through them.

Grok is a Python web framework, built on top of the Zope Toolkit, which is the core of what used to be called Zope 3 and is now rebranded as BlueBream. Confused yet? Get used to it: the small pluggable components are the heart and soul of ZTK, and the source of its flexibility. It's not surprising that people take the same approach on a larger scale: take Zope 3 apart into smaller packages and reassemble them into different frameworks such as Grok, BlueBream or repoze.bfg.

Grok 1.0 Web Development by Carlos de la Guardia

The Grok book by Carlos de la Guardia introduces the framework by demonstrating how to create a small but realistic To-do list manager. I like this technique, and it works pretty well. The author covers many topics:

Some important topics like internationalization, time zones, testing with Selenium, and (especially) database migration (which is pretty specific for ZODB) were not covered.

If you want to learn about Grok, this book will be useful, but there's a caveat: there's the usual slew of typographical mistakes and other errors I've come to expect from books published by Packt. It's their third book I've seen; all three had surprisingly high numbers of errors. Some had more, others had fewer. The Grok book was on the high side and the first one where I was tempted to record a "WTFs per page" metric. The mistakes are easy to notice and correct, so they didn't impede my understanding of the book's content. Disclaimer: I've been working with Zope 3 for the last six-or-so years, so I was pretty familiar with the underlying technologies, just not the thin Grok convenience layer. If minor errors annoy you, stay away. I haven't noticed any major factual errors, although there were what I would consider some pretty important omissions:

Overall, Grok is pretty nice, especially compared to vanilla Zope 3. However, when compared to frameworks like Pylons or Django, Grok appears more complex and seemingly requires you to do additional work for unclear gain. For example, chapter 8 has you writing three components for every new form you add: one for the form itself, one for a pagelet wrapping the form, and one for a page containing the pagelet. Most of that code is very similar with only the names being different. I'm sure there are situations where this kind of extreme componentization pays off (e.g. it lets you override particular bits on particular pages to satisfy a particular client's requests, without affecting any other clients), but the book doesn't convincingly demonstrate those advantages. Again, I may be biased here since I've been enjoying those advantages for the past six years, without ever having felt the pain of doing similar customizations with a less flexible framework. (It's a gap in my professional experience that I'm itching to fill.)

Update: some other reviews on Planet Python.

Update 2: Another review (well, part 1 of one, but I got tired waiting for part 2).

posted at 22:30 | tags: , , | permanent link to this entry | 3 comments

Tue, 01 Dec 2009

Displaying multiline text in Zope 3

zope.schema has Text and TextLine. The former is for multiline text, the latter is for a single line, as the name suggests. Zope 3 forms will use a text area for Text fields and an input box for TextLine fields. Display widgets, however, apply no special formatting (other than HTML-quoting of characters like <, > and &), and since newlines are treated the same way as spaces in HTML, your multiline text gets collapsed into a single paragraph.

Here's a pattern I've been using in Zope 3 to display multiline user-entered text as several paragraphs:

import cgi

from zope.component import adapts
from zope.publisher.browser import BrowserView
from zope.publisher.interfaces import IRequest


class SplitToParagraphsView(BrowserView):
    """Splits a string into paragraphs via newlines."""

    adapts(None, IRequest)

    def paragraphs(self):
        if self.context is None:
            return []
        return filter(None, [s.strip() for s in self.context.splitlines()])

    def __call__(self):
        return "".join('<p>%s</p>\n' % cgi.escape(p)
                        for p in self.paragraphs())

View registration

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

  <view
      for="*"
      name="paragraphs"
      type="zope.publisher.interfaces.browser.IBrowserRequest"
      factory=".views.SplitToParagraphsView"
      permission="zope.Public"
      />

</configure>

and usage

<p tal:replace="structure object/attribute/@@paragraphs" />

Update: The view really ought to be registered twice: once for basestring and once for NoneType. I was too lazy to figure out the dotted names for those (or check if zope.interface has external interface declarations for them), so I registered it for "*". You should know that this makes the view available for arbitrary objects (but won't work for most of them, since they don't have a splitlines method), and that it is, sadly, accessible to users who may try to hack your system by typing things like @@paragraphs in the browser's address bar. Ignas Mikalajūnas offers an alternative solution using TALES path adapters.

posted at 20:52 | tags: , | permanent link to this entry | 1 comments

Thu, 07 Jun 2007

Custom traversal in Zope 3

Certain things are not quite obvious in Zope 3. Custom traversal is one of those: I always have to go and look at an example when I need it. Here's the example:

Say, you have a content object that provides IMySite and is exposed to the web at /mysite. You want to implement custom traversal for names under it, e.g. have /mysite/mycalendar return some object specific to the user that's currently logged in.

You need to provide an IBrowserPublisher adapter for (IMySite, IBrowserRequest):

from zope.component import adapts, queryMultiAdapter
from zope.interface import implements
from zope.publisher.interfaces import NotFound
from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserPublisher

from mypackage.interfaces import IMySite


class MySiteTraverser(object):
    """Browser traverser for IMySite."""

    adapts(IMySite, IBrowserRequest)
    implements(IBrowserPublisher)

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

    def browserDefault(self, request):
        """Return the default view of /mysite."""
        # XXX: use getDefaultViewName instead of assuming it's index.html
        return self.context, ('index.html', )

    def publishTraverse(self, request, name):
        """Traverse to /mysite/$name."""
        if name == 'mycalendar':
            mycalendar = ... # TODO: do something to get the appropriate object
            return mycalendar

        # if self.context is a container of some sort,
        # you'll have to add traversal to items here manually.

        # fall back to views
        view = queryMultiAdapter((self.context, request), name=name)
        if view is not None:
            return view

        # give up and return a 404 Not Found error page
        raise NotFound(self.context, name, request)

Now register it in ZCML with

    <view
        for="mypackage.interfaces.IMySite"
        type="zope.publisher.interfaces.browser.IBrowserRequest"
        provides="zope.publisher.interfaces.browser.IBrowserPublisher"
        factory="mypackage.mymodule.MySiteTraverser"
        permission="zope.Public"
        />

Note that this is the regular view directive, not browser:view.

Update: Philipp von Weitershausen shows how Grok simplifies this (site disappeared; here's an Internet Archive link). Check out the Grok website.

Update: Added the missing __init__ method, thanks to Yuan Hong for noticing.

posted at 20:02 | tags: , | permanent link to this entry | 1 comments