I’m a software engineer living in San Francisco, constantly trying to level up my BBQ and powerlifting skills. 🇬🇧 🏳️‍🌈 🏋️‍♀️ 🍖

 

Django ORM: Neat (undocumented) trick

UPDATE: I’m actually working on a ticket and patch so that this feature is both documented and regression-tested for future releases of Django (starting with 1.1).

UPDATE 2: My patch was accepted and included as part of Django 1.1.

I just found out something pretty damn cool about Django’s ORM which, as it happens, is completely undocumented (as far as I can tell). Let’s assume your model definition is something like:

from django.db import models

class MyModel(models.Model):

    count = models.IntegerField(default=0)

The following is completely valid, and actually eliminates a lot of the race conditions that have plagued the Django ORM in the past:

>>> from django.db.models import F
>>> from myapp.models import MyModel
>>> obj = MyModel(count=4)
>>> obj.save()
>>> obj.count
4  
>>> obj.count = F('count') + 3
>>> obj.save()
>>> obj = MyModel.objects.get(pk=obj.pk) # We need to reload the object.
>>> obj.count
7  

Typically you’d do something like obj.count += 3, but that sets the attribute to an absolute value, which can be the cause of many a race condition wherein two threads/processes are editing the same record at a time; the obj.save() would cause one thread to clobber another’s changes. Using F(), the SQL expression instead looks like:

UPDATE "myapp_mymodel" SET "count" = "myapp_mymodel"."count" + 3 WHERE "myapp_mymodel"."id" = 1;  

Here, the ACIDity of the RDBMS ensures that parallel attempts to increment the count occur without issue.

This behaviour’s undocumented status means it could break at any minute, and reloading the object is necessary because otherwise obj.count ends up being an instance of django.db.models.expressions.ExpressionNode, even after the object is saved.