当Django只启动ONCE时执行代码?

Bob*_*_94 153 python django

我正在编写一个Django Middleware类,我想在启动时只执行一次,以初始化其他一些arbritary代码.我已经按照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')
Run Code Online (Sandbox Code Playgroud)

在我的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
Run Code Online (Sandbox Code Playgroud)

任何有关"Hello world"打印两次的想法?谢谢.

Pyk*_*ler 239

更新:Django 1.7现在有一个钩子

文件: myapp/apps.py

from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = "My Application"
    def ready(self):
        pass # startup code here
Run Code Online (Sandbox Code Playgroud)

文件: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'
Run Code Online (Sandbox Code Playgroud)

对于Django <1.7

第一个答案似乎不再起作用,urls.py在第一次请求时加载.

最近工作的是将启动代码放在任何一个INSTALLED_APPS init .py中myapp/__init__.py

def startup():
    pass # load a big thing

startup()
Run Code Online (Sandbox Code Playgroud)

使用时./manage.py runserver...这会被执行两次,但这是因为runserver有一些技巧可以首先验证模型等...正常部署甚至当runserver自动重新加载时,这只执行一次.

  • 文档说这不是进行任何数据库交互的地方。这使得它不适合大量代码。这段代码可以去哪里? (7认同)
  • 我认为这将为每个加载项目的进程执行.所以,我无法想到为什么在任何部署方案下这都不会完美.这适用于管理命令.+1 (4认同)
  • 编辑:一个可能的黑客是检查任何命令行参数(在['makemigrations','migrate']中x的sys.argv中的x) (3认同)
  • @Pykler它应该准备好(自我),因为ready是一种方法 (2认同)
  • 我知道这个解决方案可以用来在服务器启动时执行一些任意代码,但是可以*共享*一些可以加载的数据吗?例如,我想加载一个包含巨大矩阵的对象,将此矩阵放在一个变量中,并通过web api在用户可以执行的每个请求中使用它.这样的事情可能吗? (2认同)
  • @Patrick 是的,你可以,在你的启动函数中可以看起来像这样 https://gist.github.com/pykler/024334b23f18d66937f2 (2认同)
  • 尝试此操作时,我在 Django 1.10 上收到`django.core.exceptions.AppRegistryNotReady:应用程序尚未加载。` 这个答案现在是否已过时? (2认同)
  • 如果您的脚本运行了两次,请查看以下答案:/sf/answers/1995285071/ (2认同)

S.L*_*ott 98

更新Pykler的答案如下:Django 1.7现在有一个钩子


不要这样做.

你不希望"中间件"用于一次性启动.

您想在顶级执行代码urls.py.该模块导入并执行一次.

urls.py

from django.confs.urls.defaults import *
from my_app import one_time_startup

urlpatterns = ...

one_time_startup()
Run Code Online (Sandbox Code Playgroud)

  • @halilpazarlama这个答案已过时 - 你应该使用Pykler的答案. (9认同)
  • urls.py代码仅在第一次请求时执行(猜测它回答了@SteveK的问题)(django 1.5) (8认同)
  • 这对每个工人执行一次,在我的情况下,它总共执行了3次. (3认同)
  • @Andrei:管理命令完全是一个单独的问题。在**所有**管理命令之前进行特殊的一次性启动的想法很难理解。您必须提供一些**具体的**。也许在另一个问题中。 (2认同)

aug*_*men 36

这个问题在Django项目的入口点钩子的博客文章中得到了很好的回答,它适用于Django> = 1.4.

基本上,您可以使用它<project>/wsgi.py来执行此操作,并且它将仅在服务器启动时运行一次,而不是在运行命令或导入特定模块时运行.

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")

# Run startup code!
....

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这是在向网站发出第一个请求时执行,而不是在启动Apache时执行. (3认同)

J_Z*_*Zar 17

标准溶液

使用 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
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是避免环境检查,但调用 --noreload 来强制仅一个进程。

替代方法

首先要回答的问题是为什么我们需要执行一次代码:通常我们需要初始化一些服务、数据库中的数据或一次性的东西。90% 的时间是一些数据库初始化或作业队列。

使用 AppConfig.ready() 方法的方法并不可靠,在生产中并不总是可重现,并且不能保证恰好执行一次(但至少执行一次不一样)。要让某些事情完全可预测并执行一次,最好的方法是开发一个 Django BaseCommand 并从启动脚本中调用它。

例如,我们可以在文件“app/management/commands/init_tasks.py”中的“myapp”中进行编码:

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")
Run Code Online (Sandbox Code Playgroud)

最后,我们可以使用启动脚本“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
Run Code Online (Sandbox Code Playgroud)

  • BaseCommand 的答案最适合我!我认为这应该是公认的答案。 (2认同)
  • 出色的!!!我认为这也应该是公认的答案。 (2认同)

Ana*_*ana 16

如果它对某人有帮助,除了pykler的回答之外,"--noreload"选项会阻止runserver在启动时执行两次命令:

python manage.py runserver --noreload
Run Code Online (Sandbox Code Playgroud)

但是,在其他代码更改之后,该命令也不会重新加载runserver.

  • 作为替代方案,您可以检查``os.environ.get('RUN_MAIN')``的内容,只在主进程中执行一次代码(参见http://stackoverflow.com/a/28504072) (2认同)

小智 12

正如@Pykler建议,在Django 1.7+,你应该使用挂钩,在他的回答解释,但如果你想要一个你的函数只调用时运行服务器称为(而不是制作时迁移,迁移,外壳等被称为),你想要避免AppRegistryNotReady例外,你必须做如下:

文件: myapp/apps.py

import sys
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        if 'runserver' not in sys.argv:
            return True
        # you must import your modules here 
        # to avoid AppRegistryNotReady exception 
        from .models import MyModel 
        # startup code here
Run Code Online (Sandbox Code Playgroud)

  • 这是否在生产模式下运行?AFAIK in prod.模式没有"runserver"启动. (11认同)

Ric*_*rdW 9

请注意,您无法可靠地连接到数据库或与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
Run Code Online (Sandbox Code Playgroud)

显然,这个解决方案是为每个数据库连接运行一次代码,而不是每个项目启动一次。所以你需要一个合理的CONN_MAX_AGE设置值,这样你就不会在每个请求上重新运行初始化代码。另请注意,开发服务器会忽略CONN_MAX_AGE,因此您将在开发中为每个请求运行一次代码。

99% 的情况下这是一个坏主意 - 数据库初始化代码应该在迁移中进行 - 但在某些用例中,您无法避免延迟初始化,并且上述警告是可以接受的。

  • 如果您需要在启动代码中访问数据库,这是一个很好的解决方案。让它只运行一次的简单方法是让 `my_receiver` 函数将自身与 `connection_created` 信号断开,具体来说,将以下内容添加到 `my_receiver` 函数中:`connection_created.disconnect(my_receiver)`。 (4认同)

归档时间:

查看次数:

69319 次

最近记录:

7 年 前