Выполнить код, когда Django запускается только ОДИН РАЗ?
Я пишу класс промежуточного программного обеспечения 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
список.
Но когда я запускаю Django с помощью runserver и запрашиваю страницу, я попадаю в терминал
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" печатается дважды? Благодарю.
10 ответов
Обновление от ответа Пиклера ниже: Django 1.7 теперь имеет крючок для этого
Не делай так.
Вы не хотите "промежуточное ПО" для единовременного запуска.
Вы хотите выполнить код на верхнем уровне urls.py
, Этот модуль импортируется и выполняется один раз.
urls.py
from django.confs.urls.defaults import *
from my_app import one_time_startup
urlpatterns = ...
one_time_startup()
Обновление: 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'
Для Джанго < 1,7
Ответ номер один, похоже, больше не работает, urls.py загружается при первом запросе.
Что работало в последнее время, так это вставка кода запуска в любой из ваших INSTALLED_APPS init.py, например myapp/__init__.py
def startup():
pass # load a big thing
startup()
Когда используешь ./manage.py runserver
... это выполняется дважды, но это потому, что у runserver есть некоторые хитрости для проверки моделей в первую очередь и т. д.... при нормальном развертывании или даже при автоматической перезагрузке runserver это выполняется только один раз.
На этот вопрос хорошо ответили в сообщении в блоге Хук точки входа для проектов 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()
Как предлагает @Pykler, в Django 1.7+ вы должны использовать ловушку, объясненную в его ответе, но если вы хотите, чтобы ваша функция вызывалась только при вызове run server (а не при вызове миграций, миграции, оболочки и т. Д.), и вы хотите избежать исключений 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
Если это помогает кому-то, в дополнение к ответу Пиклера, опция --noreload не позволяет runserver выполнять команду при запуске дважды:
python manage.py runserver --noreload
Но эта команда не будет перезагружать runserver после других изменений кода.
В 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, чтобы принудительно запустить только один процесс.
Обратите внимание, что вы не можете надежно подключиться к базе данных или взаимодействовать с моделями внутри 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% случаев это плохая идея - код инициализации базы данных должен идти в процессе миграции, но есть некоторые случаи использования, когда вы не можете избежать поздней инициализации, и приведенные выше предостережения являются приемлемыми.
Если вы хотите печатать "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"
I used the accepted solution from here which checks if it was run as a server, and not when executing other
managy.py
commands such as
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
And since that will still get executed twice when in development mode, without using the parameter
--noreload
, I added a flag to be triggered when it is running as a server and put my start up code in which is only called once.
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')
So to summarize, I am utilizing the read() function in AppConfig to read the arguments and know how the code was executed. But since in the development mode the ready() function is run twice, one for the server and one for the reloading of the server when the code changes, while
urls.py
is only executed once for the server. So in my solution i combined the two to run my task once and only when the code is executed as a server.
В моем случае я использую Django для размещения сайта и Heroku. Я использую 1 дино (как и 1 контейнер) в Heroku, и этот дино создает двух рабочих. Я хочу запустить на нем бота Discord. Я перепробовал все методы на этой странице, и все они недействительны.
Поскольку это развертывание, поэтому не следует использовать manage.py. Вместо этого он использует пулемет, который я не знаю, как добавить
--noreload
Но я заметил одну вещь: при каждом развертывании 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 в будущем, надеюсь, что он всегда будет таким же. Если у вас есть способ лучше проверить, какой это воркер, скажите, пожалуйста.