Django yalnızca ONCE başladığında kod çalıştırılsın mı?


177

Başka bir arbritary kodu başlatmak için, başlangıçta yalnızca bir kez yürütmek istediğiniz bir Django Middleware sınıfı yazıyorum. Burada sdolan tarafından gönderilen çok güzel bir çözüm izledim , ancak "Merhaba" mesajı iki kez terminale çıktı . Örneğin

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

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

ve Django ayarları dosyamda, sınıfın MIDDLEWARE_CLASSES listeye .

Ama Django'yu runserver kullanarak çalıştırıp bir sayfa istediğimde, terminalde

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

"Merhaba dünya" nın neden iki kez basıldığı hakkında bir fikrin var mı? Teşekkürler.


1
sadece merak için, init .py içindeki kodun neden iki kez yürütüldüğünü anladınız mı?
Mutant

3
@Mutant sadece runserver altında iki kez yürütülür ... çünkü runserver onları incelemek için önce uygulamaları yükler ve sonra aslında sunucuyu başlatır. Runserver'ın otomatik olarak yüklenmesi üzerine bile kod sadece bir kez yürütülür.
Pykler

1
Vay be burada olmuştur .... çok tekrar @Pykler yorum için teşekkür ederim, ben ne olduğunu merak ediyordum.
WesternGun

Yanıtlar:


112

Aşağıdaki Pykler'in cevabından güncelleme: Django 1.7 artık bunun için bir kancaya sahip


Bu şekilde yapma.

Bir kerelik başlangıç ​​için "ara katman yazılımı" istemezsiniz.

Kodu üst düzeyde yürütmek istiyorsunuz urls.py. Bu modül bir kez içe aktarılır ve yürütülür.

urls.py

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

urlpatterns = ...

one_time_startup()

1
@Andrei: Yönetim Komutları tamamen ayrı bir sorundur. Tüm yönetim komutlarından önce tek seferlik özel başlatma fikrini anlamak zordur. Belirli bir şey sağlamanız gerekecek . Belki başka bir soruda.
S.Lott

1
Urls.py içinde basit metin yazdırmayı denedim, ancak kesinlikle çıktı yoktu. Ne oluyor ?
Steve K

8
Urls.py kodu yalnızca ilk istekte yürütülür (sanırım @SteveK'nin sorusunu yanıtlar) (django 1.5)
lajarre

4
Bu, her işçi için bir kez yürütülür, benim durumumda toplam 3 kez yürütülür.
Raphael

9
@halilpazarlama Bu cevap güncel değil - Pykler'ın yanıtını kullanıyor olmalısınız.
Mark Chackerian

271

Güncelleme: Django 1.7 şimdi bunun için bir kanca var

dosya: myapp/apps.py

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

dosya: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Django için <1.7

Bir numaralı cevap artık çalışmıyor gibi görünüyor, urls.py ilk istek üzerine yüklenir.

Son zamanlarda işe yarayan şey, başlangıç ​​kodunu INSTALLED_APPS init .py'nizin herhangi birine yerleştirmektir.myapp/__init__.py

def startup():
    pass # load a big thing

startup()

Kullanırken ./manage.py runserver... bu iki kez yürütülür, ancak bunun nedeni runserver'ın modelleri önce doğrulamak için bazı hileleri olması vb.


4
Bence bu projeyi yükleyen her işlem için yürütülüyor. Bu yüzden, bunun herhangi bir dağıtım senaryosu altında neden mükemmel çalışmadığını düşünemiyorum. Bu, yönetim komutları için işe yarar. +1
Skylar Saveland

2
Bu çözüm, sunucu başlatıldığında bazı rasgele kod yürütmek için kullanılabilir, ancak yüklenecek bazı verileri paylaşmak mümkün mü anlıyorum ? Örneğin, büyük bir matris içeren bir nesneyi yüklemek, bu matrisi bir değişkene koymak ve bir web api aracılığıyla bir kullanıcının yapabileceği her istekte kullanmak istiyorum. Böyle bir şey mümkün mü?
Patrick

2
Dokümantasyonda bunun herhangi bir veritabanı etkileşimi yapılacak yer olmadığı belirtiliyor. Bu, birçok kod için uygun değildir. Bu kod nereye gidebilir?
Mark

3
DÜZENLEME: Olası bir hack komut satırları argümanlarını kontrol etmektir (x 'sys.argv içinde x için [' makemigrations ',' migrate '])
Conchylicultor

2
Senaryonuz iki kez çalışıyorsa şu yanıtı kontrol edin: stackoverflow.com/a/28504072/5443056
Braden Holt

37

Bu soru, Django> = 1.4 için çalışacak Django projeleri için giriş noktası kancası blog yazısında iyi yanıtlanmıştır .

Temel olarak, bunu <project>/wsgi.pyyapmak için kullanabilirsiniz ve sunucu başlatıldığında yalnızca bir kez çalıştırılır, ancak komutları çalıştırdığınızda veya belirli bir modülü içe aktardığınızda çalışmaz.

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

Yine, bu yöntemin kodu yalnızca bir kez çalıştıracağını doğrulamak için bir yorum eklemek. Herhangi bir kilitleme mekanizmasına gerek yoktur.
ATOzTOA

Test çerçevesi başladığında, burada komut dosyaları eklenmiş gibi görünmüyor
Lewisou

Bu cevap, işe yaramayan çözümler bulmak için iki buçuk günlük bir aramayı sona erdirdi.
Neil Munro

3
Bunun, Apache'yi başlattığınızda değil, web sitesine ilk istek yapıldığında yürütüldüğünü unutmayın.
user984003

18

Birine yardımcı olursa, pykler'in cevabına ek olarak , "--noreload" seçeneği, runserver'ın başlangıçta iki kez komut çalıştırmasını engeller:

python manage.py runserver --noreload

Ancak bu komut, diğer kodların da değişmesinden sonra runserver'ı yeniden yüklemez.


1
Teşekkürler bu sorunumu çözdü! Umarım konuşlandırdığımda bu olmaz
Gabo

2
Alternatif olarak, os.environ.get('RUN_MAIN')kodunuzu ana işlemde yalnızca bir kez yürütmek için içeriğini kontrol edebilirsiniz (bkz. Stackoverflow.com/a/28504072 )
bdoering

Evet, bu artı pykler'ın yanıtı da benim için çalıştı, çünkü birden fazla ready(self)çağrıyı yine de bir kez başlatabildi. Şerefe!
DarkCygnus

Django'lar runservervarsayılan olarak farklı (farklı) pid numaraları ile iki işlem başlatır. --noreloadtek bir işlem başlatmasını sağlar.
Eugene Gr. Philippov

15

@Pykler tarafından önerildiği gibi, Django 1.7+ sürümünde cevabında açıklanan kancayı kullanmalısınız, ancak işlevinizin yalnızca run server çağrıldığında çağrılmasını istiyorsanız (ve geçiş yaparken, migrate, shell vb. ) ve aşağıdaki gibi yapmanız gereken AppRegistryNotReady istisnalarından kaçınmak istersiniz :

dosya: 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
üretim modunda çalışıyor mu? Üretimde AFAIK. modunda "runserver" başlatılmamıştır.
nerdoc

Bunun için teşekkürler! Uygulamamda Gelişmiş Python Zamanlayıcı var ve manage.py komutlarını çalıştırırken zamanlayıcıyı çalıştırmak istemedim.
lukik

4

O Not olamaz içindeki modelleri ile veritabanı veya etkileştiği bağlanmak güvenirliği AppConfig.readyfonksiyonu (bkz uyarı docs).

Başlangıç ​​kodunuzdaki veritabanıyla etkileşim kurmanız gerekirse, bir olasılık connection_createdda veritabanına bağlandıktan sonra başlatma kodunu yürütmek için sinyali kullanmaktır .

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

Açıkçası, bu çözüm, proje başlangıcında bir kez değil, veritabanı bağlantısı başına bir kez kod çalıştırmak içindir. Böylece CONN_MAX_AGEayar için mantıklı bir değer isteyeceksiniz, böylece her istekte başlatma kodunu yeniden çalıştırmıyorsunuz. Ayrıca geliştirme sunucusunun yok saydığını unutmayınCONN_MAX_AGE sırasında kodu bir kez çalıştıracaksınız.

Zamanın% 99'u bu kötü bir fikirdir - veritabanı başlatma kodu geçişlerde olmalıdır - ancak geç başlatmayı önleyemeyeceğiniz ve yukarıdaki uyarıların kabul edilebilir olduğu bazı kullanım durumları vardır.


2
Başlangıç ​​kodunuzdaki veritabanına erişmeniz gerekiyorsa, bu iyi bir çözümdür. Basit bir yöntem yalnızca bir kez çalıştırmak sahip olmaktır almak için my_receiverkendisini kesmek işlevi connection_created, özellikle sinyale aşağıdakileri ekleyin my_receiverişlevi: connection_created.disconnect(my_receiver).
alan

1

Eğer sunucu çalıştırdığınızda bir kez "merhaba dünya" yazdırmak istiyorsanız, print ("merhaba dünya") sınıf dışı koymak 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
Merhaba Oscar! SO'da, cevapların sadece kod değil, İngilizce bir açıklama içermesini tercih ediyoruz. Kodunuzun sorunu nasıl / neden çözdüğünü kısaca açıklar mısınız?
Max von Hippel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.