Complete Guide to Django ForeignKey

One of the basic concepts of making connections through tables in the database using Django is a ForeignKey. This concept is crucial to master to create large, scalable applications supported by relational databases. 

What is ForeignKey in Django?

ForeignKey is a Field (which represents a column in a database table), and it’s used to create many-to-one relationships within tables. It’s a standard practice in relational databases to connect data using ForeignKeys.

What’s the difference between foreign key and primary key?

The primary key defines the unique value for a row in a table. Foreign key connects tables using the primary key value from another table.

ForeignKey Syntax

ForeignKey syntax in Django is as follows:

ForeignKey(to, on_delete, **options)

ForeignKey requires two arguments:

WARNING - Don’t forget to add on_delete parameter, it’s required!
Possible options: CASCADE, PROTECT, RESTRICT, SET_NULL, SET_DEFAULT, SET(), DO_NOTHING

Django ForeignKey Example

Now, when we understand the theoretical background, we will give some practical examples.

We will define two models in the following example, Book and Author. We’ll also assume that Book can have only one Author (ManyToOne Relationship) to reduce complexity.

Our models look like this:

class Author(models.Model):
    name = models.CharField(max_length=512)


class Book(models.Model):
    title = models.CharField(max_length=512)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

Now you can see the actual purpose of the ForeignKey Field in practice. It connects the Book model with the Author model.

Django ForeignKey defining the book's author.

Where to put ForeignKey?

To define a relationship between two models, you need to define the ForeignKey field in the model from the Many side of the relationship.  In other words, ForeignKey should be placed in the Child table, referencing the Parent table.

NOTE - Without an Author, there can't be Book (Author is parent, Book is child model)

In given example – One Author can write multiple books, and one Book can be written only by one Author. That’s why we placed ForeignKey in the Book model.

AuthorBook
William ShakespeareHamlet
William ShakespeareMacbeth
William ShakespeareRomeo and Juliet
Miguel de CervantesDon Quixote

When to use ForeignKey instead of choices?

As you may already know, the Django Field class by default has a choices option. The choices option allows us to define already known data (choice) that could be inserted into the database.

So when should you use ForeignKey and when other Fields with choices?

Consider using the choices parameter if your database table doesn’t change and remains practically the same. 

Why would you choose choices over ForeignKey?

Without having an additional table in the database, you may save time on database queries (as they are some of the most time-consuming operations in web development).

Example of using choices parameter knowing our application will have only two authors:

class Book(models.Model):
    class Author(models.TextChoices):
        WS = 'William Shakespeare'
        MC = 'Miguel de Cervantes'

    title = models.CharField(max_length=512)
    author = models.CharField(max_length=512, choices=Author.choices)

Choosing choices or ForeignKey is highly dependent on the use case, the selection between optimization and scalability. But even if the wrong option is selected in some early development stages, it can always be corrected by writing custom migration files.

How is Django ForeignKey saved to the database?

ForeignKey saves, as the name suggests, the foreign key to the database. The foreign key represents the primary key of the other instance.

In standard practice, BigInt almost always references other instances as ForeignKey because the primary key of other instances is BigInt.

Let’s have a look at the database structure of the Book model from the first example:

idtitleauthor_id
bigintcharacter varying (255)bigint
NOTE - Primary key in the referenced table doesn't have to be BigInt. ForeignKey can represent other data structures as well. (CharField, DatetimeField, …)

Another important thing to say is how is a column named in the database? Standard Django behavior adds _id suffix to the name written in the model. So, for example, the author is added _id suffix making it author_id as a database column name.

Django ForeignKey ORM

Object-relational mapping (ORM) is a Django feature that helps us write queries in Django/python way.

In the following example, I will show you what can we do with ORM and ForeignKey field:

# create author
>>> author = Author.objects.create(name="Miguel de Cervantes")
<Author: Author object (1)>

# create book with given author
>>> Book.objects.create(title="Don Quixote", author=author)
<Book: Book object (1)>


# get author object
>>> Book.objects.get(id=1).author
<Author: Author object (1)>

# get author id
>>> Book.objects.get(id=1).author.id
1

# get author name
>>> Book.objects.get(id=1).author.name
'Miguel de Cervantes’

Django ORM can help us retrieve all books connected to a given Author, working with the reverse relationship. For example, we have 3 books related to Miguel de Cervantes (representing Author with id=1) in the database. With book_set, we can retrieve all books that are connected to him.

>>> Author.objects.get(id=1).book_set.all()
<QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>]>

ORM queries might change if we use built-in parameters in models, so let’s check it out.

Additional ForeignKey Parameters

In this article, we’ll cover some of the most used parameters such as:

  • related_name
  • related_query_name
  • to_field

To modify _set name, we can use the related_name parameter in ForeignKey Field. Default behavior sets related_name to column_name + _set suffix.

class Author(models.Model):
    name = models.CharField(max_length=512)


class Book(models.Model):
    title = models.CharField(max_length=512)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

As you can see, the author column in the book model now has related_name=’books’. We can now fetch ‘book_set’ using ‘books’ as a lookup field.

>>> Author.objects.get(id=1).books.all()
<QuerySet [<Book: Book object (1)>, <Book: Book object (2)>, <Book: Book object (3)>]>

Code now looks a bit cleaner and more intuitive.

NOTE - If you dont want backwards relationship, you can define related_name = ‘+’ in your model.
WARNING - If all() parameter is not added to the book_set, Django ORM returns RelatedManager object that is not iterable!
>>> Author.objects.get(id=1).book_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x7faa0941fd90>

When using ORM, we can perform queries on the connected instance. related_query_name can be changed in Django models using the related_query_name parameter, changing the syntax queries are performed.

First, let’s try to fetch queryset of Authors that wrote Book with the title “Hamlet” without related_query_name:

>>> Author.objects.filter(books__title__icontains="Hamlet")
<QuerySet [<Author: Author object (1)>]>

Using related_query_name changes lookup field for querysets. It might sound a bit abstract but let’s see this in action. First, we need to change our Book model:

class Author(models.Model):
    name = models.CharField(max_length=512)


class Book(models.Model):
    title = models.CharField(max_length=512)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books', related_query_name="book")

Now we can perform queries using the book as a lookup field.

>>> Author.objects.filter(book__title__icontains="Hamlet")
<QuerySet [<Author: Author object (1)>]>

to_field

to_field changes column referenced in table, by default Django uses the primary key of the related object.

class Author(models.Model):
    name = models.CharField(max_length=512, unique=True)


class Book(models.Model):
    title = models.CharField(max_length=512)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, to_field='name')

We set to_field='name', which means the Book model saves the name of Author as author_id in the Book table.

WARNING - The referenced field has to have unique=True.

More parameters

You can find a few additional parameters not covered in this article in official Django documentation because they are rarely used.

  • db_constraint 
    • Controls whether constraint should be created in the database
    • Defaults True – and should stay True 🙂
  • swappable
    • Defaults True
WARNING - It is recommended to leave the default settings of the specified parameters.

Summary

For any kind of working with relational databases in Django, you should feel comfortable working with ForeignKeys. In this article, we covered the basics. Soon, we’ll cover more advanced stuff covering optimization with powerful tools such as prefetch_related and select_related optimizing our queries.