Выполнить код, когда Django запускается только один раз?

Я пишу класс Django Middleware, который я хочу выполнить только один раз при запуске, чтобы инициализировать некоторый другой условный код. Я следил за очень хорошим решением, опубликованным 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.

Но когда я запускаю 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

Обновление от 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()

Ответ 2

Обновление: у Django 1.7 теперь есть для этого

file: myapp/apps.py

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

file: 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... это выполняется два раза, но это связано с тем, что у запускающего сервера есть некоторые трюки, чтобы проверить модели сначала и т.д.... нормальные развертывания или даже при перезагрузке автозагрузки при загрузке, это выполняется только один раз.

Ответ 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, опция "-noreload" не позволяет запускающему сценарию запуска запуска при запуске дважды:

python manage.py runserver --noreload

Но эта команда не перезагружает серверы после других изменений кода.

Ответ 5

Как предлагает @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

Ответ 6

Обратите внимание: вы не можете надежно подключиться к базе данных или взаимодействовать с моделями внутри функции 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% случаев это плохая идея - код инициализации базы данных должен идти в миграции - но есть некоторые варианты использования, когда вы не можете избежать поздней инициализации, и вышеописанные предостережения приемлемы.

Ответ 7

если вы хотите печатать "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"