在Django项目中保存signals.py文件的正确位置

98
基于我所阅读的Django文档,似乎在应用程序文件夹中的signals.py是一个不错的起点,但我遇到的问题是,当我为pre_save创建信号时,尝试从模型导入类会与模型中的import冲突。
# 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]

由于我在signals.py中导入了Comm_Queue,并且我还在models.py中导入了信号,因此此代码将无法运行。

有人能给我建议如何解决这个问题吗?

谢谢


8个回答

229

如果您正在使用Django<=1.6,我建议使用Kamagatos的解决方案:只需在模型模块的末尾导入信号。

对于将来版本的Django(>=1.7),推荐的方法是在应用程序的配置ready()函数中导入信号模块:

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'

7
在1.7文档中还提到,有时可能会多次调用ready函数,为了避免重复信号,将唯一标识符附加到信号连接器调用中:request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")其中dispatch_uid通常是字符串,但也可以是任何可散列的对象。https://docs.djangoproject.com/en/1.7/topics/signals/#preventing-duplicate-signals - Emeka
16
这应该是被接受的答案!上面被接受的答案在使用uwsgi部署时会出现错误。 - Patrick
2
哼,用Django 2对我不起作用。如果我直接在ready里导入模型 - 一切都很好。如果我在信号中导入模型并在ready中导入信号,那么我会遇到一个错误“没有声明明确的app_label”。 - Aldarund
@ Aldarun 你可以尝试将 'my_app.apps.MyAppConfig' 放入 INSTALLED_APPS 中。 - Ramil Aglyautdinov

69

对于 Django < 1.7 的原始回答:

您可以通过在应用程序的__init__.py文件中导入signals.py来注册信号:

# __init__.py
import signals

这将允许从 signals.py 导入 models.py 而不会出现循环导入错误。

这种方法的一个问题是,如果您使用 coverage.py,它会破坏覆盖范围结果。

相关讨论

编辑:对于 Django >= 1.7:

自从引入 AppConfig 以来,导入 signals 的推荐方法是在其 init() 函数中进行。有关详细信息,请参见 Eric Marcos 的答案


6
在Django 1.9中使用信号,请使用下面的方法(Django推荐此方法)。但是,该方法可能无法正常工作并提示AppRegistryNotReady("Apps aren't loaded yet.") - s0nskar
1
Eric Marcos的回答应该被接受:https://dev59.com/5Gw05IYBdhLWcg3wpTUV#21612050,因为Django >= 1.7使用应用程序配置。 - Nrzonline
1
同意。我会编辑答案,指向Eric Marcos的Django 1.7+答案。 - yprez

27

要解决您的问题,只需在模型定义之后导入 signals.py 即可。就这些。


2
这绝对是最简单的方法,我完全没有想到它可以在没有循环依赖的情况下工作。谢谢! - bradenm
2
太棒了。我喜欢这个比我的答案更好。虽然我不太明白为什么它不会导致循环导入... - yprez
在Eclipse中启用autopep8插件后,解决方案无法正常工作。 - ramusus

6
我也将信号放在signals.py文件中,并编写了以下代码段来加载所有信号:
# 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)))

这是针对项目的,我不确定它是否适用于应用程序层面。


这是我目前最喜欢的解决方案,因为它符合其他模式(如tasks.py)。 - dalore
1
发现了一个问题,如果你启动shell,urls.py文件不会被导入,你的信号也无法附加。 - dalore
是的,我的回答有点过时了,现在似乎django有AppConfig类。我上次使用django是1.3版本。建议你去调查一下。 - aisbaa
1
我们仍然使用1.6版本,所以我不得不将所有的signals.py移动到models中,否则Celery和管理命令无法被识别。 - dalore

6

这仅适用于您将信号放在单独的signals.py文件中的情况

我完全同意@EricMarcos的回答,但应该指出,Django文档明确建议不要使用default_app_config变量(尽管这并没有错)。对于当前版本,正确的方法应该是:

my_app/apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

确保你在已安装的应用程序中不仅仅只有应用程序名称,而是相对于你的AppConfig的路径。

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]

6
在旧版的Django中,将信号放在__init__.py或者models.py中都是可以的(尽管最终我不太喜欢把模型变得过于庞大)。但是在Django 1.9中,更好的做法是将信号放在一个signals.py文件中,并且通过apps.py进行导入,在加载模型后再加载它们。 apps.py:
from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

你也可以将 signals.pyhandlers.py 中的信号分离到模型中名为 signals 的另一个文件夹中,但对我来说那只是一种过度工程。请参考放置信号

4
我猜您这样做是为了注册信号,以便在某个位置找到它们。通常我会直接将信号放在models.py文件中。

是的,当我将信号移动到模型文件内时,问题得到解决。但我的model.py文件非常大,包含所有的类、管理器和模型规则。 - Mo J. Mughrabi
1
根据我的经验,管理器比较容易提取。Managers.py 赢了。 - Issac Kelly

1

另一种方法是从 signals.py 导入回调函数并在 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)

注意:在 signals.py 中导入 YourModel 会创建递归;请使用 sender

注意2:在回调函数中再次保存实例将创建递归。您可以在 .save 方法中添加控制参数来控制它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接