Django:在“ AppConfig.ready()”之后调用的信号/方法

时间:2018-08-17 09:37:45

标签: django django-signals django-apps

我有一个AppConfig.ready()实现,具体取决于其他应用程序的就绪状态。

是否有信号或方法(我可以实现)在调用所有应用程序ready()方法之后被调用?

我知道django按照INSTALLED_APPS的顺序处理信号。

但是我不想对用户执行特定的排序。

在这种情况下,A在B之前需要B。但是也许其他用例在B之前需要B。对我来说,订购INSTALLED_APPS并不是解决方案。

4 个答案:

答案 0 :(得分:1)

恐怕答案是否定的。填充应用程序注册表发生在django.setup()中。如果您查看源代码,将会发现apps.registry.Apps.populate()django.setup()都不会在完成时发送任何信号。

以下是一些想法:

  • 您可以自己调度自定义信号,但这需要您在Django项目的所有入口点都执行此操作,例如manage.pywsgi.py以及使用django.setup()的所有脚本。

  • 您可以连接到request_started并在调用处理程序时断开连接。

  • 如果您要初始化某种属性,则可以将该初始化推迟到第一次访问。

这些方法中的任何一种是否对您有用,显然取决于您要实现的目标。

答案 1 :(得分:1)

因此,有一种非常骇人的方式来完成您可能想要的...

django.apps.registry内是单例apps,Django使用它来填充应用程序。参见setup中的django.__init__.py

apps.populate的工作方式是使用不可重入(基于线程)的锁定机制,仅允许apps.populate以幂等,线程安全的方式发生。

Apps类的精简源代码,它是从以下实例化单例apps的地方:

class Apps(object):

    def __init__(self, installed_apps=()):
        # Lock for thread-safe population.
        self._lock = threading.Lock()

    def populate(self, installed_apps=None):
        if self.ready:
            return

        with self._lock:
            if self.ready:
                return

            for app_config in self.get_app_configs():
                app_config.ready()

            self.ready = True

有了这些知识,您可以创建一些在特定条件下等待的threading.Thread。这些使用者线程将利用threading.Condition发送跨线程信号(这将强制执行您的订购问题)。这是一个如何工作的模拟示例:

import threading

from django.apps import apps, AppConfig

# here we are using the "apps._lock" to synchronize our threads, which
# is the dirty little trick that makes this work
foo_ready = threading.Condition(apps._lock)

class FooAppConfig(AppConfig):
    name = "foo"

    def ready(self):
        t = threading.Thread(name='Foo.ready', target=self._ready_foo, args=(foo_ready,))
        t.daemon = True
        t.start()

    def _ready_foo(foo_ready):
        with foo_ready:
            # setup foo
            foo_ready.notifyAll() # let everyone else waiting continue

class BarAppConfig(AppConfig):
    name = "bar"

    def ready(self):
        t = threading.Thread(name='Bar.ready', target=self._ready_bar, args=(foo_ready,))
        t.daemon = True
        t.start()

    def _ready_bar(foo_ready):
        with foo_ready:
            foo_ready.wait() # wait until foo is ready
            # setup bar

同样,这仅允许您控制来自各个ready的{​​{1}}呼叫的流程。这不会控制订单模型的加载等。

但是,如果您的第一个断言是正确的,则您有一个AppConfig实现,这取决于首先准备好另一个应用程序,这应该可以解决问题。

理由:

为什么要使用条件?之所以使用app.ready而不是threading.Condition有两个原因。首先,条件被包裹在锁定层中。这意味着,如果需要(访问共享资源等),您将在受控的情况下继续操作。其次,由于控制的严格程度,停留在threading.Event的上下文中将使您可以按期望的顺序链接配置。您可以使用以下代码片段查看该操作如何完成:

threading.Condition

为什么使用Deamonic线程?原因是,如果您的Django应用程序在获取和释放lock = threading.Lock() foo_ready = threading.Condition(lock) bar_ready = threading.Condition(lock) baz_ready = threading.Condition(lock) 中的锁之间死了一段时间,则后台线程将继续旋转以等待锁释放。将它们设置为守护程序模式将使进程干净退出,而无需apps.populate这些线程。

答案 2 :(得分:0)

替代解决方案:

子类化AppConfig并在ready的末尾发送信号。在所有应用程序中使用此子类。如果您依赖于正在加载的信号,请连接到该信号/发送器对。

如果您需要更多详细信息,请不要犹豫!

此方法有一些细微之处:

1)将信号定义放在何处(我怀疑在manage.py中可以使用,或者甚至可以猴子补丁django.setup以确保它在任何地方都被调用)。您可以放置​​一个core应用程序,该应用程序始终是installed_apps中第一个应用程序,或者在django始终会在加载任何AppConfig之前加载该应用程序的地方。

2)在哪里注册信号接收器(您应该可以在AppConfig.__init__或在该文件中全局注册)。

请参见https://docs.djangoproject.com/en/dev/ref/applications/#how-applications-are-loaded

因此,设置如下:

  • django首次启动时,请注册信号。
  • 在每个app_config.ready发送信号的末尾(以AppConfig实例为发送者)
  • 在需要响应信号的AppConfig中,在__init__中向适当的发送方注册接收方。

让我知道怎么回事!

如果需要它用于第三方应用程序,请记住,您可以覆盖这些应用程序的AppConfig(常规做法是将它们放置在名为apps的目录中)。或者,您可以猴子修补AppConfig

答案 3 :(得分:0)

您可以添加一个虚拟应用程序,该应用程序仅用于触发自定义“ all_apps_are_ready”信号(或AppConfig上的方法调用)。

将此应用放在INSTALLED_APPS的末尾。

如果此应用收到AppConfig.ready()方法调用,则说明所有其他应用均已准备就绪。