Code ausführen, wenn Django nur EINMAL startet?

176

Ich schreibe eine Django Middleware-Klasse, die ich beim Start nur einmal ausführen möchte, um einen anderen britischen Code zu initialisieren. Ich habe die sehr nette Lösung von sdolan hier verfolgt , aber die Nachricht "Hallo" wird zweimal an das Terminal ausgegeben . Z.B

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

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

und in meiner Django-Einstellungsdatei habe ich die Klasse in der MIDDLEWARE_CLASSESListe enthalten.

Aber wenn ich Django mit runserver starte und eine Seite anfordere, komme ich ins 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

Irgendwelche Ideen, warum "Hallo Welt" zweimal gedruckt wird? Vielen Dank.

Bob_94
quelle
1
Haben Sie aus Neugier herausgefunden, warum der Code in init .py zweimal ausgeführt wird?
Mutant
3
@Mutant wird unter runserver nur zweimal ausgeführt ... das liegt daran, dass runserver zuerst die Apps lädt, um sie zu überprüfen, und dann den Server tatsächlich startet. Selbst beim automatischen Laden des Runservers wird der Code nur einmal ausgeführt.
Pykler
1
Wow, ich war hier ... also nochmals vielen Dank für den Kommentar @Pykler, das habe ich mich gefragt.
WesternGun

Antworten:

111

Update von Pyklers Antwort unten: Django 1.7 hat jetzt einen Haken dafür


Mach es nicht so.

Sie möchten keine "Middleware" für eine einmalige Startsache.

Sie möchten Code in der obersten Ebene ausführen urls.py. Dieses Modul wird einmal importiert und ausgeführt.

urls.py

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

urlpatterns = ...

one_time_startup()
S.Lott
quelle
1
@Andrei: Verwaltungsbefehle sind ein völlig separates Problem. Die Idee eines speziellen einmaligen Starts vor allen Verwaltungsbefehlen ist schwer zu verstehen. Sie müssen etwas Bestimmtes angeben . Vielleicht in einer anderen Frage.
S.Lott
1
Versucht, einfachen Text in urls.py zu drucken, aber es gab absolut keine Ausgabe. Was ist los ?
Steve K
8
Der urls.py-Code wird nur bei der ersten Anfrage ausgeführt (ich schätze, er beantwortet die Frage von @SteveK) (django 1.5)
lajarre
4
Dies wird einmal für jeden Arbeiter ausgeführt, in meinem Fall insgesamt dreimal.
Raphael
9
@halilpazarlama Diese Antwort ist veraltet - Sie sollten die Antwort von Pykler verwenden.
Mark Chackerian
271

Update: Django 1.7 hat jetzt einen Haken dafür

Datei: myapp/apps.py

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

Datei: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Für Django <1,7

Die Antwort Nummer eins scheint nicht mehr zu funktionieren, urls.py wird bei der ersten Anfrage geladen.

Was in letzter Zeit funktioniert hat, ist, den Startcode in eine Ihrer INSTALLED_APPS- Init .py zu setzen, zmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Bei Verwendung von ./manage.py runserver... wird dies zweimal ausgeführt, aber das liegt daran, dass Runserver einige Tricks hat, um die Modelle zuerst zu validieren usw. ... normale Bereitstellungen oder selbst wenn Runserver automatisch neu geladen wird, wird dies nur einmal ausgeführt.

Pykler
quelle
4
Ich denke, dies wird für jeden Prozess ausgeführt, der das Projekt lädt. Ich kann mir also nicht vorstellen, warum dies in keinem Bereitstellungsszenario perfekt funktioniert. Dies funktioniert für Verwaltungsbefehle. +1
Skylar Saveland
2
Ich verstehe, dass diese Lösung verwendet werden kann, um beim Starten des Servers beliebigen Code auszuführen. Ist es jedoch möglich, einige Daten zu teilen , die geladen werden würden? Zum Beispiel möchte ich ein Objekt laden, das eine riesige Matrix enthält, diese Matrix in eine Variable einfügen und sie über eine Web-API in jeder Anforderung verwenden, die ein Benutzer ausführen kann. Ist so etwas möglich?
Patrick
2
In der Dokumentation heißt es, dass hier keine Datenbankinteraktion durchgeführt werden kann. Das macht es für viel Code ungeeignet. Wohin könnte dieser Code gehen?
Mark
3
BEARBEITEN: Ein möglicher Hack besteht darin, die Befehlszeilenargumente beliebig zu überprüfen (x in sys.argv für x in ['makemigrations', 'migrate'])
Conchylicultor
2
Wenn Ihr Skript zweimal ausgeführt wird, überprüfen Sie diese Antwort: stackoverflow.com/a/28504072/5443056
Braden Holt
37

Diese Frage wird im Blog-Beitrag Entry Point Hook für Django-Projekte , der für Django> = 1.4 funktioniert, gut beantwortet .

Grundsätzlich können Sie dies verwenden <project>/wsgi.py, und es wird nur einmal ausgeführt, wenn der Server gestartet wird, nicht jedoch, wenn Sie Befehle ausführen oder ein bestimmtes Modul importieren.

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()
Augustomen
quelle
Fügen Sie erneut einen Kommentar hinzu, um zu bestätigen, dass diese Methode den Code nur einmal ausführt. Keine Verriegelungsmechanismen erforderlich.
ATOzTOA
Skripte, die hier hinzugefügt wurden, scheinen nicht ausgeführt zu werden, wenn das Test-Framework startet
Lewisou
Diese Antwort beendete eine zweieinhalbtägige Suche nach Lösungen, die einfach nicht funktionierten.
Neil Munro
3
Beachten Sie, dass dies ausgeführt wird, wenn die erste Anforderung an die Website gestellt wird, nicht wenn Sie Apache starten.
user984003
18

Wenn es jemandem hilft, verhindert die Option "--noreload" zusätzlich zu Pyklers Antwort, dass der Runserver den Befehl beim Start zweimal ausführt:

python manage.py runserver --noreload

Dieser Befehl lädt den Runserver jedoch auch nach Änderungen des anderen Codes nicht neu.

AnaPana
quelle
1
Danke das hat mein Problem gelöst! Ich hoffe, wenn ich das bereitstelle, passiert das nicht
Gabo
2
Alternativ können Sie den Inhalt von überprüfen os.environ.get('RUN_MAIN'), um Ihren Code nur einmal im Hauptprozess auszuführen (siehe stackoverflow.com/a/28504072 )
bdoering
Ja, diese Plus-Pykler-Antwort hat auch für mich funktioniert, da sie die mehrfachen ready(self)Anrufe verhinderte und sie dennoch nur einmal starten konnte. Prost!
DarkCygnus
Django runserverstartet standardmäßig zwei Prozesse mit unterschiedlichen (unterschiedlichen) PID-Nummern. --noreloadlässt es einen Prozess starten.
Eugene Gr. Philippov
15

Wie von @Pykler vorgeschlagen, sollten Sie in Django 1.7+ den in seiner Antwort erläuterten Hook verwenden. Wenn Sie jedoch möchten, dass Ihre Funktion nur aufgerufen wird, wenn der Ausführungsserver aufgerufen wird (und nicht, wenn Migrationen durchgeführt werden, werden Migration, Shell usw. aufgerufen ), und Sie möchten AppRegistryNotReady-Ausnahmen vermeiden, müssen Sie wie folgt vorgehen :

Datei: 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
Alberto Pianon
quelle
12
Läuft dies im Produktionsmodus? AFAIK in prod. Modus ist kein "Runserver" gestartet.
Nerdoc
Danke dafür! Ich habe Advanced Python Scheduler in meiner App und wollte den Scheduler nicht ausführen, wenn ich die Befehle manage.py ausführe.
Lukik
4

Beachten Sie, dass Sie keine zuverlässige Verbindung zur Datenbank herstellen oder mit Modellen innerhalb der AppConfig.readyFunktion interagieren können (siehe Warnung in den Dokumenten).

Wenn Sie in Ihrem Startcode mit der Datenbank interagieren müssen, besteht eine Möglichkeit darin, das connection_createdSignal zu verwenden, um den Initialisierungscode bei Verbindung mit der Datenbank auszuführen.

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

Offensichtlich dient diese Lösung dazu, Code einmal pro Datenbankverbindung und nicht einmal pro Projektstart auszuführen. Sie möchten also einen sinnvollen Wert für die CONN_MAX_AGEEinstellung, damit Sie den Initialisierungscode nicht bei jeder Anforderung erneut ausführen. Beachten Sie auch, dass der Entwicklungsserver dies ignoriert CONN_MAX_AGE, sodass Sie den Code einmal pro Anforderung in der Entwicklung ausführen.

In 99% der Fälle ist dies eine schlechte Idee - Datenbankinitialisierungscode sollte bei Migrationen verwendet werden -, aber es gibt einige Anwendungsfälle, in denen Sie eine späte Initialisierung nicht vermeiden können und die oben genannten Einschränkungen akzeptabel sind.

RichardW
quelle
2
Dies ist eine gute Lösung, wenn Sie in Ihrem Startcode auf die Datenbank zugreifen müssen. Eine einfache Methode, um es nur einmal zum Laufen zu bringen, besteht darin, die my_receiverFunktion vom connection_createdSignal zu trennen. Fügen Sie der Funktion insbesondere Folgendes hinzu my_receiver: connection_created.disconnect(my_receiver).
Alan
1

Wenn Sie "Hallo Welt" einmal drucken möchten, wenn Sie den Server ausführen, setzen Sie "Drucken" ("Hallo Welt") aus der Klasse 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"
Oscar
quelle
3
Hallo Oscar! Bei SO bevorzugen wir, dass die Antworten eine Erklärung auf Englisch und nicht nur Code enthalten. Könnten Sie bitte kurz erklären, wie / warum Ihr Code das Problem behebt?
Max von Hippel