The Art of Data Integrity: Exploring Django's on_delete Strategies

The Art of Data Integrity: Exploring Django's on_delete Strategies

ยท

5 min read

In the previous article, we discussed about the Django ORM (Object Relation Mapping) in detail. From what to why and then to how, we discussed everything in a good detail.

๐Ÿ’ก
If you haven't gone through that, follow https://musaaib.hashnode.dev/the-power-of-django-orm and master it ๐Ÿ”ฅ

Before diving into the theoretical explanation of the actual topic, let's discuss a real world example, so that we can relate everything we discuss. Let's consider a social media account. If we try to break the database design down, though it would be quite complex, we'd assume it to be quite simple just to get a good understanding of the topic we'd be discussing. Suppose, it consists of the following database models.

  • User

  • Profile

  • Posts

  • Comments

  • Archive

Let's try to understand establish relationships between the models. A graphical representation would help us visualise it easily.

I don't think there's a need to explain the fields. We'll just discuss the relationships between them, which would help us understand the on_delete parameter.

So, the first model is the User model. When you create an account, an instance of this model is created. Second one is the Profile. This, in a good system, is automatically created once a User model instance is created. Profile model has a One-to-One relationship (Foreign Key with a unique constraint) with User i.e., A user can be associated with one profile only. We have a Post model that has a Many-to-One relationship (Foreign Key) with User. We also have a Comment model that has two Foreign Keys, one with the User model and one with the Post model. Moreover, we have Archive model that has a One-to-One relationship (Foreign Key with a unique constraint) with Post.

Note: We can use Django signals to automatically create Profile instance when a User instance is created. Visit https://musaaib.hashnode.dev/django-signals-demystified-streamlining-model-interactions for a detailed article on Django Signals!

Putting it in simple words, we have some database models with some relationships between them. Now, let me ask you a question, what if a user deletes his account, how do all his posts, profile and comments disappear? Also, if we have a Messages model, how are they preserved.

That's where the on_delete parameter of Django models comes into play. This on_delete method specifies what needs to be done to the instance if the referenced object is deleted, e.g., If a User instance is deleted, what needs to be done to the Post instance that has a relationship with the deleted User instance. So, based on our requirements, we can specify how to handle the related or referencing objects upon deletion of the referenced object. Somewhere, we have to delete the referencing object, e.g, in case of posts and comments, we delete them if the user account is deleted. Somewhere, we don't do anything, e.g., in case of orders or messages, if the referenced user account is deleted, we just don't do anything with the messages or the orders placed. There are other cases as well in which, somewhere, we protect the referenced object, somewhere we nullify the key field in the referencing object, somewhere we set that to a default, somewhere we set that to something we want to or somewhere we just don't do anything, just like we don't do anything with the books we get when a semester starts, till it ends and when it ends, then we do a very important task, you know, returning the books ๐Ÿ˜‰.

Anyways. So, to manage these actions on deletion of the reference data, we can use the on_delete parameter in Django models, which includes the following options.

  • CASCADE

  • PROTECT

  • SET_NULL

  • SET

  • SET_DEFAULT

  • DO_NOTHING

So, let's see how we can use each of them and what would they do.

  1. CASCADE: The CASCADE method ensures that when the referenced object is deleted, all related objects are also deleted. Example:

     class Author(models.Model):
         name = models.CharField(max_length=100)
    
     class Book(models.Model):
         title = models.CharField(max_length=200)
         author = models.ForeignKey(Author, on_delete=models.CASCADE)
    
  1. PROTECT: The PROTECT method prevents deletion of the referenced object if there are related objects. Example:

     class Category(models.Model):
         name = models.CharField(max_length=50)
    
     class Product(models.Model):
         name = models.CharField(max_length=100)
         category = models.ForeignKey(Category, on_delete=models.PROTECT)
    

    So, what happens here is, if I try to delete a Category, which, some Product instance is related to, it would raise an error called as ProtectedError. We can handle this error wherever we have to. Example:

     category_to_delete = Category.objects.get(id=1)
    
     try:
         category_to_delete.delete()
     except models.ProtectedError as e:
         print(f"Cannot delete category: {e}")
    
  2. SET_NULL: The SET_NULL method sets the ForeignKey to NULL when the referenced object is deleted. Example:

     class Student(models.Model):
         name = models.CharField(max_length=100)
    
     class Result(models.Model):
         subject = models.CharField(max_length=100)
         marks = models.IntegerField()
         student = models.ForeignKey(Student, on_delete=models.SET_NULL())
    
  3. SET: The SET method sets the ForeignKey to a specified value when the referenced object is deleted. Example:

     class City(models.Model):
         name = models.CharField(max_length=50)
    
     class Residence(models.Model):
         address = models.CharField(max_length=200)
         city = models.ForeignKey(City, on_delete=models.SET('Unknown'))
    
  4. SET_DEFAULT: The SET_DEFAULT method sets the ForeignKey to its default value when the referenced object is deleted. Example:

     class Team(models.Model):
         name = models.CharField(max_length=100, default='Unassigned')
    
     class Player(models.Model):
         name = models.CharField(max_length=100)
         team = models.ForeignKey(Team, on_delete=models.SET_DEFAULT('Unassigned'))
    
  5. DO_NOTHING: The DO_NOTHING method is used to specify that nothing should happen to the dependent objects when the referenced object is deleted. It is a way to handle the situation where we want to leave the dependent objects unaffected and manage the relationships at the application level. Example:

     class User(models.Model):
         username = models.CharField(max_length=50)
         email = models.EmailField(unique=True)
    
     class UserProfile(models.Model):
         user = models.OneToOneField(User, on_delete=models.DO_NOTHING)
         bio = models.TextField(blank=True)
    

    Here, if we delete a User instance, that if referenced by any UserProfile instance, nothing would happen with the UserProfile.

In conclusion, the on_delete parameter in Django is a versatile tool that plays a crucial role in managing relationships and ensuring data integrity within our models. Throughout this comprehensive guide, we delved into various methods available for on_delete and provided illustrative examples to guide you through their usage.

From the straightforward CASCADE method, which ensures the deletion of related objects, to the nuanced approaches of SET and DO_NOTHING, each method offers distinct advantages based on the specific needs of your application. The flexibility to choose between these methods empowers developers to tailor their data models to align with the intricacies of their projects.

Go Django

Keep Learning, Keep Sharing and Keep Djangoing!

Peace, Dot!

ย