Exploring ForeignKey's on_delete Handlers in Django

Django models’ foreign keys require you to set an on_delete function. This is true for ForeignKey, OneToOne, and ManyToMany fields. In this blog post, I will go into detail on what each method does and illustrate when to apply each one.

First and foremost, It is important to understand that the foreign key on_delete handler only works for the related entity, not the source entity:

class Post(models.Model):
	title = models.CharField(max_length=255)

class Comment(models.Model):
	content = models.TextField()  #👇🏻
	post = models.ForeignKey(Post, on_delete=[?])

In the example above, Comment will be the target of any on_delete handler when you delete Post, but not the other way around. If you delete a Comment instance, nothing will happen to Post.

Now that we understand that, let’s get into each of the methods!

models.CASCADE

Let’s take our Post and Comment example and set on_delete to models.CASCADE

class Comment(models.Model):
	...  #                                          👇🏻
	post = models.ForeignKey(Post, on_delete=models.CASCADE)

When we call post.delete(), it will cascade down to the foreign key relationship(s). Meaning that both Post and Comment get deleted.

models.SET(function or value)

The first of the 3 in the models.SET[...] family. For this method, Post and Comment relationships don’t really make sense. You wouldn’t set the foreign key field of a model to a different post when you delete the post.

A more realistic example would be to have a ForeignKey from a Comment to a user. When we delete the user (looking at you, GDPR), we may want to keep their comments. You’ve probably seen this on Reddit

Something similar would be achieved by using the models.SET handler:

def get_deleted_user_instance():
    return User.objects.get(username='deleted')

class Comment(models.Model):
    ...
    author = models.ForeignKey(User,
            on_delete=models.SET(get_deleted_user_instance))
    #                        👆🏻

Now if we were to delete the user instance, it would be replaced by the “deleted” user instance.

models.SET_DEFAULT

Remember the SET function from a few seconds ago? SET_DEFAULT sets the foreign key it its default value as defined in the model.

ANONYMOUS_USER_ID = 1

class Comment(models.Model):
    ...
    author = models.ForeignKey(User, 
            default=ANONYMOUS_USER_ID, 
            on_delete=models.SET_DEFAULT)
    #                        👆🏻

models.SET_NULL

Now that we understand the SET family, this one isn’t very hard to grasp.

It is important to note that you can only add SET_NULL if your model has null=True as one of its properties.

models.PROTECT

Our Post / Comment relationship is very realistic here. Protect throws an exception when you try to delete the related model:

class Comment(models.Model):
	...  #                                          👇🏻
	post = models.ForeignKey(Post, on_delete=models.PROTECT)

If we create a Post, you can still delete it. However, once someone has left a Comment, deleting will be blocked and Django will throw a ProtectedError.

models.DO_NOTHING

As the method name suggests, this will do nothing. The implementation is just a pass:

# in django.db.models.deletion
def DO_NOTHING(collector, field, sub_objs, using):
    pass

This can be pretty dangerous as your model could be pointing at a non-existent entity. You should generally avoid this unless you have relationships managed on a database level (e.g. your database will make sure your foreign keys have integrity).

That’s a wrap!

Hopefully you learned a thing or two from this. Most of what you read here is also written in the official Django documentation but without detailed examples.

If you want to go even deeper (and I’d highly recommend doing so) you should read the Django on_delete handlers source code.