Django laziness at all levels

Following on from Simon Willison’s recent post about Django’s ORM, I’ve found both the lazy evaluation and chaining properties of Django’s querysets to be really useful quite deep within Django’s own view-layer framework.

Django has its own library for building forms, currently called newforms (to distinguish it from the old library, deprecated but left around for compatibility reasons). The ModelForm class in this library interrogates a particular class from the model layer and builds an HTML form—complete with post-submit validation, field widgets, the lot. The beauty of this is that you can run:

import django.newforms as forms
from myproject.models import MyObject

class MyForm(forms.ModelForm):
    class Meta:
        model = MyObject

and that’s it: you can then use an instance of MyForm anywhere in your view layer to add or edit MyObject instances in the database.

Problems arise, of course, when the automatically generated form doesn’t provide exactly what you want. It might, for example produce select dropdowns for you with thousands of items, whereas with better knowledge of the page your user is on then you might be able to whittle that list down to a handful of items.

Let’s imagine your objects can belong to Django users i.e. in your models.py you have:

class MyObject(models.Model):
    owner = models.ForeignKey(User)

This means that when you try to create an object, the HTML from newforms will give you a select dropdown of all the users in the database. But what if you have loads of inactive users, cluttering up the dropdown? How do you get rid of them with the minimal amount of coding i.e. without having to build your own form if possible?

Luckily, newforms sticks the queryset it creates somewhere that you can fiddle with it. With lazy evaluation, you get a chance to change the queryset before it hits the database; with chaining, you don’t have to worry about rebuilding the queryset yourself, but can just add to it instead.

In the MyForm class, you can override the inherited __init__ method as follows:

def __init__(self, *a, **kw):
    forms.ModelForm.__init__(self, *a, **kw)
    self.fields['owner'].queryset = self.fields['owner'].queryset.filter(is_active=True)

This calls the existing superclass constructor first, passing along any values. Then, after the queryset has been created for the select dropdown but before it’s been evaluated, the queryset is modified to whittle down the resultant set of users to only those where the “is_active” flag is set to True.

Laziness and chaining make Django’s ORM layer very effective; forms and newforms help to greatly reduce the amount of coding required by the view and control layers; together they can really simplify a codebase.