DEV Community

CH S Sankalp jonna
CH S Sankalp jonna

Posted on • Originally published at sankalpjonna.com

Overriding the save method in your Django models

The Django ORM ensures that whenever an object of a database model is created or updated either via the admin interface or somewhere in the code, the save() method is called.

This means that if one wants to perform any action that needs to run before or after saving an entry in the database, this can be achieved by overriding the save method and performing the actions.

However this is usually not recommended unless you know what you are doing because any exception caused in the save() method will prevent the entry from getting created in the database.

So the recommendation is to perform these types of functionalities in your forms, views or serializers instead.

If you would still like to proceed, here is how you can achieve this:

Perform action before database operation

Let us assume that you have a database of blogs and each blog requires a slug which has to be kept read only and should be automatically created when a blog is created.

This can be done by overriding the save method and setting the value of this field before calling the parent save method:

from django.db import models
from django.utils.text import slugify 

# Create your models here.
class Blog(models.Model):
    title = models.CharField(max_length = 50)
    description = models.CharField(max_length = 200)
    slug = models.SlugField()


    def save(self, *args, **kwargs):
        # set the value of the read_only_field using the regular field
        self.slug = slugify(self.title)

        # call the save() method of the parent
        super(Blog, self).save(*args, **kwargs)
Enter fullscreen mode Exit fullscreen mode

Perform action after database operation

Consider a case where you are building an appointment creation software. You would want to send an email to your users every time they create a new appointment with details of that appointment.

This operation needs to be performed only after the appointment is saved in the database for better resilience and it can be done by sending your email after the super method is called:

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

# Create your models here.
class Appointment(models.Model):
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=255)
    appointment_time = models.DateTimeField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)

    def save(self, *args, **kwargs):
        super(Appointment, self).save(*args, **kwargs)

        # send email to the user for setting the appointment
        send_email(self.user.email, self.title, self.description, self.appointment_time)
Enter fullscreen mode Exit fullscreen mode

Using signals

Overriding the save method can be very nifty, however there is a disadvantage of doing this. 

If you have to perform multiple business logics that are independent of each other at the time of saving the object, all of this has to be done inside the save method.

This will make the save method quite cluttered and the code unreadable. It will also make it difficult to isolate the function that is failing when you need to fix bugs.

A better approach is to use Django signals instead. Django has many inbuilt signals which will notify a particular method that an action has taken place and the method can execute some business logic.

The above functionality of performing actions before or after saving an entry in the database can be achieved by using the pre_save and post_save signals respectively.

Using pre_save signal

The above example of creation of a slug before saving a blog entry into the database can be achieved using a pre_save signal as shown below:

from django.db import models
from django.utils.text import slugify 

from django.db.models.signals import pre_save
from django.dispatch import receiver


# Create your models here.
class Blog(models.Model):
    title = models.CharField(max_length = 50)
    description = models.CharField(max_length = 200)
    slug = models.SlugField()


@receiver(pre_save, sender=Blog)
def create_slug(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)

@receiver(pre_save, sender=Blog)
def other_pre_save_actions(sender, instance, *args, **kwargs):
    # perform a different kind of pre save action here
    pass
Enter fullscreen mode Exit fullscreen mode

As you can see, we did not have to override the save method at all and this makes the logic of creating a slug decoupled. You can have any number of receivers for the pre_save signal and perform different actions in each of them.

Do note that this signal is called before the save method is fired and if there is an exception in any of these signals, the object will not get saved.

Using post_save signal

This is pretty much the same as a pre_save signal, except that it is called after the save method finishes running. 

Here is a demonstration of its use by referencing the previous example of sending an email on confirmation of the appointment.

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

from django.db.models.signals import post_save
from django.dispatch import receiver

# Create your models here.
class Appointment(models.Model):
    title = models.CharField(max_length=100)
    description = models.CharField(max_length=255)
    appointment_time = models.DateTimeField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)

@receiver(post_save, sender=Appointment)
def send_appointment_confirmation_email(sender, instance, created, **kwargs):
  if created:
    send_email(instance.user.email, instance.title, instance.description, instance.appointment_time)

@receiver(post_save, sender=Appointment)
def other_post_save_actions(sender, instance, created, **kwargs):
   # perform a different kind of post save action here
   pass
Enter fullscreen mode Exit fullscreen mode

This signal also lets us know whether the object is being created for the first time or not using the created flag. This will enable us to send the appointment confirmation email only once when the appointment is created for the first time.

Since this signal is called every time the object is saved, we have to ensure that the email does not get sent multiple times. 

Closing notes

In conclusion, using pre_save and post_save signals is more elegant than overriding a save method. 

In general one must be careful while performing these type of actions whether or not you choose to override the save method or use signals.

For instance, in the above examples of a Blog model, the slug is saved again and again on each update of the blog entry.

This means a new slug is created every time and this could negatively impact your search engine ranking. You might want to prevent this by checking if a slug already exists before creating a new one for a particular blog post.

To sum it up, do not override the save method or use signals until you are sure that you have handled all of these edge cases.

Originally posted on my blog

Top comments (0)