在服务器初始化时执行模型操作(仅一次)

Perform model operations (only once) at server init

我有一个相当特殊的要求:该应用程序应该能够将自己的正常运行时间显示为总小时数。这意味着我需要离开请求-响应周期并更新相关模型中的当前时间戳。

考虑到这一点,我按照 here 给出的说明进行操作,将代码放入 apps.py 中应用程序的 ready() 方法中。问题当然是我运行进入了Apps aren't loaded yet的错误。我该如何解决这个问题?

想到的另一种方法是取消模型并将时间戳写入文件,但这是一种无法扩展的脆弱方法。如果我想在启动时存储大量的关系信息怎么办?

有人可以给点建议吗?

=======更新=========

我使用的代码如下(我的项目名为jremind,我的应用程序名为remind)。

这是我的模型实例:

class Monitor(models.Model):
    # save automatically when object is saved()
    app_init_timestamp = models.DateTimeField(null=False, auto_now=True)

应用程序的 __init__ 文件:

default_app_config = 'remind.apps.RemindConfig'

应用的 apps.py 文件:

from django.apps import AppConfig
from remind.models import Monitor

class RemindConfig(AppConfig):
    name = 'remind'

    def ready(self):
        # There's only one instance
        monitor = Monitor.objects.get()[0]
        #Auto-update timestamp
        monitor.save()

这是我 运行 ./manage.py runserver:

时的完整堆栈跟踪
(env) jremind$ ./manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
July 13, 2016 - 15:12:08
Django version 1.9, using settings 'jremind.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^C(env) jremind$ ./manage.py runserver
Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 350, in execute_from_command_line
    utility.execute()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 342, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 176, in fetch_command
    commands = get_commands()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/functools.py", line 448, in wrapper
    result = user_function(*args, **kwds)
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/core/management/__init__.py", line 71, in get_commands
    for app_config in reversed(list(apps.get_app_configs())):
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/apps/registry.py", line 137, in get_app_configs
    self.check_apps_ready()
  File "/media/common/code/python/projects/jremind/env/lib/python3.4/site-packages/django/apps/registry.py", line 124, in check_apps_ready
    raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

您需要从方法内部导入模型:

def ready(self):
    from remind.models import Monitor

但是,您还应该注意 warning in the documentation:

Although you can access model classes as described above, avoid interacting with the database in your ready() implementation. This includes model methods that execute queries (save(), delete(), manager methods etc.)... Your ready() method will run during startup of every management command. For example, even though the test database configuration is separate from the production settings, manage.py test would still execute some queries against your production database!

另外:

In the usual initialization process, the ready method is only called once by Django. But in some corner cases, particularly in tests which are fiddling with installed applications, ready might be called more than once. In that case, either write idempotent methods, or put a flag on your AppConfig classes to prevent re-running code which should be executed exactly one time.

像这样放置一个标志:

class RemindConfig(AppConfig):
    name = 'remind'
    ready_has_run = False

    def ready(self):
        if self.ready_has_run:
            return

        # Do your stuff here, and then set the flag
        self.ready_has_run = True