เรียกใช้รหัสเมื่อ Django เริ่มครั้งเดียวเท่านั้น?


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 และขอหน้าฉันจะไปที่ 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

แนวคิดใดที่พิมพ์ "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
พยายามพิมพ์ข้อความอย่างง่ายใน url.py แต่ก็ไม่มีผลลัพธ์ เกิดอะไรขึ้น ?
Steve K

8
รหัส urls.py จะถูกดำเนินการตามคำขอแรกเท่านั้น (เดาว่าจะตอบคำถามของ @SteveK) (django 1.5)
lajarre

4
สิ่งนี้เรียกใช้ครั้งเดียวสำหรับพนักงานแต่ละคนในกรณีของฉันจะมีการดำเนินการทั้งหมด 3 ครั้ง
Raphael

9
@halilpazarlama คำตอบนี้ล้าสมัย - คุณควรใช้คำตอบจาก Pykler
Mark Chackerian

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'

สำหรับ Django <1.7

ดูเหมือนคำตอบหมายเลขหนึ่งจะไม่ทำงานอีกต่อไปโหลด urls.py เมื่อมีการร้องขอครั้งแรก

สิ่งที่ทำงานได้ไม่นานคือการใส่รหัสเริ่มต้นในหนึ่งใน INSTALLED_APPS init .py ของคุณเช่นmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

เมื่อใช้./manage.py runserver... สิ่งนี้จะถูกดำเนินการสองครั้ง แต่นั่นเป็นเพราะ runserver มีเทคนิคบางอย่างในการตรวจสอบความถูกต้องของโมเดลก่อน ฯลฯ ... การปรับใช้ตามปกติหรือแม้กระทั่งเมื่อ runserver auto reloads นี่จะถูกดำเนินการเพียงครั้งเดียวเท่านั้น


4
ฉันคิดว่าสิ่งนี้ได้รับการดำเนินการสำหรับแต่ละกระบวนการที่โหลดโครงการ ดังนั้นฉันคิดไม่ออกเลยว่าทำไมสิ่งนี้ถึงไม่สามารถทำงานได้อย่างสมบูรณ์แบบในทุกสถานการณ์ สิ่งนี้ใช้ได้กับคำสั่งการจัดการ +1
Skylar Saveland

2
ฉันเข้าใจว่าโซลูชันนี้สามารถใช้เพื่อเรียกใช้รหัสโดยอำเภอใจเมื่อเซิร์ฟเวอร์เริ่มทำงาน แต่เป็นไปได้หรือไม่ที่จะแบ่งปันข้อมูลบางอย่างที่จะโหลด ตัวอย่างเช่นฉันต้องการโหลดวัตถุที่มีเมทริกซ์ขนาดใหญ่วางเมทริกซ์นี้ในตัวแปรและใช้ผ่านเว็บ API ในคำขอแต่ละครั้งที่ผู้ใช้สามารถทำได้ เป็นสิ่งที่เป็นไปได้หรือไม่?
Patrick

2
เอกสารบอกว่านี่ไม่ใช่สถานที่ที่จะโต้ตอบกับฐานข้อมูลใด ๆ ทำให้ไม่เหมาะกับรหัสจำนวนมาก รหัสนี้ไปไหน
ทำเครื่องหมาย

3
แก้ไข: แฮ็คที่เป็นไปได้คือการตรวจสอบอาร์กิวเมนต์บรรทัดคำสั่งใด ๆ (x ใน sys.argv สำหรับ x ใน ['makemigrations', 'migrate'])
Conchylicultor

2
หากสคริปต์ของคุณทำงานสองครั้งคุณลองดูคำตอบนี้: stackoverflow.com/a/28504072/5443056
Braden Holt

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

สคริปต์ที่เพิ่มเข้ามาที่นี่ดูเหมือนจะไม่ถูกประหารเมื่อกรอบการทดสอบเริ่มขึ้น
Lewisou

คำตอบนี้สิ้นสุดการค้นหาโซลูชันสองวันครึ่งซึ่งไม่ได้ผล
Neil Munro

3
โปรดทราบว่าสิ่งนี้จะดำเนินการเมื่อมีการร้องขอครั้งแรกกับเว็บไซต์ไม่ใช่เมื่อคุณเริ่ม Apache
user984003

18

ถ้ามันช่วยใครบางคนนอกจากคำตอบของ pykler แล้วตัวเลือก "--noreload" จะป้องกันไม่ให้ runserver รันคำสั่งเมื่อเริ่มต้นสองครั้ง:

python manage.py runserver --noreload

แต่คำสั่งนั้นจะไม่รีโหลด runserver หลังจากการเปลี่ยนแปลงของรหัสอื่นเช่นกัน


1
ขอบคุณนี้แก้ปัญหาของฉัน! ฉันหวังว่าเมื่อฉันปรับใช้สิ่งนี้จะไม่เกิดขึ้น
Gabo

2
คุณสามารถตรวจสอบเนื้อหาของos.environ.get('RUN_MAIN')เพื่อรันโค้ดของคุณเพียงครั้งเดียวในกระบวนการหลัก (ดูstackoverflow.com/a/28504072 )
bdoering

ใช่คำตอบนี้บวกกับ pykler ทำงานสำหรับฉันด้วยเพราะมันป้องกันการready(self)โทรหลายสายในขณะที่ยังสามารถเริ่มต้นได้เพียงครั้งเดียว ไชโย!
DarkCygnus

Django runserverโดยค่าเริ่มต้นเริ่มต้นสองกระบวนการด้วยหมายเลข pid ที่แตกต่างกัน --noreloadทำให้มันเริ่มต้นหนึ่งกระบวนการ
Eugene Gr. Philippov

15

ตามที่แนะนำโดย @Pykler ใน Django 1.7+ คุณควรใช้ hook ที่อธิบายไว้ในคำตอบของเขา แต่ถ้าคุณต้องการให้ฟังก์ชั่นของคุณถูกเรียกเมื่อเรียกใช้เซิร์ฟเวอร์เท่านั้น (และไม่ใช่เมื่อทำการโยกย้ายโยกย้ายเชลล์ ฯลฯ ) ) และคุณต้องการหลีกเลี่ยงข้อยกเว้น 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 ใน Prod โหมดไม่มี "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

ถ้าคุณต้องการพิมพ์ "hello world" หนึ่งครั้งเมื่อคุณใช้เซิร์ฟเวอร์ให้พิมพ์ ("hello world") ออกจากคลาส 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
สวัสดีออสการ์! บน SO เราต้องการคำตอบที่มีคำอธิบายเป็นภาษาอังกฤษไม่ใช่แค่โค้ด คุณช่วยอธิบายสั้น ๆ เกี่ยวกับวิธีการ / ทำไมรหัสของคุณแก้ไขปัญหาได้หรือไม่?
Max von Hippel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.