Exécuter le code lorsque Django démarre UNE SEULE FOIS?


177

J'écris une classe Django Middleware que je souhaite exécuter une seule fois au démarrage, pour initialiser un autre code arbritaire. J'ai suivi la très belle solution postée par sdolan ici , mais le message "Hello" est envoyé deux fois sur le terminal . Par exemple

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

et dans mon fichier de paramètres Django, j'ai la classe incluse dans la MIDDLEWARE_CLASSESliste.

Mais quand je lance Django en utilisant runserver et que je demande une page, j'arrive dans le terminal

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

Des idées pourquoi "Hello world" est imprimé deux fois? Merci.


1
juste par curiosité, avez-vous compris pourquoi le code de init .py est exécuté deux fois?
Mutant du

3
@Mutant, il n'est exécuté que deux fois sous runserver ... c'est parce que runserver charge d'abord les applications pour les inspecter, puis démarre réellement le serveur. Même lors du chargement automatique de runserver, le code n'est exécuté qu'une seule fois.
Pykler

1
Wow j'ai été ici .... alors merci encore pour le commentaire @Pykler, c'est ce que je me demandais.
WesternGun

Réponses:


112

Mise à jour de la réponse de Pykler ci-dessous: Django 1.7 a maintenant un crochet pour cela


Ne fais pas ça de cette façon.

Vous ne voulez pas de "middleware" pour une chose de démarrage ponctuelle.

Vous souhaitez exécuter du code au niveau supérieur urls.py. Ce module est importé et exécuté une fois.

urls.py

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

urlpatterns = ...

one_time_startup()

1
@Andrei: les commandes de gestion sont un problème entièrement distinct. L'idée d'un démarrage spécial unique avant toutes les commandes de gestion est difficile à comprendre. Vous devrez fournir quelque chose de spécifique . Peut-être dans une autre question.
S.Lott

1
J'ai essayé d'imprimer du texte simple dans urls.py, mais il n'y avait absolument aucune sortie. Qu'est-ce qui se passe ?
Steve K

8
Le code urls.py n'est exécuté qu'à la première demande (je suppose qu'il répond à la question de @SteveK) (django 1.5)
lajarre

4
Cela s'exécute une fois pour chaque travailleur, dans mon cas, il est exécuté 3 fois au total.
Raphael

9
@halilpazarlama Cette réponse n'est pas à jour - vous devriez utiliser la réponse de Pykler.
Mark Chackerian

271

Mise à jour: Django 1.7 a maintenant un crochet pour cela

fichier: myapp/apps.py

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

fichier: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Pour Django <1.7

La réponse numéro un ne semble plus fonctionner, urls.py est chargé à la première demande.

Ce qui a fonctionné ces derniers temps, c'est de mettre le code de démarrage dans l'un de vos init .py par exemple INSTALLED_APPSmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Lorsque vous utilisez ./manage.py runserver... cela est exécuté deux fois, mais c'est parce que runserver a quelques astuces pour valider les modèles en premier, etc ... déploiements normaux ou même lorsque runserver se recharge automatiquement, cela n'est exécuté qu'une seule fois.


4
Je pense que cela est exécuté pour chaque processus qui charge le projet. Donc, je ne vois pas pourquoi cela ne fonctionnerait pas parfaitement dans n'importe quel scénario de déploiement. Cela fonctionne pour les commandes de gestion. +1
Skylar Saveland

2
Je comprends que cette solution peut être utilisée pour exécuter du code arbitraire au démarrage du serveur, mais est-il possible de partager des données qui seraient chargées? Par exemple, je veux charger un objet qui contient une énorme matrice, mettre cette matrice dans une variable et l'utiliser, via une API Web, dans chaque requête qu'un utilisateur peut faire. Une telle chose est-elle possible?
Patrick

2
La documentation indique que ce n'est pas le lieu idéal pour interagir avec la base de données. Cela le rend inadapté à beaucoup de code. Où ce code pourrait-il aller?
Marquez le

3
EDIT: Un hack possible est de vérifier les arguments des lignes de commande (x dans sys.argv pour x dans ['makemigrations', 'migrate'])
Conchylicultor

2
Si votre script s'exécute deux fois, consultez cette réponse: stackoverflow.com/a/28504072/5443056
Braden Holt

37

Cette question est bien répondue dans l'article de blog Point d'entrée pour les projets Django , qui fonctionnera pour Django> = 1.4.

Fondamentalement, vous pouvez utiliser <project>/wsgi.pypour le faire, et il ne sera exécuté qu'une seule fois, au démarrage du serveur, mais pas lorsque vous exécutez des commandes ou importez un module particulier.

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()

Encore une fois en ajoutant un commentaire pour confirmer que cette méthode n'exécutera le code qu'une seule fois. Pas besoin de mécanismes de verrouillage.
ATOzTOA

Les scripts ajoutés ici ne semblent pas être exécutés lorsque le framework de test démarre
Lewisou

Cette réponse a mis fin à une recherche de deux jours et demi de solutions qui n'ont tout simplement pas fonctionné.
Neil Munro

3
Notez que cela s'exécute lorsque la première requête est effectuée sur le site Web, pas lorsque vous démarrez Apache.
user984003

18

Si cela aide quelqu'un, en plus de la réponse de pykler, l'option "--noreload" empêche runserver d'exécuter la commande au démarrage deux fois:

python manage.py runserver --noreload

Mais cette commande ne rechargera pas non plus runserver après les modifications d'autres codes.


1
Merci cela a résolu mon problème! J'espère que lorsque je déploierai cela ne se produira pas
Gabo

2
Comme alternative, vous pouvez vérifier le contenu de os.environ.get('RUN_MAIN')pour n'exécuter votre code qu'une seule fois dans le processus principal (voir stackoverflow.com/a/28504072 )
bdoering

Oui, cette réponse de pykler plus a fonctionné pour moi aussi, car elle a empêché les ready(self)appels multiples tout en ne pouvant les démarrer qu'une seule fois. À votre santé!
DarkCygnus

Django runserverdémarre par défaut deux processus avec des nombres pid distincts (différents). --noreloadfait démarrer un processus.
Eugene Gr. Philippov

15

Comme suggéré par @Pykler, dans Django 1.7+ vous devriez utiliser le hook expliqué dans sa réponse, mais si vous voulez que votre fonction ne soit appelée que lorsque run server est appelé (et non lors de migrations, migrate, shell, etc. sont appelés ), et vous souhaitez éviter les exceptions AppRegistryNotReady vous devez procéder comme suit:

fichier: 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
cela fonctionne-t-il en mode production? AFAIK en prod. mode il n'y a pas de "runserver" démarré.
nerdoc

Merci pour cela! J'ai Advanced Python Scheduler dans mon application et je ne voulais pas exécuter le planificateur lors de l'exécution des commandes manage.py.
lukik

4

Notez que vous ne pouvez pas vous connecter de manière fiable à la base de données ou interagir avec les modèles à l'intérieur de la AppConfig.readyfonction (voir l' avertissement dans la documentation).

Si vous avez besoin d'interagir avec la base de données dans votre code de démarrage, une possibilité est d'utiliser le connection_createdsignal pour exécuter le code d'initialisation lors de la connexion à la base de données.

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

Évidemment, cette solution consiste à exécuter le code une fois par connexion à la base de données, pas une fois par démarrage de projet. Vous voudrez donc une valeur raisonnable pour le CONN_MAX_AGEparamètre afin de ne pas réexécuter le code d'initialisation à chaque demande. Notez également que le serveur de développement ignore CONN_MAX_AGE, donc vous exécuterez le code une fois par requête en développement.

99% du temps, c'est une mauvaise idée - le code d'initialisation de la base de données devrait aller dans les migrations - mais il existe des cas d'utilisation où vous ne pouvez pas éviter une initialisation tardive et les mises en garde ci-dessus sont acceptables.


2
C'est une bonne solution si vous avez besoin d'accéder à la base de données dans votre code de démarrage. Une méthode simple pour le faire fonctionner qu'une seule fois est d'avoir la my_receiverfonction elle - même se déconnecter du connection_createdsignal spécifique, ajoutez ce qui suit à la my_receiverfonction: connection_created.disconnect(my_receiver).
alan le

1

si vous voulez imprimer "hello world" une fois lorsque vous exécutez le serveur, mettez print ("hello world") hors de la classe 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
Salut Oscar! Sur SO, nous préférons que les réponses incluent une explication en anglais, et pas seulement du code. Pourriez-vous s'il vous plaît expliquer brièvement comment / pourquoi votre code résout le problème?
Max von Hippel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.