Django :: i18n

What’s up with so many languages? E.g. in Switzerland there are four official languages, and in Norway there are two: bokmål and nynorsk (details). I’ll be describing what I had to do to get a good user experience with these two. The reason I care is because government contracts usually have a hard requirement that you support all official languages.

My first problem was to figure out which language codes I should use, you know like en-us for English as spoken in the US. After consulting five different browsers on two platforms (I need a Mac!) I ended up with no for bokmål and nn for nynorsk as the only sane choices.

The Django documentation does a great job of describing the process of marking up your code and templates. Basically, wherever you’ve got a string that’ll be presented to the user, change it from

[sourcecode language=”Python”]
v = ‘Some string’
[/sourcecode]

to

[sourcecode language=”Python”]
v = _(‘Some string’)
[/sourcecode]

and in your templates use either of:

[sourcecode language=”Python”]
{% trans "Some string" %}
[/sourcecode]

or

[sourcecode language=”Python”]
{% blocktrans %}
Some string
{% endblocktrans %}
[/sourcecode]

Then you’ll have to find a linux machine — if you install the right packages this can be done on Windows too, but it was just easier to turn to my left and use the other keyboard 😉

Put site-packages/django/bin on your path if you haven’t allready done so, that’s where make-messages.py and compile-messages.py live.

Go into your project directory and issue the following commands:

[sourcecode language=”bash”]
mkdir locale
make-messages.py -l no
make-messages.py -l nn
make-messages.py -l en
[/sourcecode]

why did I add en (for English) as an option? Well, turns out that things go sour if you try to pass unicode outside the ascii range to _(). So

[sourcecode language=”Python”]
v = _(u’Født’) # doesn’t work
v = _(‘Fodt’) # will work, then put Født as a translation.
v = _(‘Date of birth’) # works and provides English support.
[/sourcecode]

the make-messages commands above created django.po files somewhere under the locale directory. Edit them with emacs:

[sourcecode language=”bash”]
find locale -name "*.po" -print | xargs emacs
[/sourcecode]

since Emacs has a po-mode that makes this very easy (or send them off to get translated, which is even easier). When you’re finished translating, compile your translations:

[sourcecode language=”bash”]
compile-messages.py
[/sourcecode]

if you want to update your .po files use make-messages.py -a before compile-messages.py. Now, the site will display Norwegian bokmål to users that have set this in their browser and English to users that have set en as their language. It didn’t show nynorsk to users with nn set as their language though… Turns out Django doesn’t allow you to use a language unless it’s part of Django’s set of core languages (found in django/conf/locale/…). Sigh. A quick copy of django/conf/locale/no/* to django/conf/locale/nn and all was fine. (I’ll submit nynorsk translations when I get them back from our translator 😉 ).

I’m not sure whether it was necessary to change the LANGUAGES in settings.py, but I did:

[sourcecode language=”Python”]
LANGUAGES = ( # IMO, these shouldn’t be translated
(‘no’, ‘Norsk’),
(‘nn’, ‘Nynorsk’),
(‘en’, ‘English’),
)
[/sourcecode]

Now for the final touch… While all this automatic translation is cool, it assumes that users have set up their browser correctly — yeah, right! What we need is a little ‘gadget’ listing the languages available, so that you can click on what you want. Turns out this is pretty easy with Django, although perhaps a little spread out.

First, in your base template, add:

[sourcecode language=”Python”]
{{ lang_switch }}
[/sourcecode]

then in urls.py add:

[sourcecode language=”Python”]
(r’^i18n/’, include(‘django.conf.urls.i18n’)),
[/sourcecode]

then define a function to create the necessary html:

[sourcecode language=”Python”]
def switch_language(lang):
languages = (
(‘no’, u’Bokmål’.encode(‘utf-8’)), # django doesn’t always like unicode..
(‘nn’, ‘Nynorsk’),
(‘en’, ‘English’),
)
res = []
for lc, lname in languages:
if lang == lc:
res.append(lname) # no sense chosing what you’re using
else:
t = ‘<a href="/i18n/setlang/?language=%s">%s</a>’ % (lc, lname)
res.append(t)

return ‘ | ‘.join(res)
[/sourcecode]

and use it in your context:

[sourcecode language=”Python”]
return render_to_response(…, { "lang_switch" : switch_language(request.LANGUAGE_CODE) })
[/sourcecode]

and that’s it.

The magic happens in the /i18n/setlang/ view, where Django sets the language in either a cookie or the current session (depending on what’s available). It then returns to the current page (based on the Referer header).

Posted in django | Leave a comment

Python :: flatten

Although it can seem like a CS excercise, everyone will sooner or later have to write a “flatten” function — a function that takes a nested “list” and makes it one-dimensional:

[sourcecode language=”Python”]
>>> flatten([1,2, [3,4], 5])
[1, 2, 3, 4, 5]
[/sourcecode]

it should also handle Python data types in a reasonable manner:

[sourcecode language=”Python”]
>>> flatten([‘hello’, ‘world’])
[‘hello’, ‘world’]
>>> flatten(i**2 for i in range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[/sourcecode]

The trick to the implementation is to realize that everything that is iterable should be flattened… except strings and unicode. To test if something is iterable, call iter on it and be prepared to catch a TypeError: iterator over non-sequence. To test if something is string or unicode use isinstance(item, basestring). That results in something like this:

[sourcecode language=”Python”]
def flatten(lst):
def _flatten(lst, res):
for item in lst:
if isinstance(item, basestring):
res.append(item)
else:
try:
_flatten(iter(item), res)
except TypeError: # iterator over nonsequence
res.append(item)
return res
return _flatten(lst, [])
[/sourcecode]

That generates the entire list in the res variable of the inner function as it’s recursing through the datastructure. That could be a very long list, so perhaps a generator works better?:

[sourcecode language=”Python”]
def flatten(lst):
for item in lst:
if isinstance(item, basestring):
yield item
else:
try:
flatten(iter(item))
except TypeError: # iterator over nonsequence
yield item
return
[/sourcecode]

That looks pretty good 🙂

So what was the reason I had to write flatten this time?…

[sourcecode language=”Python”]
>>> from html import table, tr, td
>>> td_num = html.mktag(‘td’, width="20%", style="background:aqua")
>>> table(tr(td_num(x), td(x**2)) for x in range(6))
[/sourcecode]

Which gives

0 0
1 1
2 4
3 9
4 16
5 25

the __str__ for all the classes is only defined for the base class, which needed to flatten its contents…

Posted in Python | Leave a comment

Django :: making a date dropdown field

It seems like it is hard to input valid dates in a free-form text format for a significant portion of users. Personally, I don’t like many of the popup calendar widgets since they usually rely on Javascript for rendering — making your form useless to people that don’t use Javascript in their browser. (To Javascript, or not to Javascript is a discussion for another time, but I’m quite sure Javascript shouldn’t be used for pretty graphic effects.) Quite a number of the Javascript calendar widgets I’ve seen only allow you to skip backwards one month at a time, and that gets really tedious if you’re as old as me and trying to find your birthday 😉

An easy alternative is the traditional dropdown lists for months, days, and years. One way to implement that in Django is of course to have a separate SelectField for months, days, years and leave it to the template code to lay them out sensibly. I don’t like that solution for several reasons: it’s too much complexity at the wrong abstraction layer (I want to deal with a date-widget as one unit in my template code), formatting according to local customs becomes harder, and it will end up sprinkling conversion code all over your app if you’re not careful. It turns out that it’s not that hard to create a custom widget that does it all for you automatically…

Note: This code is minimalist in many ways. E.g. using localized month names, localized day/month/year ordering, using empty defaults, etc. is left as an excercise for the reader.

First lets define a convenience function for creating a dropdown list (we’ll be responsible for the raw html in our widget, so it’s probably not a good idea to use an existing Django Field — I haven’t tried that though, so I might be wrong):

[sourcecode language=”Python”]
def select(options, selected, name):
selected = str(selected)
options = [(str(v),str(v)) for v in options]
res = [‘<select name="%s">’ % name]
for k,v in options:
tmp = ‘<option ‘
if k == selected:
tmp += ‘selected ‘
tmp += ‘value="%s">%s</option>’ % (k,v)
res.append(tmp)
res.append(‘</select>’)
return ‘\n’.join(res)
[/sourcecode]

Now for the DropDown (DD) DateField (it’s about 35 LOC without all the explanatory comments):

[sourcecode language=”Python”]
class DDDateField(forms.FormField):
# you might want to add a year range to this…
def __init__(self, field_name=”, is_required=False):
self.field_name = field_name
self.validator_list = [self.validator]
self.is_required = is_required

# for convenience (we’ll insert html select tags into the
# form to hold year/month/day data, and we’ll name
# them as follows):
self.dayname = field_name + ‘_day’
self.monthname = field_name + ‘_month’
self.yearname = field_name + ‘_year’

# extract_data is called with a copy of the POST data.
# Remember that all the fields will be empty the first time
# around and that POST data is always text (and so should
# any default values that you set be).
# The return value from this method is later passed on to
# the render method (after the user has had an opportunity
# to mess with it).
# NOTE: if anything throws here, your widget won’t show up.
def extract_data(self, post):
name = self.field_name
day = post.get(self.dayname, ‘1’)
month = post.get(self.monthname, ‘1’)
year = post.get(self.yearname, ‘1970’)
value = post.get(name, ‘%s-%s-%s’ % (year, month, day))
return dict(day=day, month=month, year=year, value=value)

# now for the actual rendering. We’re getting data from extract_data (above).
def render(self, data):
name = self.field_name
vals = (self.get_id(), name, forms.escape(data[‘value’]))

# put a hidden input tag here fun fun (we’ll have to overwrite this value
# later..)
h = ‘<input type=hidden id="%s" name="%s" value="%s" />’ % vals

# using the convenience function from above
day = select(range(1,32), data[‘day’], self.dayname)

# I haven’t checked if it’s possible to get localized month names from
# the calendar module, but if it is, they could go here:
month = select(range(1,13), data[‘month’], self.monthname)
year = select(range(1920, 2007), data[‘year’], self.yearname)
slash = ‘&nbsp;/&nbsp;’

# use US ordering
widget = h + str(month) + slash + str(day) + slash + str(year)

return widget

# Unfortunately there is no way for the hidden field to get updated
# on a successful submission since render isn’t called. The validator
# is called at this point though, and it has access to the entire set
# of form data. We’re going to hijack it to tie up our lose ends
def validator(self, field_data, alldata):
# get the values from the subwidgets
y = alldata[self.yearname]
m = alldata[self.monthname]
d = alldata[self.dayname]

# set the fieldname to the combined value
val = ‘%s-%s-%s’ % (y,m,d)
alldata[self.field_name] = val

# delete all the gunk we put in the form
del alldata[self.yearname], alldata[self.monthname], alldata[self.dayname]

# there is no way for a user to enter an invalid date, so just
# return True.
return True

# now all that’s left for html2python is to extract data from above
# and return a Python object.
@staticmethod
def html2python(data):
# we’re getting the data from the validator…
y, m, d = map(int, data.split(‘-‘))
return datetime.date(y,m,d)
[/sourcecode]

That’s it, it can be used as any other field.

Posted in django | Leave a comment

Python :: defprop – a very simple idiom for defining properties

Most attempts at making it easier to define properties in Python seem to quickly descend into metaclass obscurity. Metaclasses can surely solve the problem for an appropriate definition of solve, but “Explicit is better than implicit”, and “Simple is better than complex,” so none of the metaclass solutions (mine included) have had great appeal. It’s always fun to visit the Cookbook, and today I found a great recipe from Sean Ross at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183.

To recap, the traditional way of defining properties looks something like this

[sourcecode language=”Python”]
class MyClass(object):
def __init__(self):
self._foo = "foo"

def getfoo(self):
return self._foo
def setfoo(self, value):
self._foo = value
def delfoo(self):
del self._foo
foo = property(getfoo, setfoo, delfoo, "property foo’s doc string")
[/sourcecode]

All the getters and setters pollute your class namespace (think about adding a bar property), visually there is no Pythonic grouping of the code that belongs to the property, and you don’t really know that it’s going to be a property until the end.

At the end of the recipe Sean says that if decorators are accepted (they were) his idiom would be even better, so here’s my attempt:

[sourcecode language=”Python”]
# a simple decorator
def defprop(doc):
def _defprop(fn):
return property(doc=doc, **fn())
return _defprop

class MyClass(object):
def __init__(self):
self._x = 42

@defprop("The answer to life the universe and Everything")
def foo():
def fget(self):
return self._x
def fset(self, v):
self._x = v
return locals()
[/sourcecode]

Overall, I think it doesn’t suck too bad. The name defprop is based on the fact that def is an abbreviation also and property is taken.

Update: turns out I’ve found it easier to not use the decorator, just like the recipe mentioned above:

[sourcecode language=”Python”]
class MyClass(object):
def __init__(self):
self._x = 42

def foo():
def fget(self):
return self._x
def fset(self, v):
self._x = v
return locals()
foo = property(**foo())
[/sourcecode]

it’s an easy idiom to remember, easier than writing the property from scratch, and apparantly easier than figuring out where to put a property so that it will be available everywhere I happen to be…

Posted in Python | Leave a comment