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.