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
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
- First, you have to provide a
ForeignKeyrelation to the built-in Django model
ContentType, 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
PositiveIntegerFieldto 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
CharFieldthen you have to use
CharFieldas object_id because an integer can be coerced to char type).
- Third, the part where you specify the
GenericForeignKeyrelation 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
GenericForeignKeywill 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
GenericRelationwhenever 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
GenericRelationin 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
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
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.
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
GenericForeignKeyas you used to do for
profile = Profile.objects.first()# you cannot do this
Address.objects.filter(content_object=profile)# as well you cannot do this
- It does not accept an
on_deleteargument 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.