【问题标题】:Where should signal handlers live in a django project?信号处理程序应该放在 django 项目中的什么位置?
【发布时间】:2011-02-12 17:25:02
【问题描述】:

我刚刚开始在 django 项目中实现信号侦听器。虽然我了解它们是什么以及如何使用它们。我很难弄清楚我应该把它们放在哪里。来自 django 网站的文档是这样说的:

Where should this code live?

你可以把信号处理和 您喜欢的任何地方的注册码。 但是,您需要确保 它所在的模块会提前导入 on 以便信号处理得到 在任何信号需要之前注册 被发送。这使您的应用程序的 models.py 是一个放置的好地方 信号处理程序的注册。

虽然这是一个很好的建议,但在我的 models.py 中包含非模型类或方法只会让我犯错。

那么,存储和注册信号处理程序的最佳实践/规则是什么?

【问题讨论】:

    标签: django signal-handling


    【解决方案1】:

    这是在 Django 1.7 发布时添加到 documentation 的:

    严格来说,信号处理和注册代码可以放在任何你喜欢的地方,但建议避免应用程序的根模块及其模型模块,以尽量减少导入代码的副作用。

    实际上,信号处理程序通常定义在与其相关的应用程序的信号子模块中。信号接收器连接在应用程序配置类的 ready() 方法中。如果您使用的是 receiver() 装饰器,只需在 ready() 中导入信号子模块。

    在 Django 1.7 中更改:由于之前版本的 Django 中不存在 ready(),因此信号注册通常发生在模型模块中。

    最佳实践是在信号子模块的 handlers.py 中定义您的处理程序,例如一个看起来像这样的文件:

    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
    

    注册信号处理程序的最佳位置是在定义它的应用的 AppConfig 中,使用 ready() 方法。这将如下所示:

    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
    

    确保通过直接在 settings.py 的 INSTALLED_APPS 或应用的 __init__ 中指定来加载 AppConfig。请参阅see the ready() documentation 了解更多信息。

    注意:如果您也为其他应用提供信号来监听,请将它们放在信号模块中的 __init__ 中,例如一个看起来像这样的文件:

    yourapp/signals/__init__.py

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

    然后另一个应用可以通过导入和注册来监听您的信号,例如from yourapp.signals import task_generate_pre_save。将信号与处理程序分开可以保持干净。

    Django 1.6 说明:

    如果您仍然停留在 Django 1.6 或更低版本,那么您将做同样的事情(在 yourapp/signals/handlers.py 中定义您的处理程序),但您将通过 __init__ 加载处理程序而不是使用 AppConfig您的应用程序的 .py 文件,例如类似:

    yourapp/__init__.py

    import signals
    

    这不如使用 ready() 方法好,因为它经常导致循环导入问题。

    【讨论】:

    • 正如文档所说,你覆盖了 ready,你可能想做一些类似 super(ReportsConfig, self).ready() 的事情,以防 django 决定用一些东西填充 ready() (从 1.7.1 开始)。 0 目前为空)
    • 我认为这个答案是最好的,因为它是唯一解决进口副作用的答案。我来这里是为了寻找最佳实践,因为我正在清理一个应用程序,该应用程序正是由于这种副作用而损坏的。唉,该应用程序在 django 1.6 上运行,最佳实践仅适用于 django 1.7。让__init__ 导入信号的临时解决方法对我不起作用,所以我想知道是否还有其他地方可以导入信号,直到我们准备好升级到更高的 django 版本。
    • yourapp/signals/__init__.py中不应该有from . import handlers(或类似的)吗?
    • 你不应该在某个地方也导入 handlers.py 模块吗?我正在尝试这个,它似乎没有定义信号的处理程序。
    • fwiw 我不需要在 TaskConfig 类代码块的最后一行中的yourproject.。我已经用这个结构来工作了,所以考虑一下这个 qa :)
    【解决方案2】:

    我实际上喜欢让它们成为模型本身的类方法。这将所有内容都放在一个类中,这意味着您不必担心导入任何内容。

    【讨论】:

    • 您通常在哪里将处理程序连接到信号?
    • @DataGreed:在相关models.py的底部。
    • 如果您正在收听该模型发出的信号,那么将所有听众都放在那里也会使整个练习毫无意义,不是吗?信号的重点是解耦。听众不应该接受对这些远程事件感兴趣的代码吗?问题是如何确保在发射器之前加载监听器。
    • 就我而言,我想收听模型Foo 的信号,它是fooapp 的一部分。但是信号接收器是一个扩展,并且确实存在于不同的应用程序中(例如otherapp)。
    • 就 John Mee 而言,这与仅覆盖 save() 等没有太大区别。
    【解决方案3】:

    我刚刚遇到这个问题,由于我的信号与模型无关,我想我会添加我的解决方案。

    我正在记录有关登录/注销的各种数据,并且需要连接到django.contrib.auth.signals

    我已将信号处理程序放入 signals.py 文件中,然后从 __init__.py 模块文件中导入信号,因为我相信这会在应用程序启动后立即调用(使用 print 语句进行测试表明甚至在读取设置文件之前就调用它。)

    # /project/__init__.py
    import signals
    

    在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)
    

    我对 Django (/python) 很陌生,所以我愿意接受任何人告诉我这是一个糟糕的主意!

    【讨论】:

    • 这感觉合乎逻辑,但我建议在应用程序级别进行。
    • 小心,这种逻辑很可能会导致重复信号被触发。 user_logged_in.connect(on_logged_in) 很可能会传入 dispatch_uid 参数。更多信息请访问docs.djangoproject.com/en/dev/topics/signals/…
    • 谢谢您-很高兴知道。我正在使用这种方法(记录 IP / 用户代理)记录所有登录,并且到目前为止没有任何重复 - 尽管这并不意味着线下的小改动不会造成问题!
    【解决方案4】:

    我最近刚刚阅读了this 文章,该文章介绍了在布置项目/应用程序时的最佳实践,它建议您所有的自定义调度程序信号都应该放在一个名为signals.py 的文件中。但是,这并不能完全解决您的问题,因为您仍然需要在某处导入它们,而且越早导入越好。

    模型建议很好。由于您已经在 signals.py 文件中定义了所有内容,因此文件顶部不应超过一行。这类似于admin.py 文件的布局方式(顶部是类定义,底部是注册所有自定义管理类的代码),如果您定义信号,则将它们连接到同一个文件中。

    希望对您有所帮助!最终取决于您的喜好。

    【讨论】:

    • 我也想将我的信号处理程序放在signals.py 文件中,但不知道之后应该如何调用它。通过在我的models.py 文件中导入它,我得到了一个非常干净的解决方案,而不会“污染”我的 models.py 文件。谢谢! :)
    • 那里有交叉导入:signals.py 尝试从 models.py 导入模型
    【解决方案5】:

    每个应用程序中的models.py 和signals.py 都是推荐的连接信号的地方,但是,在我看来,它们并不是保持信号和处理程序调度的最佳解决方案。调度应该是在 django 中发明信号和处理程序的原因。

    苦苦挣扎了很久,终于找到了解决办法。

    在应用文件夹中创建一个连接器模块

    所以我们有:

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

    在 app/connectors.py 中,我们定义了信号处理程序并连接它们。提供了一个例子:

    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)
    

    然后在models.py中,我们在文件末尾添加如下一行:

    from app import connector
    

    一切都在这里完成。

    这样,我们可以把信号放在signals.py中,所有的handlers放在connectors.py中。模型和信号没有混乱。

    希望它提供另一种解决方案。

    【讨论】:

    • 那么,signals.py 中有什么内容?从您的示例看来,这只是自定义信号。通常我们只是将信号和连接器结合起来,因为大多数都没有自定义信号。
    • @dalore 是的,所有自定义信号都放在 signals.py 中。我们有许多定制的信号。但是如果你没有很多,这个文件可以省略。
    • 与@dal 相同的问题
    • 请注意,所有这些现在都是旧建议,django 现在的方式是使用 appconfig 导入信号处理程序所在的处理程序。在 signals.py 中自定义信号
    【解决方案6】:

    关于AppConfig的小提醒。不要忘记设置:

    # yourapp/__init__.py
    
    default_app_config = 'yourapp.apps.RockNRollConfig'
    

    【讨论】:

      【解决方案7】:

      我将它们保存在一个单独的文件中 signals.py ,在定义所有模型之后在 models.py 中。我导入它们并将模型连接到信号。

      信号.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)
      

      这为我提供了逻辑上的分离,当然将它们保存在 models.py 中没有错,但是这样更易于管理。

      希望这会有所帮助!

      【讨论】:

      • 您将信号处理程序放在“signals.py”中,如果我们将其命名为“handlers.py”会怎样
      • 将文件命名为 signals.py 或 handler.py 都没有关系。这只是一个约定而不是规则。
      猜你喜欢
      • 1970-01-01
      • 2022-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-23
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      相关资源
      最近更新 更多