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


88

根据我正在阅读的Django文档,似乎signals.py在app文件夹中是一个不错的起点,但是我面临的问题是,当我为创建信号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]

此代码将无法运行,因为我Comm_Queue在内部signals.py导入了信号,也在内部也导入了信号models.py

谁能建议我如何解决这个问题?

问候


Answers:


65

对于Django <1.7的原始答案:

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

# __init__.py
import signals

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

这种方法的一个问题是,如果您使用coverage.py,它会弄乱覆盖率结果。

相关讨论

编辑:对于Django> = 1.7:

自从引入AppConfig以来,推荐的信号导入方式是其init()功能。有关更多详细信息,请参见 Eric Marcos的答案


6
在Django 1.9中使用信号,请使用以下方法(由django命令)。这种方法无法正常工作AppRegistryNotReady("Apps aren't loaded yet.")
s0nskar '16

1
埃里克·马科斯(Eric Marcos)的答案应该是公认的答案:stackoverflow.com/a/21612050/3202958,因为Django> = 1.7,使用应用程序配置
Nrzonline

1
同意 我将编辑答案以指向Eric Marcos针对Django 1.7+的答案
yprez

194

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

对于将来的Django版本(> = 1.7),建议的方法是将信号模块导入应用程序的config 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通常是一个字符串但可以是任何可哈希对象。 docs.djangoproject.com/en/1.7/topics/signals/…–
Emeka

13
这应该是公认的答案!接受的答案使用uwsgi部署时,上述抛出一个错误
帕特里克

2
嗯,不适用于django2。如果我直接准备好导入模型-一切都很好。如果我在信号中导入模型并在准备就绪时导入信号,我会出错doesn't declare an explicit app_label..
Aldarund

@Aldarun,您可以尝试将“ my_app.apps.MyAppConfig”放入INSTALLED_APPS。
拉米尔Aglyautdinov

26

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


2
到目前为止,这是最简单的,而且我不知道如果没有周期性的依赖关系,这将不会起作用。谢谢!
bradenm 2012年

2
辉煌。像这样比我的答案更好。虽然我不太清楚它怎么不会引起循环进口……
yprez

解决方案不适用于Eclipse中启用的autopep8插件。
ramusus

5

我也将信号放在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)))

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


到目前为止,这是我最喜欢的解决方案,因为它适合其他模式(例如task.py)
dalore 2013年

1
发现一个问题,这一个,如果你开始壳urls.py没有得到进口,你的信号不会重视
dalore

是的,我的回答有点过时了,看来django最近有了AppConfig类。上一次我使用django的时候是1.3版。建议对此进行调查。
aisbaa 2014年

1
我们仍然是1.6,所以我不得不将所有signal.py移入模型,否则celery和管理命令不会被执行
dalore 2014年

5

在旧的Django版本中,最好将信号放在__init__.pymodels.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.py以及handlers.py另一个文件夹中划分信号signals,但是对我而言,这仅仅是工程上的问题。看看放置信号


3

我猜想您正在这样做,因此您的信号已注册,因此可以在某处找到它们。通常,我只是将信号正确放置在models.py文件中。


是的,当我在模型文件中移动信号时,它可以解决问题。但是我的model.py文件很大,包含所有类,管理器和模型规则。
Mo J. Mughrabi

1
管理人员可以更轻松地了解我的经验。Managers.py ftw。
伊萨克·凯利

3

仅当您将信号放在单独的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',
    # ...
]

1

一种替代方法是从中导入回调函数signals.py并将其连接到models.py

signal.py

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

模型

# 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:导入YourModelsignals.py将创建一个递归; 使用sender代替。

Ps2:将实例再次保存在回调函数中将创建递归。您可以在.save方法中创建一个控制参数来控制它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.