Signals in Django

Signals are used to do any action in response to a model instance being modified. Signals are tools that assist us in connecting occurrences to actions. We can create a function that will execute when a signal is received. In other words, Signals are used to respond to the alteration or creation of a specific database entry.

For example, when a new user instance is generated in the database, one might want to create a billing profile instance if you are dealing with an e-commerce system.

What are Signals and how do they work?

Signals allow programs to be notified when a specific event occurs, as the name suggests. Let’s say you want to alert the author of a blog post anytime someone comments or reacts to it, but you have multiple places in your codebase where people can remark or respond to it.

How do you go about doing it? Signals, as you may have guessed. When the save method of a given model is activated, Signal hooks some code to be executed.

When should you use signals?

When different code sections are interested in the same model instance events, signals are the ideal option. Put another way, model instance events are when a row is created, updated, or deleted.

There are three different sorts of signals.

  • pre_save/post_save – this Signal is used before and after the save() method.
  • pre_delete/post_delete -this Signal is triggered before and after a model’s instance is deleted (method delete()).
  • pre_init/post_init – this Signal is triggered before/after the init(,) method instantiates a model.

What is the best way to use Signals in Django?

For instance, in creating a user’s billing profile in an e-commerce system, as soon as the user is created, we can utilize the post_save Signal.

While there are various ways to use signals, we like to use the ‘@receiver’ decorator.


Here’s an illustration:

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

@receiver(post_save, sender=BlogComments)
def alert_user(sender, instance, created, **kwargs):
  if created:
    # you can call your function here to notify the author

The receiver decorator informs the code that the function(‘alert_user’ in this example) is about to receive a signal. In this case, it is the post_save Signal.

There are many different kinds of Django signals. We have utilized one of them, post_save, in this demo. We will explore further the essential signals one by one in the coming sections.

But before we cover the type of signals comprehensively, we will first explore the code snippets below on a model, a view, a form, and a signal to illustrate how they work in harmony.

# model.py

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


class BillingProfile(models.Model):
  user = models.OneToOneField(User, on_delete=models.CASCADE)
  user_image = models.ImageField(default='user_image.jpg', upload_to='profile_images')
  
  def __str__(self):
    return f'{self.user.username}'

# view.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm, UserUpdateForm, ProfileUpdateForm


def userRegistration(request):
  if request.method == 'POST':
    form = UserRegistrationForm(request.POST)
    
    if form.is_valid():
      form.save()
      username = form.cleaned_data.get('username')
      messages.success(request, f'Account created successfully! proceed to log in')
      
      return redirect('login')
    else:
      form = UserRegistrationForm()
    return render(request, 'user_info/register.html', {'form': form})
  
@login_required
def billingProfile(request):
  if request.method == 'POST':
    
    u_form = UserRegistrationUpdateForm(request.POST, instance=request.user)
    p_form = billingProfileUpdateForm(request.POST,request.FILES,instance=request.user.profile)
    
  if u_form.is_valid() and p_form.is_valid():
    u_form.save()
    p_form.save()
    
    messages.success(request, f'Account updated!')
    
	return redirect('billing-profile')
  
  else:
    u_form = UserRegistrationUpdateForm(instance=request.user)
    p_form = billingProfileUpdateForm(instance=request.user.profile)
    
    context = {
    'u_form': u_form,
    'p_form': p_form
     }

return render(request, 'billing/billing_profile.html', context)

# forms.py

from django.contrib.auth.models import User
from .models import BillingProfile
from django.contrib.auth.forms import UserCreationForm
from django import forms

class UserRegistrationForm(UserCreationForm):
  email = forms.EmailField()
  
  class Meta:
    model = User
    fields = ['first_name','last_name',  'username', 'email', 'password1', 'password2']
    
 
class UserRegistrationUpdateForm(forms.ModelForm):
  email = forms.EmailField()
  
class Meta:
    model = User
    fields = ['first_name','last_name',  'username', 'email']
    
    
class billingProfileUpdateForm(forms.ModelForm):
  
  class Meta:
    model = BillingProfile 
    fields = ['user_image']

# signals.py

from django.db.models.signals import post_save, pre_delete
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import BillingProfile


@receiver(post_save, sender=User)
def create_billing_profile(sender, instance, created, **kwargs):
  if created:
    BillingProfile.objects.create(user=instance)
    
@receiver(post_save, sender=User)
def save_billing_profile(sender, instance, **kwargs):
  instance.profile.save()

If you’re new or not very advanced in Django, you might be perplexed by the above piece of code. So, what about we break it down for you.

When the User model is saved, a signal called create_billing_profile is sent, which generates a BillingProfile instance with a foreign key referring to the user’s instance. The save_billing_profile method saves the instance.

Let’s look at the arguments now:

  • receiver – The function that receives and processes the Signal
  • sender – Sends the Signal created
  • created – Determines whether the Model has been created or not
  • instance – This is the created model instance
  • kwargs –these are keyword arguments with wildcards

Another technique to link the Signal to the function is as follows:

from django.apps import AppConfig

class UsersConfig(AppConfig):
  
  name = 'users'
  
  def ready(self):
    import users.signals

How to pre_save through the Receiver Method

The pre_save method is triggered right before the save function is called, and the model is saved only once the pre_save process is successfully executed.

# code

from django.db.models.signals import post_save, pre_delete,pre_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import BillingProfile


@receiver(pre_save, sender=User)
def checker(sender, instance, **kwargs):
  if instance.id is None:
    pass
  
  else:
    current=instance
    previous=User.objects.get(id=instance.id)
    
if previous.reaction!= current.reaction:
  #save method can be called

Connect Method Using Signals

To fire signals, an alternative to the method mentioned earlier is to use the connect method. For example, if you use post_save.connect(my function), it will be called whenever a save method is called.

post_save.connect(my_function_post_save, sender=MyModel)
pre_save.connect(my_function, sender= UserTextMessage)

Types of Signals

Somehow, we chose to have the different types of signals here after going through a few examples above so that when we finally dissect them, they will form a deeper rooting.

In this section, we will explore the essential signals you should know as a Django Developer.

pre_init (django.db.models.signals.pre_init)

This Signal is sent at the start of the model’s init() method whenever you instantiate a Django model.

Arguments sent with this Signal are as follows:

  • sender – The sender is the model class for which an instance has just been created
  • Args – init() receives a list of positional arguments
  • kwargs – The keyword arguments supplied to init() are stored in a dictionary

For example,

pre_init_signal = BlogPost(title=" Discussing pre_init signals", _date_created=timezone.now())

post_init (django.db.models.signals.post_init)

This one, like pre_init, is sent when the init() procedure completes.

Arguments sent with this Signal are as follows:

  • sender – The model class was recently instantiated, and it is the same as the case with pre_init
  • Instance – The instance is the model’s actual instance, which has just been constructed.

It would be best if you didn’t run queries in pre_init or post_init signal receivers for performance reasons because they’ll be executed for each instance returned during queryset iteration.

pre_save (django.db.models.signals.pre_save)

The pre_save is triggered right before the model save() function is invoked. Or the model save method is invoked only after pre_save has completed its task without issues.

We know how to alert the author when a new comment is made (you can even generate a signal when creating a PostReaction), but what if we wish to notify the author when the reaction changes? For similar situations, pre_save comes in handy.

commonly used arguments with the pre_save Signal include:

  • sender -the sender is the instance of the model class
  • instance -It is the instance that is being stored.
  • raw – True if the model is stored precisely as it is shown (i.e. when loading a fixture). Other records in the Database should not be queried or modified since the Database may not yet be in a consistent condition.
  • Using – The database alias is currently in use.
  • update_fields – As supplied to Model, this is the list of fields to update. If update fields were not provided, then it is None otherwise save().

Let us look at the following example,

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

@receiver(pre_save, sender=PostReaction)
def notify_user_the_reaction(sender, instance, **kwargs):
  # do nothing if the instance or the row is being created

if instance.id is None:
  pass

# but if it is being modified
else:
    current = instance
    previous =PostReaction.objects.get(id=instance.id)

    # in case the previous reaction is not of the same magnitude as the current reaction
    if previous.reaction != current.reaction:
        # notify instance.post.author

The ‘created’ variable is not available for usage in pre_save. But, no worries, we’ve got a solution for that as well.
The ‘id’ is the auto-generated primary key for all model instances (or rows).

As new instances of the model are created, this ‘id’ is numerically increased. However, the ‘id’ can only be generated/assigned after the row has been created. As a result, attempting to access the instance.id before the instance(or row) is created will return None in pre_save.

What problem are we trying to solve?

The author should be notified if the reaction changes, according to our problem statement. Let’s try to retrace our steps. We’ll need the current reaction and the previous reaction to see if the response has changed.

If the current response differs from the initial reaction, it is assumed that the reaction has been altered, and we must inform the author. But how can we know what the previous and current responses were? To get this, we must first understand one VERY IMPORTANT feature of the variable instance in post _save and pre_save:

The attributes with values already stored in your model are in the post_save instance. However, the attributes with values that have yet to be saved in your model are in the pre_save instance.

post_save ( django.db.models.signals.post_save)

post_save is similar to pre_save, but it is sent at the end of the save() method.

Arguments sent with this Signal are as follows:

  • sender – the instance of the model class
  • instance – It is the instance that is being stored.
  • created – If a new record is created, this boolean value will be valid.
  • raw – True if the model is stored precisely as it is shown (i.e. when loading a fixture). Other records in the Database should not be queried or modified since the Database may not yet be in a consistent condition.
  • Using – Using is the database alias currently in use.
  • update fields – As supplied to Model, it is the list of fields to update. Use None if update fields were not given or save() otherwise.

pre_delete (django.db.models.signals.pre_delete)

The delete() function of a model and the delete() method of a queryset can send this message at the beginning.

Arguments sent with this Signal are as follows:

  • sender – the sender is the instance of the model class
  • instance – It is the instance that is being removed.
  • using – The database alias is currently in use.

post_delete( django.db.models.signals.post_delete)

Like pre_delete, but sent at the end of a model’s delete() function and the delete() method of a queryset.

Arguments sent with this Signal are as follows:

  • sender – The sender is a model class.
  • Instance – It is the instance that is being removed. In addition, keep in mind that the object will no longer be in the Database, so be cautious with what you do with it.
  • Used – It is the database alias that is being used.

m2m_changed (django.db.models.signals.m2m_changed)

When a ManyToManyField on a model instance is modified, this message is sent. Although this is not strictly a model signal because the ManyToManyField sends it, it is included here because it complements the pre_save/post_save and pre_delete/post_delete signals in terms of tracking model changes.

Arguments sent with this Signal are as follows:

  • sender – The ManyToManyField is described by the intermediate model class ManyToManyField. This class is automatically formed when a many-to-many field is defined; you may access it using the through attribute on the many-to-many field.
  • Instance – It is the instance with the updated many-to-many relationship. In fact, it could be an instance of the sender or the class to which the ManyToManyField belongs.
  • action – Normally the action is a string that specifies the type of update done on the given relationship and can usually be “pre_add”, “post_add”, “pre_remove”, “post_remove”, “pre_clear”, and “post_clear”.
  • reverse – This value indicates which side of the relationship has been updated. That means if the forward or a reverse relation is modified.
  • model – It refers to the type of object that is added to, removed from, or cleared from a relationship.
  • Using – Using denotes the Database in use
  • pk_set – It is a list of primary key values that will be (or have been) added to the relation for the pre_add and post_add operations. Because inserts must filter existing values to avoid a database IntegrityError, this could be a subset of the values submitted to be added. It is a list of primary key values provided to be deleted from the relation for the pre_remove and post_remove operations. It is independent of whether the values will be removed or have already been removed. Non-existent values, in particular, may be provided and will appear in pk_set, even though they do not affect the Database. In addition, the pk_set is None for the pre_clear and post_clear operations.

The Management Signals

Django-admin sends out management signals.

pre_migrate (django.db.models.signals.pre_migrate)

Before starting to install a program, the migrate command sends this message. It isn’t sent out if the application doesn’t have a models module.

post_migrate( django.db.models.signals.post_migrate)

These signals are sent at the end of the migrate and flush commands (even if no migrations are run). Also, note that it isn’t sent out if the application doesn’t have a models module.

Handlers of this Signal should avoid making changes to the database schema, as doing so may cause the flush command to fail if it executes concurrently with the migrate command.

The Request/Response signals

When the core framework processes a request, it sends out signals.

request_started (django.core.signals.request_started)

When Django starts processing an HTTP request, this message is sent.

request_finished( django.core.signals.request_finished)

These signals are sent when an HTTP response to the client is completed by Django

got_request_exception (django.core.signals.got_request_exception)
It is the type of Signal that is sent on the off chance that an exception is encountered during the incoming HTTP response by Django.

Test signals

When running tests, the kind of signals sent are usually called test signals.

setting_changed( django.test.signals.setting_changed)

When a setting’s value is updated using the django.test.TestCase.settings() context manager or the django.test.override_settings() decorator/context manager, this signal is sent.

It is typically sent twice when the new value is applied (“setup”) and when the original value is recovered (“teardown”). To distinguish between the two, use the entering argument.

To avoid importing from django.test in non-test scenarios, you can import this Signal from django.core.signals.

template_rendered ( django.test.signals.template_rendered)

When the test system renders a template, this message is sent. This Signal is only available during testing and is not emitted during regular Django server operation.

Wrappers for databases

During the creation of the database, the database wrapper sends signals.

connection_created (django.db.backends.signals.connection_created)

When the database wrapper connects to the database for the first time, this message is sent. It is essential if you want to transmit any SQL backend post-connection commands.

Conclusion

Even though signals help perform things behind the scenes, you must exercise extreme caution when using them. For instance, do not sacrifice speed by overloading the pre_save or post_save signals. On the off chance that you do, it may cause the model save() method to slow down.

When the save() method for the sender model is called within the post_save or pre_save, the signals are called again. As a result, it may lead to a repetitive loop.

Overall, we use post_save when we want to create a model instance without changing the values, and we use pre_save when we want to monitor the change in the model instance’s value or if we want to change the values of the instance’s attributes ourselves.

Finally, signals update() and save don’t get along because the ‘update()’ method of a Django model does not send any pre_save or post_save signals.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *