When developing with Django, you often encounter situations where you want to perform an action when a certain event occurs. For example, you might want to log every time a specific model is saved, or automatically update related data when a user profile is updated. In such cases, Django Signals are very useful. Signals create an event-driven structure that allows you to perform necessary actions while keeping the coupling between your code loose.

In this post, we will explore how to utilize the most commonly used Django Signals: pre_save and post_save.

Django Signals concept illustration

1. Overview of Django Signals

Django Signals provide the functionality to automatically call predefined functions when specific events occur in your application. Developers can define their own signals, but Django provides built-in signals such as pre_save, post_save, pre_delete, and post_delete. These signals are triggered when a model instance is saved or deleted, making them especially useful for database-related actions.

Tip: When setting up signals, it is important to clearly specify the type of signal and the target (model) to avoid unexpected errors.


2. Differences between pre_save and post_save

  • pre_save: Executes before the model instance is saved to the database.
  • post_save: Executes after the model instance is saved to the database.

These two signals fire at different times, so you need to choose the appropriate one depending on what task you want to perform. For example, if you need to change a value before saving, use pre_save. If you want to perform another action after saving, post_save is more suitable.


3. Example of Using pre_save

Now let's take a look at how to use the pre_save signal in practice. For instance, let's say we want to automatically convert a username to lowercase before saving it.

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

@receiver(pre_save, sender=UserProfile)
def lowercase_username(sender, instance, **kwargs):
    instance.username = instance.username.lower()

In the code above, the @receiver decorator connects the pre_save signal to the lowercase_username function. Now, this function will be automatically called before saving a UserProfile model instance, converting the username field to lowercase.

Tip: The pre_save signal is useful for tasks such as data validation or field value transformation before the data enters the database.

Common Mistakes with pre_save

One of the most common mistakes when using the pre_save signal is calling the save method again. For instance, if you accidentally call instance.save() while updating a specific field before saving, you can end up in an infinite loop. Be careful not to call save() again within the signal handling function.


4. Example of Using post_save

Now let's explore using the post_save signal. Suppose we want to send a welcome email when a user signs up. In this case, the post_save signal is very useful.

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from .models import UserProfile

@receiver(post_save, sender=UserProfile)
def send_welcome_email(sender, instance, created, **kwargs):
    if created:  # Only send the email if the user was newly created
        send_mail(
            'Welcome!',
            'Thank you for signing up!',
            'from@example.com',
            [instance.email],
            fail_silently=False,
        )

Here, we've set up the function to send an email only if the created parameter indicates a new object. The post_save signal is triggered after the object has been saved to the database, making it ideal for performing follow-up actions and verifying data.

Example of Utilizing post_save: Updating Related Models

The post_save signal is usually used when you need to perform additional tasks after a model has been saved. For instance, you could use it to automatically update the counts of tags and categories after saving a blog post or log changes to product stock.

from .models import BlogPost, Category

@receiver(post_save, sender=BlogPost)
def update_category_count(sender, instance, created, **kwargs):
    if created:
        category = instance.category
        category.post_count = BlogPost.objects.filter(category=category).count()
        category.save()

The above example updates the post_count of the related category each time a new BlogPost instance is saved. Using the post_save signal allows you to dynamically modify associated data after saving.


5. Considerations When Using pre_save and post_save

  • Avoid Infinite Loops: Be cautious not to call save() again within the signal handling function. Calling save() will trigger the pre_save and post_save signals again, leading to an infinite loop.
  • Conditional Processing: It's good practice to set up your signals to process only under certain conditions. For example, in post_save, you can differentiate between newly created and updated objects by using the created parameter.
  • Location of Signal Registration: The location of signal registration is also important. Generally, it's best to register signals inside the ready() method of the apps.py file or create a separate signals.py file for management. Registering signals in multiple places may lead to unexpected behavior.

In Conclusion

The pre_save and post_save signals in Django enable you to execute various tasks before and after storing data. By utilizing signals, you can improve development efficiency and code flexibility by going beyond simply saving data—such as validating data before saving or updating relationships with other models after saving.

In the next post, we'll look at the pre_delete and post_delete signals, discussing various use cases for operations at the deletion phase. Signals are a tool that makes Django development more interesting and efficient, so be sure to apply them appropriately!