Djangocon: lookups, transforms and expressions - Anssi Kääriäinen

Tags: django, djangocon

(One of the summaries of a talk at the 2015 Djangocon EU conference).

(Anssi Kääriäinen gave a great talk last year at “django under the hood” about the django ORM. Look at that one, too.)

Lookups, transforms and expressions are new features in the ORM (part in 1.7, part in 1.8). They are ways to inject custom SQL in a well-structured way.

Custom lookups are things like __glob that you can write yourself in addition to the build-in ones like __gte. Custom transforms? __lower, as an example.

How you could do this previously was by using .extra(), you could tweak the sql this way. The new approach is more reusable. It can also be chained (somefield__lower__glob)! Another drawback of .extra() is that it quite a hacky part of the django code. The new functionality

Lookups. Those are basically “a condition in the query’s WHERE clause”. Examples in django are lt, gt, gte, contains and so. To create a custom lookup, you subclass models.Lookup. You set the lookup_name and you implement the .as_sql() method. Afterwards you register it for the various Field types for which it is valid.

An example (very quickly and incompletely copied over from the presentation):

class GlobLookup(models.Lookup):
    lookup_name = 'glob'
    def as_sql(self, compiler, connection):
        lhs_sql, lhs_params = .....
        # ^^^ lhs means "left hand side", same rhs
        rhs_sql, rhs_params = .....

        return "%s glob %s" % (lhs_sql, rhs_sql) # + plus something more

# try it out

Transform example, in this case for lowercasing a string:

@models.CharField.register_lookup  # this will be register_transform later
class LowerTransform(models.Transform):
     lookup_name = 'lower'
     # bilateral = True
     # ^^^ Enable this if you also want to transform the right hand side.
     def as_sql(self, compiler, connection):
         lhs_sql, lhs_params = compiler.compile(....)
         return 'lower(%s)' % ...

# try it out

Expressions are an element in the SELECT clause. Subclass models.Func (or models.Aggregate) and set a method:

class GroupConcat(models.aggregate):
      function = 'group_concat'


You can combine it all (pardon me any typos…):


You can do all sorts of queries with this that weren’t possible in earlier versions!

Some extra notes:

  • You can add output_field if the field type changes. If you have a Length expression, you probably effectively convert a list of strings into one single number.

  • Multiple sql dialects are supported, too. If a certain database needs special treatment, you can add methods like .as_postgres() or .as_oracle(). If available, when you use that database, they are used in preference to the generic .as_sql().

The above functionality allowed django to support some specific postgres fields for HStore, for instance:

                   data={'breed': 'labrador',
                         'something': 'else'})
# ^^^ Data is an hstore field: key/value store.


Same with a postgres list field. .filter(tags__1__lower__glob='*django')…. You can combine anything.

What’s next?

  • Implement extra common expressions in django core (or in contrib).

  • Ordering by transform results.

  • Unify expression and transform implementations.

If you want to know more, check the django documentation:

And… a lot of this work was possible because of, so support django!

Holiday picture from the Eifel region logo

About me

My name is Reinout van Rees and I work a lot with Python (programming language) and Django (website framework). I live in The Netherlands and I'm happily married to Annie van Rees-Kooiman.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):