Gdzie w projekcie django powinny mieszkać programy obsługi sygnałów?

143

Właśnie zacząłem wdrażać odbiorniki sygnału w projekcie django. Chociaż rozumiem, czym one są i jak ich używać. Trudno mi się wymyślić, gdzie je umieścić. Dokumentacja ze strony django ma do powiedzenia:

Gdzie powinien znajdować się ten kod?

Możesz umieścić obsługę sygnału i kod rejestracyjny w dowolnym miejscu. Musisz jednak upewnić się, że moduł, w którym się znajduje, zostanie zaimportowany wcześnie, aby obsługa sygnału została zarejestrowana, zanim jakiekolwiek sygnały będą musiały zostać wysłane. To sprawia, że ​​models.py twojej aplikacji jest dobrym miejscem do umieszczenia rejestracji programów obsługi sygnału.

Chociaż jest to dobra sugestia, posiadanie w moich models.py klas lub metod innych niż modelowe po prostu źle mnie przeciera.

Jaka jest zatem najlepsza praktyka / zasada dotycząca przechowywania i rejestrowania programów obsługi sygnału?

Jason Webb
źródło

Odpowiedzi:

41

Właściwie to lubię uczynić z nich metody klasowe samego modelu. To utrzymuje wszystko w jednej klasie i oznacza, że ​​nie musisz się martwić o importowanie czegokolwiek.

Daniel Roseman
źródło
2
A gdzie zazwyczaj podłączasz programy obsługi do sygnałów?
DataGreed
1
@DataGreed: na dole odpowiednich modelis.py.
Daniel Roseman
102
Jeśli słuchasz sygnałów emitowanych przez ten model, to umieszczenie tam wszystkich słuchaczy sprawia, że ​​całe ćwiczenie jest bezcelowe, prawda? Celem sygnałów jest odsprzęgnięcie. Czy słuchacze nie powinni żyć z kodem, który jest zainteresowany tymi odległymi wydarzeniami? Pytanie brzmi, jak upewnić się, że słuchacze są ładowani przed emiterami.
John Mee
W moim przypadku chcę posłuchać sygnału modelu, Fooktórego jest częścią fooapp. Ale odbiornik sygnału jest rozszerzeniem i działa w innej aplikacji (na przykład otherapp).
guettli
2
Według Johna Mee, nie różni się to zbytnio od zwykłego zastępowania save () itp.
Matt
246

Zostało to dodane do dokumentacji po wydaniu Django 1.7 :

Ściśle mówiąc, obsługa sygnału i kod rejestracyjny mogą znajdować się w dowolnym miejscu, chociaż zaleca się unikanie modułu głównego aplikacji i jego modułu modeli, aby zminimalizować skutki uboczne importu kodu.

W praktyce programy obsługi sygnałów są zwykle definiowane w podmodule sygnałów aplikacji, do której się odnoszą. Odbiorniki sygnału są połączone w metodzie ready () klasy konfiguracyjnej Twojej aplikacji. Jeśli używasz dekoratora receiver (), po prostu zaimportuj podmoduł sygnałów do ready ().

Zmienione w Django 1.7: Ponieważ ready () nie istniał w poprzednich wersjach Django, rejestracja sygnału zwykle miała miejsce w module models.

Najlepszą praktyką jest zdefiniowanie swoich handlerów w handlers.py w module podrzędnym sygnałów, np. W pliku, który wygląda tak:

twojaaplikacja / sygnały / 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

Najlepszym miejscem do zarejestrowania modułu obsługi sygnału jest następnie w AppConfig aplikacji, która go definiuje, przy użyciu metody ready () . Będzie to wyglądać tak:

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

Upewnij się, że ładujesz swoją AppConfig, określając ją bezpośrednio w __init__aplikacji INSTALLED_APPS settings.py lub w aplikacji. Zobacz dokumentację ready (), aby uzyskać więcej informacji.

Uwaga: jeśli zapewniasz sygnały, które mają nasłuchiwać również inne aplikacje, umieść je __init__w swoim module sygnałów, np. Plik, który wygląda tak:

twojaaplikacja / sygnały / __ init__.py

import django.dispatch

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

Inna aplikacja może wtedy słuchać Twojego sygnału, importując go i rejestrując, np from yourapp.signals import task_generate_pre_save. Oddzielenie sygnałów od obsługi pozwala zachować porządek.

Instrukcje dla Django 1.6:

Jeśli nadal utkniesz w Django 1.6 lub niższym, zrobiłbyś to samo (zdefiniowałbyś swoje programy obsługi w yourapp / signal / handlers.py), ale zamiast używać AppConfig, załadowałbyś moduły obsługi przez __init__.py Twoja aplikacja, np. coś takiego:

yourapp / __ init__.py

import signals

Nie jest to tak przyjemne jak użycie metody ready (), ponieważ często powoduje problemy z importowaniem cyklicznym.

Aidan
źródło
3
ponieważ documentaiton mówi, że nadpisujesz ready, możesz chcieć zrobić coś takiego jak super (ReportsConfig, self) .ready () na wypadek, gdyby django kiedykolwiek zdecydował się wypełnić ready () czymś (od 1.7.0 jest obecnie puste)
w- -
3
Myślę, że ta odpowiedź jest najlepsza, ponieważ jako jedyna odnosi się do skutków ubocznych importu. Przyszedłem tutaj w poszukiwaniu najlepszych praktyk, ponieważ czyszczę aplikację, która jest zepsuta właśnie z powodu tego rodzaju skutków ubocznych. Niestety aplikacja działa na django 1.6, a najlepsze praktyki działają tylko na django 1.7. Tymczasowe obejście pozwalające na __init__importowanie sygnałów nie zadziała dla mnie, więc zastanawiam się, czy jest inne miejsce, z którego mógłbym importować sygnały, dopóki nie będziemy gotowi do aktualizacji do późniejszej wersji django.
kasperd
Czy nie powinno być from . import handlers(lub podobnego) w yourapp/signals/__init__.py?
dhobbs
Nie powinieneś też gdzieś importować modułu handlers.py? Próbuję tego i wygląda na to, że nie definiuje modułu obsługi sygnału.
Andrés,
1
fwiw Nie potrzebowałem yourproject.w ostatnim wierszu bloku kodu klasy TaskConfig. Mam to działające z dokładnie taką strukturą, więc rozważ to qa :)
Greg Kaleka
40

Dopiero co się z tym spotkałem, a ponieważ moje sygnały nie są związane z modelem, pomyślałem, że dodam swoje rozwiązanie.

Loguję różne dane dotyczące logowania / wylogowania i muszę się podłączyć django.contrib.auth.signals.

Umieściłem programy obsługi sygnałów w signals.pypliku, a następnie zaimportowałem sygnały z __init__.pypliku modułu, ponieważ uważam, że jest to wywoływane zaraz po uruchomieniu aplikacji (testowanie z printinstrukcją sugeruje, że jest wywoływane nawet przed odczytaniem pliku ustawień).

# /project/__init__.py
import signals

i w signal.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)

Jestem całkiem nowy w Django (/ python), więc jestem otwarty na każdego, kto powie mi, że to okropny pomysł!

Hugo Rodger-Brown
źródło
3
Wydaje się to logiczne, ale sugerowałbym zrobienie tego na poziomie aplikacji.
Nils
2
Ostrożnie, ta logika najprawdopodobniej spowoduje wystrzelenie zduplikowanych sygnałów. user_logged_in.connect(on_logged_in)powinien najprawdopodobniej przekazać dispatch_uidargument. Więcej na docs.djangoproject.com/en/dev/topics/signals/… .
Scott Coates,
Dzięki za to - dobrze wiedzieć. Loguję wszystkie logowania za pomocą tej metody (rejestruję adres IP / klienta użytkownika) i do tej pory nie miałem żadnych duplikatów - chociaż nie oznacza to, że mała zmiana w dół linii nie spowoduje problemu!
Hugo Rodger-Brown
13

Niedawno przeczytałem ten artykuł o najlepszych praktykach, jeśli chodzi o układanie projektów / aplikacji, i sugeruje, że wszystkie niestandardowe sygnały dyspozytora powinny znajdować się w pliku o nazwie signals.py. Jednak nie rozwiązuje to w pełni problemu, ponieważ nadal musisz je gdzieś zaimportować, a im wcześniej zostaną zaimportowane, tym lepiej.

Propozycja modelu jest dobra. Ponieważ zdefiniowałeś już wszystko w swoim signals.pypliku, nie powinno to zająć więcej niż linię na początku pliku. Jest to podobne do sposobu admin.pyułożenia pliku (z definicjami klas na górze i kodem do rejestracji wszystkich niestandardowych klas administracyjnych na dole), jeśli zdefiniujesz sygnały, połącz je w tym samym pliku.

Mam nadzieję, że to pomoże! Ostatecznie wszystko sprowadza się do tego, co wolisz.

hora
źródło
1
Chciałem również umieścić moje programy obsługi sygnałów w signals.pypliku, ale nie wiedziałem, jak to się później nazywa. Importując go do mojego models.pypliku, otrzymałem bardzo czyste rozwiązanie, bez „zanieczyszczania” mojego pliku models.py. Dziękuję Ci! :)
Danilo Bargen
10
jest tam import krzyżowy: sygnały.py próbują zaimportować model z models.py
Ivan Virabyan
8

Models.py i signal.py w każdej aplikacji były zalecanymi miejscami do łączenia sygnałów, jednak moim zdaniem nie są one najlepszym rozwiązaniem do wysyłania sygnałów i obsługi. Wysyłanie powinno być przyczyną sygnałów i procedur obsługi wymyślonych w django.

Walczyłem przez długi czas i w końcu znaleźliśmy rozwiązanie.

utwórz moduł łącznika w folderze aplikacji

więc mamy:

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

w app / connector.py zdefiniowaliśmy programy obsługi sygnału i połączyliśmy je. Podano przykład:

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)

następnie w models.py dodajemy następujący wiersz na końcu pliku:

from app import connector

Wszystko zrobione tutaj.

W ten sposób możemy umieścić sygnały w plikach signal.py, a wszystkie programy obsługi w złączach.py. Bez bałaganu w modelach i sygnałach.

Mam nadzieję, że zapewnia inne rozwiązanie.

samuel
źródło
1
Więc co się dzieje w signal.py? Wygląda na to, że z twojego przykładu to tylko sygnały niestandardowe. Zwykle po prostu łączymy sygnały i złącza, ponieważ większość nie ma niestandardowych sygnałów.
dalore
@dalore tak, wszystkie sygnały niestandardowe są umieszczane w plikach signal.py. Mamy wiele niestandardowych sygnałów. Ale jeśli nie masz ich wielu, ten plik można pominąć.
samuel
to samo pytanie co @dal
olleh
1
Zauważ, że wszystko to jest teraz starą radą, sposobem django jest teraz użycie appconfig do importowania programów obsługi, w których znajdują się programy obsługi sygnałów. A w signal.py przejdź do niestandardowych sygnałów
dalore
3

Trzymam je w osobnym pliku signals.pyInmodels.py po zdefiniowaniu wszystkich modeli. Importuję je i podłączam modele do sygnałów.

sygnały.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)

To zapewnia mi logiczną separację, oczywiście nie ma nic złego w trzymaniu ich w models.py , ale w ten sposób jest łatwiejsze do zarządzania.

Mam nadzieję że to pomoże!!

allsyed
źródło
umieszczasz moduły obsługi sygnału w „signal.py”, a jeśli nazwiemy go „handlers.py”
Abdul Fatah
1
Nie ma znaczenia, czy nazwiesz plik jako sygnały.py czy handler.py. To tylko konwencja, a nie reguła.
wysłano
3

Małe przypomnienie o AppConfig. Nie zapomnij ustawić:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
valex
źródło