Django Signals – uma explicação extremamente simplificada para iniciantes.

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 Usermodelo 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.authUserUserProfile

Ok, eu escolho a opção LATTER.

Tudo bem, digamos que nosso UserProfilemodelo 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 UserProfileinstância correspondente seja criada (ou já exista) cada vez que uma Userinstância for salva no banco de dados. Ei, poderíamos substituir o savemétodo no Usermodelo 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 Usermodelo 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_saveque é emitido sempre que uma instância de modelo é salva no banco de dados – até mesmo o Usermodelo! 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_savesinal. 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=UserUser

O que o decorador essencialmente afirma é o seguinte: “Fique atento a qualquer instância de um Usermodelo sendo salvo no banco de dados e diga ao Django que temos um receiveraqui 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_savesinal for interceptado. Para facilitar o entendimento e a legibilidade do código, nomeamos essa função ensure_profile_existse 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_savesinal. 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 kwargsvariável é um exemplo de o post_savesinal ser extraeficiente – ela envia informações altamente relevantes pertencentes ao remetente do sinal. Normalmente, o post_savesinal envia uma cópia do salvo instancee uma variável booleana chamada createdque 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 kwargsdicionário, mas são essas duas variáveis ​​que usaremos para realizar nossa tarefa final – a de criar um UserProfilesempre que um novo Userfor criado.

Esta parte do nosso trecho de código, portanto, simplesmente verifica o valor (booleano-) da createdchave no kwargsdicionário (assumindo que seja False, por padrão) e cria um UserProfilesempre 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.pysignals.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

  1. 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 aceitar nullvalores, 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, o nickname, bio, dob, gender, etc.) e pré-preenchê-los ao criar o UserProfile– por exemplo, extraindo os dados relevantes de um perfil de rede social.

  2. 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