Sam Keller

Django Model Managers
nov 08, 2016
Model Managers (and custom QuerySets) might be one of the best kept secrets about Django. They're well documented (see: Django Documentation) and Shawn Inman gave an awesome presentation about them at DjangoCon 2016 (some of which I'm going to cover here!), and yet I still feel like I just don't hear as much about them as I should.

They're really useful -- mostly because if you find yourself doing some complicated queryset logic over and over again, you can put that logic in one place and just refer to it with a simple name. Let me go into an example.

Say we have the following type of model setup in a Django project:
class Painting(models.Model):
name = models.CharField(max_length=100)
artist = models.ForeignKey(Artist, on_delete=models.PROTECT)
year_painted = models.IntegerField()
style = models.ForeignKey(PaintingStyle, on_delete=models.PROTECT, null=True)

class Artist(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
year_born = models.IntegerField()
nationality = models.ForeignKey(Country, on_delete=models.PROTECT)

class PaintingStyle(models.Model):
name = models.CharField(max_length=50)

class Country(models.Model):
name = models.CharField(max_length=50)
Nothing too fancy -- just a way to track artists from various countries, which paintings they painted, and in what style they painted them.

Now let's say you wanted to query all paintings by Italian painters of the Renaissance -- you could query for that with the following:
Painting.objects.filter(artist__nationality__name='Italy', style__name='Renaissance', year_painted__range=(1400,1600))
Now, that's great, but if you were using this query really often, it would become a hassle to type out and a liability -- other developers on your team could get confused and think you wanted to include paintings from as far back as the late 13th century or that you wanted to include non-Italian artists who may have lived in Italy at the time.

So how to do you make sure everyone's on the same page? Use a custom QuerySet and convert it to a custom Model Manager using the QuerySet.as_manager() method! Observe:
class PaintingQueryset(QuerySet):
def italian_renaissance(self):
return self.filter(artist__nationality__name='Italy', style__name='Renaissance', year_painted__range=(1400,1600))

class Painting(models.Model):
....
objects = PaintingQueryset.as_manager()
Now, if you want that same group you can simply do:
Painting.objects.italian_renaissance()
Voila! With Model Managers, you're able to define important business logic in one place and only one place -- this is what makes them so powerful