Thực thi mã khi Django chỉ bắt đầu ONCE?


176

Tôi đang viết một lớp Django Middleware mà tôi muốn chỉ thực hiện một lần khi khởi động, để khởi tạo một số mã đơn vị khác. Tôi đã theo dõi giải pháp rất hay được đăng bởi sdolan tại đây , nhưng thông báo "Xin chào" được xuất ra thiết bị đầu cuối hai lần . Ví dụ

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

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

và trong tệp cài đặt Django của tôi, tôi đã có lớp trong MIDDLEWARE_CLASSESdanh sách.

Nhưng khi tôi chạy Django bằng máy chủ và yêu cầu một trang, tôi nhận được trong thiết bị đầu cuối

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

Bất cứ ý tưởng tại sao "Hello world" được in hai lần? Cảm ơn.


1
chỉ vì tò mò, bạn đã hiểu tại sao mã trong init .py được thực thi hai lần chưa?
Mutant

3
@Mutant nó chỉ được thực thi hai lần trong máy chủ ... đó là vì trước tiên máy chủ tải lên các ứng dụng để kiểm tra chúng và sau đó thực sự khởi động máy chủ. Ngay cả khi tự động tải lại máy chủ, mã chỉ được thực thi một lần.
Pykler

1
Wow tôi đã ở đây .... vì vậy cảm ơn bạn một lần nữa cho nhận xét @Pykler, đó là những gì tôi đã tự hỏi.
WesternGun

Câu trả lời:


111

Cập nhật từ câu trả lời của Pykler bên dưới: Django 1.7 hiện có một kết nối cho việc này


Đừng làm theo cách này.

Bạn không muốn "phần mềm trung gian" cho một thứ khởi động một lần.

Bạn muốn thực thi mã ở cấp cao nhất urls.py. Mô-đun đó được nhập và thực hiện một lần.

urls.py

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

urlpatterns = ...

one_time_startup()

1
@Andrei: Các lệnh quản lý hoàn toàn là một vấn đề riêng biệt. Ý tưởng khởi động một lần đặc biệt trước tất cả các lệnh quản lý là khó hiểu. Bạn sẽ phải cung cấp một cái gì đó cụ thể . Có lẽ trong một câu hỏi khác.
S.Lott

1
Đã thử in văn bản đơn giản trong urls.py, nhưng hoàn toàn không có đầu ra. Chuyện gì đang xảy ra vậy?
Steve K

8
Mã urls.py chỉ được thực thi ở yêu cầu đầu tiên (đoán nó trả lời câu hỏi của @SteveK) (django 1.5)
lajarre

4
Điều này thực thi một lần cho mỗi công nhân, trong trường hợp của tôi, nó thực hiện tổng cộng 3 lần.
Raphael

9
@halilpazarlama Câu trả lời này đã hết hạn - bạn nên sử dụng câu trả lời từ Pykler.
Mark Chackerian

271

Cập nhật: Django 1.7 hiện có một cái móc cho việc này

tập tin: myapp/apps.py

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

tập tin: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Dành cho Django <1.7

Câu trả lời số một dường như không còn hoạt động nữa, urls.py được tải theo yêu cầu đầu tiên.

Những gì đã hoạt động gần đây là đặt mã khởi động vào bất kỳ một trong những initALLED_APPS init .py của bạn, vdmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Khi sử dụng ./manage.py runserver... điều này được thực thi hai lần, nhưng đó là vì máy chủ có một số thủ thuật để xác thực các mô hình trước tiên, v.v ... triển khai bình thường hoặc ngay cả khi máy chủ tự động tải lại, điều này chỉ được thực hiện một lần.


4
Tôi nghĩ rằng điều này được thực hiện cho mỗi quá trình tải dự án. Vì vậy, tôi không thể nghĩ tại sao điều này sẽ không hoạt động hoàn hảo trong bất kỳ kịch bản triển khai nào. Điều này không làm việc cho các lệnh quản lý. +1
Skylar Saveland

2
Tôi hiểu rằng giải pháp này có thể được sử dụng để thực thi một số mã tùy ý khi máy chủ khởi động nhưng có thể chia sẻ một số dữ liệu sẽ được tải không? Ví dụ, tôi muốn tải một đối tượng có chứa một ma trận lớn, đặt ma trận này vào một biến và sử dụng nó, thông qua một api web, trong mỗi yêu cầu người dùng có thể thực hiện. Là một thứ sở hữu?
Patrick

2
Các tài liệu nói rằng đây không phải là nơi để thực hiện bất kỳ tương tác cơ sở dữ liệu. Điều đó làm cho nó không phù hợp với rất nhiều mã. Mã này có thể đi đâu?
Đánh dấu

3
EDIT: Một hack có thể là để kiểm tra các dòng lệnh đối số bất kỳ (x in sys.argv cho x trong ['makemigations', 'di chuyển'])
Conroblicultor

2
Nếu tập lệnh của bạn đang chạy hai lần, bạn hãy xem câu trả lời này: stackoverflow.com/a/28504072/5443056
Braden Holt

37

Câu hỏi này được trả lời tốt trong bài đăng trên blog Điểm nhập cảnh cho các dự án Django , sẽ hoạt động cho Django> = 1.4.

Về cơ bản, bạn có thể sử dụng <project>/wsgi.pyđể làm điều đó và nó sẽ chỉ được chạy một lần, khi máy chủ khởi động, nhưng không phải khi bạn chạy các lệnh hoặc nhập một mô-đun cụ thể.

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

Một lần nữa thêm một bình luận để xác nhận rằng phương thức này sẽ chỉ thực thi mã một lần. Không cần bất kỳ cơ chế khóa.
ATOzTOA

Các tập lệnh đã được thêm vào ở đây dường như không được thực thi khi khung kiểm tra bắt đầu
Lewisou

Câu trả lời này đã kết thúc một cuộc tìm kiếm kéo dài hai ngày rưỡi cho các giải pháp đơn giản là không hiệu quả.
Neil Munro

3
Lưu ý rằng điều này thực thi khi yêu cầu đầu tiên được thực hiện cho trang web, không phải khi bạn khởi động Apache.
user984003

18

Nếu nó giúp được ai đó, ngoài câu trả lời của pykler , tùy chọn "--noreload" sẽ ngăn máy chủ thực thi lệnh khi khởi động hai lần:

python manage.py runserver --noreload

Nhưng lệnh đó sẽ không tải lại máy chủ sau khi thay đổi mã khác.


1
Cảm ơn điều này đã giải quyết vấn đề của tôi! Tôi hy vọng khi tôi triển khai điều này không xảy ra
Gabo

2
Cách khác, bạn có thể kiểm tra nội dung os.environ.get('RUN_MAIN')để chỉ thực thi mã của mình một lần trong quy trình chính (xem stackoverflow.com/a/28504072 )
đấu tranh vào

Vâng, câu trả lời này cộng với pykler cũng có hiệu quả với tôi, vì nó đã ngăn chặn nhiều ready(self)cuộc gọi trong khi vẫn có thể bắt đầu chúng chỉ một lần. Chúc mừng!
DarkCygnus

Django runservertheo mặc định bắt đầu hai quá trình với số pid riêng biệt (khác nhau). --noreloadlàm cho nó bắt đầu một quá trình.
Eugene Gr. Phi-líp

15

Theo đề xuất của @Pykler, trong Django 1.7+, bạn nên sử dụng hook được giải thích trong câu trả lời của anh ấy, nhưng nếu bạn muốn chức năng của mình chỉ được gọi khi chạy máy chủ (và không phải khi thực hiện di chuyển, di chuyển, shell, v.v. ) và bạn muốn tránh các ngoại lệ AppRegistryNotReady bạn phải làm như sau:

tập tin: 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
Cái này có chạy trong chế độ sản xuất không? AFAIK trong sản phẩm. chế độ không có "máy chủ" bắt đầu.
nerdoc

Cảm ơn vì điều đó! Tôi có Trình lập lịch trình Python nâng cao trong ứng dụng của mình và tôi không muốn chạy trình lập lịch biểu khi chạy các lệnh Manage.txt.
lukik

4

Lưu ý rằng bạn không thể tin cậy kết nối với cơ sở dữ liệu hoặc tương tác với các mô hình bên trong AppConfig.readychức năng (xem cảnh báo trong tài liệu).

Nếu bạn cần tương tác với cơ sở dữ liệu trong mã khởi động của mình, một khả năng là sử dụng connection_createdtín hiệu để thực thi mã khởi tạo khi kết nối với cơ sở dữ liệu.

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

Rõ ràng, giải pháp này là để chạy mã một lần cho mỗi kết nối cơ sở dữ liệu, không phải một lần cho mỗi dự án bắt đầu. Vì vậy, bạn sẽ muốn một giá trị hợp lý cho CONN_MAX_AGEcài đặt để bạn không chạy lại mã khởi tạo cho mỗi yêu cầu. Cũng lưu ý rằng máy chủ phát triển bỏ qua CONN_MAX_AGE, vì vậy bạn S run chạy mã một lần cho mỗi yêu cầu trong quá trình phát triển.

99% đây là một ý tưởng tồi - mã khởi tạo cơ sở dữ liệu nên được di chuyển - nhưng có một số trường hợp sử dụng mà bạn không thể tránh khởi tạo muộn và các cảnh báo ở trên có thể chấp nhận được.


2
Đây là một giải pháp tốt nếu bạn cần truy cập cơ sở dữ liệu trong mã khởi động của mình. Một phương pháp đơn giản để làm cho nó chỉ chạy một lần là có my_receiverchức năng tự ngắt kết nối khỏi connection_createdtín hiệu, cụ thể, thêm các mục sau vào my_receiverchức năng : connection_created.disconnect(my_receiver).
alan

1

nếu bạn muốn in "hello world" một lần khi bạn chạy máy chủ, hãy đặt print ("hello world") ra khỏi lớp 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
Chào Oscar! Trên SO, chúng tôi muốn các câu trả lời bao gồm một lời giải thích bằng tiếng Anh, và không chỉ mã. Bạn có thể vui lòng giải thích ngắn gọn về cách / tại sao mã của bạn khắc phục vấn đề không?
Max von Hippel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.