Make your Django Query Logic DRYer

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 models
class 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 models
class 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?

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.