Django 启动时仅执行一次代码?

2025-01-08 08:50:00
admin
原创
18
摘要:问题描述:我正在编写一个 Django 中间件类,我只想在启动时执行一次,以初始化一些其他任意代码。我遵循了 sdolan在此处发布的非常好的解决方案,但“Hello”消息输出到终端两次。例如from django.core.exceptions import MiddlewareNotUsed from d...

问题描述:

我正在编写一个 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”被打印了两次吗?谢谢。


解决方案 1:

更新: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

文件:myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

对于 Django <1.7

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

最近有效的方法是将启动代码放在任何一个 INSTALLED_APPS init.py中,例如myapp/__init__.py

def startup():
    pass # load a big thing

startup()

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

解决方案 2:

以下是 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()

解决方案 3:

博客文章“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()

解决方案 4:

按照@Pykler的建议,在Django 1.7+中你应该使用他在答案中解释的钩子,但如果你想要你的函数只在调用运行服务器时被调用(而不是在进行迁移、迁移、shell等时被调用),并且你想避免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

解决方案 5:

如果它对某人有帮助,除了pykler 的回答之外,“--noreload”选项还可以防止 runserver 在启动时执行命令两次:

python manage.py runserver --noreload

但是在其他代码改变后,该命令也不会重新加载运行服务器。

解决方案 6:

标准溶液

使用 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

解决方案 7:

请注意,您无法可靠地连接到数据库或与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% 的时间里这是一个坏主意 - 数据库初始化代码应该进行迁移 - 但有一些用例无法避免延迟初始化,并且上述警告是可以接受的。

解决方案 8:

如果您希望在运行服务器时打印一次“hello world”,请将 print(“hello world”) 放在 StartupMiddleware 类之外

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        #print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

print "Hello world"

解决方案 9:

对于那些希望在使用 gunicorn 的生产环境中启动时仅运行一次代码的人来说,可以使用gunicorn 提供的--preload命令。

默认情况下,Django 对任何启动代码使用多个 worker,但是通过传递 --preload 命令,Django 将仅在父 worker 中运行启动命令。

这篇文章很好地解释了如何添加 --preload 命令。然后只需在 ready 函数下添加要在启动时运行的代码,如下所示

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'app_name'

    def ready(self):
       code_to_run()

解决方案 10:

就我而言,我使用 Django 来托管网站,并使用 Heroku。我在 Heroku 上使用 1 个 dyno(就像 1 个容器),并且这个 dyno 创建了两个工作器。我想在其上运行一个 discord 机器人。我尝试了此页面上的所有方法,但均无效。

因为是部署,所以不应该使用manage.py,而是用gunicorn,不知道该怎么加--noreload参数,每个worker都运行一次wsgi.py,所以每段代码都会运行两次,而且两个worker的本地环境是一样的。

但我注意到一件事,每次 Heroku 部署时,它都使用相同的 pid worker。所以我只是

if not sys.argv[1] in ["makemigrations", "migrate"]: # Prevent execute in some manage command
    if os.getpid() == 1: # You should check which pid Heroku will use and choose one.
        code_I_want_excute_once_only()

我不确定 pid 将来是否会改变,希望它永远保持不变。如果您有更好的方法来检查它是哪个 worker,请告诉我。

解决方案 11:

我使用了这里接受的解决方案,它检查它是否作为服务器运行,而不是在执行其他managy.py命令时运行,例如migrate

应用程序.py:

from .tasks import tasks

class myAppConfig(AppConfig):
    ...

    def ready(self, *args, **kwargs):
        is_manage_py = any(arg.casefold().endswith("manage.py") for arg in sys.argv)
        is_runserver = any(arg.casefold() == "runserver" for arg in sys.argv)

        if (is_manage_py and is_runserver) or (not is_manage_py):
            tasks.is_running_as_server = True

由于在开发模式下如果不使用参数仍会执行两次,因此--noreload我添加了一个在作为服务器运行时触发的标志,并将启动代码放入其中,urls.py该代码只被调用一次。

任务.py:

class tasks():
    is_running_as_server = False

    def runtask(msg):
        print(msg)

urls.py:

from . import tasks

task1 = tasks.tasks()

if task1.is_running_as_server:
    task1.runtask('This should print once and only when running as a server')

总而言之,我利用 AppConfig 中的 read() 函数来读取参数并了解代码的执行方式。但由于在开发模式下,ready() 函数运行两次,一次用于服务器,一次用于代码更改时重新加载服务器,而urls.py只为服务器执行一次。所以在我的解决方案中,我将两者结合起来,只在代码作为服务器执行时运行一次我的任务。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   984  
  在项目管理领域,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节,它不仅验证了项目团队的专业能力,还直接关系到项目的成功与否。在这一评审过程中,沟通技巧的运用至关重要。有效的沟通不仅能够确保信息的准确传递,还能增强团队协作,提升评审效率。本文将深入探讨CDCP...
华为IPD流程   0  
  IPD(Integrated Product Development,集成产品开发)是一种以客户需求为核心、跨部门协同的产品开发模式,旨在通过高效的资源整合和流程优化,提升产品开发的成功率和市场竞争力。在IPD培训课程中,掌握关键成功因素是确保团队能够有效实施这一模式的核心。以下将从五个关键成功因素展开讨论,帮助企业和...
IPD项目流程图   0  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司在其全球化进程中逐步构建和完善的一套高效产品开发管理体系。这一流程不仅帮助华为在技术创新和产品交付上实现了质的飞跃,还为其在全球市场中赢得了显著的竞争优势。IPD的核心在于通过跨部门协作、阶段性评审和市场需求驱动,确保...
华为IPD   0  
  华为作为全球领先的通信技术解决方案提供商,其成功的背后离不开一套成熟的管理体系——集成产品开发(IPD)。IPD不仅是一种产品开发流程,更是一种系统化的管理思想,它通过跨职能团队的协作、阶段评审机制和市场需求驱动的开发模式,帮助华为在全球市场中脱颖而出。从最初的国内市场到如今的全球化布局,华为的IPD体系在多个领域展现...
IPD管理流程   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用