Onde os manipuladores de sinal devem viver em um projeto de django?

143

Acabei de começar a implementar ouvintes de sinal em um projeto de django. Enquanto eu entendo o que são e como usá-los. Estou tendo dificuldade para descobrir onde devo colocá-los. A documentação do site django tem o seguinte a dizer:

Onde esse código deve estar?

Você pode colocar código de manuseio e registro de sinal onde quiser. No entanto, você precisará garantir que o módulo em que ele está seja importado desde o início, para que o tratamento do sinal seja registrado antes que qualquer sinal precise ser enviado. Isso torna o models.py do seu aplicativo um bom local para registrar os manipuladores de sinal.

Embora seja uma boa sugestão, ter classes ou métodos não modelados em meus models.py apenas me atrapalha.

Então, qual é a melhor prática / regra para armazenar e registrar manipuladores de sinal?

Jason Webb
fonte

Respostas:

41

Na verdade, gosto de torná-los métodos de classe do próprio modelo. Isso mantém tudo dentro de uma classe e significa que você não precisa se preocupar em importar nada.

Daniel Roseman
fonte
2
E onde você costuma conectar manipuladores aos sinais?
DataGreed
1
@DataGreed: na parte inferior dos models.py relevantes.
Daniel Roseman
102
Se você está ouvindo os sinais emitidos por esse modelo, colocar todos os ouvintes também torna todo o exercício inútil, não é? O ponto dos sinais é desacoplar. Os ouvintes não deveriam viver com o código que está interessado nesses eventos remotos? A questão é como garantir que os ouvintes sejam carregados diante dos emissores.
John Mee
No meu caso, quero ouvir um sinal de modelo do Fooqual faça parte fooapp. Mas o receptor de sinal é uma extensão e vive em um aplicativo diferente (por exemplo otherapp).
6758 guettli #
2
Para o ponto de John Mee, não é muito diferente do que apenas substituindo save (), etc.
Matt
246

Isso foi adicionado à documentação quando o Django 1.7 foi lançado:

Estritamente falando, o código de manipulação e registro de sinal pode viver em qualquer lugar que você quiser, embora seja recomendável evitar o módulo raiz do aplicativo e o módulo de modelos para minimizar os efeitos colaterais da importação de código.

Na prática, os manipuladores de sinais são geralmente definidos em um submódulo de sinais do aplicativo ao qual se relacionam. Os receptores de sinal são conectados no método ready () da sua classe de configuração do aplicativo. Se você estiver usando o decorador receiver (), basta importar o submódulo de sinais para ready ().

Alterado no Django 1.7: Como o ready () não existia nas versões anteriores do Django, o registro do sinal geralmente acontecia no módulo de modelos.

A melhor prática é definir seus manipuladores em handlers.py em um submódulo de sinais, por exemplo, um arquivo parecido com:

yourapp / signs / 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

O melhor lugar para registrar seu manipulador de sinal é o AppConfig do aplicativo que o define, usando o método ready () . Isso ficará assim:

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

Verifique se você está carregando o AppConfig, especificando-o diretamente em INSTALLED_APPS do settings.py ou no __init__aplicativo. Veja a documentação ready () para mais informações.

Nota: Se você estiver fornecendo sinais para outros aplicativos também ouvirem, coloque-os __init__no módulo de sinais, por exemplo, um arquivo parecido com:

yourapp / signs / __ init__.py

import django.dispatch

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

Outro aplicativo pode ouvir seu sinal importando e registrando-o, por exemplo from yourapp.signals import task_generate_pre_save. Separar os sinais dos manipuladores mantém as coisas limpas.

Instruções para o Django 1.6:

Se você ainda está preso no Django 1.6 ou inferior, faça o mesmo (defina seus manipuladores em yourapp / signs / handlers.py), mas em vez de usar o AppConfig, carregue os manipuladores através do __init__.py de seu aplicativo, por exemplo, algo como:

yourapp / __ init__.py

import signals

Isso não é tão bom quanto usar o método ready () porque geralmente causa problemas de importação circular.

Aidan
fonte
3
como a documentação diz que você substitui o ready, você pode fazer algo como super (ReportsConfig, self) .ready () caso o django decida preencher o ready () com algo (a partir da versão 1.7.0 atualmente está vazia)
w- -
3
Penso que esta resposta é a melhor porque é a única que resolve os efeitos secundários das importações. Eu vim aqui em busca de práticas recomendadas, porque estou limpando um aplicativo, que está quebrado exatamente devido a esse tipo de efeitos colaterais. Infelizmente, o aplicativo está sendo executado no django 1.6, e as práticas recomendadas funcionam apenas no django 1.7. A solução temporária de deixar __init__sinais de importação não funcionaria para mim, então me pergunto se há outro lugar para importar sinais de até que estejamos prontos para atualizar para uma versão posterior do django.
kasperd
Não deveria haver from . import handlers(ou similar) yourapp/signals/__init__.py?
dhobbs
Você também não deve importar o módulo handlers.py em algum lugar? Estou tentando isso e não parece estar definindo o manipulador para o sinal.
Andrés
1
fwiw Eu não precisava do yourproject.na última linha do bloco de código da classe TaskConfig. Eu tenho esse trabalho com exatamente essa estrutura, por isso considero este qa :)
Greg Kaleka
40

Acabei de descobrir isso e, como meus sinais não são relacionados ao modelo, pensei em adicionar minha solução.

Estou registrando vários dados em torno de login / logout e necessário conectar django.contrib.auth.signals.

Coloquei os manipuladores de sinal em um signals.pyarquivo e depois importei os sinais do __init__.pyarquivo do módulo, pois acredito que isso é chamado assim que o aplicativo é iniciado (o teste com uma printinstrução sugere que ele seja chamado antes mesmo de o arquivo de configurações ser lido).

# /project/__init__.py
import signals

e em signs.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)

Eu sou bem novo no Django (/ python), então estou aberto a qualquer um que me diga que essa é uma péssima idéia!

Hugo Rodger-Brown
fonte
3
Isso parece lógico, mas eu sugiro fazê-lo no nível do aplicativo.
Nils
2
Cuidado, essa lógica provavelmente resultará no envio de sinais duplicados. user_logged_in.connect(on_logged_in)provavelmente deve estar passando no dispatch_uidargumento. Mais em docs.djangoproject.com/en/dev/topics/signals/… .
Scott Coates
Obrigado por isso - é bom saber. Estou registrando todos os logins usando esse método (registrando o agente IP / usuário) e não tive duplicatas até o momento - embora isso não signifique que uma pequena alteração na linha não cause problemas!
Hugo Rodger-Brown
13

Recentemente, li este artigo sobre as práticas recomendadas no que diz respeito ao layout de seus projetos / aplicativos e sugere que todos os seus sinais personalizados do despachante devem ir em um arquivo chamado signals.py. No entanto, isso não resolve completamente o seu problema, pois você ainda precisa importá-los para algum lugar, e quanto mais cedo eles forem importados, melhor.

A sugestão do modelo é boa. Como você já definiu tudo no seu signals.pyarquivo, não deve demorar mais do que uma linha na parte superior do arquivo. É semelhante à maneira como o admin.pyarquivo é organizado (com definições de classe na parte superior e o código para registrar todas as classes administrativas personalizadas na parte inferior), se você definir seus sinais, conecte-os no mesmo arquivo.

Espero que ajude! Em última análise, tudo se resume ao que você prefere.

hora
fonte
1
Eu também queria colocar meus manipuladores de sinal em um signals.pyarquivo, mas não sabia como deveria ser chamado depois. Ao importá-lo no meu models.pyarquivo, obtive uma solução muito limpa, sem "poluir" meu arquivo models.py. Obrigado! :)
Danilo Bargen
10
há um cross-import lá: tentativas signals.py ao modelo de importação de models.py
Ivan Virabyan
8

models.py e signs.py em cada aplicativo foram os locais recomendados para conectar sinais; no entanto, eles não são a melhor solução, na minha opinião, para manter os sinais e manipuladores enviados. O envio deve ser a razão pela qual os sinais e manipuladores inventados no django.

Eu estava lutando por um longo tempo e finalmente descobrimos a solução.

crie um módulo de conector na pasta do aplicativo

então nós temos:

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

em app / connectors.py, definimos manipuladores de sinais e os conectamos. Um exemplo é fornecido:

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)

depois, em models.py, adicionamos a seguinte linha no final do arquivo:

from app import connector

Tudo feito aqui.

Dessa forma, podemos colocar sinais em signs.py e todos os manipuladores em connectors.py. Sem bagunça nos modelos e sinais.

Espero que ele forneça outra solução.

samuel
fonte
1
Então, o que se passa no signs.py? Parece que, a partir do seu exemplo, são apenas os sinais personalizados. Normalmente, apenas combinamos os sinais e conectores, pois a maioria não possui sinais personalizados.
dalore
@dalore sim, todos os sinais personalizados são colocados em signs.py. Temos muitos sinais personalizados. Mas se você não tiver muitos, esse arquivo poderá ser omitido.
samuel
mesma pergunta que @dal
olleh 18/06/19
1
note que tudo isso agora é um conselho antigo, a maneira do django agora é usar o appconfig para importar manipuladores para onde os manipuladores de sinal vivem. E em sinais Go signals.py
dalore
3

Eu os mantenho em um arquivo separado signals.py, models.pydepois que todos os modelos estiverem definidos. Eu os importo e conecto modelos a sinais.

signs.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)

Isso me fornece uma separação lógica, é claro que não há nada errado em mantê-los em models.py , mas é mais gerenciável dessa maneira.

Espero que isto ajude!!

allsyed
fonte
você está colocando os manipuladores de sinal em "signals.py", o que se nomeá-lo como "handlers.py"
Abdul Fatah
1
Não importa se você nomeia o arquivo como signs.py ou handler.py. É apenas uma convenção, não regra.
Allsyed
3

Pequeno lembrete sobre AppConfig. Não se esqueça de definir:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
valex
fonte