DEV Community

Cover image for Harnessing Generic Relationships in Django
Abdelrahman hassan hamdy
Abdelrahman hassan hamdy

Posted on • Edited on

Harnessing Generic Relationships in Django

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)


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

But this approach comes with problems:

  • Both the post and author fields can be null, meaning a comment could potentially not be associated with either a post or an author.
  • Both the post and author 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:

  1. A ForeignKey field pointing to a ContentType (usually named content_type).
  2. A PositiveIntegerField storing the primary key of the related object (usually named object_id).
  3. A GenericForeignKey field, a special type of field that looks up the object based on the content_type and object_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")


Enter fullscreen mode Exit fullscreen mode

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()


Enter fullscreen mode Exit fullscreen mode


In [7]: c1.content_object
Out[7]: <Post: An Example Post>


Enter fullscreen mode Exit fullscreen mode

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()


Enter fullscreen mode Exit fullscreen mode


In [10]: c2.content_object
Out[10]: <User: username>


Enter fullscreen mode Exit fullscreen mode

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.

Image description

Top comments (1)

Collapse
 
mohamed24awwad profile image
M.Awwad

Great 👏