Personal tools
You are here: Home Weblog Archive 2007

Entries For: 2007

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-11-14

Trying out eggs and applications with workingenv, without poluting the system python

Filed Under:

Ever since I've started working with zc.buildout I've grown to love the whole "environment as project" setup that it provides: eggs and python packages are installed and contained in the buildout and they don't polute the system packages. There is another alternative: workingenv, which, while simpler in scope, it is easier to setup and use. For example, recently I've wanted to see if lxml 2.0 can be installed in my system. I've had problems with the earlier alphas, which made it difficult or impossible to compile the egg. First, I've installed workingenv.py in my system:

sudo easy_install workingenv

Then I made a folder ~/tmp/lx, which I then used as a workingenv environment:

workingenv ~/tmp/lx

Next, I've activated the environment:

source ~/tmp/lx/bin/activate

And finally, I could easy_install my desired egg:

~/tmp/lx/bin/easy_install lxml

It took a while to compile, but in the end I had the egg installed in ~/tmp/lx/lib/python2.5/ and, while the workingenv was activated with "source bin/activate", I could run the system's python and have the eggs installed in the workingenv available to me.

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-08-15

Scripting Adobe Indesign with Python

Filed Under:

I'm working on a larger project, with various components and one of the components is producing content with Plone which is then to be imported into Indesign, where each item from Plone gets to fill in a 2 page template. There are about 500 of these items, so this can't be done manually. InDesign is stupid (or maybe plugin friendly / commercially clever) enough that, even though it can import XML files and even perform XSLT transformations on them and has a "Clone repeating elements" option on import, it can't create new frames or pages. The import procedure, which is a beast in itself, depends on having content placeholders tagged with the proper tags, but it doesn't keep the tags when copy&pasting elements. So the only option left is to script the entire import procedure. Fortunately, InDesign can be scripted through COM and its DOM is the same when accessing it through either its own JavaScript engine or Python, with the help of win32com.

First steps to get started is to install the win32com extensions. Next, start PythonWin and use the Tools menu to start the "COM Makepy utility",  select the "Adobe InDesign CS2 Type Library". Next, try this in a Python prompt.

>>> import win32com.client
>>> app = win32com.client.Dispatch('InDesign.Application')
>>> doc = app.Documents.FirstItem()
>>> pages = doc.Pages
>>> newpage = pages.Add()

Unfortunately, doing a dir(app) won't show all elements available, for example the Documents collection. There are some very good resources to get me started, though: several InDesign tutorials and scripting references from Adobe, the ExtendedScriptToolkit which shows the DOM tree for the document once you assign it to a local variable and execute the script, and the Visual Basic Editor, whose Object Browser can be used to look at the available methods and properties for objects (you'll need to add the Indesign Type library as reference first).

2007-06-05

Updates to Zope & Plone tips section

Filed Under:

I've updated the Zope & Plone tips and tutorials section with a bunch of new pages and several additions to the svn.zope.org package listing. I thought it might be interesting to highlight this here, I'm not exactly sure how visible (or interesting!) is that section to the readers of this blog.

2007-05-28

Some thoughts about the purpose of this blog

Filed Under:

I've been thinking lately at the purpose of this blog, what it wants to be, what is really a blog and what I should write here. The number of posts have gone down in the last half of year, partly due to increased work load and partly due to some restructuring that I have done.

In general, I'm unhappy about posting small tips and bits of information on the blog, that's why I have created a Tips & Tutorials section on this site. I have copied or moved a lot of the content from the blog to this section and I'm also adding new stuff. I'll write new entries in the blog if there's anything worth noticing that I add there. At the moment, my "masterpiece" is an index of the packages available on svn.zope.org, done as an exercise in learning about the available extensions but also to provide a needed resource.

I'm also planning on writing a complete series of Zope 3 beginner to advanced tutorials, for which I already have the code and module outlines and only need to put them together.

2007-05-25

Running Python based software in the GPU?

Filed Under:
I'm not a GFX or 3d programmer, but looking at this blog entry, I understand that these guys are getting ready a way to run Python based software in the GPU. I wonder what kind of speed improvements can be achieved through that, although I fear that it's only going to work for numeric type of programs.

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-05-08

Software is lame

I may sound like a fanboy or something, but I'm starting to think that it's impossible to have a perfect piece of software, especially with larger systems. Two things that happened to me lately that made me think this:

  • How lame is that Windows doesn't automount USB sticks when they're present at boot time? Why would I have to remove+reinsert the stick just so Windows will see the bloody thing? My Kubuntu, of course, handles perfectly everything in this department (at least in my case).
  • How lame is that Prototype handles in a really stupid way forms that have multiple submit buttons and are submitted through Ajax? Not even Form.request() will do the proper thing, which is to only leave one of the submit inputs in the stream, the one that has been clicked on. The problem is with zope.formlib, which gets the action and the validation from the submit button that was pressed. More then one submit input in the request and things become uncontrollable.
    To fix this I have added the following onsubmit handler to my Javascript code:
my_form.onsubmit = function(event){
    button = document.activeElement || event.explicitOriginalTarget;    // IE, Mozilla, Opera
    this.getInputs('submit').each(function(el){
          if (el.name != button.name) {
                el.disable();
          }
}
}

This disables all the submit input controls before serializing the form and doing an ajax request with Form.request(), as disabled inputs are ignored by serialize().

UPDATE: I am told that Prototype 1.6 will support multiple submit buttons.

2007-05-03

Brettspielwelt - the free, online boardgame portal

Filed Under:

I live in Europe, I'm a geek, so I must also be a boardgame freak. I love games, and especially German style boardgames, with their intricate rules. Unfortunately, there's not much time to play, and not a lot of people that I can play with. Following a link on boardgamesgeek.com I've stumbled on this website, an online portal of boardgames that you can play, something similar to IGS (the Internet Go Server), but that implements loads of boardgames to be played in multiplayer style. The people are friendly and are willing to teach you to play (even in English). The client defaults to the German language, but this can be easily changed by changing the 'Nation = de' line to 'Nation = en' in the props file from the client download folder.

Being a Java application, it goes a long way to adding a few more entertainment choices to the ones available for the Linux platform, in terms of games.

UPDATE: There's a ton more of sites with online boardgames at the boargamegeek wiki

Some of these sites:

http://www.flexgames.com/ (2 games)

http://www.yucata.de/Default.aspx (several, growing)

games.asobrain.com (few, but has Settlers and Carcassonne)

http://www.spielbyweb.com/ (forum based)

http://yourturnmyturn.com/ (many, most classic)

http://hilinski.net/games/online/ (Taj Mahal and Tycoon)


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')()


Starting the fans for an HP Compaq NX6125 with Ubuntu Feisty after suspend

Filed Under:

In the saga to make my laptop behave as best as I can I've encountered a new obstacle: using Ubuntu Feisty Fawn (the beta) with kubuntu-desktop installed and using KDE, I can put the laptop in suspend mode, but the CPU fan won't turn on when I wake it. After a bit of searching I've found out that I need to do:

root@ubuntu:~# echo 0 > /proc/acpi/fan/C262/state

This starts the fan. Writing 3 in that fan ACPI state will turn off the fan.

References:

UPDATE:
The last update of Ubuntu broke the suspend, as shaky as it was before. The HAL package was updated and now Kubuntu won't wake up from sleep anymore. Also, I've updated my desktop from Edgy to Feisty and the update process wasn't without pains: the update-manager software would freeze and after updating with aptitude distr-upgrade, Gnome won't start anymore, the apparent reason being some problem with alsa. Funny enough, after I've installed kubuntu-desktop I can run KDE and play music with amarok without problems.

2007-03-26

Open source Zope app in the news!

Filed Under:

Today, on the front page of Slashdot, there was a link for an open source server monitoring & system management application. Well, what do you know, it runs on Zope 2! The visible source code is here

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-24

Roll for initiative, monkey boy!

Filed Under:

I thought I'd put a link here for a source of great entertainment of mine: The Knights of the Dinner Table. Hope you'll be as entertained as I am every time I'm watching them or reading the KODT comic strip.

And another link: Barbarian Prince, a downloadable solitaire game, a mixture of boardgame/RPG/choose your own adventure. I found a review of this game in the KODT comics magazine, but there are a couple of other games there, for two players or solitaire.

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-03-20

Freeciv rediscovered

Filed Under:

I've recently "rediscovered" Freeciv. I'm a very casual gamer (15 minutes/day), but also an old, addicted, Civilization fan. Lately I've been looking for a nice game to play on my Linux desktops and I've found that Freeciv is finally getting some good graphics, as this was my biggest turn off I've had with it before. I've tried first the SDL client on Windows, but it is buggy and tends to freeze its popup windows. The GTK Windows client is a lot better, works quite flawless as far as I can tell. I've compiled the Linux version (on Ubuntu Edgy), first I've tried the SDL client, but it complained (at configure time) about a missing sdl-image library, which I have (and no -dev version in my apt sources). Using the freeland-big tiles, the game had a huge memory footprint (40% of 1024 Mb RAM). The most apparent improvements are new tile graphics, with bigger size, new graphics for the city titles on the play screen, a new full-screen mode, even for the GTK version and a tree-based research screen. There is another tileset on the freeciv.org site, called Freeland-big. This has, IMHO, some improved tiles. Click on the image below to see the new graphics I'm talking about (this is the default tileset). And another one, this time on Linux, with Freeland-big tileset.

Freeciv 2.1 screenshot

UPDATE: Using the svn/trunk version the huge memory footprint problem of the Linux client is solved, but I can't use the freeland-big tileset anymore. To compile it on Ubuntu, I first had to apt-get install automake1.8 and libgtk2.0-dev. After that, run autoconf.sh to start the configure script generation and then make install.

2007-03-10

Embeding the Gecko engine in Python applications

Filed Under:

I've got to deploy a new desktop based application, created, of course, with Zope 3. When you've got a hammer, everything looks like a nail, indeed. This application uses HTML and CSS as its presentation layer (right, it's a web page), with a bit of Ajax thrown in. Due do time constraints, I'm not even trying to get it to work properly on Internet Explorer (although it looks about 90% right, but I have some problems with Javascript), so I won't be embeding the Internet Explorer this time engine in my Python application, I'll just try to get the Gecko rendering engine, the one used in Firefox.

First, I've installed the ActiveX control for the Gecko engine. Following some ActiveX migration details on wxpython.org, I've generated a wxpython "binding" class, using the genaxmodule.py tool (after a quick dig in the Windows registry to find out which is the Mozilla control class name):

C:\Python24\Lib\site-packages\wx-2.6-msw-unicode\wx\tools>genaxmodule.py "Mozilla.Browser" Gecko
Creating module in: .\Gecko.py
ProgID: Mozilla.Browser.1
CLSID: {1339B54C-3453-11D2-93B9-000000000000}

The python code would that would use the ActiveX control would be something like this:

# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Sat Mar 10 16:40:22 2007 from C:\Work\CourseBuilder.wxg

import wx
import Gecko

def createGeckoControl(parent, id):
gecko = Gecko.Gecko(parent, id)
return gecko

# begin wxGlade: dependencies
# end wxGlade

class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MainFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.panel_1 = wx.Panel(self, -1)
self.gecko = createGeckoControl(self.panel_1, -1)

self.__set_properties()
self.__do_layout()
# end wxGlade
self.gecko.SetFocus()
self.gecko.Navigate2('http://slashdot.org', 0)

def __set_properties(self):
# begin wxGlade: MainFrame.__set_properties
self.SetTitle("frame_1")
# end wxGlade

def __do_layout(self):
# begin wxGlade: MainFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.gecko, 1, wx.EXPAND, 0)
self.panel_1.SetSizer(sizer_2)
sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade

# end of class MainFrame

And the main app file:

#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-
# generated by wxGlade 0.4.1cvs on Sat Mar 10 16:40:22 2007 from C:\Work\CourseBuilder.wxg

import wx
from MainFrame import MainFrame

class CourseBuilderApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
main_frame = MainFrame(None, -1, "")
self.SetTopWindow(main_frame)
main_frame.Show()
return 1

# end of class CourseBuilderApp

if __name__ == "__main__":
CourseBuilderBrowser = CourseBuilderApp(0)
CourseBuilderBrowser.MainLoop()

Unfortunately I hit a problem that I can't find a solution for: this application crashes when I type something in the Gecko window. Blah :-( If anyone reading this know the answer, help me! :) Please! (tibi@life.org.ro)


My only option left is to go for the XULRunner and the MyBrowser demo. I'm not extremely happy about this, but at this point I don't see other options. Plus, it will be fun learning some about the Mozilla development platform.

Update: I've tested the Mozilla ActiveX Control using a simple Delphi 7 form and, while I don't get a crash, I still can't get it to accept keyboard input.

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.

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: