Django Models Foreign Key Integrity
Django database integrity: ForeignKey on_delete options
Django comes with a very robust Object Relational Mapper(ORM) for managing Relational Management Database Management Systems. I will be writing about Django’s ForeignKey on_delete options for determining how deletion of referenced data is handled by the database so as to maintain data integrity.
The options for the on_delete include:
1 CASCADE
2 PROTECT
3 SET_NULL
4 SET_DEFAULT
5 SET()
6 DO_NOTHING
We are going to create two models to demonstrate the use cases for the above options. A Post and a Comment model.
I have defined the serializers and views for the post and comment respectively. For brevity, I have omitted them.
I am using the browerable API to make the requests to the server. I have a DB Browser for SQLite and I will show the contents of the database as we progress.
CASCADE
Cascade emulates the SQL constraint of ON DELETE CASCADE. Whenever the referenced object (post) is deleted, the objects referencing it (comments) are deleted as well. This behaviour is a reasonable default and makes sense for most relationships — for this one as well. You don’t want orphaned comments lying around in your database when the associated post is deleted.
I am going to create a new a post and add a comment to it below:
Here is the post instance in the database:
And here is the comment:
That is the comment referencing the newly created post. Remember we are using an on_delete argument of CASCADE. Now, we are going to delete a post and we expect the comment to go with it.
If you noticed on the browserable API, there is a red DELETE button. This is present because I have defined a DELETE endpoint on the post object.
After pressing the delete the post, the comments should be gone with it; let’s inspect our database to make sure of that.
As expected, deleting a post also deletes all comments referencing it.
While using the CASCADE argument of on_delete option, it is important to think carefully about the use cases, as using this carelessly could wipe relevant data in your database you do not want to go.
While using the CASCADE argument of on_delete option, it is important to think carefully about the use cases, as using this carelessly could wipe relevant data in your database you do not want to go.
PROTECT
The PROTECT argument of the ForeignKey on_delete option prevents the referenced object from being deleted if it already has an object referencing it in the database. Put simply, Django will prevent a post from deletion if it already has comments.
This might not look like a reasonable thing to do in our use case but it has its use cases. A simple example of PROTECT being used is If you were developing an ATM application, and you don’t want an ATM instance deleted if it has money in it.
An important point to note is that, when you make an attempt to delete a post with comment(s), and it has an on_delete constraint of PROTECT, it will raise PROTECTEDERROR, which you can handle when creating your views.
Let’s modify our code and verify all of what we’ve just said.
We are going to make the following changes:
We have modified the argument from CASCADE to PROTECT.
Then we migrate the changes to reflect in the database with:
python manage.py makemigrationspython manage.py migrate
Now, a new POST instance has been created in the database, with a comment just to fast track things.
Now, we tried deleting this POST that already has a comment, forgetting that we have a PROTECT constraint on it. This nice, descriptive error greets us.
Now that we have gotten this sweet, very descriptive error, the only way we can successfully delete the post is if we delete the comments first. Moving my itchy fingers to our nice red DELETE button to remove the comment on the post:
Now the post can be wiped without errors. Sweet!
As with the cascade option, think carefully about the constraints of your project before using it.
SET_NULL
SET_NULL argument of the ForeignKey on_delete option is only available to you when you have set the null option on the ForeignKey field to True. When you use this argument, and, in our case, delete a post, it is going to leave the comments in the database without deleting it.
Sometimes this option is what you need. For instance, if you want to delete a user and keep the number of times he has logged into your app for audit purposes.
I have updated the post ForeignKey field from PROTECT to SET_NULL, and also set null=True.
python manage.py makemigrationspython manage.py migrate
A post has been created with a comment below:
Now, when I delete the post from the browserable API, the referenced post is set to null in the comments table
This approach makes sense if your use case allows reassigning of the referencing instance to a new object.
An example would be tags in a blog post. You might want to still retain tags even after the tagged item has been deleted, so that they can be reassigned to another item.
A word of caution though, use this when you are absolutely certain. We will have orphaned data in our database if not carefully used — causing data integrity violations.
SET_DEFAULT
This argument on the ForeignKey on_delete option requires you to set a default value when defining the relationship. When you delete a post that has comments, the comments are automatically assigned to a default post you had set when creating the model.
You need to set a DEFAULT option on the ForeignKey. We are updating our model to the following:
Created two posts, a default post, and another post just to demonstrate this:
Notice this comment is for the post with id 6.
After deleting the 6th post, the comment is reassigned to the default of id 5, which is the default we set in the model. Cool!
SET()
This is similar to SET_DEFAULT, but more powerful. It allows you to set the value of the ForeignKey to the value passed in the SET(), or a callable you have defined and passed to it as an argument.
This is similar to SET_DEFAULT, but more powerful. It allows you to set the value of the ForeignKey to the value passed in the SET(), or a callable you have defined and passed to it as an argument.
This example below was gotten from Django docs.
When you delete the referenced user, a user with the username of ‘deleted’ is assigned to the instance of MyModel that referenced it.
DO_NOTHING
As the name implies, it does nothing when a referenced object is deleted. This is essentially discouraged because it defeats the purpose of an RDBMS. Your comments are still referencing posts that do not exist, causing a whole lot of bugs, and data integrity errors. INTEGRITYERROR will be raised if your database enforces referential integrity.
As the name implies, it does nothing when a referenced object is deleted. This is essentially discouraged because it defeats the purpose of an RDBMS. Your comments are still referencing posts that do not exist, causing a whole lot of bugs, and data integrity errors. INTEGRITYERROR will be raised if your database enforces referential integrity.
CONCLUSION
As we have seen, ForeignKey on_delete arguments are really powerful and should be used with absolute certainty in your project.
Code available here if you want to play.
Comments
Post a Comment