Выполнить код, когда Django запускается только ОДИН РАЗ?

92

Я пишу класс 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
    просто для любопытства, вы поняли, почему код в init .py выполняется дважды?
  • 3
    @Mutant выполняется только дважды под runserver ... потому что runserver сначала загружает приложения для их проверки, а затем фактически запускает сервер. Даже при автоматической перезагрузке runserver код выполняется только один раз.
Показать ещё 1 комментарий
Теги:

5 ответов

61
Лучший ответ

Обновление от Pykler ответ ниже: Django 1.7 теперь имеет hook для этого


Не делайте этого так.

Вам не требуется "промежуточное ПО" для однократной загрузки.

Вы хотите выполнить код на верхнем уровне urls.py. Этот модуль импортируется и выполняется один раз.

urls.py

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

urlpatterns = ...

one_time_startup()
  • 0
    Хорошо, я попробую. Спасибо за быстрый ответ :)
  • 0
    Просто чтобы быть уверенным, будет ли это работать с Django поверх apache / mod_wsgi? Или просто с помощью команды django runrever?
Показать ещё 12 комментариев
163

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

  • 0
    В последней версии django это выполняется только один раз с командами manage.py. Я даже не совсем уверен, что это из-за новой версии django или чего-то еще, но, в основном, даже когда он выполнялся дважды, первый раз был просто проверкой, второй - когда это считается.
  • 4
    Я думаю, что это выполняется для каждого процесса, который загружает проект. Поэтому я не могу понять, почему это не будет работать идеально при любом сценарии развертывания. Это работает для команд управления. +1
Показать ещё 24 комментария
24

Этот вопрос хорошо ответил в сообщении блога Крюк точки входа для проектов 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()
  • 0
    Да, это работает ...
  • 0
    Снова добавив комментарий, чтобы подтвердить, что этот метод выполнит код только один раз. Нет необходимости в каких-либо механизмах блокировки.
Показать ещё 3 комментария
9

Если это помогает кому-то, помимо ответа pykler, опция "-noreload" не позволяет запускающему сценарию запуска запуска при запуске дважды:

python manage.py runserver --noreload

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

  • 1
    Спасибо, это решило мою проблему! Я надеюсь, что когда я разверну это не произойдет
  • 2
    В качестве альтернативы вы можете проверить содержимое os.environ.get('RUN_MAIN') чтобы выполнить код только один раз в основном процессе (см. Stackoverflow.com/a/28504072 )
Показать ещё 1 комментарий
0

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

  • 2
    Это хорошее решение, если вам нужен доступ к базе данных в вашем коде запуска. Простой способ заставить его работать только один раз - my_receiver функцию my_receiver отключиться от сигнала connection_created , в частности добавить следующее в функцию my_receiver : connection_created.disconnect(my_receiver) .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню