Tag Archives: python

Encoding lists in Django for jQuery

Several times I have needed to implement a form for a [Django][] model where
one field’s value determines the available choices for a second field.
Recently I finished a project where the user had to choose from a list of
departments in our office, and then choose from a list of staff working
for the chosen department.

[Django]: http://www.djangoproject.com/

The Django model looks like this:

EMPLOYEES = (
(‘Finance’, ‘Adam’),
(‘Finance’, ‘Clare’),
(‘Finance’, ‘Dave’),
(‘Housekeeping’, ‘Frank’),
(‘Housekeeping’, ‘Nat’),
(‘Marketing’, ‘Nigel’),
(‘Marketing’, ‘Valerie’),
)

def department_choices():
depts = list(set(d for d, e in EMPLOYEES))
depts.sort()
for d in depts:
yield (d, d)

def employee_choices():
for d, e in EMPLOYEES:
yield (e, e)

class EmployeeOfTheYear(models.Model):
department = models.CharField(max_length=250, choices=department_choices())
employee = models.CharField(max_length=250, choices=employee_choices())

In this application the site visitor will be creating `EmployeeOfTheYear`
objects, and the new object form should display department and employee
fields as lists of pre-defined department names and pre-defined
employee names.

So then wickles! A quick Django view and template for making a new object
creates markup with SELECT inputs for department and employee fields
with the OPTION elements restricted to just those
departments and employees defined by the evil management types on
the 100th floor. The markup will be similar to this:

(If your corporate culture lacks a 100th floor you might imagine a
variation where departments and employees are defined in [your corporate
directory][msad], and where you have mad skillz sufficient to
create lists of the departments and employees using [LDAP and
Python][pyldap].)

[msad]: http://www.microsoft.com/windowsserver2003/technologies/directory/activedirectory/
[pyldap]: http://python-ldap.sourceforge.net/

The problem is that choosing a department from the department SELECT menu
has no bearing on the choices available in the employee SELECT menu. It
jolly well ought to.

I use [jQuery][] for nearly every piece of JavaScript functionality in my
projects. jQuery makes so many tedious tasks a matter of a few lines, I
wish I could reclaim the hours spent debugging my scripts before I
discovered this magical library.

[jQuery]: http://jquery.com/

With jQuery and the [texotela plugin for select boxes][texotela] we can
create an event handler that fires whenever the visitor makes a choice from
the department menu so that the choices in the employee menu are restricted to just
the employees matching the chosen department. This is known as a cascading
select input or two-level select input.

[texotela]: http://www.texotela.co.uk/code/jquery/select/

The script to do this does the following:

* Take the chosen value for the department
* Remove all options for the employee SELECT input element
* Query the server via AJAX for a list of employees in the chosen department
* Insert the results as options for the employee SELECT element

Here is the JavaScript to do all that, using jQuery’s excellent selector
syntax to install the handler for the form’s `id_department` SELECT element:

employee_url = ‘/find_employees/’

$(document).ready(function() {
$(“#id_department”).change(function() {
var dept = $(this).selectedValues();
$(“#id_employee”).removeOption(/./);
$(“#id_employee”).ajaxAddOption(employee_url, {dept: dept}, false);
});
});

There are implementation details hard-coded in there.

1. The URL for retrieving a list of employees is `/find_employees/`
2. The `/find_employees/` URL is queried with the chosen department
passed in as the `dept` query variable
3. The Django form inputs must be named `id_department` and `id_employee`

When a visitor chooses ‘Housekeeping’ from the department SELECT the
event handler will GET `/find_employees/?dept=Housekeeping`
and will expect a JSON-encoded list of options for insertion in the
employees menu. The texotela plugin says the format for the employee list
has to be like so:

{
“option_value_1”: “option_text_1”,
“option_value_2”: “option_text_2”
}

(At which point I wonder if my mistrust of JavaScript is misplaced. That
damn JSON-encoded data just is a Python dictionary! But no, a Python
dictionary is unordered, whereas those conniving JavaScript curly-braces
denote object properties. And the absence of a final comma on the last
value/option pair can make all the debugging difference in the world. Not
the same thing at all.)

And finally the view itself. This view must return a JSON-encoded dictionary
of employee names that match the department given by the ‘dept’ query
parameter. However a regular Python dictionary is no good because the order
of items is significant: the choices in the employee SELECT should be listed
alphabetically. We can use Django’s `SortedDict`, a sub-class of dict that
maintains key order.

from django.http import HttpResponse

def find_employees(request):
“””Return a JSON list matching search term.”””
from django.utils.datastructures import SortedDict
from django.utils import simplejson
from models import EMPLOYEES

dept = request.GET.get(‘dept’, ”).lower()

if dept:
employees = [e for d, e in EMPLOYEES if dept == d.lower()]
else:
employees = [e for d, e in EMPLOYEES]
employees.sort()

d = SortedDict([(e, e) for e in employees])
return HttpResponse(simplejson.dumps(d, ensure_ascii=False), mimetype=’application/json’)

I need a matching rule in urls.py to direct requests to the JSON view:

urlpatterns = patterns(‘myproject.myapp.views’,
url(r’^find_employees/$’, ‘find_employees’, name=”find_employees”),
)

Things I like about this approach:

* The form works perfectly without JavaScript
* If the submitted form doesn’t validate, the visitor’s department and
employee choices are still selected when the form is redisplayed
* The JavaScript is clear and concise

In the real application the EMPLOYEES and DEPARTMENTS are lists of objects
wrapping LDAP results, but I hope this explanation is clear enough to show
how the it all hangs together to help the visitor use what would otherwise
be an unhelpful couple of SELECT inputs.

Using relative paths in your settings.py

Several important settings in your [Django project’s
settings.py][settings] are annotated with warnings about the need for
absolute path names. When I start a new project with the development
server I don’t want to have to think what directory my MEDIA_ROOT should
point to. Isn’t all that going to change anyway when it’s deployed to
the gigantic server in the sky?

[settings]: http://www.djangoproject.com/documentation/settings/

My first tip is that when you are running the development server, you
*can* get away with relative paths. Paths will be relative to the
directory you were in when you started the development server (in my
case I always `cd ~/my_project && ./manage.py runserver` so that
means relative to my project’s directory).

Therefore if you want to keep project-wide templates within the project,
just create a `templates` directory and add it to settings.py:

TEMPLATE_DIRS = (
‘templates’,
)

Sweet! Relative paths and it all works and I don’t have to edit my
`TEMPLATE_DIRS` setting whenever I am editing on a work machine (where
the project is in `/Users/dbuxton/my_project`) instead of home (where
the project is in `/Users/david/my_project`).

But it all goes pear-shaped when you move the project to the deployment
platform. There the project is running under mod_python where the notion
of the current working directory is going to be very different. All I
know is my relative paths *do not get resolved* and I wonder if them
Django developers knew a thing or two when they warned me to use
absolute paths.

My current approach to this is to refuse to do what I am told. Instead
one can take advantage of a [Python module’s `__file__` attribute][__file__] to establish where on disk your `settings.py` is,
and armed with that knowledge you can construct absolute paths from the
relative settings.

[__file__]: http://www.python.org/doc/2.5.1/ref/types.html#l2h-111

Near the top of `settings.py` I have:

import os

INSTALL_DIR = os.path.dirname(os.path.abspath(__file__))

Then anywhere I need an absolute path in settings.py I have something
similar to this:

TEMPLATE_DIRS = (
os.path.join(INSTALL_DIR, ‘templates’),
)

Shabooba! These paths change according to the value of `INSTALL_DIR`,
and that in turn is determined when the Django project is loaded. When
my project’s templates are sitting on a distant FreeBSD server in
`/home/webapps/django/my_project/templates` mod_python can locate them
just as surely as Django’s development server can locate the folder
`/Users/david/my_project/templates` on my MacBook.

Old-style Python class system and parent methods

I rather like Python’s explicit object reference requirement, whereby method definitions for a class instance have to use `self` as the first parameter (I should write some classes that use `this` instead of `self` some time, just to annoy myself).

But I was tripped up debugging a problem that centred on a simple class I had that needed to do a bit of housekeeping for byte streams:

from StringIO import StringIO

class MyString(StringIO):
def __init__(self, *args, **kwargs):
self._customized = True # Or similar housekeeping
super(MyString, self).__init__(*args, **kwargs)

Creating an object from this class raises a `TypeError`:

>>> s = MyString()
Traceback (most recent call last):
File “”, line 1, in
File “”, line 4, in __init__
TypeError: super() argument 1 must be type, not classobj

Wuh? I always used `super` like that before, and it always worked. But my mistake here was I was sub-classing an [*old-style* class][old], and `super` only works with [*new-style* classes][new].

The correct way of calling the parent method for old-style classes:

class MyString(StringIO):
def __init__(self, *args, **kwargs):
self._customized = True # Or similar housekeeping
StringIO.__init__(self, *args, **kwargs)

That works! But the distinction between the two class models is so inelegant, so clunky. It is a nasty bit of Python’s historic implementation that one needs to keep in mind, and it is knowledge that makes me no cleverer (although it does mean I’m less stupid than I was).

Of course Python 3000 removes this distinction…

[old]: http://docs.python.org/ref/node33.html
[new]: http://docs.python.org/tut/node11.html#SECTION0011500000000000000000