In the realm of Django development, Generic Relationships provide a flexible way to establish relationships between models. Let's take a look at how they work and why they're so useful.
The Problem with Standard Foreign Keys
To understand the importance of generic relationships, let's consider a blogging application where we want users to be able to leave comments. In this case, we might create a Comment model with a ForeignKey to a Post model, like so:
class Comment(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
content = models.TextField()
post = models.ForeignKey(Post, on_delete=models.CASCADE)
But what if we wanted users to comment not only on posts but also on authors? Implementing this with standard ForeignKey fields can be challenging. One approach might be to add another ForeignKey on the Comment model pointing to the User being commented on:
class Comment(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
content = models.TextField()
post = models.ForeignKey(Post, on_delete=models.CASCADE, null=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
But this approach comes with problems:
- Both the
post
andauthor
fields can be null, meaning a comment could potentially not be associated with either a post or an author. - Both the
post
andauthor
fields could be populated simultaneously, meaning a comment could be associated with - both a post and an author, which might not make sense. Extra code would be required to query the correct field when - fetching comments, depending on the context. If we wanted to allow comments on other models in the future, we'd need to add another ForeignKey field to store this relationship.
The Power of Generic Relationships
Enter Generic Relationships. With Django's contenttypes
framework, we can establish a relationship with any model using three fields:
- A
ForeignKey
field pointing to aContentType
(usually namedcontent_type
). - A
PositiveIntegerField
storing the primary key of the related object (usually namedobject_id
). - A
GenericForeignKey
field, a special type of field that looks up the object based on thecontent_type
andobject_id
fields.
Here's how we could re-implement our Comment model using Generic Relationships:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Comment(models.Model):
creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
content = models.TextField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
Now, a Comment
can be associated with any model simply by assigning the object to content_object
. The GenericForeignKey
field handles storing the correct ContentType
and object ID:
p = Post.objects.first()
u = User.objects.first()
c1 = Comment(creator=u, content="What a great post!", content_object=p)
c1.save()
In [7]: c1.content_object
Out[7]: <Post: An Example Post>
And just as easily, we can add a comment on a User:
c2 = Comment(creator=u, content="I like Django!", content_object=u)
c2.save()
In [10]: c2.content_object
Out[10]: <User: username>
Despite its power, the Generic Relationships framework does have its quirks
- for example, in the Django admin interface, you'll need to enter the ID of the related object manually. However, the flexibility and potential for code reuse that it provides makes it an invaluable tool in the Django developer's toolkit.
Top comments (1)
Great 👏