What is the difference between using django signals and calling a method?

Viewed 280

I am trying to implement a function which sends a notification to all employee records whenever a new document record is published. In the models, I still needed to import the receiver function because my sender model lives in a different project:

receiver function (lives in a different app in project):

def new_document_version_published(sender, instance, **kwargs):
    print("New version of document published!")
    print(sender)
    print(instance)

    # Get all employees
    employees = []

    # Send notifications to employees
    buttons = [NotificationButton(button_text="New version of document", value="Ok", style="primary")]
    notifyUsers("A new version of the document has been published", buttons, employees, [])

sender (lives in a different app in project):

from django.db.models.signals import post_save
from api.views import new_document_version_published

    class DocumentVersion:
        ...
        def save(self, *args, **kw):
            if self.pk is not None:
                orig = DocumentVersion.objects.get(pk=self.pk)
                if orig.date_published != self.date_published:
                    print('date_published changed')
                    notify_employees()
                if orig.date_approved != self.date_approved:
                    print('date_approved changed')
            super(DocumentVersion, self).save(*args, **kw)
    
    def notify_employees():
        post_save.connect(new_document_version_published, sender=DocumentVersion)

I know there is something wrong with my implementation because I don't understand what is the difference between using the signal and just importing and calling the receiver function. All help appreciated!

1 Answers

What is the difference

Calling a function makes the caller dependent on (or at least aware of) the receiver function, while using Django signals makes the receiver function dependent on the signal being called by its caller(s).

From https://docs.djangoproject.com/en/3.2/topics/signals/:

... helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.

When to use Django signals

From https://www.django-antipatterns.com/antipattern/signals.html:

Signals have a variety of problems and unforeseen consequences. ...

Often it is better to avoid using signals. One can implement a lot of logic without signals. ...

Signals can still be a good solution if you want to handle events raised by a third party Django application. ...

What that difference looks like

Calling a function

Pre-save:

# notify_employees()                                  # Replace this
new_document_version_published(self.__class__, self)  # with this

Post-save:

class DocumentVersion(models.Model):
    ...
    def save(self, *args, **kw):
        orig = None  # .........  # Add this
        if self.pk is not None:
            orig = DocumentVersion.objects.get(pk=self.pk)
            if orig.date_published != self.date_published:
                print('date_published changed')
                # notify_employees()                              # Replace this
            if orig.date_approved != self.date_approved:
                print('date_approved changed')

        super(DocumentVersion, self).save(*args, **kw)

        if orig and orig.date_published != self.date_published:   # with this
            new_document_version_published(self.__class__, self)  #

Using Django Signals

Since Django's post_save signal doesn't pass orig, let's use a custom signal:

  1. Define a signal.
  2. Send the signal.
  3. Implement the receiver function.
  4. Connect the receiver function, usually where the receiver function is defined.

Post-save:

post_save_published = Signal()  # Add this


class DocumentVersion(models.Model):
    ...
    def save(self, *args, **kw):
        orig = None  # .........  # Add this
        if self.pk is not None:
            orig = DocumentVersion.objects.get(pk=self.pk)
            if orig.date_published != self.date_published:
                print('date_published changed')
                # notify_employees()                                        # Replace this
            if orig.date_approved != self.date_approved:
                print('date_approved changed')

        super(DocumentVersion, self).save(*args, **kw)

        if orig and orig.date_published != self.date_published:             # with this
            post_save_published.send(sender=self.__class__, instance=self)  #
def new_document_version_published(sender, instance, **kwargs):
    # ...


post_save_published.connect(new_document_version_published, sender=DocumentVersion)  # Add this
Related