シグナルハンドラーはdjangoプロジェクトのどこに住むべきですか?


143

私はちょうどdjangoプロジェクトでシグナルリスナーを実装し始めました。私はそれらが何であるか、そしてそれらの使い方を理解しています。私はそれらをどこに置くべきかを理解するのに苦労しています。djangoサイトのドキュメントには、次のように書かれています。

このコードはどこに置くべきですか?

信号処理と登録コードを好きな場所に配置できます。ただし、シグナルが送信される前にシグナル処理が登録されるように、モジュールが含まれているモジュールが早い段階でインポートされていることを確認する必要があります。これにより、アプリのmodels.pyがシグナルハンドラの登録を配置するのに適した場所になります。

それは良い提案ですが、私のmodels.pyに非モデルクラスまたはメソッドがあると、間違った方法で私をこするだけです。

それでは、シグナルハンドラを保存して登録するためのベストプラクティス/ルールは何ですか?

回答:


41

私は実際には、モデル自体のクラスメソッドを作成するのが好きです。これにより、すべてが1つのクラス内に保持され、何もインポートすることを心配する必要がなくなります。


2
また、ハンドラーは通常どこでシグナルに接続しますか?
DataGreed 2010

1
@DataGreed:関連するmodels.pyの下部。
ダニエルローズマン

102
そのモデルが発する信号を聞いている場合、そこにすべてのリスナーを配置すると、全体の練習が無意味になりますよね?信号のポイントは分離することです。リスナーは、これらのリモートイベントに関心のあるコードと共存すべきではありませんか?問題は、エミッターの前にリスナーがロードされるようにする方法です。
John Mee、

私の場合、のFoo一部であるモデルの信号を聞きたいですfooapp。ただし、シグナルレシーバーは拡張機能であり、別のアプリ(たとえばotherapp)にあります。
guettli 2014年

2
John Meeの要点としては、save()などをオーバーライドすることと大差ありません
Matt

246

これは、Django 1.7のリリース時にドキュメントに追加されました。

厳密に言えば、信号処理と登録コードは好きな場所に配置できますが、アプリケーションのルートモジュールとそのモデルモジュールを避けて、コードのインポートによる副作用を最小限に抑えることをお勧めします。

実際には、シグナルハンドラは通常、関連するアプリケーションのシグナルサブモジュールで定義されます。シグナルレシーバーは、アプリケーション構成クラスのready()メソッドで接続されます。receiver()デコレーターを使用している場合は、ready()内に信号サブモジュールをインポートするだけです。

Django 1.7で変更:以前のバージョンのDjangoにはready()が存在しなかったため、通常、信号の登録はモデルモジュールで行われていました。

ベストプラクティスは、次のようなファイルなど、シグナルサブモジュールのhandlers.pyでハンドラーを定義することです。

yourapp / signals / handlers.py

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel

@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    pass

シグナルハンドラーを登録するのに最適な場所は、ready()メソッドを使用して、シグナルハンドラーを定義するアプリのAppConfig内です。これは次のようになります。

yourapp / apps.py

from django.apps import AppConfig

class TasksConfig(AppConfig):
    name = 'tasks'
    verbose_name = "Tasks"

    def ready(self):
        import yourproject.yourapp.signals.handlers #noqa

settings.pyのINSTALLED_APPSで直接、または__init__アプリので指定して、AppConfigをロードしていることを確認してください。詳細については、ready()のドキュメントを参照してください

注:他のアプリも同様にリッスンするシグナルを提供する場合は、それらを__init__シグナルモジュールに入れます(例:次のようなファイル)。

yourapp / signals / __ init__.py

import django.dispatch

task_generate_pre_save = django.dispatch.Signal(providing_args=["task"])

次に、別のアプリが信号をインポートして登録することで信号を聞くことができますfrom yourapp.signals import task_generate_pre_save。ハンドラーからシグナルを分離することで、物事をきれいに保ちます。

Django 1.6の手順:

それでもDjango 1.6以下でスタックしている場合は、同じことを行います(yourapp / signals / handlers.pyでハンドラーを定義します)。ただし、AppConfigを使用する代わりに、__ init__.pyを介してハンドラーをロードします。あなたのアプリ、例えば次のようなもの:

yourapp / __ init__.py

import signals

これは、ready()メソッドの使用ほどよくありません。循環インポートの問題を引き起こすことが多いからです。


3
documentaitonは準備ができ上書き言うように、あなたが何か(1.7.0のよう、それは現在空です)とスーパーのようなものを場合(ReportsConfig、自己).ready()はDjangoのこれまで(レディ移入することを決定した)やりたいこと
W- -

3
この回答は、輸入による副作用に対処するための唯一の回答であるため、最良の回答だと思います。私は、この種の副作用のためにアプリケーションが完全に壊れてしまうため、ベストプラクティスを探してここに来ました。残念ながら、アプリケーションはdjango 1.6で実行されており、ベストプラクティスはdjango 1.7でのみ機能します。__init__インポートシグナルを許可する一時的な回避策は私にとっては機能しないので、新しいdjangoバージョンにアップグレードする準備ができるまで、シグナルをインポートできる別の場所があるかどうか疑問に思います。
kasperd 2014年

from . import handlers(または同様の)存在すべきではありませんyourapp/signals/__init__.pyか?
ドブス

また、handlers.pyモジュールをどこかにインポートすべきではありませんか?私はこれを試していますが、それはシグナルのハンドラを定義しているようではありません。
アンドレス

1
fwiw yourproject.TaskConfigクラスのコードブロックの最後の行は必要ありませんでした。私はこれをこの構造で正確に機能させているので、このqaを検討してください:)
Greg Kaleka '14

40

私はこれに遭遇しただけであり、私の信号はモデルに関連していないので、自分の解決策を追加すると思いました。

ログイン/ログアウトに関するさまざまなデータをログに記録しており、にフックする必要がありますdjango.contrib.auth.signals

シグナルハンドラーをsignals.pyファイルに入れ、__init__.pyモジュールファイルからシグナルをインポートしました。これは、アプリが起動するとすぐに呼び出されると思います(printステートメントでテストすると、設定ファイルが読み取られる前でも呼び出されることが示唆されています)。

# /project/__init__.py
import signals

そしてsignals.pyで

# /project/signals.py
from django.contrib.auth.signals import user_logged_in

def on_logged_in(sender, user, request, **kwargs):
    print 'User logged in as: \'{0}\''.format(user)

user_logged_in.connect(on_logged_in)

私はDjango(/ python)にかなり慣れていないので、これはひどいアイデアだと言ってくれる人なら誰でも受け入れることができます。


3
これは理にかなっていると感じますが、アプリレベルで行うことをお勧めします。
ニルス

2
注意してください。このロジックは、おそらく重複したシグナルを発生させます。user_logged_in.connect(on_logged_in)おそらくdispatch_uid引数を渡す必要があります。詳細については、docs.djangoproject.com / en / dev / topics / signals /…をご覧ください
Scott Coates 2013

それをありがとう-知っておくと良い。私はこの方法(記録IP /ユーザーエージェント)を使用してすべてのログインをログに記録していますが、これまでのところ重複はありません。
Hugo Rodger-Brown

13

プロジェクト/アプリケーションのレイアウトに関するベストプラクティスについてのこの記事を最近読んだところ、すべてのカスタムディスパッチャーシグナルをというファイルに入れる必要があることを示唆していますsignals.py。ただし、これらはまだどこかにインポートする必要があるため、問題が完全に解決されるわけではありません。

モデルの提案は良いものです。signals.pyファイル内のすべてをすでに定義しているので、ファイルの上部にある1行以上は必要ありません。これは、admin.pyファイルをレイアウトする方法に似ています(上部にクラス定義を、下部にすべてのカスタム管理クラスを登録するためのコードを使用)。信号を定義して同じファイルに接続する場合。

お役に立てば幸いです。結局、それはあなたが好むものに帰着します。


1
また、シグナルハンドラーをsignals.pyファイルに入れたいのですが、後でそれを呼び出す方法がわかりませんでした。models.pyファイルにインポートすることで、models.pyファイルを「汚染」することなく、非常にクリーンなソリューションを得ることができました。ありがとうございました!:)
Danilo Bargen

10
そこにクロスインポートがあります:signals.pyはmodels.pyからモデルをインポートしようとします
Ivan Virabyan '05

8

各アプリのmodels.pyとsignals.pyは、信号を接続するための推奨される場所ですが、私の意見では、信号とハンドラーをディスパッチしたままにしておくための最良の解決策ではありません。ディスパッチは、djangoで発明されたシグナルとハンドラーの理由です。

私は長い間苦労していましたが、最終的に解決策を見つけました。

アプリフォルダーにコネクタモジュールを作成する

だから私たちは:

app/
    __init__.py
    signals.py
    models.py
    connectors.py

app / connectors.pyで、シグナルハンドラーを定義して接続します。例を示します。

from signals import example_signal
from models import ExampleModel
from django.db.models.signals import post_save, post_delete

def hanndler(sender, *args, **kwargs):
    pass

post_save.connect(hander, sender=ExampleModel)

次に、models.pyで、ファイルの最後に次の行を追加します。

from app import connector

ここですべてが行われました。

このようにして、signals.pyにシグナルを配置し、connectors.pyにすべてのハンドラーを配置できます。モデルと信号に混乱はありません。

別のソリューションが提供されることを願っています。


1
では、signals.pyには何が入るのでしょうか?あなたの例からそれはちょうどカスタム信号であるように見えます。ほとんどはカスタム信号を持たないため、通常は信号とコネクタを組み合わせるだけです。
ダロア2013

@daloreはい、すべてのカスタムシグナルはsignals.pyに配置されます。多くのカスタマイズされた信号があります。ただし、数が少ない場合は、このファイルを省略できます。
サミュエル2013

@dalと同じ質問
olleh

1
これはすべて古いアドバイスであり、djangoの方法は、appconfigを使用して、シグナルハンドラーが存在する場所にハンドラーをインポートすることです。また、signals.pyでカスタムシグナルを実行する
ダロア

3

私は別のファイルにそれらを保つsignals.pyには、models.pyすべてのモデルが定義されていた後。それらをインポートして、モデルを信号に接続します。

signal.py

#  necessary imports

def send_mail_on_save(<args>):
    # code here 

models.py

# imports
class mymodel(models.Model):
    # model here

# import signals
from signals import send_mail_on_save
# connect them 
post_save.connect(send_mail_on_save,sender=mymodel)

これは私に論理的な分離を提供します。もちろん、それらをmodels.pyに保持することには何の問題もありませんが、 この方法の方が扱いやすいです。

お役に立てれば!!


シグナルハンドラーを "signals.py"に配置している場合、 "handlers.py"と名前を付ければどうなるでしょうか
Abdul Fatah

1
ファイルの名前をsignals.pyとhandler.pyのどちらにするかは関係ありません。それは規則ではなく単なる慣例です。
2018年

3

についての小さなリマインダーAppConfig。設定することを忘れないでください:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.