Django generic foreign keys for beginners

Duration: 7 minutes

Level: beginner

Objective: understand the principle behind a generic foreign key and how to use it in Django

The Problem

Let’s say we are having a table in our database named Device which holds information about our users devices. Some information is common (a Foreign Key to the owner, the version of the os, etc), but other is specific to the device type (IOS and Android).

The question is: how are we going to represent this in our database?

Solution 1: Very bad!

We could hold all the information in the same table. When we are dealing with an IOS device, all the android_specific fields will be empty and vice-versa. This can work in simple cases (like a school project), it’s true.

Explaining how this approach is breaking the basic principles of relational databases it’s outside the scope of this post – but trust me, it’s bad -.

Moreover, there are many inconveniences with this approach. You have to check every time the type when using an instance of Device. If by accident, a bug in your code sets the ios_specific fields for and Android device, things can get nasty. Adding other types (Windows Phone for example) will add even more columns in the table which you will not use.

Solution 2: Better, but still bad!

We split the specific info into separate tables. In the main device, we hold a one-to-one relation with each of the table. For every instance, we have to make sure that only one of the foreign keys is not None. Better, but we’re not quite there yet.

Solution 3: Best!

Ideally, we want to have a single foreign key through which we can access the platform specific information. Even more, we want to be sure that when we are instantiating or updating an instance of Device, the platform specific table is set behind the scene.

Please welcome to the stage: Generic Foreign Key

One foreign key – platform_specific_info (I suck at naming, it’s a common problem of software developers 🙂 ) – which reference to either AndroidDevice or IosDevice depending on the device type. No information is duplicated and no columns are left empty in the database.

Let’s see how this is implemented in Django.

Implementation in Django

It’s clear that this is not just a simple foreign key that holds the id of the row in the other table. In addition to that, it has to know from which table to take that id. So a generic foreign key would look like this (table_id, row_id).

A short introduction to ContentType

Luckily for us, Django got us covered with the table_id. It’s a table called content_type which comes Django and holds references to all other tables in the database. Just make sure you have 'django.contrib.contenttypes' in your INSTALLED_APPS from settings.py.

Let’s see how to code will look like in the models.py:

Things are looking good in the db, but let’s make sure that the platform_specific_info will reference the prober table every time. Mixing a different type with a different table can cause serious damage. Let’s add this method in the same file:

Great! Now we are sure that in any circumstance, the platform_specific_info is pointing to the right table.

Understanding the principle first makes the implementation a real pleasure.