The right place to keep my signals.py file in a Django project
Asked Answered
B

8

100

Based on Django's documentation I was reading, it seems like signals.py in the app folder is a good place to start with, but the problem I'm facing is that when I create signals for pre_save and I try to import the class from model it conflicts with the import in my model.

# models.py

from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext as _
from signals import *

class Comm_Queue(CommunicatorAbstract):
    queue_statuses = (
        ('P', _('Pending')),
        ('S', _('Sent')),
        ('E', _('Error')),
        ('R', _('Rejected')),
    )
    status          = models.CharField(max_length=10, db_index=True, default='P')
    is_html         = models.BooleanField(default=False)
    language        = models.CharField(max_length=6, choices=settings.LANGUAGES)
    sender_email    = models.EmailField()
    recipient_email = models.EmailField()
    subject         = models.CharField(max_length=100)
    content         = models.TextField()

# signals.py

from django.conf import settings
from django.db.models.signals import pre_save
from django.dispatch import receiver
from models import Comm_Queue

@receiver(pre_save, sender=Comm_Queue)
def get_sender_email_from_settings(sender, **kwargs):
    obj=kwargs['instance']
    if not obj.sender_email:
        obj.sender_email='%s' % settings.ADMINS[0][1]

This code will not run because I import Comm_Queue inside signals.py and I also import the signals inside models.py.

Can anyone advice on how I could over come this issue?

Regards

Bartonbartosch answered 18/8, 2011 at 22:53 Comment(1)
possible duplicate of Where should signal handlers live in a django project?Griffie
F
69

Original answer, for Django < 1.7:

You can register the signals by importing signals.py in the app's __init__.py file:

# __init__.py
import signals

This will allow to import models.py from signals.py without circular import errors.

One problem with this approach is that it messes up the coverage results if you're using coverage.py.

Related discussion

Edit: For Django >= 1.7:

Since AppConfig was introduced, the recommended way of importing signals is in its init() function. See Eric Marcos' answer for more details.

Flareup answered 7/1, 2013 at 8:28 Comment(3)
using signals in Django 1.9, use below method(recommanded by django). this method doesn't work giving AppRegistryNotReady("Apps aren't loaded yet.")Clifford
Eric Marcos his answer should be the accepted answer: https://mcmap.net/q/151744/-the-right-place-to-keep-my-signals-py-file-in-a-django-project since Django >= 1.7, using the app configTheorize
Agreed. I'll edit the answer to point to Eric Marcos's answer for Django 1.7+Flareup
C
231

If you're using Django<=1.6 I'd recommend Kamagatos solution: just import your signals at the end of your models module.

For future versions of Django (>=1.7), the recommended way is to import your signals module in your app's config ready() function:

my_app/apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

my_app/__init__.py

default_app_config = 'my_app.apps.MyAppConfig'
Czarina answered 6/2, 2014 at 19:21 Comment(4)
They also mention in the 1.7 documentation that sometimes ready can be called multiple times and so to avoid duplicate signals, attach a unique identifier to your signal connector call: request_finished.connect(my_callback, dispatch_uid="my_unique_identifier") Where dispatch_uid is usually a string but can be any hashable object. docs.djangoproject.com/en/1.7/topics/signals/…Nanna
This should be the accepted answer! Accepted answer above throws an error when deploying using uwsgiIncumber
Hm, doesnt work for me with django 2. If i import model directly in ready - all fine. If i import model in signals and import signals in ready i'm getting a error doesn't declare an explicit app_label ..Contemptible
@Aldarun you can try to put 'my_app.apps.MyAppConfig' inside INSTALLED_APPS.Alcaraz
F
69

Original answer, for Django < 1.7:

You can register the signals by importing signals.py in the app's __init__.py file:

# __init__.py
import signals

This will allow to import models.py from signals.py without circular import errors.

One problem with this approach is that it messes up the coverage results if you're using coverage.py.

Related discussion

Edit: For Django >= 1.7:

Since AppConfig was introduced, the recommended way of importing signals is in its init() function. See Eric Marcos' answer for more details.

Flareup answered 7/1, 2013 at 8:28 Comment(3)
using signals in Django 1.9, use below method(recommanded by django). this method doesn't work giving AppRegistryNotReady("Apps aren't loaded yet.")Clifford
Eric Marcos his answer should be the accepted answer: https://mcmap.net/q/151744/-the-right-place-to-keep-my-signals-py-file-in-a-django-project since Django >= 1.7, using the app configTheorize
Agreed. I'll edit the answer to point to Eric Marcos's answer for Django 1.7+Flareup
K
27

To solve your problem you just have to import signals.py after your model definition. That's all.

Kwangju answered 24/3, 2012 at 12:44 Comment(3)
This is by far the easiest, and I had no idea this would work without a cyclic dependency. Thanks!Dior
Brilliant. Like this one better than my answer. Although I don't really understand how come it doesn't cause a circular import...Flareup
solution doesn't work with enabled autopep8 plugin in Eclipse.Rood
L
6

I also put signals in signals.py file and also have this code snippet that loads all signals:

# import this in url.py file !

import logging

from importlib import import_module

from django.conf import settings

logger = logging.getLogger(__name__)

signal_modules = {}

for app in settings.INSTALLED_APPS:
    signals_module = '%s.signals' % app
    try:
        logger.debug('loading "%s" ..' % signals_module)
        signal_modules[app] = import_module(signals_module)
    except ImportError as e:
        logger.warning(
            'failed to import "%s", reason: %s' % (signals_module, str(e)))

This is for project, I'm not sure if it works at app level.

Listerism answered 8/3, 2012 at 9:28 Comment(4)
This is my favourite solution so far as it fits the other patterns (like tasks.py)Mediate
Found an issue with this one, if you start shell the urls.py doesn't get imported and your signals won't attachMediate
yes, my answer is kind of outdated, it seems that django has AppConfig class these days. Last time I've used django it was version 1.3. Suggesting to investigate around it.Listerism
we are still 1.6 and so i had to move all our signals.py into models otherwise celery and management commands didn't get picked upMediate
M
6

In old Django versions would be fine to put the signals on the __init__.py or maybe in the models.py(although at the end models will be way to large for my taste).

With Django 1.9, it is better I think, to place the signals on a signals.py file and import them with the apps.py, where they are going to be loaded after loading the model.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

    def ready(self):
        from . import signals  # NOQA

You can also divide your signals on signals.py and handlers.py in another folder within your model named signals as well, but for me that is just over engineering. Take a look at Placing Signals

Meaty answered 12/1, 2017 at 13:26 Comment(0)
W
6

This only applies if you have your signals in a separate signals.py file

In completely agree with the answer of @EricMarcos but it should be stated that the django docs explicitly advice not to use the default_app_config variable (although it is not wrong). For current versions, correct way would be:

my_app/apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Make sure you don't just have your app name in installed apps but instead the relative path to your AppConfig)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]
Writein answered 14/5, 2019 at 16:31 Comment(0)
C
4

I'm guessing that you're doing that so your signals are registered, so that they're found somewhere. I just put my signals right in a models.py file normally.

Cleliaclellan answered 18/8, 2011 at 23:38 Comment(2)
yeah when I move the signal inside the model file it solves the problem. But my model.py file is pretty large with all the classes, managers and model rules.Bartonbartosch
Managers are a bit easer to pull out in my experience. Managers.py ftw.Cleliaclellan
A
1

An alternative is to import the callback functions from signals.py and connect them in models.py:

signals.py

def pre_save_callback_function(sender, instance, **kwargs):
    # Do stuff here

model.py

# Your imports here
from django.db.models.signals import pre_save
from yourapp.signals import pre_save_callback_function

class YourModel:
    # Model stuff here
pre_save.connect(pre_save_callback_function, sender=YourModel)

Ps: Importing YourModel in signals.py will create a recursion; use sender, instead.

Ps2: Saving the instance again in the callback function will create a recursion. You can make a control argument in .save method to control it.

Astra answered 21/3, 2012 at 18:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.