Entries For: November 2009
2009-11-30
A "don't do" for internationalizing Django templates
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 %}
Odd thing in Django: the date filter takes PHP as reference instead of Python
I wonder what possible explanation there is for the behaviour of the date template filter.
Uses the same format as PHP's date() function (http://php.net/date) with some custom extensions.
I understand where Django comes from, but I think this sort of things should be more aligned with the rest of the Python world.
2009-11-18
If Django templates are an improvement over XML templates, then, by all means, please give me XML
I fail to see how
{% block %}
...
{% endblock %}
is in any way better or "less scary" then, let's say
<dj:block> ... </dj:block>
Yet another rant, this time triggered by the error I got when writing this piece of code:
{% blocktrans with offer.offerer.username as offerer_username
and offer.offered_time|date as offerer_date %}
...
{% endblocktrans %}
I just wanted to split the tag on multiple lines, but it seems that's not possible. If Django templates would have been XML, then it wouldn't have been any problem formatting that piece just how I want it. Right now, the joined line takes two times the amount of my screen width.
One more thing to grudge about is that vim, even with djangohtml syntax type installed, is not very knowledgeble about how to format the template file (it treats the tags as regular piece of text). Probably this could be fixed, so I shouldn't complain about this too much.
I think Django templates are compiled to python code, so it's natural that they're treated in an imperative, dumb way, but that's not the only way of doing things. For example, Chameleon is another templating library that compiles its templates as python code, has no problem working with an XML based templating language frontends.
2009-11-17
The case against Django templates
I have many grudges against the django templating language and its templates (in short, I hate them), so I'm gathering evidence to support what my "spider sense" tells me. Today the template tag system goes under fire.
Given the following template fragment:
{% load i18n %}
{% load avatar_tags %}
{% load voting_tags %}
{% load pagination_tags %}
{% load extra_voting_tags %}
{% load in_filter %}
{% load extra_tagging_tags %}
{% load sorting_tags %}
Which one is responsible for the following "anchor" tag?
{% anchor "hotness" "reddit-like hotness" %}
That's the equivalent of diving into a python module, with lots of "from X import *" at the top. Where do you find the definition of a symbol? At least, if it were Python, I could do a tag search in Vim, or a "go to definition" in Eclipse. If this practice is frowned upon in the rest of the Python world, why are so many programmers praising the Django templating system? Am I the only mad man here? My problems with this tag is that it doesn't translate the content, so I'll need to grep for its source and change it.
The template tags in Django are about extending the templating language, as to provide the programmer with new and specialized ways to interact with the template and its environment. The reason for this "tag inflation" is that the django templating language, for all its richness (by tags and filters numbers, I mean), is really limited. Python expressions are not allowed, and for every imaginable use case, there needs to be a tag, specialized or not.
How would Zope 3 solve, for example, a problem similar to the one the "anchor" tag handles? Well, rendering a special link for a content item could be as easy as
<a href="" tal:replace="someobject/@@hotness_link"><img src="hotness.gif" /></a>
Is this better? I think so. I'm editing HTML, and the <a /> tag is way better in expressing what the end result will be, compared to a simple {% anchor %} tag. Even more, the <img /> tag inside is purely cosmetic, just to cue the viewer of what the final result will be. The entire <a /> tag, with its content, will be replaced by whatever result is rendered calling the the someobject/@@hotness_link view. Finding the source of the hotness_link view is easily introspectable TTW using a debug tool such as lovely.skinbrowser.
The ZPT templates from Zope 3 can also give you a mechanism where you can add new expression types, but there's just one or two packages in the wild that define new expression types. Now compare this to the regular Django projects, were defining new tags is something that almost all projects do.
In conclusion, even though Django templates are much more imperative then ZPT, which are very declarative, they don't achieve the power and simplicity that they strive for.
2009-11-12
Django's makemessages sucks for my use cases
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-09
Get a project imported into subversion and start working imediately on it
I managed to figure out how to beat one awkward piece of workflow when starting new project: I always start hacking on a project, then I notice that I haven't been working on a svn checkout (it's the chicken or the egg problem). So I need to do a svn import, delete my copy of the project, checkout the svn version and start hacking at my project again. This workaround is agravated, though, when working with buildouts. I can't svn import the entire folder, because lots of generated folders and files will end up in subversion, which I don't want. Cleaning my original, running import and regenerate the buildout is a workflow killer.
The solution is simple, and goes like this: instead of importing the entire buildout folder, I can run
svn import . http://my/subversion/path/ --depth empty -m "initial import"
or I can just create a folder in my repository with svn mkdir
Next, checkout the empty folder from the repository into my buildout
svn co http://my/subversion/path/ .
Now I can cherry-pick whatever I want committed, with svn add. Problem solved!
2009-11-08
Using CherryPy to work around a Django/flup bug
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
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.