Personal tools
You are here: Home Weblog cookbook

cookbook


2010-02-25

Can you do this on your shiny Mac?

Filed Under:

Probably you can, but you have never done it because you have a shiny interface for everything. I'm talking about this discovery of mine:

svn diff | kompare -

What it does is to take the output from svn diff and pipe it into Kompare, a merge/diff utility from the KDE Project. I can do this from the command line, straight from the directory that I'm in, and bang! I get a nice graphical overview, complete with the tree structure that I can navigate to see what I'm about to commit. Honestly, this little command gets me excited everytime I run it.

2010-02-24

Generating products outside of the Products.* namespace with ArchGenXML

Filed Under:

I'm a die hard in regards to ArchGenXML usage. The number of things to know about when creating new content types for Plone is just too high. Package structure, Zope package registration, content types registration, QuickInstaller registration, GenericSetup profiles, skins registration, workflows, etc. I can go in and do changes to the code, and add to it, but generating it from scratch is a gigantic task, especially for my use case, where I need to start a new project with about 7 content types.

Now to the problem: ArchGenXML assumes (and hardcodes in its templates) the Products.* prefix for your package. I don't have any problems with it, but my employer uses a different package structure so for uniformity I need to follow their standards. 15 minutes of poking and changing through agx enabled me to change it so it would generate GS xml files with the proper namespace (based on a new model level TGV named "namespace" that I have created). After assessing the difficulty of the task and being under time pressure, I've decided to go the dumb route, which I'm documenting below:

My generation script is something like this:

./archgenxml -c archgenxml.cfg myproduct.zuml 

#rename Products.myproduct to ns.myproduct
find myproduct/* -type f -print | xargs sed -i 's/Products\.myproduct/ns\.myproduct/g'

#in the xml type profiles, replace myproduct by ns.myproduct
find myproduct/* -type f -print | xargs sed -i 's/>myproduct</>ns\.myproduct</g'

#in the profiles.zcml, rename the profile to ns.myproduct
find myproduct/* -type f -print | xargs sed -i 's/title=\"myproduct\"/title=\"ns\.myproduct\"/g'

Notice the sed lines, which change the source code to point to "ns.myproduct" instead of "Products.myproduct". Now there's just two more problems, which are actually AGX bugs:

  • the FilesystemDirectoryViews registered for the skin are incorect, so you'll need to insert this in ns/myproduct/__init__.py
from Products.CMFCore import utils
from Globals import package_home
from os.path import dirname
ppath = utils.ProductsPath
utils.ProductsPath.append(dirname(package_home(product_globals)))
DirectoryView.registerDirectory('skins', product_globals)
utils.ProductsPath = ppath
  • trying to get AGX to output the code inside a two level deep folder doesn't work (it crashes), so you'll need to move this script file and the model just above the "myproduct" folder, inside the "ns" namespace.

That's about it. Hopefully I'll find the time to fix the two bugs and add the namespace improvement in the next week. Still need to do the review for Plone for Education, which I have received for free from the publisher.

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

A "don't do" for internationalizing Django templates

Filed Under:

I'm internationalizing a Pinax website and I've encountered this piece of code in a template:

<input type="submit" value="{% trans "invite" %}"/>
{% blocktrans %}{{ other_user }} to be a friend.{% endblocktrans %}

The message ids for this code would be two separate blocks: "invite" and " %{other_user}s to be a friend". Both offer very little in terms of context and make the translators job difficult. Correct, in my point of view, would be the more convoluted form of:

{% blocktrans %}
<input type="submit" value="invite"/>
{{ other_user }} to be a friend.
{%  endblocktrans %}

This implies that the translators know enough HTML to notice that the value attribute needs to be translated, but the end result is a lot more flexible and provides real context to them.

TL;DR: don't split paragraphs into separate translation units. It's a NO-NO.

UPDATE: I have found what is probably the worst example of how to create a translatable template. Remember, don't assume the English language resembles anything like another language.

{% trans "edited by user" %} {{ obj.editor.username }} {% trans "at"%} {{ obj.modified|date:"H:i" }}

This should be done this way:

{% blocktrans with obj.editor.username as editor_username and obj.modified|date:"H:i" as obj_modified
edited by user {{ editor_username }} at {{ obj_modified }}
{% endblocktrans %}

2009-11-12

Django's makemessages sucks for my use cases

Filed Under:

Yet another angry rant, caused, of course, by using Django in anger. Nothing wrong with using something in anger, that's the real way I learn something. Zope 3 even has an online book on how to use it in anger.

That said, Django's makemessages administrative command sucks by being way too inflexible to anything but the ideal Django development environment. My environment looks like this: I have a project based on Pinax, which I'm developing and deploying using zc.buildout. My source code sits in src, where I have several packages. I also have a "localsettings.py" module located in the root of the buildout, because I don't want to have it inside the src folder. Pinax is located in parts/Pinax, and it's actually a git checkout, based on my own fork of Pinax. Pinax doesn't have translations at the moment (I think I saw a ticket in its tracker about reintroducing a translation package), so I'm on my own here with regards to translation.

With this setup, it is close to impossible for me to generate anything useful without a lot of hacking and swearing. Makemessages insists on being run from inside a Django project, and when I did that, it complains about missing localsettings module. Pointing the root of the buildout as pythonpath didn't do anything. A good thing that I have already extracted messages from the templates, before switching to the buildout project structure.

Some solutions that I have found:

  • I can extract messages from the Pinax python modules using this homegrown script:
PYFILES=/tmp/pyfiles
PINAX=parts/Pinax/pinax/ 
BASE=/home/tibi/work/ProjectBuildout/src/project/locale/ro/LC_MESSAGES/
POTFILE_PYTHON=$BASE/python.pot
POTFILE_TEMPLATES=$BASE/templates.pot
POTFILE=$BASE/django.pot
POFILE=$BASE/django.po

#extract messages from python code
find $PINAX | grep ".*py$" > /tmp/pyfiles
touch $POTFILE_PYTHON
xgettext -j -L python -d django -f $PYFILES -o $POTFILE_PYTHON

#merge the templates + python messages into one pot file
msgcat -o $POTFILE $POTFILE_TEMPLATES $POTFILE_PYTHON

#merge the potfile with the po file
msgmerge -U -N $POFILE $POTFILE
  • I have copied all the templates from pinax and its associated applications inside a template folder in my project. Now I can generate the po file, from my src/project folder, with
../../bin/py ./../../manage.py makemessages -e .py -e .html -l ro

Of course, I can't run this over the other apps and packages in my src/ folder to extract messages from the python modules, so I am forced to adjust the first script to take those folders into consideration.

2009-11-08

Using CherryPy to work around a Django/flup bug

Filed Under:

A bug creeped in one of my Django 1.1 projects that is in beta-testing right now: the Pinax wiki app looks for a REMOTE_ADDR value in request.META, which was not set in my environment. My environment is a pretty standard (as far as this setup goes) nginx + fcgi (flup on the django side) + django. Further work on this issue revealed, step by step, that:

  • REMOTE_ADDR needs to be somehow set by a Django middleware, based on an http header,
  • so I've added django.middleware.http.SetRemoteAddrFromForwardedFor to the list of loaded middleware
  • but that middleware is deprecated in Django 1.1 and does nothing, so I rewrote this middleware based on Chapter 15 of Django Book
  • this new middleware did its job, but for some reason flup stripped that header from the request and the proper values never got to Django
  • this made me look for a replacement for flup, which I found in django-cpserver.

This package adds a new admin command, 'runcpserver', which replaces the default development server by something more appropriate to production. The word on the 'net-streets' is that it runs well and does its job, so I'll be using it for the time being. The recommended solution is to run apache+mod_wsgi and proxy that to nginx, but right now I don't want the extra administrative overhead that Apache represents. If I can't get enough "juice" out of one instance of cpserver, I'll just add an extra instance and balance them with nginx or haproxy.

2009-11-05

Django gotcha: the urls.py needs to define the 404 view

Filed Under:

Probably this is documented somewhere in the Django docs, but, I mean, who has time to read the docs? :-) In the urls.py file for my project I didn't do a

from django.conf.urls.defaults import *

because that's just bad style. Instead I've just imported what I needed: patterns, include and url. Later on I got this error in my email:

Traceback (most recent call last):

  File "/home/zope/djangoprojects/lib/python2.5/site-packages/Django-1.1.1-py2.5.egg/django/core/handlers/base.py", line 118, in get_response
    callback, param_dict = resolver.resolve404()

  File "/home/zope/djangoprojects/lib/python2.5/site-packages/Django-1.1.1-py2.5.egg/django/core/urlresolvers.py", line 263, in resolve404
    return self._resolve_special('404')

  File "/home/zope/djangoprojects/lib/python2.5/site-packages/Django-1.1.1-py2.5.egg/django/core/urlresolvers.py", line 255, in _resolve_special
    callback = getattr(self.urlconf_module, 'handler%s' % view_type)

AttributeError: 'module' object has no attribute 'handler404'

Just looking at the error I could determine the cause: my urlconf module (urls.py) didn't have a handler404 view defined, so I just gave up and replaced my specific imports with the asterisk import.

On a side note, what's up with Django logging? By default, the only way to log errors in production environments is by email. Really!? If I would want emails, I'd setup the syslog to email me those entries. I already have the logwatch emailing me stuff that happens on my server. One step above is django-db-log, which I haven't tried yet, but which seems to be logging errors to the db. Word is that real logging, using the logging python module, will be introduced in Django 1.2.

2009-09-17

Customizing Django LFS (Lightning Fast Shop) for the non-django developer

Filed Under:

I'm not a Django developer, but I have a project that involves deploying a LFS site, and it took me a bit of time and effort to understand how to customize its templates and resources. This all may seem obvious to the any Django developer, but I'm not, so I think this info needs to be out there.

As a side note, at first I have reviewed Satchmo, as it seemed to have more traction in the Django community (being a Zope/Plone developer, I am more aware of the previous work done by the LFS developer, the EasyShop). The Satchmo installation, for the un-initiated, seems to be a nightmare of many different settings that needed to be tweaked, skeletons that don't work OOTB, at least in the way I have installed them. On top of all this, it seems to think that I also need to develop some sort of project that will integrate with the Satchmo shop (but this may be due to Django). What happened to treating your product as the end product? After a bit of effort, I had one instance running and ready to review it. Several days, in another Linux virtual machine (I've switched from VMWare to VirtualBox), I've tried to install Satchmo again, this time using djangorecipe. I didn't have the same patience, though.

A short look at LFS convinced me to at least give it a try: there is a buildout that can be installed easily (although there were a few dependencies that I had to install), the "backend" is polished compared to the default django admin views used by Satchmo, and, while it is simpler, less featured, than Satchmo, it does everything I need.

Onward to customizing the LFS: my goal is to be able to change templates (for example, the base layout) and publish a new logo.

I've created a new folder "mytheme" inside the buildout, with two subfolders: "templates" and "static". The path to the templates folder needs to be inserted in the "TEMPLATES_DIRS" variables from settings.py. To customize the base template, for example, I've created a "lfs/base.html" file inside the "templates" folder. Its content can be copied from the original in the lfs_theme folder. I've added a new line in the header section, pointing to a new CSS file that will customize the base LFS stylesheet.

<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}mytheme/css/custom.css" />

As a side note, this templates folder can be used for overriding any other template: templates in Django are usually denoted by a relative path, and you need only to replicate this path inside our overriding templates folder.

Next, overriding and creating new resources: I have created a "static" folder inside "mytheme" and I've linked to it inside the lfs_project/media folder. Inside the "mytheme" folder I can now host images and files, in 2 new "css" and "img" subfolders. Back in my customized base.html, I have changed the logo path:

<img class="logo" src="{{ MEDIA_URL }}mytheme/img/logo.jpg" alt="Logo" />

That's about it: I got a custom CSS, the ability to customize templates and to point to new images. I'm happy for now.

As I have said in the beginning, this is all basic stuff. No harm done in pointing to the obvious though, as there seem to be many new developers flocking to Django, all with different level of expertise.

2009-03-02

A bug in subversion and a workaround

Filed Under:

I'm checking out a large repository and svn keeps getting blocked, for some reason (bad network, maybe). When running update again, it complains that the checkout should be cleaned, but the cleanup operation doesn't succeed, with something like this:

tibi@xps:~/svn/vs/data$ svn cleanup                    
svn: In directory '.'                                                
svn: Error processing command 'modify-wcprop' in '.'                 
svn: 'setup.config' is not under version control

Searching on then net for a solution didn't yield anything, so the one that I have found was to empty the .svn/log file (by default is read only) and then to run svn up again.

2009-02-28

Success on a one year old problem installing CacheFu

Filed Under:

I've upgraded CacheFu (Products.CacheSetup) to the latest 1.2 for some of the websites that I manage, in a Plone 2.5 cluster. One of them had a problem that I haven't been able to track previously, due to limited time: on a reinstall of CacheSetup, due to product upgrades, CacheFu couldn't be installed anymore. The traceback was something like:

this product has already been installed without Quickinstaller!failed:
Traceback (most recent call last):

  File "/home/zope/z29/Products/CMFQuickInstallerTool/QuickInstallerTool.py", line 330, in installProduct

  File "/home/zope/p25/parts/zope2/lib/python/Products/ExternalMethod/ExternalMethod.py", line 225, in __call__
    try: return f(*args, **kw)

  File "/home/zope/p25/eggs/Products.CacheSetup-1.2-py2.4.egg/Products/CacheSetup/Extensions/Install.py", line 35, in install
    policy_utils.addCachePolicies(self, out)

  File "/home/zope/p25/eggs/Products.CacheSetup-1.2-py2.4.egg/Products/CacheSetup/Extensions/policy_utils.py", line 72, in addCachePolicies
    p.addCacheRules(rules)

  File "/home/zope/p25/eggs/Products.CacheSetup-1.2-py2.4.egg/Products/CacheSetup/Extensions/policy_2.py", line 13, in addCacheRules
    rules.invokeFactory(id=id, type_name='PolicyHTTPCacheManagerCacheRule')

  File "/home/zope/z29/Products/CMFCore/PortalFolder.py", line 408, in invokeFactory

  File "/home/zope/z29/Products/CMFCore/TypesTool.py", line 934, in constructContent

  File "/home/zope/z29/Products/CMFCore/TypesTool.py", line 343, in constructInstance

  File "/home/zope/z29/Products/CMFCore/TypesTool.py", line 574, in _constructInstance

  File "", line 6, in addPolicyHTTPCacheManagerCacheRule

  File "/home/zope/p25/parts/zope2/lib/python/OFS/ObjectManager.py", line 301, in _setObject
    v = self._checkId(id)

  File "/home/zope/z29/Products/CMFCore/Skinnable.py", line 223, in _checkId

  File "/home/zope/p25/parts/zope2/lib/python/OFS/ObjectManager.py", line 95, in checkValidId
    raise BadRequest, (

BadRequest: The id "httpcache" is invalid - it is already in use.

One other weird thing are the paths in this traceback: /home/zope/z29 doesn't exist anymore, the database was moved from a different server. I think it's related to the persistent product entries in the Control_Panels, which can be cleared. Not a big problem. In the log, there was also an entry related to this traceback:

2009-02-28 17:01:18 CRITICAL txn.-1223480432 A storage error occurred during the second phase of the two-phase commit.  Resources may be in an inconsistent state

Now, the solution is really simple, but I needed to debug the policy_2 module to find this: 

rules.invokeFactory(id=id, type_name='PolicyHTTPCacheManagerCacheRule')

didn't work because there was a document in the root called "rules". Nobody expects the spamish Acquisition! And I didn't either... Lesson? Zope 2 was designed to be too smart for its own good, thus violating the KISS principle. Still love it, though.

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

Using mechanize to process protected Plone pages

Filed Under:

One of my long-running projects involves a workflow where content is produced in a Plone site, with the data later extracted and processed in various ways (including scripting Scribus to layout this data in a book). Initially the site where the content was produced wasn't protected, so I could run a simple urllib script to download the content and process it using lxml. A recent change in the workflow security settings meant this script didn't work anymore and I had to remember how to login into a Plone site using urllib2. Some google searches found me nothing, but I remembered that the zope.testbrowser can be easily used to run a programatical browsing session, complete with cookies support. But trying to install zope.testbrowser standalone in a buildout didn't lend to too much success, due to some dependency problems (and even after I covered for those dependencies, it still broke somewhere in zope.app.testing).

The solution was to use just the mechanize package, on top of which zope.testbrowser is built. mechanize has a slightly different API (more modern) and doesn't do so much handholding as zope.testbrowser, but I only need to process one form. In the end my script looks something like this (the asxmllist page is just an xml page that returns a list of urls to the entities that I want to process):

import lxml.etree
import os
import os.path
import urllib
import mechanize

loginurl = "http://example.com/login_form"
listurl = "http://example.com/asxmllist"

def run():
    curdir = os.getcwd()
    datadir = os.path.join(curdir, 'data')
    if not os.path.exists(datadir):
        os.makedirs(datadir)
    
    b = mechanize.Browser()
    b.open(loginurl)
    b.select_form(nr=1)
    b['__ac_name'] = "username"
    b['__ac_password'] = "password"
    b.submit()
    b.open(listurl)
    etree = lxml.etree.parse(b.response())
    
    for entry in etree.xpath('//entry'):
        url = entry.get('url')
        print "Processing " + url
        e = lxml.etree.parse(b.open(url + '/asxml'))
        id = e.find('id').text
        print "Got entry " + id
        fpath = os.path.join(datadir, id + '.xml')
        f = open(fpath, 'w')
        xml = lxml.etree.tostring(e)
        f.write(xml)
        f.close()
        print "Saved " + fpath

if __name__ == "__main__":
    run()

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

Crossroads: a very good load balancing solution

Filed Under:

Recently Martin Aspeli published a very nice buildout recipe that implements a complete setup for a production Zope/Plone server. One of the key components in that setup is the load balancer, which is implemented using the load balancer capabilities of nginx. Nginx might be a good load balancer, but it has a problem with the way Zope/Plone works: the first page loads, for a newly restarted Plone instance are very slow (especially if the site or the catalogs are big). It would be nice to have a way of telling nginx to take out one of the Zope instances from the cluster and then add it back once it's restarted and has its caches warmed. Sure, you could probably script something that would change the buildout options and reload nginx, but why bother when there's a load balancer that offers this and much more?

Crossroads (in its second version, at the moment 2.41) might lack the popularity when compared to perlball, pound or haproxy and may not even have the same features or performances (it's good enough for my needs and I haven't cross-benchmarked), but it's fast, easy to configure and, best of all, it can be configured to provide a web configuration/administration interface. This way, when a Zope server needs to be updated, I can take out the ZEO client from the balancer, restart it in supervisor, warm up the caches by calling the zope sites directly and then add it back to the cluster. The only faults that I could find to it is that the compilation mechanism is not very well tested (I couldn't complete an install with a specific destination prefix. Just checkout the Crossroads trunk or a tag and do a "make local", then you can use the xr binary from that folder). On the other hand, it's pretty well featured: it has sticky sessions, HTTP awarness (although its documentation recommends treating the http servers with the TCP algorithm), can use an external program to determine backend states (and could be plugged this way with Zope's ICP support), etc.

Below is a screenshot of the Crossroads administration interface, with Crossroads started with the following script:

#!/bin/sh

XR=/usr/sbin/xr
SERVER="-s http:127.0.0.1:7000"
BACKENDS="-b 127.0.0.1:9080 -b 127.0.0.1:9081"
ALGORITHM="-dl"
HTTP_FLAGS="-x -X"
TIMEOUTS="-t 10"
CHECK_CALLS="-c 10"
WEB_INTERFACE="-W 127.0.0.1:7020"
#DEBUGGING="-v"
DEBUGGING=""

$XR $SERVER $BACKENDS $ALGORITHM $HTTP_FLAGS $TIMEOUTS $CHECK_CALLS $WEB_INTERFACE $DEBUGGING

Crossroads administration interface

2008-11-18

Subversion 1.5 + default instalation Plone 3.1 buildout: no problems here

Filed Under:

I've hit the setuptools + subversion 1.5 problem again with a freshly install Plone 3.1.7 buildout: I've added my egg develop folder in zinstance/src/, I've added the egg in the relevant sections in buildout.cfg, but it wouldn't work because of the incompatibility with the old setuptools version.

The solution is to upgrade the installed setuptools to the latest version. For example, for a standalone Plone installed at /home/tibi/Plone, we have this structure:

/home/tibi/Plone
   /Python-2.4
   /buildout-cache
   /zinstance

I've changed directory to the Python-2.4 from above and ran:

bin/easy_install -U setuptools

Then I could succesfully run the buildout in the zinstance folder.

2008-11-01

A wxPython based tagcloud renderer

Filed Under:

This is a small example app that will render a tag cloud with various font weights/height, based on their weight in the cloud. Not much to say here, hope it is useful to someone. It has actually been easier to design and create then expected, the only difficulty was in figuring out how to resize the buttons based on the size of their label. The algorithm could be improved to generate the cloud in a single pass, but I'm not gonna bother, it works fast enough right now.

import wx

TAGS = [
 ['animals', 0],
 ['architecture', 2],
 ['art', 5],
 ['august', 1],
 ['australia', 1],
 ['autumn', 3],
 ['baby', 5],
 ['band', 1],
 ['barcelona', 3],
 ['beach', 2],
 ['berlin', 5],
 ['bird', 1],
 ['birthday', 0],
 ['black', 1],
 ['blackandwhite', 5],
 ['blue', 2],
 ['boston', 3],
 ['bw', 5],
 ['california', 1],
 ['cameraphone', 1],
 ['camping', 1],
 ['canada', 4],
 ['canon', 0],
 ['car', 5],
 ['cat', 3],
 ['chicago', 4],
 ['china', 5],
 ['christmas', 0],
 ['church', 0],
 ['city', 1],
 ['clouds', 3],
 ['color', 5],
 ['concert', 5],
 ['cute', 3],
 ['dance', 0],
 ['day', 5],
 ['de', 4],
 ['dog', 0],
 ['england', 5],
 ['europe', 4],
 ['fall', 1],
 ['family', 4],
 ['festival', 1],
 ['film', 1],
 ['florida', 2],
 ['flower', 1]
]

WIDTH = 500

def getwidth(line):
    return sum([l.GetSize()[0] for l in line])

def getheight(line):
    return max([l.GetSize()[1] for l in line])


class TopFrame(wx.Frame):
    
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.SetSize((500,400))

        lines = []
        
        for tag in TAGS:
            l = wx.Button(self, -1, tag[0], style=wx.NO_BORDER|wx.BU_EXACTFIT)
            weight = ((tag[1] % 2) and wx.BOLD) or wx.NORMAL
            l.SetFont(wx.Font(10 + tag[1], wx.DEFAULT, wx.NORMAL, weight, 0, ""))
            l.SetSize(l.GetBestSize())
            w, _h = l.GetSize()
            if lines:
                line_width = getwidth(lines[-1]) 
                if (line_width + w) > WIDTH:   #make a new line
                    lines.append([l])
                else:
                    lines[-1].append(l)   
            else:   #lines is empty
                lines.append([l])
        
        row_pos = 0
        for line in lines:
            height = getheight(line)
            row_w = 0
            w = getwidth(line)
            spacer = (WIDTH - w) / len(line)
            
            for l in line:
                dh = row_pos + height - l.GetSize()[1]
                l.MoveXY(row_w, dh)
                row_w += l.GetSize()[0] + spacer
                
            row_pos += height


class MyApp(wx.App):

    def OnInit(self):
        wx.InitAllImageHandlers()
        frame = TopFrame(None, - 1, "")
        self.SetTopWindow(frame)
        
        frame.Show()
        return 1
    

def start():
    app = MyApp(0)
    app.MainLoop()
    
if __name__ == "__main__":
    start()

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

A few ATReferenceBrowserWidget tips

Filed Under:

On a Plone 2.5 project I'm working I have a content type that has 3 reference association to another content type. ArchGenXML generated the fields with the same name, which means that in the interface there will be just one field, as they overwrite each other. To have them working I need to rename them, but how to do this from the model? Agx, at first glance, doesn't have support for this. While playing and even trying to change agx to support this use case, I've noticed that agx considers the "association end" as the one meaningful: all tagged values set to it are reflected in the generated case. So the solution to this problem is to set a "name" tagged value in the "end" of the direct associations relationships. Check the following screenshot from Poseidon to see what I mean:

 

Field name in association

 

Another issue, this time directly related to the ATReferenceBrowserIssue, is the startup directory support. The widget has support for defaults set in portal_properties/site_properties, a property called refwidget_startupdirectories. The wording of this property's description in the README left me, as a non-native English speaker, a bit clueless. After some digging and debugging, I found out that the format for the lines there is:

/pathA:/pathB

where pathA is a path relative to the Plone site root; when you call the reference browser widget for an object in this path, the second path, pathB is returned as a base for browsing for objects. Even after I have understood the thing about the two colon separated path, it didn't work for me because I was refering to paths with their absolute paths (based in the Zope root) instead of their paths relative to the Plone site.

 

2008-03-13

Tutorial: run ArchGenXML 2.0 under virtualenv

Filed Under:

The "modern" (post 1.5) version of ArchGenXML is packaged as egg, available on pypi. While in theory you could run "sudo easy_install archgenxml" and have it running with minimal effort, because it depends on zope.component and zope.configuration, things tend to get muddy and complicated. If you'll "easy_install zope.component" you'll get a bunch of zope eggs installed in python's site-packages and this will probably cause problems. When I've started developing with Zope 3 I had some hard time tracking some problems that ultimately turned out to be caused by zope packages installed in the system python "conflicting" with my regular zope instances. Even the Plone installer prefers to create its own Python directory, to keep the its packages separated from other python packages installed in the system. To solve this problem, ArchGenXML has a mechanism to specify a path to the zope packages, of which I've already blogged about, but I now consider it an extra step which can be avoided by using virtualenv to install the archgenxml egg.

This following recipe is specific to Ubuntu, but probably adaptable to any Linux system, and even Windows. First, if you don't have easy_install in your system, install it with:

#sudo apt-get install python-setuptools
#easy_install virtualenv

In my ~/Software folder I've ran:

#virtualenv agx

This will create a ~/Software/agx virtual environment. We need to activate it:

#source agx/bin/activate

Now all python commands we will run (python, easy_install) will 'belong' to this virtual environment. We can safely install agx, zope.component and zope.configuration, as they will be installed there:

(agx)#easy_install archgenxml
(agx)#easy_install zope.component
(agx)#easy_install zope.configuration

The ArchGenXML egg has defined several console scripts, they're available as scripts "tied" to this virtual python environment, for example, this is the 'archgenxml' script that was installed in my ~/Software/agx/bin folder:

#!/home/tibi/tmp/arch/bin/python2.4
# EASY-INSTALL-ENTRY-SCRIPT: 'archgenxml==2.0-rc1','console_scripts','archgenxml'
__requires__ = 'archgenxml==2.0-rc1'
import sys
from pkg_resources import load_entry_point

sys.exit(
load_entry_point('archgenxml==2.0-rc1', 'console_scripts', 'archgenxml')()
)

What this tells us is that you can safely run this script from "outside" the virtual environment, without needing to first "source bin/activate" it.

Weblog
Atom
RDF
RSS 2.0
Powered by Quills
Technorati
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: