Jalankan kode ketika Django mulai SEKALI saja?

177

Saya sedang menulis kelas Django Middleware yang ingin saya jalankan hanya sekali saat startup, untuk menginisialisasi beberapa kode arbriter lainnya. Saya telah mengikuti solusi yang sangat bagus yang diposting oleh sdolan di sini , tetapi pesan "Halo" adalah output ke terminal dua kali . Misalnya

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

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

dan dalam file pengaturan Django saya, saya punya kelas yang termasuk dalam MIDDLEWARE_CLASSESdaftar.

Tetapi ketika saya menjalankan Django menggunakan runserver dan meminta halaman, saya masuk ke 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

Adakah ide mengapa "Hello world" dicetak dua kali? Terima kasih.

Bob_94
sumber
1
hanya untuk rasa ingin tahu, apakah Anda tahu mengapa kode di init .py dieksekusi dua kali?
Mutant
3
@Mutant itu hanya dijalankan dua kali di bawah runserver ... itu karena runserver pertama memuat aplikasi untuk memeriksanya dan kemudian benar-benar memulai server. Bahkan pada autoreload dari runserver, kode ini hanya exec sekali.
Pykler
1
Wow saya sudah di sini .... jadi terima kasih lagi atas komentar @Pykler, itulah yang saya ingin tahu.
WesternGun

Jawaban:

112

Pembaruan dari jawaban Pykler di bawah ini: Django 1.7 sekarang memiliki kaitan untuk ini


Jangan lakukan seperti ini.

Anda tidak ingin "middleware" untuk startup satu kali.

Anda ingin mengeksekusi kode di tingkat atas urls.py. Modul itu diimpor dan dieksekusi sekali.

urls.py

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

urlpatterns = ...

one_time_startup()
S.Lott
sumber
1
@Andrei: Perintah Manajemen sepenuhnya merupakan masalah yang terpisah. Gagasan startup satu kali khusus sebelum semua perintah manajemen sulit dipahami. Anda harus memberikan sesuatu yang spesifik . Mungkin di pertanyaan lain.
S.Lott
1
Sudah mencoba mencetak teks sederhana di urls.py, tetapi sama sekali tidak ada keluaran. Apa yang terjadi ?
Steve K
8
Kode urls.py dieksekusi hanya pada permintaan pertama (tebak itu menjawab pertanyaan @SteveK) (django 1.5)
lajarre
4
Ini dijalankan sekali untuk setiap pekerja, dalam kasus saya, ini dieksekusi total 3 kali.
Raphael
9
@halilpazarlama Jawaban ini sudah ketinggalan zaman - Anda harus menggunakan jawaban dari Pykler.
Mark Chackerian
271

Pembaruan: Django 1.7 sekarang memiliki pengait untuk ini

mengajukan: myapp/apps.py

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

mengajukan: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Untuk Django <1.7

Jawaban nomor satu tampaknya tidak berfungsi lagi, urls.py dimuat berdasarkan permintaan pertama.

Apa yang telah bekerja akhir-akhir ini adalah untuk menempatkan kode startup di salah satu dari Anda INSTALLED_APPS init Py misalnyamyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Ketika menggunakan ./manage.py runserver... ini dieksekusi dua kali, tetapi itu karena runserver memiliki beberapa trik untuk memvalidasi model pertama dll ... ... penyebaran normal atau bahkan ketika runserver memuat otomatis, ini hanya dijalankan sekali.

Pykler
sumber
4
Saya pikir ini dijalankan untuk setiap proses yang memuat proyek. Jadi, saya tidak bisa memikirkan mengapa ini tidak akan bekerja dengan sempurna dalam skenario penempatan apa pun. Ini berfungsi untuk perintah manajemen. +1
Skylar Saveland
2
Saya mengerti bahwa solusi ini dapat digunakan untuk mengeksekusi beberapa kode arbitrer ketika server mulai tetapi apakah mungkin untuk berbagi beberapa data yang akan dimuat? Misalnya, saya ingin memuat objek yang berisi matriks besar, meletakkan matriks ini dalam variabel dan menggunakannya, melalui api web, dalam setiap permintaan yang dapat dilakukan pengguna. Apakah hal semacam itu mungkin?
Patrick
2
Dokumentasi mengatakan ini bukan tempat untuk melakukan interaksi basis data. Itu membuatnya tidak cocok untuk banyak kode. Di mana kode ini bisa pergi?
Tandai
3
EDIT: Kemungkinan peretasan adalah untuk memeriksa argumen baris perintah apa saja (x di sys.argv untuk x di ['makemigrations', 'migrate'])
Conchylicultor
2
Jika skrip Anda berjalan dua kali Anda memeriksa jawaban ini: stackoverflow.com/a/28504072/5443056
Braden Holt
37

Pertanyaan ini dijawab dengan baik di posting blog Entri titik kait untuk proyek Django , yang akan bekerja untuk Django> = 1.4.

Pada dasarnya, Anda dapat menggunakannya <project>/wsgi.pyuntuk melakukan itu, dan itu akan dijalankan hanya sekali, ketika server mulai, tetapi tidak ketika Anda menjalankan perintah atau mengimpor modul tertentu.

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
sumber
Sekali lagi menambahkan komentar untuk mengonfirmasi bahwa metode ini hanya akan mengeksekusi kode sekali. Tidak perlu mekanisme penguncian.
ATOzTOA
Skrip yang telah ditambahkan di sini tampaknya tidak akan dieksekusi ketika kerangka uji coba dimulai
Lewisou
Jawaban ini mengakhiri pencarian dua setengah hari untuk solusi yang sama sekali tidak berhasil.
Neil Munro
3
Perhatikan bahwa ini dijalankan ketika permintaan pertama dibuat ke situs web, bukan saat Anda memulai Apache.
user984003
18

Jika membantu seseorang, selain pykler ini jawabannya, "--noreload" pilihan mencegah runserver dari mengeksekusi perintah pada startup dua kali:

python manage.py runserver --noreload

Tapi perintah itu tidak akan memuat ulang server setelah perubahan kode lain juga.

AnaPana
sumber
1
Terima kasih ini menyelesaikan masalah saya! Saya harap ketika saya menyebarkan ini tidak terjadi
Gabo
2
Sebagai alternatif, Anda dapat memeriksa konten os.environ.get('RUN_MAIN')untuk hanya mengeksekusi kode Anda satu kali dalam proses utama (lihat stackoverflow.com/a/28504072 )
bdoering
Yup, jawaban ini ditambah pykler bekerja untuk saya juga, karena mencegah beberapa ready(self)panggilan sementara masih dapat memulai hanya sekali. Bersulang!
DarkCygnus
Django runserversecara default memulai dua proses dengan nomor pid yang berbeda (berbeda). --noreloadmembuatnya memulai satu proses.
Eugene Gr. Philippov
15

Seperti yang disarankan oleh @Pykler, dalam Django 1.7+ Anda harus menggunakan hook yang dijelaskan dalam jawabannya, tetapi jika Anda ingin fungsi Anda dipanggil hanya ketika run server dipanggil (dan bukan saat melakukan migrasi, bermigrasi, shell, dll. Disebut ), dan Anda ingin menghindari pengecualian AppRegistryNotReady yang harus Anda lakukan sebagai berikut:

mengajukan: 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
sumber
12
apakah ini berjalan dalam mode produksi? AFAIK dalam prod. mode tidak ada "runserver" dimulai.
nerdoc
Terima kasih untuk ini! Saya memiliki Advanced Python Scheduler di aplikasi saya dan saya tidak ingin menjalankan scheduler ketika menjalankan perintah manage.py.
lukik
4

Perhatikan bahwa Anda tidak dapat keandalan terhubung ke database atau berinteraksi dengan model di dalam AppConfig.readyfungsi (lihat peringatan di dokumen).

Jika Anda perlu berinteraksi dengan database dalam kode start-up Anda, satu kemungkinan adalah menggunakan connection_createdsinyal untuk mengeksekusi kode inisialisasi saat koneksi ke database.

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

Jelas, solusi ini adalah untuk menjalankan kode satu kali per koneksi basis data, bukan sekali per proyek dimulai. Jadi Anda akan menginginkan nilai yang masuk akal untuk CONN_MAX_AGEpengaturan sehingga Anda tidak menjalankan kembali kode inisialisasi pada setiap permintaan. Perhatikan juga bahwa server pengembangan mengabaikan CONN_MAX_AGE, jadi Anda AKAN menjalankan kode sekali per permintaan dalam pengembangan.

99% dari waktu ini adalah ide yang buruk - kode inisialisasi database harus dalam migrasi - tetapi ada beberapa kasus penggunaan di mana Anda tidak dapat menghindari inisialisasi terlambat dan peringatan di atas dapat diterima.

RichardW
sumber
2
Ini adalah solusi yang baik jika Anda perlu mengakses database dalam kode startup Anda. Metode sederhana untuk menjalankannya hanya sekali adalah memiliki my_receiverfungsi putuskan sendiri dari connection_createdsinyal, khususnya, tambahkan berikut ini ke my_receiverfungsi:connection_created.disconnect(my_receiver) .
alan
1

jika Anda ingin mencetak "hello world" satu kali ketika Anda menjalankan server, letakkan print ("hello world") di luar kelas 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
sumber
3
Hai Oscar! Pada SO, kami lebih suka bahwa jawaban mencakup penjelasan dalam bahasa Inggris, dan bukan hanya kode. Bisakah Anda memberikan penjelasan singkat tentang bagaimana / mengapa kode Anda memperbaiki masalah?
Max von Hippel