ready() 是放信号注册、启动检查的标准位置,但它被调用的时机比想象中多:每条管理命令、runserver 的两个进程。这篇把这些情形逐个数出来,并复现最常见的坑——在 ready() 里查数据库。
a.ready() b 的模型已加载?True apps.ready=False b.ready() urls 被导入
两个 app a、b,各自的 ready() 打点。a 先、b 后;a 的 ready() 里 b 的模型已经加载。apps.ready 要等所有 ready() 都跑完才变 True。
manage.py check a.ready() 调用 1 次 manage.py showmigrations a.ready() 调用 1 次 manage.py makemigrations --check --dry-run a.ready() 调用 1 次 manage.py test --noinput a.ready() 调用 1 次 manage.py help a.ready() 调用 1 次
每个命令启动时都会 django.setup(),所以 ready() 里的耗时或副作用在任何命令下都发生。
runserver(默认,自动重载) a.ready() 2 次,来自 2 个不同进程;urls 导入 1 次(pid: 18341, 18342) runserver --noreload a.ready() 1 次,来自 1 个不同进程;urls 导入 1 次(pid: 18368)
默认的自动重载让 runserver 启动一个监视进程和一个真正服务的子进程,两个进程都会 django.setup(),所以 ready() 里的一次性逻辑(比如启动后台线程、发通知)会执行 2 次。--noreload 只有一次。用 gunicorn 多个 worker 时每个 worker 进程各一次(本文没有测)。
全新库上 python manage.py migrate(rc=1),最后一行: django.db.utils.OperationalError: no such table: lab_item 同一个库上 check(表还不存在),最后一行: django.db.utils.OperationalError: no such table: lab_item
这是最常见的坑:ready() 里 Item.objects.count() 在全新数据库上会让 migrate 报错。ready() 里应避免访问数据库;确实需要,放到信号、第一次请求、或独立的管理命令里。