Make your Django Query Logic DRYer

Robert Roskam
Apr 30, 2016 · 2 min read

Let’s say you’re making a database of music. It’s going to include Albums and Singles and Artists. Multiple artists could have participated on the same Song. Here’s a relatively simple data relationship:

from django.db import modelsclass Artist(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
class Album(models.Model):
name = models.CharField(max_length=50)
release_date = models.DateField()
class Song(models.Model):
artists = models.ManyToManyField(Artist)
albums = models.ManyToManyField(Album)
name = models.CharField(max_length=100)
release_date = models.DateField()

You’ll note it’s a pain in the butt to find out who sang a song. Let’s say you want to find the list of Artists in the Lion King. Your query may look something like this:

songs = Song.objects.filter(albums__name='Lion King') 
artists_from_lion_king = Artist.objects.filter(song_set__in=songs)

Admittedly, this is really bad logic, since it’s only useful for the Lion King. But it’s totally possible you might find yourself doing something like that in a view, where you’re passing in the name as parameter. But that’s dumb. Don’t do that bit code right above. Do this instead:

from django.db import modelsclass Artist(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@property
def albums(self):
songs = self.song_set.all()
return Album.objects.filter(song_set__in=songs)
class Album(models.Model):
name = models.CharField(max_length=50)
release_date = models.DateField()
@property
def artists(self):
songs = self.song_set.all()
return Artist.objects.filter(song_set__in=songs)
class Song(models.Model):
artists = models.ManyToManyField(Artist)
albums = models.ManyToManyField(Album)
name = models.CharField(max_length=100)
release_date = models.DateField()

A little explanation as to the magic going on. You’re collecting the song_set related to each model, Album and Arists, and having a generalized function run to collect for you a queryset (note not a list, which makes this doubly awesome).

I do this pattern all the time. It’s extremely convenient.

However, I’m sure there are other options out there. How do you all keep your query logic in Django DRY?

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store