Django ORM: Accessing foreign key objects

Django ORM: Accessing foreign key objects

·

4 min read

One to many, many to one relationships

For demonstration, let’s create a Django project and start an app call books. We will take the example of the relationship between books and reviews and create two simple models for them. As you know, one book can have many reviews. The models are shown below:

class Book(models.Model):
    title = models.CharField(max_length=50)
    description = models.CharField(max_length=255)

    def __str__(self):
        return self.title

class Review(models.Model):
    body = models.CharField(max_length=255)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)

    def __str__(self):
        return self.body

In the Review model above, as you can see, we have a foreign key to Book. This indicates a one-to-many relationship between Book and Review. Assuming you have run the migrations, let’s start up Django shell and run following commands:

>>> from books.models import Book, Review
>>> b = Book.objects.create(title='ABC book', description='Book of alphabets')
>>> r = Review.objects.create(body='This is a great book', book=b)

Now, in order to access the Book object from the Review object, you can just do:

>>> r.book
<Book: ABC Book>

review_set

But, how do you access the Review object from the Book object b? You can use review_set as shown below:

>>> b.review_set.all() # returns a queryset 
<QuerySet [<Review: This is a great book>]>
>>> b.review_set.first() # using first() since we have only one review attached
<Review: This is a great book>

_set indicates that there can be more than one review object linked to the book object. Also, note that if you use dir(b), you can see all the attributes of b.

Adding related_name

Now, let’s add a related name to the foreign key book in the Review model

class Review(models.Model):
    body = models.CharField(max_length=255)
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')

    def __str__(self):
        return self.body

Now, make migrations and apply them. Let’s start the shell and take the same example above to notice the changes:

>>> from books.models import Book, Review
>>> b = Book.objects.create(title='ABC book', description='Book of alphabets')
>>> r = Review.objects.create(body='This is a great book', book=b)

The way you would access the Book object b from Review object r would stay the same.

>>> r.book
<Book: ABC Book>

However, if you use review_set as above to access the book objects, you will get an AttributeError as shown below:

>>> b.review_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Book' object has no attribute 'review_set'

Instead, you need to use the related name reviews:

>>> b.reviews.all()
<QuerySet [<Review: This is a great book>]>
>>> b.reviews.first()
<Review: This is a great book>

Many to many relationships

We will create a new app called playlists to demonstrate a many-to-many relationship. Let’s take the example of the relationship between playlist and tracks and create two simple models for them:

class Track(models.Model):
    title = models.CharField(max_length=50)

    def __str__(self):
        return self.title

class Playlist(models.Model):
    name = models.CharField(max_length=50)
    tracks = models.ManyToManyField(Track)

    def __str__(self):
        return self.name

As you know, a playlist can have many tracks and also a track can be part of many playlists. Hence, this is a many-to-many relationship. In the Playlist model above, as you can see, we have a m2m foreign key to Track. Assuming you have run the migrations, let’s start up Django shell and run the following commands:

>>> from playlists.models import Playlist, Track
>>> t1 = Track.objects.create(title='Awesome Track 1')
>>> t2 = Track.objects.create(title='Awesome Track 2')

Now, in order, to create the m2m relationship between the above track objects and a playlist, you need to first create the playlist and then add the tracks as shown below:

>>> p = Playlist.objects.create(name='My Playlist')
>>> p.tracks.set((t1,t2))

Now, in order, to get the tracks from the playlist you can do:

>>> p.tracks.all()  # returns a queryset
<QuerySet [<Track: Awesome Track 1>, <Track: Awesome Track 2>]>
>>> tuple(p.tracks.all()) # returns a tuple
(<Track: Awesome Track 1>, <Track: Awesome Track 2>)

playlist_set

And here, again, if you want to access the playlist from the tracks, you need to use the playlist_set attribute:

>>> t1.playlist_set.all() # returns a queryset
<QuerySet [<Playlist: My Playlist>]>
>>> t1.playlist_set.first() # using first() since we have only one playlist attached
<Playlist: My Playlist>

Adding related_name

Now, let’s add a related name to tracks in the Playlist model

class Playlist(models.Model):
    name = models.CharField(max_length=50)
    tracks = models.ManyToManyField(Track, related_name='playlists')

    def __str__(self):
        return self.name

Now, make migrations and apply them. Let’s start the shell and take the same example above to notice the changes:

>>> from playlists.models import Playlist, Track
>>> t1 = Track.objects.create(title='Awesome Track 1')
>>> t2 = Track.objects.create(title='Awesome Track 2')
>>> p = Playlist.objects.create(name='My Playlist')
>>> p.tracks.set((t1,t2))

The way you would access the Track objects from Playlist object p would stay the same as shown below:

>>> p.tracks.all()
<QuerySet [<Track: Awesome Track 1>, <Track: Awesome Track 2>]>

However, if you use playlist_set as above to access the Playlist objects from the either Track object, you will get an AttributeError as shown below:

>>> t1.playlist_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Track' object has no attribute 'playlist_set'

Instead, you need to use the related name playlists:

>>> t1.playlists.all()
<QuerySet [<Playlist: My Playlist>]>
>>> t1.playlists.first()
<Playlist: My Playlist>
>>> t2.playlists.all()
<QuerySet [<Playlist: My Playlist>]>
>>> t2.playlists.first()
<Playlist: My Playlist>

I hope this article was helpful. Please feel free to share any feedback.