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

时间:2011-07-22 15:00:11

标签: python django

我正在编写一个Django Middleware类,我想在启动时只执行一次,以初始化其他一些arbritary代码。我遵循了sdolan here发布的非常好的解决方案,但是“Hello”消息输出到终端两次。 E.g。

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”打印两次?感谢。

9 个答案:

答案 0 :(得分:214)

更新:Django 1.7现在有一个hook for this

档案: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自动重新加载时,这只执行一次。

答案 1 :(得分:83)

更新Pykler的答案如下:Django 1.7现在有一个hook for this


不要这样做。

您不希望“中间件”用于一次性启动。

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

urls.py

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

urlpatterns = ...

one_time_startup()

答案 2 :(得分:34)

这个问题在博客文章Entry point hook for Django projects中得到了很好的解答,该文章适用于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()

答案 3 :(得分:15)

除了pykler's回答之外,如果它对某人有帮助,&#34; - noreload&#34;选项可防止runserver在启动时执行两次命令:

python manage.py runserver --noreload

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

答案 4 :(得分:9)

正如@Pykler所建议的,在Django 1.7+中你应该使用他的答案中解释的钩子,但如果你想要你的函数只在调用运行服务器时被调用(而不是在制作时)迁移,迁移,shell等被调用,并且您希望避免AppRegistryNotReady例外,您必须执行以下操作:

档案:myapp/apps.py

import sys
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = 'My Application'

    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 :(得分:4)

请注意,无法可靠性连接到数据库或与AppConfig.ready函数内的模型进行交互(请参阅文档中的warning)。

如果需要在启动代码中与数据库进行交互,则可以使用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%的时间这是一个坏主意 - 数据库初始化代码应该进行迁移 - 但是有些用例可以避免延迟初始化,并且上面的警告是可以接受的。

答案 6 :(得分:0)

如果你想打印&#34; hello world&#34;一旦你运行服务器,将print(&#34; hello world&#34;)放在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"

答案 7 :(得分:0)

就我而言,我使用 Django 托管站点,并使用 Heroku。我在 Heroku 使用 1 个 dyno(就像 1 个容器),这个 dyno 创建了两个工人。 我想在它上面运行一个不和谐的机器人。这个页面上的方法我都试过了,都无效。

因为是部署,所以不应该使用manage.py。相反,它使用了 gunicorn,我不知道如何添加 --noreload 参数。 每个 worker 运行 wsgi.py 一次,所以每个代码都会运行两次。并且两个worker的local env是一样的。

但我注意到一件事,每次 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 将来是否会改变,希望它永远不变。如果您有更好的方法来检查它是哪个工人,请告诉我。

答案 8 :(得分:0)

我使用了 here 中接受的解决方案,它检查它是否作为服务器运行,而不是在执行其他 managy.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,我添加了一个标志,当它作为服务器运行时触发,并将我的启动代码放在 {{1 }} 只被调用一次。

tasks.py:

urls.py

urls.py:

class tasks():
    is_running_as_server = False

    def runtask(msg):
        print(msg)

总而言之,我利用 AppConfig 中的 read() 函数来读取参数并了解代码是如何执行的。但由于在开发模式下,ready() 函数会运行两次,一次用于服务器,另一次用于在代码更改时重新加载服务器,而 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') 只为服务器执行一次。因此,在我的解决方案中,我将两者结合起来运行我的任务一次,并且仅当代码作为服务器执行时。