Muito deste artigo é baseado na minha própria (bastante limitada) compreensão do conceito. Não sou, de forma alguma, um especialista e mal comecei a aprender programação. Não consegui encontrar nenhum bom artigo explicando Signals como um conceito e, portanto, escrevi este artigo na esperança de ajudar outras pessoas que possam estar lutando da mesma forma.
Pode (e haverá) erros gritantes e afirmações e afirmações totalmente incorretas na postagem e eu imploro que você me corrija sempre que necessário para que eu possa alterar a postagem como e onde for necessário para que outros leiam e aprendam mais tarde.
“Sinais de merda, como eles funcionam ?!”
Quando django recebe uma solicitação de conteúdo em uma url específica, o mecanismo de roteamento de solicitação pega a visão apropriada, conforme definido por seu urls.py
, para gerar conteúdo. Para uma solicitação GET típica, isso envolve extrair informações relevantes do banco de dados e passá-las para o modelo que constrói o HTML a ser exibido e o envia ao usuário solicitante. Muito trivial, na verdade.
No caso de uma solicitação POST, no entanto, o servidor recebe os dados do usuário. Pode haver um caso em que você precise / queira modificar esses dados para atender aos seus requisitos antes de confirmá-los / armazená-los em seu banco de dados.
Considere, por exemplo, a situação em que você precisa gerar um perfil para cada novo usuário que se inscreve no seu site. Por padrão, Django fornece um User
modelo básico por meio do módulo. Qualquer informação extra pode ser adicionada por meio da personalização de seu modelo ou criando um modelo separado em um aplicativo separado.django.contrib.auth
User
UserProfile
Ok, eu escolho a opção LATTER.
Tudo bem, digamos que nosso UserProfile
modelo se pareça com este:
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
('P', 'Prefer not to answer'),
)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
nickname = models.TextField(max_length=64, null=True, blank=True)
dob = models.DateField(null=True, blank=True)
gender = models.CharField(max_length=1,
choices=GENDER_CHOICES, default='M')
bio = models.TextField(max_length=1024, null=True, blank=True)
[...]
Agora, precisamos garantir que uma UserProfile
instância correspondente seja criada (ou já exista) cada vez que uma User
instância for salva no banco de dados. Ei, poderíamos substituir o save
método no User
modelo e conseguir isso, certo? Talvez o código seja mais ou menos assim:
def save(self, *args, *kwargs):
u = super(User, self).save(*args, **kwargs)
UserProfile.objects.get_or_create(user_id=u.id)
return u # save needs to return a `User` object, remember!
Mas espere! O User
modelo vem do qual faz parte da própria instalação do django! Você realmente deseja substituir isso? Eu certamente não recomendaria isso!django.contrib.auth
E agora?
Ok, se houvesse uma maneira de podermos de alguma forma ‘ouvir’ os mecanismos internos do Django e descobrir um ponto no processo onde podemos ‘enganchar’ nosso pequeno trecho de código, isso tornaria nossas vidas muito mais fáceis. Na verdade, não seria ótimo se Django ‘anunciasse’ sempre que tal ponto no processo fosse alcançado? Não seria ótimo se o Django pudesse ‘anunciar’ que terminou de criar um novo usuário? Dessa forma, você pode simplesmente esperar que tal ‘anúncio’ aconteça e escrever seu código para agir sobre ele apenas quando tal ‘anúncio’ acontecer.
Bem, estamos com sorte porque Django faz exatamente isso. Esses anúncios são chamados de ‘Sinais’. O Django ’emite’ sinais específicos para indicar que atingiu uma etapa particular em sua execução de código. Esses sinais fornecem um ‘ponto de entrada’ em seu mecanismo de execução de código, permitindo que você execute seu próprio código, no ponto onde o sinal é emitido. O Django também fornece a identidade do remetente (e outros dados extras relevantes) para que possamos ajustar nosso código ‘hook-in’ da melhor maneira possível!
Excelente! Mas isso ainda não explica COMO …
Para nosso cenário, temos um sinal muito conveniente chamado post_save
que é emitido sempre que uma instância de modelo é salva no banco de dados – até mesmo o User
modelo! Portanto, tudo o que precisamos fazer é ‘receber’ este sinal e conectar nosso próprio código para ser executado naquele ponto do processo, ou seja, o ponto onde uma nova instância do usuário acabou de ser salva no banco de dados!
from django.dispatch import receiver
from django.core.signals import post_save
from django.contrib.auth.models import User
@receiver(post_save, sender=User)
def ensure_profile_exists(sender, **kwargs):
if kwargs.get('created', False):
UserProfile.objects.get_or_create(user=kwargs.get('instance'))
Confuso? Não se preocupe. Vamos tentar ler o código linha por linha para entender o que ele diz.
> @receiver(post_save, sender=User)
(NB: Espero que as três linhas de importação sejam meio óbvias.)
A primeira linha do snippet é um decorador chamado @receiver
. Este decorador é simplesmente um atalho para um wrapper que invoca uma conexão com o post_save
sinal. O decorador também pode receber argumentos extras que são passados para o sinal. Por exemplo, estamos especificando o argumento neste caso para garantir que o receptor seja invocado apenas quando o sinal for enviado pelo modelo.sender=User
User
O que o decorador essencialmente afirma é o seguinte: “Fique atento a qualquer instância de um User
modelo sendo salvo no banco de dados e diga ao Django que temos um receiver
aqui que está esperando para executar o código quando tal evento acontecer.”
> def ensure_profile_exists(sender, **kwargs):
Estamos agora definindo uma função que será chamada quando o post_save
sinal for interceptado. Para facilitar o entendimento e a legibilidade do código, nomeamos essa função ensure_profile_exists
e estamos passando o remetente do sinal como um argumento para essa função. Também estamos passando argumentos de palavras-chave extras para a função e em breve veremos como eles serão benéficos.
> if kwargs.get('created', False):
UserProfile.objects.get_or_create(user=kwargs.get('instance'))
Para entender isso, primeiro precisamos entender que tipo de dados extras são enviados junto com o post_save
sinal. Então, vamos antropomorfizar a situação um pouco.
Imagine instruir uma pessoa em uma gráfica (onde um certo livro está sendo impresso) dizendo a ela: “Diga-me quando cada cópia publicada de um livro sair da impressora.” Ao fazer isso, você garantiu que essa pessoa irá abordá-lo e informá-lo sempre que uma cópia do livro aparecer no final da linha de produção da impressora. Agora, imagine que essa pessoa é extraeficiente e também traz consigo um exemplar do livro publicado que saiu do prelo.
Agora você tem duas informações – a primeira é que um livro acabou de ser publicado e a segunda é a cópia física real do livro que foi trazida para você fazer o que quiser.
A kwargs
variável é um exemplo de o post_save
sinal ser extraeficiente – ela envia informações altamente relevantes pertencentes ao remetente do sinal. Normalmente, o post_save
sinal envia uma cópia do salvo instance
e uma variável booleana chamada created
que indica se uma nova instância foi criada ou uma instância mais antiga foi salva / atualizada. Existem algumas outras coisas que estão agrupadas no kwargs
dicionário, mas são essas duas variáveis que usaremos para realizar nossa tarefa final – a de criar um UserProfile
sempre que um novo User
for criado.
Esta parte do nosso trecho de código, portanto, simplesmente verifica o valor (booleano-) da created
chave no kwargs
dicionário (assumindo que seja False
, por padrão) e cria um UserProfile
sempre que descobrir que um novo usuário foi created
…
… que é precisamente o que sempre quisemos!
Hmm … Mas onde todo esse código deve morar? Em qual arquivo?
De acordo com a documentação oficial do Django sobre sinais :
Você pode colocar o manuseio do sinal e o código de registro em qualquer lugar que desejar. No entanto, você precisará certificar-se de que o módulo em que está inserido 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 lugar para colocar o registro dos manipuladores de sinal.
Observe que é o local recomendado. Isso certamente não significa que é o local para despejar todos os seus registros e manipuladores de sinal. Se você quiser se aventurar, pode escrever seu código em um arquivo separado (digamos ou algo semelhante) e importá-lo no ponto exato onde seus sinais precisam ser registrados / manipulados.models.py
signals.py
Faça o que fizer, onde quer que você escolha colocar seu código, certifique-se de que seu manipulador de sinal foi devidamente registrado e está sendo chamado corretamente e na hora certa, sempre e onde for necessário.
Boa sorte!
PONTOS A SEREM OBSERVADOS
Observe que não adicionamos nenhuma informação extra ao
UserProfile
– simplesmente criamos uma instância vazia (mas associada) e a deixamos para ser modificada posteriormente. Como definimos que a maioria deles foi definida para aceitarnull
valores, nosso código ainda funcionará. No entanto, se assim o quiser, você pode usar outras APIs (internos, bem como externo) para adquirir os dados relevantes para os atributos (ou seja, onickname
,bio
,dob
,gender
, etc.) e pré-preenchê-los ao criar oUserProfile
– por exemplo, extraindo os dados relevantes de um perfil de rede social.Sempre verifique os argumentos fornecidos por um sinal sempre que ele é enviado. Nem todos os sinais enviam os mesmos argumentos e alguns aplicativos de terceiros podem enviar argumentos que serão úteis para você em mais de uma maneira. Experimente e faça bom uso desses argumentos nas funções do receptor da melhor maneira possível.
Respostas relacionadas:
Achatar uma lista de listas em uma linha em Python