How and when to use GenericForeignKey in Django?

Dipesh Bajgain
5 min readMay 27, 2021
Photo by Jaye Haych on Unsplash

Let’s first understand the meaning of generic, according to the oxford dictionary generic means;

shared by, including, or typical of a whole group of things; not specific”

So as you can see the meaning states clearly that it is not specific to any things. That means GenericForeignKeyis not specific to one model rather it can have a relation between any model in your Django application. If you have been a Rails developer you would be familiar with Polymorphic Associations. So in Django, it’s pretty much the same as the Polymorphic Associations in Rails.

If you are going to use GenericForeignKey in your current project, make sure you have a contenttypes framework in your INSTALLED_APPS settings because GenericForeignKey depends on it.

INSTALLED_APPS = [
# other installed apps
'django.contrib.contenttypes', # -> make sure you have this
]

Let’s understand GenericForeignKey in Django with some use cases.

If you want to have an activity log.

If you have gone through the Django source code to figure out how they have implemented the activity log in the admin site, you will find out that they have used ContentType to achieve that. So let’s make our own activity log using ContentType and GenericForeignKey.

Let’s create few models so that we can start working.

Here I have created ActivityLog model to store the activity log (create, update, delete), Profile to have a user profile, and Company where the user can have a company. (You can notice that I have extended the Django default User model to create my profile but it is highly recommended that you set up your own custom user model when starting a project).

Let walk step by step in ActivityLog model:

  • First, you have to provide a ForeignKey relation to the built-in Django modelContentType, which keeps track of all models in the Django application form INSTALLED_APPS (this is why I was saying GenericForeignKeydepends on contenttypes frameworks).
  • Second, you have to create a PositiveIntegerField to store the primary key value of whichever model you will reference later on (Be sure that this field can hold the pk of an instance of the model that you will reference. If your pk for other models is CharField then you have to use CharField as object_id because an integer can be coerced to char type).
  • Third, the part where you specify the GenericForeignKey relation and then pass the names of the two fields created above. If these fields are named “content_type” and “object_id”, you can omit this — those are the default field names GenericForeignKey will look for.

This is how you create your GenericForeignKey relationship that is not specific to a single model rather now can be linked to any model. Now you can store the activity as below;

In this way, your ActivityLog model can have a relation to any model. Let’s improve our relationship between models by using GenericRealtion which is a generic way to have a relationship between models.

Now after adding the generic relation we can now create the activity log for the above code as below;

This is just an example code to demonstrate how you can store the activity. Instead of doing it manually, you may create signals in Django to create the activity log whenever the record in the database is created, updated, or deleted. Let me show you by implementing simple Django signals. Create a signals.py file in your app.

Now go again and create a profile or a company instance the activity log will be generated by signal.

Note: By using the GenericRelation whenever we delete the data of profile and company our activity log will be deleted for that specific instance. If you don’t want that behaviour then you souldn’t use the GenericRelation in models. I wanted to show you what you can do with generic relation and generic foreign key.

If you have common fields in different models.

If you have noticed that I have added contact, and address in both of my Profile and Company model. That was on purpose to show you now how we can separate those fields and make use of GenericForeignKey for those fields. Let’s create a new Address model to keep the address information in one place.

Here we have the address model now to link with our profile and company either we can have a ForeignKey relation to both model like:

We can have an address for either a user or a company not for both at the same time. So, we are setting the ForeignKeyto have a null value for each of them. How can we improve the address model by using GenericForeignKey and GenericRelation?

Now by using the GenericForeignKey in the Address model, I have removed the use of two ForeignKey reference in the Address model. So, now whenever you want to create the address for either profile or company, you can simply use the generic relation as;

This was all about the GenerciForeignKey in Django and how you can use it with GenericRelation. It seems like it can help and save you a lot of code. But in reality, it will save you some code and at the same time, it will add complexity to your code as well. It’s up to you if you want to use it or not.

Using GenericForeignKey and GenericRelation you can have great power but with great power always comes great responsibility. So, let’s have a look at some of the responsibility you have to take care of:

  • It will add complexity to your code.
  • You cannot use filter() and exclude() with GenericForeignKey as you used to do for ForeignKey.
profile = Profile.objects.first()# you cannot do this
Address.objects.filter(content_object=profile)
# as well you cannot do this
Address.objects.get(content_object=profile)
  • It does not accept an on_delete argument to customize the behavior for deletions of data.

I hope now you can use GenericForeignKey in your project easily. If you got lost while following the article you can always have a reference to the source code in my GitHub repo genericproject.

References:

--

--

Dipesh Bajgain

I’m a software engineer with a passion to learn new technology that interests me.