Виконати код, коли Django запускає ONCE?


177

Я пишу клас Django Middleware, який хочу виконати лише один раз при запуску, щоб ініціалізувати якийсь інший арбітальний код. Я дотримувався дуже приємного рішення, розміщеного тут sdolan , але повідомлення "Привіт" виводиться в термінал двічі . Напр

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" друкується двічі? Дякую.


1
просто для цікавості, ви зрозуміли, чому код у init .py виконується двічі?
Мутант

3
@Mutant він виконується лише двічі під runserver ... це тому, що runserver спочатку завантажує програми, щоб перевірити їх, а потім фактично запускає сервер. Навіть при автоматичному завантаженні runserver код виконується лише один раз.
Pykler

1
Ого, я був тут .... тож ще раз дякую за коментар @Pykler, саме це мені було цікаво.
WesternGun

Відповіді:


112

Оновлення з відповіді 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()

1
@Andrei: Команди управління - це зовсім окрема проблема. Ідею спеціального одноразового запуску перед усіма командами управління важко зрозуміти. Вам доведеться надати щось конкретне . Можливо, в іншому питанні.
S.Lott

1
Спробував друкувати простий текст у urls.py, але виходу абсолютно не було. Що відбувається ?
Стів К

8
Код urls.py виконується лише на перший запит (здогадуйтесь, що він відповідає на питання @SteveK) (django 1.5)
lajarre

4
Це виконується один раз для кожного працівника, в моєму випадку воно виконується 3 рази.
Рафаель

9
@halilpazarlama Ця відповідь застаріла - ви повинні використовувати відповідь від Pykler.
Марк Чакерян

271

Оновлення: 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, це виконується лише один раз.


4
Я думаю, що це виконується для кожного процесу, що завантажує проект. Отже, я не можу подумати, чому це не спрацювало б ідеально за будь-якого сценарію розгортання. Це працює для команд управління. +1
Skylar Saveland

2
Я розумію, що це рішення може бути використане для виконання якогось довільного коду при запуску сервера, але чи можна обмінюватися деякими даними, які будуть завантажені? Наприклад, я хочу завантажити об’єкт, який містить величезну матрицю, помістити цю матрицю в змінну і використовувати її через веб-api в кожному запиті, який може зробити користувач. Чи таке можливо?
Патрік

2
У документації сказано, що це не місце для взаємодії з базою даних. Це робить його непридатним для великої кількості коду. Куди міг піти цей код?
Марк

3
EDIT: Можливий хак - перевірити аргументи командних рядків будь-які (x in sys.argv for x in ['makemigrations', 'migrate'])
Conchylicultor

2
Якщо ваш сценарій працює двічі, ви знайдете цю відповідь: stackoverflow.com/a/28504072/5443056
Бреден Холт

37

На це запитання добре дано відповідь у дописі на щоденнику в блоку " Вступний пункт для проектів 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()

Знову додайте коментар, щоб підтвердити, що цей метод виконає код лише один раз. Не потрібно жодних механізмів блокування.
ATOzTOA

Сценарії, додані сюди, схоже, не виконуються, коли розпочнеться тестова основа
Льюїсу

Ця відповідь закінчилася дводенним пошуком рішень, які просто не спрацювали.
Ніл Мунро

3
Зауважте, що це виконується, коли перший запит робиться на веб-сайті, а не при запуску Apache.
user984003

18

Якщо це допоможе кому - то, на додаток до pykler в відповідь, «--noreload» опція запобігає runserver від виконання команди на старті двічі:

python manage.py runserver --noreload

Але ця команда також не завантажить runserver після змін інших кодів.


1
Спасибі це вирішило мою проблему! Я сподіваюсь, що при розгортанні цього не станеться
Габо

2
В якості альтернативи, ви можете перевірити вміст os.environ.get('RUN_MAIN')тільки виконати свій код один раз в головному процесі (див stackoverflow.com/a/28504072 )
bdoering

Так, ця відповідь pykler також працювала на мене, оскільки вона перешкоджала численним ready(self)викликам, але все ще змогла запустити їх лише один раз. Ура!
DarkCygnus

runserverЗа замовчуванням Django запускає два процеси з різними (різними) номерами pid. --noreloadзмушує запустити один процес.
Євген Гр. Філіппов

15

Як запропонував @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

12
це працює в режимі виробництва? AFAIK у прод. режим не запущений "runserver".
nerdoc

Дякую за це! У мене в додатку Advanced Python Scheduler, і я не хотів запускати планувальник під час запуску команд manage.py.
lukik

4

Зауважте, що ви не можете надійно підключитися до бази даних або взаємодіяти з моделями всередині 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функцію відключитися від connection_createdсигналу, в зокрема, додати наступне до my_receiverфункції: connection_created.disconnect(my_receiver).
алан

1

якщо ви хочете надрукувати "привіт світ" раз під час запуску сервера, поставте print ("привіт світ") поза класом 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"

3
Привіт Оскаре! Натомість ми вважаємо за краще, щоб відповіді містили пояснення англійською мовою, а не лише код. Скажіть, будь ласка, коротке пояснення, як / чому ваш код вирішує проблему?
Макс фон Гіппель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.