How and when to use GenericForeignKey in Django?
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 GenericForeignKey
is 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 formINSTALLED_APPS
(this is why I was sayingGenericForeignKey
depends 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 isCharField
then you have to useCharField
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 namesGenericForeignKey
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 theGenericRelation
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 ForeignKey
to 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()
andexclude()
withGenericForeignKey
as you used to do forForeignKey
.
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:
- I have followed the article How to Use Django’s Generic Relations from simple is better than complex.
- The official Django documentation is really great for understanding the concept of contenttypes framework and Generic Relation.