Personal tools
You are here: Home Weblog zope3

zope3


2010-01-15

Test if developer mode is set in Zope 3 and Grok

Filed Under:

I've started an application that uses Dolmen, a lightweight CMS built on top of Grok, and I want to be able to "rollup" the megrok.resource files based on the devmode setting that was set in the zope.conf file. After a bit of digging, I came up with this code that tells me the current devmode setting for the running instance:

from zope.app.applicationcontrol.applicationcontrol import applicationController
from zope.app.applicationcontrol.interfaces import IRuntimeInfo

def get_debug_mode():
    """Returns the devmode setting for a running zope instance"""

    rti = IRuntimeInfo(applicationController)
    return rti.getDeveloperMode() == "On"

 

2009-02-16

Making peace with the system-wide installed zope.interface

Filed Under:

While testing software to play music from a computer on my network, I have discovered Elisa, which is a wonderful piece of software built in Python and zope.interface. The package manager helpfully installed a python-zopeinterface package, which turned out to break one of the apps I've been working on (one of the packages that is used depends on a more recent version of zope.interface and breaks with a missing object import). Adding an explicit dependency on zope.interface>=3.5 didn't help either. The egg was installed and a reference to it was inserted in the generated script wrapper for the buildout's bin folder, but the system zope.interface was found.

The solution that I have found was to make sure the zope.interface dependency is listed in the last position in the install_requires section of setup.py. This has the effect of placing the zope.interface egg path first in the generated script, and thus solving the problem.

UPDATE: On another project I'm working on, this solution didn't work. Buildout would complain about a version conflict and would drop the building process. The solution was to setup a separate virtualenv bootstrapped with --no-site-packages and use the python from that virtualenv to bootstrap the buildout environment. I think it's a bug in zc.buildout, as it should have obeyed the versions section of buildout, plus the explicit dependency in install_requires of my package setup.py

2009-02-11

Variable keys in dictionaries with Page Templates TALES syntax

Filed Under:

I admit, I didn't knew this until now. In the following construction:

<div tal:content="somedict/keyname/someattr" />

"keyname" is taken as a string, it's the literal name of the key for the somedict mapping. To use a variable instead of the literal value of the key name, I used to do:

<div tal:content="python somedict[key].someattr" />

Browsing through the zope.app.catalog code, I saw that there's actually a way to use the TALES syntax:

<div tal:content="somedict/?key/someattr" />

I'm not sure that this works with TTW code in Zope 2 (I expect that it works with browser views), so I'll just have to try this next time I have the chance.

2009-02-05

Reset the generations level for a Zope application

Filed Under:

While developing an application and writing some migration code (using zope.app.generations), I had the need to reset the generation number recorded in the database for my application to a version lower than the current generation number (because my generation code didn't run properly and I didn't want to create bogus generation files). To solve this issue, in a pdb prompt I had to run:

(Pdb) db = self.request.publication.db
(Pdb) conn = db.open()
(Pdb) conn.root()['zope.app.generations']['myapp.generations'] = 0
(Pdb) import transaction
(Pdb) transaction.commit()
(Pdb) c

Not much to it, and this info can be easily obtained by reading the zope.app.generations source code.

2009-01-16

LearningJourney: A Zope 3 tutorial

Filed Under:

Last year, in December, I held a 3 day Zope 3 training for about 10 Zope 2 and Python programmers. For this training I had prepared a 50 pages booklet with some tutorials and reference information (most of it written by me especially for this occasion). The feedback was pretty positive, but only time will tell if I was succesful or not with my training.

I have published the training material on Google Code in a project called z3ergo. My intention is to publish there more packages and tutorials, as they come. The beef of the tutorial was a simple but complete application called LearningJourney. My initial intention was to have an application where students could enter a log of the things that they learn, but it could be thought as a simple blog system as well. This is a full Zope 3 tutorial application, complete with a site, skin and registration process. I hope somebody can find it useful. The leaflet is published there in two languages (English and Romanian), in pdf format. You can find the source code for the docs in the LearningJourney docs folder.

The materials cover a lot of the Zope 3 landscape, as this training was for knowledgeable Zope 2/Plone programmers who were evaluating Zope 3 and also had access to other Zope 3 docs, such as Philipp's Zope 3 book. Keep this in mind when going through the docs and wonder why they're so slim.

The training itself was pretty non-formal. I've used the booklet and the sourcecode to guide them through what a Zope 3 application means. There were a lot of questions (practically, the training flow was more question based, with just a few interventions from me to guide it to a different direction), but many things that were covered mostly at surface. We didn't have any practical exercises, although I wish I was able to have them, it might have helped the students feel a little bit less lost. A more structured approach could have helped as well, who knows. It was both tiring and fun and I got to meet lots of cool people, with which I will hopefull colaborate in the future.

If there's an interest in getting more out of these docs, or you might need me as a Zope 3 trainer, feel free to contact me using this site's contact form.

2008-08-22

Bug in PyPi

Filed Under:

The zope.app.form PyPi page looks awful, it should be fixed. Who's fault is that? Django, the framework that sits underneath (AFAIK), or the docutils libraries that probably parse the RST pages?

2008-07-11

Hosting Plone and Zope 3 applications using nginx

Filed Under:

I'm doing a setup on a new server, I've decided to replace the default Apache 2.2 with an nginx http server. The setup which is needed for Zope 3 and Plone applications is the following:

[buildout]
parts =
    nginx
    nginxctl

[nginx]
recipe = gocept.cmmi
url = http://sysoev.ru/nginx/nginx-0.7.6.tar.gz
md5sum = ae7ce6f66a2cf5a5970d9a9a0da0cf7d

[nginxctl]
recipe = gocept.nginx
hostname = localhost
port = 80
configuration =
    worker_processes 1;
    events {
        worker_connections 1024;
    }
    http {
            upstream z3 {
                server 127.0.0.1:8080;
            }
            upstream plone {
                server 127.0.0.1:9080;
            }
        server {
            listen      ${nginxctl:port};
            server_name z3.example.org;
            root html;
            include /etc/nginx/proxy.conf

            location / {
                proxy_pass http://z3/++lang++ro/++skin++myskin/mysite/++vh++http:z3.example.org:80/++/;
            }
        }
        server {
            server_name plone.example.org;
            include /etc/nginx/proxy.conf

            location / {
                proxy_pass http://plone/VirtualHostBase/http/plone.example.org:80/t1/VirtualHostRoot/;
            }
        }
        server {
            server_name plone.example.org;
            rewrite ^/(.*)  /VirtualHostBase/http/plone.example.org:80/t1/VirtualHostRoot/$1 last;
            location / {
                proxy_pass http://plone;
            }
        }
    }

Note: this is a buildout.cfg. Using it together with zc.buildout makes the nginx instalation a very simple process: install zc.buildout (easy_install zc.buildout), and then run buildout in the folder that contains the .cfg file.

The settings in proxy.conf are important. Without a valid proxy_temp_path, for some reason delivery of all content that came from a Plone 2.5 site that used CacheFu setup with no proxy cache was freezing at 16014 bytes. The paths in /var/nginx need to be created and set to be writable by the nginx process (user nobody in my case).

client_max_body_size            0;
client_body_buffer_size    128k;
client_body_temp_path      /var/nginx/client_body_temp;

proxy_connect_timeout      90;
proxy_send_timeout         90;
proxy_read_timeout         90;
proxy_buffer_size          4k;
proxy_buffers              4 32k;
proxy_busy_buffers_size    64k;
proxy_temp_file_write_size 64k;
proxy_temp_path            /var/nginx/proxy_temp;
proxy_redirect                  off;
proxy_set_header                Host $host;
proxy_set_header                X-Real-IP $remote_addr;
proxy_set_header                X-Forwarded-For $proxy_add_x_forwarded_for;

 

Resources

A more complete nginx sample configuration file (but that only covers how to configure Plone)

Grok guide on hosting Zope 3 with nginx (note, at this moment the document is wrong, the setup line is missing a slash at the end).

2008-06-01

Using views as information mixins in templates

Filed Under:

This may be basic trick for some, a non-obvious usage of views for others, who knows, I'm documenting it here anyway. I've been using this technique for quite some time without giving it much thought.

There are times when I have an object in a template. I want to display information associated with that object. This information is already coded in a @@detail view on this object. Suppose this example (in mostly pseudocode):

class PersonDetail(BrowserView):
    """Show detail about a person"""
    def name(self):
        return compute_somehow_name()

class CommentDetail(BrowserView):
    """Show details about a comment"""

Now we have the following template for the CommentDetail view:

<div tal:define='person comment/author; person_info nocall:person/@@detail'>
    <a tal:attributes="href person/@@absolute_url" tal:content="person_info/name">The author</a>
</div>

Notice the nocall: keyword placed in from of the person/@@detail call. This ensures that the @@detail view is instantiated, but not called (so it is not rendered). This way we have access to the view class attributes, properly associated to the Person context.

2008-04-15

In case you're having problems installing ssl-for-setuptools...

Filed Under:

I've stumbled on this error when trying to install a easyshop buildout:

Exception: No SSL support found
An error occured when trying to install ssl-for-setuptools 1.10.Look above this message for any errors thatwere output by easy_install.
While:
  Installing instance.
  Getting distribution for 'ssl-for-setuptools'.
Error: Couldn't install: ssl-for-setuptools 1.10

After scratching my head for a while, I've found the solution:

# apt-get install libssl-dev

2008-03-19

Why do I use Zope 3?

Filed Under:

I'm in the process of beginning a new project and I'm debating on what framework to use. Of course it will be Zope 3, but why do I use it. Well, it's sure something that has to do with these facts:

  • it's open source, with a strong, mature community around it
  • while it's still actively development, it has a stable API
  • it's written in Python, one of the easiest and most powerful languages
  • it's built around a component architecture, which means writing pluggable applications comes naturally
  • solves the problem of publishing objects through the web
  • everything is transaction based. You won't lose data with it, you won't get garbage data inside your database
  • it has fast, configurable storages (ZODB, Relstorage)
  • internationalization and localization is easy
  • it's a library as much as it is an application server. When it's a library, it's very slim and as a full application server has a lot going on, including XMLRPC, ftp, webdav, sql connectivity, its own http server, etc.
  • has a cool, extensible templating language (the Zope Page Templates)
  • makes it extremely easy to create and store objects through ZODB
  • has facilities to query for objects using indexed data, through zope.catalog and extensions
  • writing a new catalog index is not an extremely complex task
  • it has an event framework; no complex application should run without an event framework
  • has advanced, flexible form libraries that can generate forms introspected from the models (zope.formlib, z3c.form)
  • has advanced templating concepts and content placement, such as the pagelets and viewlets
  • it's "enterprise ready": it's possible to load balance zope clients using ZEO or relstorage
  • comes with extensible authentication and user sources
  • promotes an extensible build system for applications (zc.buildout)
  • follows python standards, for the most part: library packaged as independent eggs and while it's not built around WSGI, it has full support for it
  • it can do document workflows (hurry.workflow)
  • it has some very cool packages from the community:
    • zc.table
    • z3c.form
    • Storm and sqlalchemy wrappers
    • zc.resourcelibrary, z3c.resourceinclude
    • z3c.pagelet
    • lovely.remotetask
    • grok
    • gocept.registration
    • z3c.traverser
  • is very well documented
  • it fits comfortably in my brain


2007-12-17

Template layout options when developing with Zope 3

Filed Under:

A high level overview over available methods of laying out a website

When developing a Zope 2 site, the way the templates would be laid out is obvious: create a template for the site layout, use macros and slots to fill in that template and use the CMF skin overriding mechanism when you have to customize a certain template or Python script for another skin. Plone has been using this mechanism very successfully.

With Zope 3 the path is not very clear. There are several mechanisms, each with its advantages and disadvantages. Let's take an example website, let's say a multinational company website and discuss how will these various solutions apply.

Insert rendered HTML with TAL

This method of including content from another page (let's say template for now) resembles PHP's include(); I'd still call this method superior to the PHP method of including another page as, with Zope, you can render that page based on the context on which it is called:

<div tal:replace="structure context/@@footer" />

Simple, but has drawbacks: on each page of the website you'll have to "copy" the basic site structure and insert the specifics of the page in clearly delimited areas. For more then a few pages, this makes it very hard to change the basic website structure, as it would require changing all the pages in the site. Plus, you can't easily define reusable templates because you can't fill those templates with values except based on the context for which they are rendered.

METAL: Macros and Slots, just like "classic Plone"

The classic PageTemplates method of separating the site layout from the general page layout has been to use the METAL extension, namely macros and slots. This would be achieved as such:

First, we have the site template, let's call it template.pt

<html metal:define-macro="page">
<head metal:define-slot="header">
<title>Some title</title>
</head>
<body metal:define-slot="body">
Body content comes here
</body>
</html>

Next, we need to make this macro available. First, we publish this page under the "view_macros" page name and add it to the 'page_macros' tuple of the `standard_macros` view. Some notes about this special view: it is a special browser view which implements the zope.interface.common.mapping.IItemMapping (see for example the standard_macros.py from zope.app.basicskin and zope.app.rotterdam). This special view has a list of page names that provide macros and a list of aliases between macros (for example, you might want to have the "page" macro also available under the "dialog" macro name).

You then reference this macro from your page, let's say our main page of the website, the `page.pt` file:

<html metal:use-macro="context/@@standard_macros/page">
<head metal:fill-slot="header">
<title>MyTitle</title>
</head>
<body metal:fill-slot="body">
Content here...
</body>
</html>

You can, of course, add new pages that will hold macros and add them to the macro_pages tuple of the StandardMacros view, reference them from the template and so on.

Another method of getting some macros inside your templates is to reference that template from the browser page class. For example:

class MainPage(BrowserPage):
macros = ViewPageTemplate('/path/to/macros.pt')

And, inside the template associated with this MainPage class:

<div metal:use-macro="view/macros/some_macro">

z3c.macro: no more bickering with standard_macros

One of the problems with the method described in the previous section is that, if you want to add new macro pages you need to override standard_macros in your skin layer or define another view to act as a macro provider. z3c.macro is a package that tries to overcome this by creating an easy way to register and retrieve new macros.

To register a new macro, let's say the `page` macro from template.pt, you'd do something like this:

<configure xmlns:z3c="http://namespaces.zope.org/z3c">
<z3c:macro template="template.pt" name="page" />
</configure>

To use this macro, in our study case, inside page.pt we'd do:

<html metal:use-macro="macro:page">
...
</html>

Viewlets and content providers

Let's continue developing our study website. We'll probably want a navigation menu for the website. If we would continue to use macros, we would develop a macro, insert it into the main template and be done with it. But what happens if we want to customize this navigation menu for just one page? Or maybe several special case pages... We'd have to implement a lot of logic on the macro, to check for the special cases, etc. Ugly and hard.

The solution in Zope 3 are the Content Providers, which, thanks to interfaces, would allow you to override per interface what is being rendered. They are a special type of zope views (as they are dependent on the browser layer) that provide content. For example, in our study website, a navigation menu can be inserted in every page by creating a content provider, something like this:

from zope.contentprovider.interfaces import IContentProvider
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.publisher.interfaces.browser import IBrowserView

class MainSiteNavigation(object):
implements(IContentProvider)
adapts(Interface, IDefaultBrowserLayer, IBrowserView)

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

def update(self):
pass

render = ViewPageTemplateFile('navigation.pt')

And register it like:

<adapter factory=".browser.MainSiteNavigation" name="main_site_navigation" />

Overriding this provider for, lets say, the press releases content objects, is a simple matter of:

class PressReleasesNavigation(object):
adapts(IPressRelease, IDefaultBrowserLayer, IBrowserView)
render = ViewPageTemplateFile('press_releases_navigation.pt')

And register it the same way:

<adapter factory=".browser.PressReleasesNavigation" name="main_site_navigation" />

Viewlets and viewlet managers are a step forward: a viewlet manager is a content provider for which you can register, per context interface type, `viewlets`. The viewlet manager then places all the rendered viewlets in its allocated slot in the template.

The viewlet mechanism is beneficial through the fact that you can "decouple" the content from the template: now you can control what "boxes" appear in each page by just adding/removing registrations for viewlets, no editing of macros, templates or code required.

Break the template from class with z3c.viewtemplate

Let's continue with our study case website. Suppose this company has multiple websites, hosted one the same server, one for each country, with some shared content and slightly different layout and templates. In this case, multiple skins applied to the same Zope site are great, but you get into the pains of having to override (or reregister) the classes just to be able to specify a different template.

One of the possible solutions is the z3c.viewtemplate packages. It allows you to register the template separately for a view, so that you can override it, per browser layer.

Let's have an example. Suppose we want to be able to change the main page on one of the skin layers, to add another column. Presume we were using a MainSitePage browser page, with a main_page.pt template and now we want to override it. We'll need to change the MainSitePage class, something like this:

class MainSitePage(object):
template = RegisteredPageTemplate()

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

or simply inherit from BaseView:

class MainSitePage(BaseView):
...

And now we can register a templates per browser layer:

<browser:template for=".browser.MainSitePage" template="main_page.pt" layer=".SkinLayerOne" />

You can override the template for viewlets, too, if you inherit from a superclass such as this:

class BaseViewlet(object):

template = RegisteredPageTemplate()

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

In practice you'll have something like this:

  • a main site template that will provide the layout and maybe insert viewlet managers, using the standard_macros mechanism
  • the page will use the `page` macro and fill in the `content`slot of the main template and only deal with the specifics of the page (there's nothing special about these names, they're just the usual convention).

You can define a viewlet manager and use viewlets for the `content area`, but you should probably avoid this as it will probably mean that you'll have to deal with forms inside viewlets and this will be difficult to handle properly.

Introducing z3c.template, a better version of z3c.viewtemplate

z3c.template is a package similar to z3c.viewtemplate (it allows separate registration of templates from the view code), but it also aims to separate the definition and registration of the layout of a page from the actual `content`.

Let's suppose we're implementing our site using z3c.template and we have the press releases page. For each page we will have a layout template and a content template, but we can skip the definition of the layout template by inheriting a base class.

The layout template will contain:

<html>
<head>
<title tal:content="view/title" />
<head>
<body>
<div tal:replace="view/render" />
</body>
</html>

The layout template, which, in the previous step, provided the `page` macro will be registered as:

<configure xmlns:z3c="http://namespaces.zope.org/z3c">
<z3c:layout template="main_template" for=".interfaces.ISitePage" />
</configure>

We need a browser view that knows how to use the layout template and the content template:

class SitePage(BrowserPage):
zope.interface.implements(ISitePage)

template = None
layout = None

title = None

def update(self):
pass

def render(self):
if self.template is None:
template = zope.component.getMultiAdapter(
(self, self.request), IContentTemplate)
return template(self)
return self.template()

def __call__(self):
self.update()
if self.layout is None:
layout = zope.component.getMultiAdapter((self, self.request),
interfaces.ILayoutTemplate)
return layout(self)
return self.layout()

Our view class will then inherit the SitePage class:

class PressReleaseViewPage(SitePage):

@property
def title(self):
return u"Press release: " + self.context.title

And then we can simply register a content template for the class:

<configure xmlns:z3c="http://namespaces.zope.org/z3c">
<z3c:template template="press_review_view.pt" for="IPressReview" />
</configure>

Note: z3c.template has several other features: named templates and the possibility to 'publish' macros from templates.

All this is very nice, but there are a couple of problems: you'll need to do some work to support forms, and why bother writing the SitePage class, when there's a package that does all this, and more? Its name is...

z3c.pagelet

This package introduces a new type of browser page: pagelet. A pagelet is a page with a layout template: you define the layout with the mechanisms introduced in z3c.template, but the `SitePage` base class is no longer necessary, as it is provided by the z3c.pagelet package. Inside the layout template you include the `pagelet` content provider:

<div tal:replace="structure provider: pagelet" />

The list of goodies continues: the package includes replacements for the formlib base classes: EditForm, AddForm and so on. Even more, using z3c.skin.pagelet you get a starter skin that has all the bits in place to bootstrap developing a pagelet based website (including, for example, "pagelet-ified" exception pages).

To register a pagelet you do something like (notice the similarity to a browser:page registration):

  <z3c:pagelet
name="index.html"
for=".interfaces.PressRelease"
class=".views.IndexPagelet"
layer=".interfaces.ICompanyWebsiteLayer"
permission="zope.View"
/>

More pointers and packages

  • z3c.macroviewlet - Allows you to write the a complete template of the website in one file and also define some of the macros of this template as viewlets.
  • z3c.pt: a faster version of PageTemplates, but with several restrictions (no macro support, no TAL paths and expressions)
  • gocept.form: contains integration of z3c.form with z3c.pagelet
  • z3c.formui: Use overridable templates with z3c.form using the z3c.template mechanism
  • Detailed experience (and troubles) of using z3c.pagelet
  • Overview of Zope skins and layers
Updated to fix some mistakes

2007-09-09

The problem with buildout and PyDev

Filed Under:

Lately it has been possible to develop and simply deploy a Zope 3 based application by organizing code in a single Python package that uses zope3 packages as dependency,  plus a buildout recipe that creates a running zope 3 instance based on these eggs. For anyone using Eclipse + PyDev, this approach has the following inconvenience: the `eggs` folder can't be properly imported as external source folder for the project. Eclipse knows about eggs, but only when they're in marked in a .pth file, inside the site-packages folder. Creating a .pth file in the eggs folder and reimporting the folder won't do any good, PyDev continues to ignore the eggs.

Solution? Let me introduce pb.recipes.pydev, which generates a .pydevproject file, based on the dependencies of a bunch of specified eggs (for example, the zope3-dependent application that you're developing). Closing and reopening the project in Eclipse, after generating that file, will reindex the packages and will enable sweet auto-completion, auto-import and "go to source", provided you have the PyDev Extensions.

The recipe has a couple of problems at the moment:

  • it triggers recomputing the egg dependencies, which is already done once by the buildout process,
  • it overwrites the .pydevproject file with a possibly simplified version
  • Eclipse doesn't import zipped egg files, so you'll have to keep the the eggs unpacked.

2007-05-24

Using lovely.tag in an edit form (or advanced usage for zope.formlib's EditForm)

Filed Under:

lovely.tag is a nice package that has almost all the functionality one can desire from a tagging engine. Recently I needed to integrate it with some of my content types (but not exactly as a tagging widget, but as a global registry for a global vocabulary), so I've created a couple of new widgets (an AJAX based multi-autocomplete one, based on z3c.widget.autocomplete and an add/remove widget, based on Plone's 3rd party AddRemoveWidget). I'll describe here the technique used to integrate the tagging editing in an edit form by using adapters (although this technique is described also in the form.txt from zope.formlib)

The formlib EditForm class creates a dictionary of adapters that are used for each interface to look up the fields (as attributes to the adapter objects). This translates into the following code (IActivity is a content type item):

class IActivity(Interface):
title = TextLine(title=u"Title")

class IItemWithTags(Interface):
tags = List(title=u"Tags", value_type=TextLine(title="Tag"))

class Activity(Persistent):
implements(IActivity)
title = u""

class ActivityEditForm(EditForm):
form_fields = form.Fields(IActivity) + form.Fields(IItemWithTags)
form_fields['tags'].custom_widget = MultiAutoCompleteWidget #custom built widget

Because the IItemWithTags interface is not directly offered by IActivity, there needs to be an adapter that knows how to deal with an Activity in relation with tags.

class ActivityTagsEditor(object):
adapts(IActivityItem)
implements(IActivityTags)
uid = None #the principal id. It is set externally

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

intids = getUtility(IIntIds)
self.tag_engine = getUtility(ITaggingEngine)
self.oid = intids.getId(self.context) #the object id

def _setTags(self, value):
self.tag_engine.update(self.oid, self.uid, value)

def _getTags(self):
return self.tag_engine.getTags(items=[self.oid])

tags = property(_getTags, _setTags)

Because of the way the tagging engine works, the adapter needs to know a principal id for whom to set those tags. So we need to overwrite the setUpWidgets method of the form class to set this id on the adapter object (see the setUpEditWidget code to see how the self.adapters registry is filled in).

    def setUpWidgets(self, ignore_request=False):
        self.adapters = {}
        self.widgets = setUpEditWidgets(
            self.form_fields, self.prefix, self.context, self.request,
            adapters=self.adapters, ignore_request=ignore_request
            )
        self.adapters[IActivityTags].uid = self.request.principal.id

Another way to set up the adapter (if you don't want to mess with setUpWidgets) is something like this in the submit handler, right before the applyChanges call:

self.adapters = {IItemsWithTags:getAdapter(self.context, IActivityWithTags)}
self.adapters[IItemsWithTags].uid = self.request.principal.id
form.applyChanges(self.context, self.form_fields, data, adapters=self.adapters)

Reading zope.formlib.form.applyChanges is very educational in order to understand how formlib applies changes to objects and how it builds the self.adapters mapping.

And finally, the adapter is simply registered with:

<zope:adapter factory=".adapters.ActivitityTagsEditor" />

2007-04-19

Passing data to viewlets from the parent template

Filed Under:

To insert a rendered viewlet manager into a page, one uses something like "tal:replace="structure provider: ILeftSide". Of course, viewlet managers are providers, they implement the IContentProvider interface and are looked up with this interface in the 'provider' TALES handler. One particular trick that content providers do is that they allow insertion of TAL data from the template, to make it accessible inside the content provider class. A sort of "parameters", as it is detailed in the README.txt file, chapter "Additional data from TAL". Things are a bit more complicated for the viewlet managers, because the viewlet manager object is created with a new type at runtime, a type which inherits only the minimum information from the new viewlet manager interface. The only way to make the created viewlet manager provide a new interface (as required by the TALESProviderExpression to insert data from the context) is to subclass ViewletManagerBase and use this class as the factory class for the viewlet manager. I'm giving a simplified example, mostly inspired by the zope.contentprovider documentation:

The viewlet manager interface,

class ILeftSide(IViewletManager):
    """The left side"""

which is registered with:

<viewletManager name="ILeftSide" provides=".ILeftSide"
class=".MessagePassingViewletManager" permission="zope.View" />

The viewlet manager is implemented with this new class:

class MessagePassingViewletManager(ViewletManagerBase):
implements(IMessage)

The class is, as I have found, a key component, because you need it to declare to implement a new interface, the IMessage:

class IMessage(Interface):
message = TextLine(u'Some message')

And the IMessage interface is declared as having a type, zope.contentprovider.interfaces.ITALNamespaceData

<zope:interface interface=".interfaces.IMessage"
type="zope.contentprovider.interfaces.ITALNamespaceData" />

The idea is that all attributes specified in the IMessage interface can then be specified as attributes for the element that renders the viewlet manager in the template:

<div tal:define="message string:Hello World"
            tal:replace="structure provider:ILeftSide" />

Then, in any viewlet subscribed to the ILeftSide manager, you' can have:

<div tal:content="view/manager/message" />

which will render of course the 'Hello world' string. You can actually pass any object this way, there's no constraint imposed by the type defined in the interface schema, as I could tell from my tests.

2007-04-13

Traversing from Python a Zope 3 page that uses the traverse subpath

Filed Under:

Getting the traverse subpath in Zope 2 is relatively easy and involves almost no tricks. Doing the same thing in Zope3 is a bit more involved, but nevertheless still relatively easy. What might not be obvious at first is how to traverse to the page from Python code. Call me slow, but it took me about 2 minutes to think of the solution.
The idea is: I have a page along these lines: http://localhost/site/tips/page_context/next. 'tips' is the page that is published, 'page_context' is a string that contains a unique page context key for which I get the tips and 'next' is an operation I want to do, based on which I get the tips content. I'm doing this because I'm storing in the session a history of 'seen tips', so that I can navigate them forward/backward.

So how does one get that page from python code? Like this:

page_context = 'my_page'
tips_page = getMultiAdapter((self.context, self.request), name='tips')
tip_content = tips_page.publishTraverse(self.request, page_context).publishTraverse(self.request, 'next')()


2007-03-25

One issue using Prototype's Ajax form submission with Zope 3

Filed Under:

I've found a weird issue while trying to debug why on of my Ajax loaded forms wasn't, apparently, properly validated. Even though that the schema specified that the fields are required, the form action would get executed as if the validation wouldn't have been done or the request was properly validated. I thought at first that there's an issue with the form class/handling itself, but after some testing I came to the conclusion that it has to be the way I'm doing the AJAX request.

What I have discovered is that I was using the parameters option of the Ajax.Request to make a form submit with POST method, which apparently causes Zope to validate the fields as having a value, even though they were empty. The right way to do this request is using the postBody parameter, as fixing that made my forms work again. I'm not sure what causes this behaviour in Zope: the visible difference is that postBody includes all the form inputs, even the empty ones, while the parameters version includes just those with a value (in my case, the submit button). And I have tried leaving out a field from the Zope request by saving the html page, deleting one of the inputs and doing a form submission, but Zope does the right thing in this case.

I'm not sure how popular is using the parameters option with POST and Ajax.Request, but I'm adding this note here, just in case.

postBody version

POST /++skin++course/site/new_design.html HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)
Accept: text/javascript, text/html, application/xml, text/xml, */*
Accept-Language: en-us,en;q=0.7,ro;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.5.1_rc1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:8080/++skin++course/site/
Content-Length: 94
Cookie: top_http___localhost_9080_t2_sticky_view=354px; left_http___localhost_9080_t2_sticky_view=646px; state_http___localhost_9080_t2_sticky_view=hide; zindex_http___localhost_9080_t2_sticky_view=5; WT_FPC=id=82.79.74.153-1913305056.29841814:lv=1172488789513:ss=1172484509013
Authorization: Basic dGliaTp2aWNlcm95
Pragma: no-cache
Cache-Control: no-cache
add_design.title=&add_design.description=&add_design.actions.4164642064657369676e=Add%20design
HTTP/1.x 200 OK
Content-Length: 4408
X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Accept-Ranges: bytes
Server: Twisted/2.5.0+rUnknown TwistedWeb/[twisted.web2, version 0.2.0 (SVN rUnknown)]
Date: Sun, 25 Mar 2007 11:56:52 GMT
Content-Type: text/html;charset=utf-8
----------------------------------------------------------

parameters version


POST /++skin++course/site/new_design.html HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)
Accept: text/javascript, text/html, application/xml, text/xml, */*
Accept-Language: en-us,en;q=0.7,ro;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.5.1_rc1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:8080/++skin++course/site/
Content-Length: 52
Cookie: top_http___localhost_9080_t2_sticky_view=354px; left_http___localhost_9080_t2_sticky_view=646px; state_http___localhost_9080_t2_sticky_view=hide; zindex_http___localhost_9080_t2_sticky_view=5; WT_FPC=id=82.79.74.153-1913305056.29841814:lv=1172488789513:ss=1172484509013
Authorization: Basic dGliaTp2aWNlcm95
Pragma: no-cache
Cache-Control: no-cache
add_design.actions.4164642064657369676e=Add%20design
HTTP/1.x 200 OK
Content-Length: 3491
X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Accept-Ranges: bytes
Server: Twisted/2.5.0+rUnknown TwistedWeb/[twisted.web2, version 0.2.0 (SVN rUnknown)]
Date: Sun, 25 Mar 2007 11:56:15 GMT
Content-Type: text/html;charset=utf-8
----------------------------------------------------------

2007-03-23

Getting the registered Zope 3 skin name for an interface

Filed Under:

Problem: I need to get the skin name for a Zope 3 interface registered as skin.

Solution

Starting with Zope 3.3, the skinning mechanism has been simplified and skins are now just interfaces. This means that a skin interface is now a named utility for the IBrowserSkinType. To get the name with which this class has been registered as an utility, something along these lines is needed:

>>> from myapp.layer import IMyAppSkin
>>> from zope.app.apidoc.component import getUtilities
>>> from zope.publisher.interfaces.browser import IBrowserSkinType
>>> skins = getUtilities(IBrowserSkinType)
>>> for skinreg in skins:
... if skinreg.component == IMyAppSkin:
... skin_name = skinreg.name
... break

I'm using the apidoc module here, which feels a bit like cheating. Another, "apidoc-free" version is something like this:

>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> skins = gsm.getUtilitiesFor(IBrowserSkinType)
>>> for skinreg in skins:
... if IMyAppSkin == skinreg[1]:
... skin_name = skinreg[0]
... break

getUtilitiesFor() returns a list of tuples, for example: [(u'Basic', <InterfaceClass zope.app.basicskin.IBasicSkin>), (u'Debug', <InterfaceClass zope.app.debugskin.IDebugSkin>), ... ]

UPDATE: fixed a small bug in the second example, that's what you get if I don't test...

You can get the current skin interface with:

curentSkinInterface = [iskin for iskin in interface.directlyProvidedBy(self.request) if 
IBrowserSkinType.providedBy(iskin)][0]

2007-02-28

A Zope 3 AJAX viewlet manager

Filed Under:

I've finally got tired of writing small fragments of JavaScript code and views to be able to make different areas updatable through ajax for my current Zope 3 application. Plus, when I've started writing this application, I've started doing the layout with viewlets and managers, and it seems that for each viewlet that I'm creating I have to have a way to get its content through AJAX, which means having a page available for it, and this is very odd, as it almost defeats the point of having viewlets.

So I've decided to create the "Ajax viewlet manager". I'm not sure how good of an idea this is, I'll have to give it some thought and see how I feel about it in the future. Let me describe my solution.

First, I want to be able to have the manager available at a certain URL, to be able to reload this URL through an Ajax call later on. So I've created a new namespace traverser, ++vmanager++. A viewlet manager would be accessible (as rendered HTML) at a location such as http://localhost:8080/mysite/myobject/++vmanager++ITop. This is the traverser code:

from zope.publisher.interfaces.browser import IBrowserView
from zope.traversing.interfaces import TraversalError
from zope import component
from zope.interface import implements
from zope.traversing.namespace import SimpleHandler
from zope.viewlet.interfaces import IViewletManager

class vmanager(SimpleHandler):
implements(IBrowserView)
def __init__(self, context, request):
self.context = context
self.request = request

def traverse(self, name, ignored):
manager = component.queryMultiAdapter((self.context, self.request, self), IViewletManager, name=name)
if manager:
return manager
else:
raise TraversalError(self.context, name)

The traverser is registered like this:

<adapter
name="vmanager" for="*"
provides="zope.traversing.interfaces.ITraversable"
factory=".namespace.vmanager"
/>

<view
name="vmanager" for="*"
type="zope.interface.Interface"
provides="zope.traversing.interfaces.ITraversable"
factory=".namespace.vmanager"
/>

The traverse() in the vmanager class will return the manager. One thing to note is that once the manager returned, Zope will try to get the default view for this object, so a default page (index.html) is needed for IViewletManager

class ViewViewletManager(object):
"""View a rendered viewlet manager"""

def __call__(self):
manager = self.context
manager.update()
return manager.render()

This page is registered as

<browser:page
name="index.html"
class=".browser.ViewViewletManager"
for="zope.viewlet.interfaces.IViewletManager"
permission="zope.View"
/>

Next, to automate things a bit more, I wrote a template, along these lines:

<tal:vars
    tal:define="vmgr_name view/__name__; here_url context/@@absolute_url">
    <div id="provider_ITop"
        tal:attributes="id string:provider_$vmgr_name">
        <div tal:repeat="viewlet view/viewlets" tal:omit-tag="">
            <div tal:replace="structure viewlet/render" />
        </div>
    </div>
    <script
        tal:content="string:
function reload_${vmgr_name}(){
    new Ajax.Updater('provider_${vmgr_name}', '$here_url/++vmanager++${vmgr_name}')
}" />
</tal:vars>

This template inserts the viewlet contents in a named container and creates a JavaScript function (which uses Prototype) that can be called later to update that container. The last piece is to tell my viewlet providers to use this template (observe the template argument):

<viewletManager name="IFooter" provides=".IFooterSlotManager"
        class="z3c.viewlet.manager.WeightOrderedViewletManager"
        template="vmanager.pt"
        permission="zope.View" />

I'll probably still have to write some views and JavaScript to make things dynamics, but this solution will sure help to reduce the amount of code I have to write.

2007-02-16

Fresh meat for programmers

I've just discovered (or rediscovered) some resources that I think are important enough to highlight here.

  • The Zope Corporation has several eggs that are not published in the svn.zope.org subversion repository, eggs located at download.zope.org. There are several very interesting packages there, including some to build an intranet. I'll definately have a look at them.
  • I've rediscovered OpenJSAN, a Javascript repository full of goodies.
  • And its really nice Planet Javascript

2007-02-15

Using Zope Page Templates macros in other templates

Filed Under:

I won't try to discuss here the benefits of ZPT macros vs. viewlets and content providers or viceversa or when to use each of them, I'll just show a simple (basic even) technique of reusing template code across page templates, using the macros mechanism.

Trying to get the macros with something like context/@@page_with_macros/macros/the_macro doesn't work, as BrowserView (and BrowserPage) doesn't know anything about Zope Page Templates. Of course, that's also because Zope 3 doesn't directly publishes templates, only pages. The technique involves setting the page template who's macros you want to access as attributes of the curent page, something along these lines:

class MyPage(BrowserPage):rows
     macro_template = ViewPageTemplate('template_with_macros.pt)

In the template associated with this page, you can have:

<metal:macro metal:use-macro="view/macro_template/macros/the_macro">


Weblog
Atom
RDF
RSS 2.0
Powered by Quills
Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.
 

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: