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.