Sometimes you are creating a model with a foreign key, but you want to be flexible. For example, let's look at an example where you are creating a website for a music store and you have a bunch of albums. The albums could be performed/written by a single artist, or by a band. Let's say you have the following models:
class Album(models.Model): name = models.CharField(max_length = 50) performer = #what do we put here? class Artist(models.Model): name = models.CharField(max_length = 75) band = models.ForeignKey(Band, on_delete = models.CASCADE, blank = True, null = True) class Band(models.Model): name = models.CharField(max_length = 50) genre = models.CharField(max_length = 50) year_established = models.IntegerField()
DISCLAIMER: I AM NOT CLAIMING THAT THIS STRUCTURE IS THE ONLY WAY OR EVEN THE BEST WAY TO SET UP A DATABASE IN THIS CONTEXT. IT IS JUST AN EXAMPLE.
Now that we have gotten that part out of the way, we can look at what we have. We have an album that has a name, and we want to have a foreign key type relationship so that we know who the person is that performed the album. This could be a single person of type
Artist or an instance of
Band. We could create two nullable fields that will hold the
Band in each, like this:
class Album(models.Model): name = models.CharField(max_length = 50) performer_artist = models.ForeignKey(Artist, on_delete = models.CASCADE, blank = True, null = True) performer_band = models.ForeignKey(Band, on_delete = models.CASCADE, blank = True, null = True)
The idea is that one of them is empty. But then we would need to add additional validators to make sure one of them is not null and when we refer to the performer we would need to check both fields. I smell a headache. So, we are going to find a way to make one performer field where it can be an
Band. Throughout this example, we are going to want to add two albums to our database - AC/DC's Thunderstruck and Eminem's The Eminem Show, assuming we have a
Band instance of AC/DC and an
Artist instance of Eminem.
Generic Foreign Keys
With Generic Foreign Keys (GFKs), we can have a foreign key relationship with any type of model we want, and only have one relationship in our model. This does not come without some sort of cost, however. GFKs are made up of the following parts:
The content type refers to the type of model itself. We need to tell the Generic Foreign Key which model type we are pointing to. In our example, we can make a field called
content_type that will end up holding the model, which will end up being
We will need to create some sort of field that will point to our model instance. The
content_type will only refer to our actual model class. However, we need to be able to tell our field which instance of the model class we are pointing to. By using an
id, we know it can refer to nearly any model class because virtually every model will have an id. This field will be fittingly called
This field will combine the content_type and the object_id to store the instance we are pointing to. This is using a field
GenericForeignKey, and the field will be called
Putting it together
Let's add this to our code.
We can change our Album model to the following:
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models class Album(models.Model): name = models.CharField(max_length = 50) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id')
content_type is a little funny. The way it is structured is by pointing to another model,
ContentType. Don't be confused by this.
ContentType is simply another model provided by Django that has a bunch of instances of your model types in your project. If you are confused by this, I suggest going into your Django shell (
python manage.py shell) and querying
ContentType.objects.all() to see what this does.
Next, we are going to and the
object_id. This will hold the
id of the object we are pointing to. Since it is just a number, we will make it a
Lastly, we need to define the actual object. We imported the GenericForeignKey field to our project, so we can use this to define the Foreign Key Relationship. This will take the
'content_type' and the
'object_id' to let our
GenericForeignKey what type of model and the instance of the model we are pointing to.
So, let's try to add our two
Albums. Let's assume we already have a
Band instance of AC/DC and an
Artist instance of Eminem. We can now add our performer to our
Albums via our
content_object field. We can do this in our shell (
python manage.py shell).
>>> from albums.models import Album >>> from artists.models import Artist >>> from bands.models import Band >>> acdc = Band.objects.get(name = "AC/DC") >>> eminem = Artist.objects.get(name = "Eminem") >>> thunderstruck = Album.objects.create(name = "Thunderstruck", content_object = acdc) >>> eminem_show = Album.objects.create(name = "The Eminem Show", content_object = eminem)
Now, we have a our two new Albums by AC/DC and Eminem. We can now get the performer via the album.content_object property.
Generic Foreign Keys are great because we can refer to any type of model we want. However, be careful so we don't overuse it.
Subscribe now to get updates on our blog and content