我正在编写一个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来托管一个站点,并使用Heroku。我在Heroku使用1个dyno(就像1个容器),这个dyno创建了两个工人。 我想在上面运行一个不和机器人。我尝试了这个页面上的所有方法,都是无效的。

因为它是一个部署,所以不应该使用manage.py。相反,它使用gunicorn,我不知道如何添加-noreload参数。 每个worker运行一次wsgi.py,因此每个代码将运行两次。两个工人的当地环境是一样的。

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

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在未来是否会改变,希望它永远是一样的。如果你有更好的方法来检查是哪个工人,请告诉我。

其他回答

如果它能帮助某些人,除了pykler的答案之外,"——noreload"选项可以防止runserver在启动时执行两次命令:

python manage.py runserver --noreload

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

我使用了这里公认的解决方案,即检查它是否作为服务器运行,而不是在执行其他manage .py命令(如migrate)时检查

apps.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中,只调用一次。

tasks.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仅为服务器执行了一次。所以在我的解决方案中,我将两者结合起来,只在代码作为服务器执行时运行我的任务。

标准溶液

在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

在我的例子中,我使用Django来托管一个站点,并使用Heroku。我在Heroku使用1个dyno(就像1个容器),这个dyno创建了两个工人。 我想在上面运行一个不和机器人。我尝试了这个页面上的所有方法,都是无效的。

因为它是一个部署,所以不应该使用manage.py。相反,它使用gunicorn,我不知道如何添加-noreload参数。 每个worker运行一次wsgi.py,因此每个代码将运行两次。两个工人的当地环境是一样的。

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

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在未来是否会改变,希望它永远是一样的。如果你有更好的方法来检查是哪个工人,请告诉我。

正如@Pykler所建议的,在Django 1.7+中,你应该使用他回答中解释的钩子,但是如果你想让你的函数只在run server被调用时被调用(而不是在进行迁移,migrate, shell等被调用时),并且你想避免AppRegistryNotReady异常,你必须这样做:

: myapp / app . 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