160

I have just started implementing signal listeners in a django project. While I understand what they are and how to use them. I am having a hard time figuring out where I should put them. The documentation from the django site has this to say:

Where should this code live?

You can put signal handling and registration code anywhere you like. However, you'll need to make sure that the module it's in gets imported early on so that the signal handling gets registered before any signals need to be sent. This makes your app's models.py a good place to put registration of signal handlers.

While its a good suggestion, having non model classes or methods in my models.py just rubs me the wrong way.

So then, what is the best practice/rule for storing and registering signal handlers?

Jason Webb
  • 7,938
  • 9
  • 40
  • 49

7 Answers7

269

This was added to the documentation when Django 1.7 was released:

Strictly speaking, signal handling and registration code can live anywhere you like, although it’s recommended to avoid the application’s root module and its models module to minimize side-effects of importing code.

In practice, signal handlers are usually defined in a signals submodule of the application they relate to. Signal receivers are connected in the ready() method of your application configuration class. If you’re using the receiver() decorator, simply import the signals submodule inside ready().

Changed in Django 1.7: Since ready() didn’t exist in previous versions of Django, signal registration usually happened in the models module.

Best practice is to define your handlers in handlers.py in a signals submodule, e.g. a file that looks like:

yourapp/signals/handlers.py:

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

@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    pass

The best place to register your signal handler is then in the AppConfig of the app that defines it, using the ready() method. This will look like this:

yourapp/apps.py:

from django.apps import AppConfig

class TasksConfig(AppConfig):
    name = 'tasks'
    verbose_name = "Tasks"

    def ready(self):
        import yourproject.yourapp.signals.handlers #noqa

Make sure you're loading your AppConfig by specifying it either directly in your settings.py's INSTALLED_APPS, or in the __init__ of your app. See see the ready() documentation for more information.

Note: If you're providing signals for other apps to listen too as well, put them in the __init__ in your signals module, e.g. a file that looks like:

yourapp/signals/_init_.py

import django.dispatch

task_generate_pre_save = django.dispatch.Signal(providing_args=["task"])

Another app can then listen to your signal by importing and registering it, e.g. from yourapp.signals import task_generate_pre_save. Separating your signals from your handlers keeps things clean.

Instructions for Django 1.6:

If you're still stuck on Django 1.6 or lower, then you'd do the same thing (define your handlers in yourapp/signals/handlers.py) but rather than using AppConfig, you would load the handlers via the _init_.py of your app, e.g. something like:

yourapp/_init_.py

import signals

This isn't as nice as using the ready() method because it often causes circular import issues.

mlissner
  • 17,359
  • 18
  • 106
  • 169
Aidan
  • 4,150
  • 2
  • 20
  • 16
  • 3
    as the documentaiton says you override ready, you may want to do something like super(ReportsConfig, self).ready() in case django ever decides to populate ready() with something (as of 1.7.0 it's currently empty) – w-- Oct 04 '14 at 19:00
  • 3
    I think this answer is the best because it is the only one to address the side-effects from imports. I came here searching for best practices, because I am cleaning up an application, which is broken exactly due to this kind of side effects. Alas the application is running on django 1.6, and best practices only work on django 1.7. The temporary workaround of letting `__init__` import signals wouldn't work for me, so I wonder if there is another place I could import signals from until we are ready to upgrade to a later django version. – kasperd Nov 17 '14 at 14:45
  • 1
    Shouldn't there be `from . import handlers` (or similar) in `yourapp/signals/__init__.py`? – dhobbs Jun 03 '15 at 19:29
  • 1
    Shouldn't you also import the handlers.py module somewhere? I am trying this and it doesn't seem to be defining the handler for the signal. – Andrés Jun 14 '15 at 01:09
  • I've updated my examples to make it clearer -- thanks. – Aidan Jun 16 '15 at 13:28
  • 2
    fwiw I did not need the `yourproject.` in the last line of the TaskConfig class code block. I've got this working with exactly this structure, so consider this qa :) – Greg Kaleka Sep 14 '17 at 01:06
  • importing my signals in the ready() method did not work. I went back to just doing it in models.py and setting dispatch_uid to avoid duplicates – Aaron McMillin Sep 27 '17 at 15:32
  • This should really be the accepted answer. Thanks ! https://docs.djangoproject.com/en/2.1/topics/signals/ – rowman Aug 08 '18 at 10:55
  • Should it not be "from yourapp.signals import handlers"? – Zack Plauché Sep 04 '20 at 14:31
  • 1
    @ZackPlauché no, because you're not importing handlers to use `handlers` you're simply importing that file. – Aidan Sep 05 '20 at 21:22
  • 1
    @AaronMcMillin your app handler isn't running, probably because you forgot to add default_app_config to your __init__.py, see https://docs.djangoproject.com/en/3.1/ref/applications/ – Aidan Sep 05 '20 at 21:23
  • @Aidan yeah I actually see that it wasn't owrking because I forgot to use "myapp.app.MyappConfig" in settings haha. The code above works perfect without defining the "myproject" – Zack Plauché Sep 06 '20 at 12:45
  • This really felt like a lot of ceremony. We just put our signals in `my_app.signals.py`. No idea why you need a module for something so simple that will only ever have one file. – mlissner Aug 09 '22 at 18:04
  • @mlissner Do you mean ‘myapp/signals.py’ or do you have all your files in one folder? Where do you put your handlers? – Aidan Aug 10 '22 at 20:55
  • @Aidan, instead of putting stuff in handlers.py in your example, we just put it in `my_app/signals.py`. – mlissner Aug 10 '22 at 21:44
  • As your project gets larger, you might start dealing with issues with circular imports — separating your signals and your handlers helps with this, and gives you a little more control over model load ordering, but use the simplest structure that works for your codebase. – Aidan Aug 12 '22 at 00:13
41

I actually like to make them classmethods of the model itself. That keeps everything within one class, and means you don't have to worry about importing anything.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 2
    And where do you usually connect handlers to the signals? – DataGreed Dec 28 '10 at 17:42
  • 1
    @DataGreed: at the bottom of the relevant models.py. – Daniel Roseman Dec 28 '10 at 19:05
  • 109
    If you're listening to the signals emitted by that model then putting all the listeners there too makes the whole exercise pointless, doesn't it? The point of signals is to decouple. Shouldn't the listeners live with the code that is interested in these remote events? The question is how to ensure the listeners are loaded before the emitters. – John Mee May 04 '11 at 02:02
  • In my case I want to listen to a signal of model `Foo` which is part of `fooapp`. But the signal receiver is an extension and does live in an different app (for example `otherapp`). – guettli May 06 '14 at 08:02
  • 3
    To John Mee's point, it's not much different than just overriding save(), etc. – Matt May 23 '14 at 17:28
  • This works better as a staticmethod (no cls parameter required).Then you can `post_delete.connect(Model.ensure_delete)` below the class. – psilocybin Oct 31 '19 at 11:33
  • 1
    Here's a good example of when model methods are better, and recommendations for when/how Signals are useful: https://lincolnloop.com/blog/django-anti-patterns-signals/ – theUtherSide Mar 30 '20 at 20:43
40

I've only just come across this, and as my signals are not model-related I thought I'd add my solution.

I am logging various data around log in / log out, and needed to hook into django.contrib.auth.signals.

I have put the signal handlers into a signals.py file, and then imported signals from the __init__.py module file, as I believe this is called as soon as the app starts up (testing with a print statement suggests that it's called even before the settings file is read.)

# /project/__init__.py
import signals

and in signals.py

# /project/signals.py
from django.contrib.auth.signals import user_logged_in

def on_logged_in(sender, user, request, **kwargs):
    print 'User logged in as: \'{0}\''.format(user)

user_logged_in.connect(on_logged_in)

I'm pretty new to Django (/python) so am open to anyone telling me that this is a terrible idea!

Hugo Rodger-Brown
  • 11,054
  • 11
  • 52
  • 78
  • 3
    This feels logical but I'd suggest doing it at the app level. – Nils Jul 28 '12 at 03:11
  • 3
    Careful, this logic will most likely result in duplicate signals beign fired. `user_logged_in.connect(on_logged_in)` should most likely be passing in the `dispatch_uid` argument. More at https://docs.djangoproject.com/en/dev/topics/signals/#preventing-duplicate-signals. – Scott Coates Apr 23 '13 at 00:25
  • Thanks for that - good to know. I'm logging all logins using this method (recording IP / user agent), and haven't had any duplicates so far - although that doesn't mean a small change down the line won't cause a problem! – Hugo Rodger-Brown Apr 23 '13 at 10:01
13

I just recently read this article about best practices when it comes to lay out your projects/applications, and it suggests that all your custom dispatcher signals should go in a file called signals.py. However, that doesn't fully solve your problem, since you still need to import these somewhere, and the earlier they get imported the better.

The model suggestion is a good one. Since you already defined everything in your signals.py file, it shouldn't take more than a line at the top of the the file. This is similar to the way the admin.py file is laid out (with class definitions at the top and the code for registering all the custom admin classes at the bottom), if you define your signals then connect them in the same file.

Hope that helps! Ultimately it comes down to what you prefer.

hora
  • 3,661
  • 5
  • 25
  • 26
  • 1
    I also wanted to put my signal handlers in a `signals.py` file, but didn't know how it should be called afterwards. By importing it in my `models.py` file, I got a very clean solution, without "polluting" my models.py file. Thank you! :) – Danilo Bargen Jul 22 '11 at 15:54
  • 11
    there is a cross-import there: signals.py tries to import model from models.py – Ivan Virabyan Aug 05 '11 at 16:27
9

models.py and signals.py in each app have been the recommended places to connect signals, however, they are not the best solution, in my opinion, to keep signals and handlers dispatched. Dispatching should be the reason signals and handlers invented in django.

I was struggling for long time, and finally we figured out the solution.

create a connector module in app folder

so we have:

app/
    __init__.py
    signals.py
    models.py
    connectors.py

in app/connectors.py, we defined signal handlers and connect them. An example is provided:

from signals import example_signal
from models import ExampleModel
from django.db.models.signals import post_save, post_delete

def hanndler(sender, *args, **kwargs):
    pass

post_save.connect(hander, sender=ExampleModel)

then in models.py, we add the following line in the end of the file:

from app import connector

Everything done here.

In this way, we can put signals in signals.py, and all the handlers in connectors.py. No mess in models and signals.

Hope it provides another solution.

samuel
  • 399
  • 4
  • 11
  • 1
    So what goes in signals.py? Looks like from your example it's just the custom signals. Usually we just combine the signals and connectors as most won't have custom signals. – dalore Jul 11 '13 at 10:50
  • @dalore yes, all custom signals are put in signals.py. We have many customized signals. But if you don't have many, this file could be omitted. – samuel Aug 30 '13 at 02:45
  • same question as @dal – olleh Jun 18 '18 at 02:39
  • 1
    note all of this is now old advice, the django way now is to use appconfig to import handlers where the signal handlers live. And in signals.py go custom signals – dalore Jun 19 '18 at 13:40
6

Small reminder about AppConfig. Don't forget to set:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
valex
  • 5,163
  • 2
  • 33
  • 40
  • 1
    THIS SAVED ME! I was trying to use django-tenant-schemas and it complains when I register my apps using `my_app.apps.MyAppConfig` (This should be a problem with django-tenant-schemas).. With `default_app_config` set, I can use my app's name to register it.. – Ebram Shehata Mar 10 '22 at 23:41
3

I keep them in a separate file signals.py , In models.py after all models are defined. I import them and connect models to signals.

signals.py

#  necessary imports

def send_mail_on_save(<args>):
    # code here 

models.py

# imports
class mymodel(models.Model):
    # model here

# import signals
from signals import send_mail_on_save
# connect them 
post_save.connect(send_mail_on_save,sender=mymodel)

This provides me logical separation, of course there is nothing wrong on keeping them in models.py , But it is more manageable this way.

Hope this helps!!

ALLSYED
  • 1,523
  • 17
  • 15