我正在编写一个Django中间件类,我想在启动时只执行一次,以初始化其他一些任意代码。我遵循了sdolan在这里发布的非常好的解决方案,但是“Hello”消息输出到终端两次。如。
from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings
class StartupMiddleware(object):
def __init__(self):
print "Hello world"
raise MiddlewareNotUsed('Startup complete')
在我的Django设置文件中,我已经在MIDDLEWARE_CLASSES列表中包含了这个类。
但是当我使用runserver运行Django并请求一个页面时,我进入了终端
Django version 1.3, using settings 'config.server'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Hello world
[22/Jul/2011 15:54:36] "GET / HTTP/1.1" 200 698
Hello world
[22/Jul/2011 15:54:36] "GET /static/css/base.css HTTP/1.1" 200 0
知道为什么"Hello world"打印了两次吗?谢谢。
标准溶液
在Django 3.1+中,你可以编写这样的代码,让一个方法在启动时只执行一次。与其他问题的不同之处在于主启动进程被检查(runserver默认启动2个进程,其中一个作为快速重载代码的观察者):
import os
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'app_name'
def ready(self):
if os.environ.get('RUN_MAIN'):
print("STARTUP AND EXECUTE HERE ONCE.")
# call here your code
另一个解决方案是避免环境检查,但是调用——noreload来强制只执行一个进程。
替代的方法
要回答的第一个问题是为什么我们需要一次执行代码:通常我们需要一次性初始化一些服务、数据库中的数据或其他东西。90%的时间是一些数据库初始化或作业队列。
使用AppConfig.ready()方法的方法是不可靠的,在生产环境中并不总是可重复的,并且不能保证精确地执行一次(但至少执行一次是不相同的)。最好的方法是开发一个Django BaseCommand,并从启动脚本中调用它。
例如,我们可以在“myapp”中,在“app/management/commands/init_tasks.py”文件中编码:
from django.core.management.base import BaseCommand
from project.apps.myapp.tasks import scheduler
from project import logger, initialize_database_data
class Command(BaseCommand):
help = "Init scheduler or do some staff in the database."
def handle(self, *args, **options):
scheduler.reload_jobs()
initialize_database_data()
logger.info("Inited")
最后,我们可以有一个启动脚本“start .bat”(在示例中是一个windows批处理)来设置整个应用程序启动:
start /b python manage.py qcluster
start /b python manage.py runserver 0.0.0.0:8000
start /b python manage.py init_tasks
注意,您不能可靠地连接到数据库或与AppConfig内的模型交互。Ready函数(参见文档中的警告)。
如果需要在启动代码中与数据库交互,一种可能是使用connection_created信号在连接到数据库时执行初始化代码。
from django.dispatch import receiver
from django.db.backends.signals import connection_created
@receiver(connection_created)
def my_receiver(connection, **kwargs):
with connection.cursor() as cursor:
# do something to the database
显然,这个解决方案是为每个数据库连接运行一次代码,而不是在每个项目启动时运行一次。因此,您需要为CONN_MAX_AGE设置一个合理的值,这样就不会对每个请求重新运行初始化代码。还要注意,开发服务器忽略了CONN_MAX_AGE,因此您将在开发过程中对每个请求运行一次代码。
99%的情况下,这是一个坏主意——数据库初始化代码应该放在迁移中——但在某些用例中,您无法避免延迟初始化,上面的警告是可以接受的。