Django: How to add ForeignKey to multiple models

Basically, how to make relation to more than one type of model, using one ForeignKey. (using contenttypes app and Generic Relations)

Suppose you have two models: Article and Post

class Article(models.Model):
content = models.CharField(max_length=100)
class Post(models.Model):
content = models.CharField(max_length=100)

Now, we’ll have a Comment model. Here, both an Article or a Post can have a comment. So, how to add a ForeignKey value in the Comment model, which could point to either of the above models.

We’ll use the concept of Generic Relation, which allows us to do so. Django includes a ‘contenttypes’ application, and relations between your models and ContentType model in that application can also be used to enable “generic” relationships between an instance of one of your models and instances of any model you have installed.

The format of our Comment model will be like:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Comment(models.Model):
comm = models.CharField(max_length=50)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object=GenericForeignKey('content_type', 'object_id')

(The last three lines will be same every time you want to use generic relations like this.)

ContentType is a model in contenttypes app. Instances of ContentType represent and store information about the models installed in your project and have methods for returning the model classes they represent and for querying objects from those models.

Thus, we can relate a Comment object to any kind of model, store it in the content_type attribute (by sending it which model we have to relate it to in the ‘content_type’ parameter, and the id of the object of that model to relate to in the ‘object_id’ parameter).

Now, we’ll create the Comment instances, and relate it to either an Article instance, or a Post instance.

>>> art = Article.objects.get(id=1)
>>> c = Comment(content_object=art, comm='asdf')
>>> c.content_object
<Article: article1>
>>> pos= Post.objects.get(id=1)
>>> c= Comment(content_object=pos, comm='new comment')
>>> c.content_object
<Post: post1>

Now, to get all the comments related to an Article or a Post, we can use the GenericRelation class for this. We can define a new attribute under the Article and Post models, and can get comments related to them. Add the attribute for reverse query under your models as:

from django.contrib.contenttypes.fields import GenericRelationclass Article(models.Model):
content = models.CharField(max_length=100)
comments = GenericRelation(Comment)
class Post(models.Model):
content = models.CharField(max_length=100)
comments = GenericRelation(Comment)

Now, a query of model_object.comments.all() will give all the comments related to that object. Like, if art is an Article instance, and pos is a Post instance:

>>> art.comments.all()
<QuerySet [<Comment: asdf>, <Comment: test>]>
>>> pos.comments.all()
<QuerySet [<Comment: new_comment>, <Comment: test2>]>

That is all you need to know for basic working required for ‘relating an instance to multiple Models’.

You can know more about contenttypes application, ContentType model and GenericRelations on their docs: The contenttypes framework.

[This article is also published on my personal website. Check it out there along the other articles. I’ve started to mostly write in my personal website only instead of Medium]

Developer | Software Engineer