How to use Foreign Keys in your Django project

Relational databases store data in multiple tables (one table per entity type) with relationships between the tables as required.

In Django, we define relationships by including a OneToOne, ForeignKey or ManyToMany field on the model. The field we need depends on what type of relationship exists between the two tables.

This post will focus on the implementation of foreign keys, but first, let’s recap when we’d use each type of relationship:

One To One

A user can have one profile and a profile can only belong to one user.

One To Many / Many to One

A post can have one author but an author can have multiple posts.

A post can have multiple comments but a comment can only belong to one post.

Many To Many

A blog post can have multiple tags and a tag can have multiple blog posts.

The table below summarises what field you need for each kind of relationship. Today, we are going to focus on how to use a Foreign Key, the field used for One-to-Many and Many-to-One relationships.

![A diagram showing what kind of field you need for different relations. One to One requires a One to One field. One to Many requires a foreign key Many to one requires a foreign key Many to Many requires a Many to Many field.## Case Study: blog

Below is a schema for a blog with users.

Users can publish posts and they can leave comments on the posts of other users.](ctrlzblog.com/wp-content/uploads/2022/06/re.. align="left")

Case Study: blog

Below is a schema for a blog with users.

Users can publish posts and they can leave comments on the posts of other users.

How to Use Foreign Keys in Django Models

Let’s turn this into code. Here is models.py:

from django.db import models
from django.contrib import auth

USER = auth.get_user_model()

class UserProfile(models.Model):
    user = models.OneToOneField(to=USER, related_name="profile", on_delete=models.PROTECT)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    bio = models.TextField()

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

class Tag(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return f"{self.name}"

class Post(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    author = models.ForeignKey(to=UserProfile, on_delete=models.CASCADE, related_name="posts")
    tags = models.ManyToManyField(to=Tag, related_name="posts", blank=True)

    def __str__(self):
        return f"{self.title} by {self.author.first_name} {self.author.last_name}"

class Comment(models.Model):
    post = models.ForeignKey(to=Post, on_delete=models.CASCADE, related_name="comments")
    body = models.TextField()
    author = models.ForeignKey(to=UserProfile, on_delete=models.CASCADE, related_name="comments")

    def __str__(self):
        return f"Comment by {self.author.first_name} {self.author.last_name} on {self.post.title}"

So what goes into a Foreign Key?

to

The first thing we must set is which model we are setting the relationship with. A comment can have one post, so we set a Foreign Key on the Comment model and point it to the Post model.

post = models.ForeignKey(to=Post, ...)

on_delete

The on_delete argument specifies what should happen when the referenced object is deleted. In a One-to-Many or Many-to-One relationship, it is asking what do you do with the Many when the One is deleted. In our example, it is asking what to do with the comments when their post is deleted.

Your options include CASCADE, PROTECT, SET_NULL and SET_DEFAULT

We have used CASCADE for the foreign key on Comment to Post. This means, when we delete the post, we will also delete the comments.

If we were to use PROTECT , then we would be unable to delete the post until we have deleted the comments.

related_name

When you define a relationship between models, the Foreign Key only goes on one of the models, never both. It always goes on the model of Many and reference the model of the One.

The related_name argument lets you specify how to access the reverse relationship.

The Comment model has a ForeignKey to the Post model. However, you still might want to find all the comments for a given post.

What you must not do is add a new attribute to the Post model called ‘comments’. Instead, you set the related name on the Foreign Key of the Comment model. By setting the related name to “comments”, you can access all the comments belonging to one post through post.comments.all().

Troubleshooting

Model is not defined

You might get errors saying something along the lines of "Post" is not defined.

This happens when the model you’re pointing your Foreign Key at is defined later on in the file. So if your Comment model has a Foreign Key pointing at the Post model, you must define your Post model before defining Comment.

Missing on_delete

If you get this error, it means that there is a Foreign Key in your models that hasn’t set a value for on_delete . Set it to either models.CASCADE, models.PROTECT or models.DO_NOTHING

TypeError: __init__() missing 1 required positional argument: 'on_delete'

Conclusion

Foreign keys are used to define relationships between tables when there is a one-to-many or many-to-one relationship between the tables.

One example is the relationship between a blog post and comments. A post can have multiple comments but a comment can only belong to one post.

Foreign Keys are always defined on the model that can only have one of the other. This means we set the Foreign Key on the Comment model, not the Post model.

By setting the “related_name”, you can access all comments for a given post without adding anything to the Post model.

Django has a rule where you have to set the “on_delete” attribute of Foreign Keys. Here, you are instructing Django what to do with the comments when the linked post is deleted (delete them or keep them).