O Django incorpora um robusto mecanismo de sinais, implementando o padrão de design conhecido como Observer (ou Publicador-Assinante). Essa funcionalidade permite que componentes de uma aplicação notifiquem outros componentes sobre a ocorrência de determinados eventos, promovendo um alto nível de desacoplamento. Essencialmente, quando uma ação específica é realizada, um sinal é emitido, e todas as funções configuradas para "ouvir" esse sinal são automaticamente executadas.
Em termos práticos, sinais possibilitam que um remetente específico alerte múltiplos receptores quando certas operações acontecem, o que é ideal para cenários onde você precisa executar código em resposta a ações de um framwork, sem a necessidade de modificar o código central desse framework.
Sinais Nativos do Django
O framework Django oferece uma série de sinais embutidos que permitem que o código do desenvolvedor reaja a operações internas específicas. Estes sinais abrangem diversas áreas do Django, incluindo:
Sinais de Modelo (django.db.models.signals)
pre_init: Disparado antes que o método construtor (__init__) de um modelo Django seja chamado.post_init: Disparado após o método construtor (__init__) de um modelo Django ser chamado.pre_save: Disparado antes que o métodosave()de uma instância de modelo seja invocado.post_save: Disparado após o métodosave()de uma instância de modelo ter sido invocado.pre_delete: Disparado antes que o métoddodelete()de uma instância de modelo ou de umQuerySetseja invocado.post_delete: Disparado após o métododelete()de uma instância de modelo ou de umQuerySetter sido invocado.m2m_changed: Disparado quando um campo ManyToManyField em um modelo é modificado (aidcionar, remover, limpar).class_prepared: Disparado quando uma classe de modelo é completamente inicializada (normalmente na inicialização da aplicação).
Sinais de Gerenciamento (django.core.signals)
pre_migrate: Disparado antes da execução do comandomigrate.post_migrate: Disparado após a conclusão do comandomigrate.
Sinais de Requisição/Resposta (django.core.signals)
request_started: Disparado no início do processamento de uma requisição HTTP pelo Django.request_finished: Disparado no final do processamento de uma requisição HTTP pelo Django.got_request_exception: Disparado quando ocorre uma exceção durante o processamento de uma requisição HTTP.
Outros Sinais Relevantes
setting_changed(django.test.signals): Disparado quando as configurações são alteradas em ambientes de teste.template_rendered(django.test.signals): Disparado quando um template é renderizado durante testes.connection_created(django.db.backends.signals): Disparado quando uma nova conexão com o banco de dados é estabelecida.
Utilizando Sinais Nativos
Para interagir com os sinais embutidos do Django, é necessário conectar uma função receptora (um "callback") a um sinal específico. Quando a ação associada ao sinal ocorre, a função conectada é automaticamente executada.
As funções receptoras de sinais devem aceitar um argumento sender e podem aceitar argumentos **kwargs adicionais, que contêm dados específicos do evento. O argumento sender refere-se à classe que enviou o sinal (por exemplo, a classe de um modelo).
Método 1: Conexão Direta com .connect()
Este método envolve a importação do sinal desejado e a chamada do método connect() para vincular uma função.
# myapp/signals.py (ou um módulo de sinais dedicado)
from django.db.models.signals import post_save
from django.conf import settings
from django.contrib.auth import get_user_model
# Supondo que você tenha um modelo de usuário ou perfil.
# Se for o User padrão do Django, você pode usar get_user_model().
User = get_user_model()
def registrar_atualizacao_perfil(sender, instance, created, **kwargs):
"""
Função receptora para o sinal post_save.
Registra uma mensagem quando um perfil de usuário é salvo ou criado.
"""
if created:
print(f"DEBUG: Novo perfil criado para o usuário: {instance.username} (ID: {instance.pk})")
else:
print(f"DEBUG: Perfil do usuário {instance.username} (ID: {instance.pk}) foi atualizado.")
# Conecta a função ao sinal post_save.
# O argumento 'sender' especifica qual modelo estamos "escutando".
post_save.connect(registrar_atualizacao_perfil, sender=User)
Para garantir que o código de conexão do sinal seja executado, você deve importá-lo em algum lugar que o Django carregue. O local recomendado é dentro do método ready() da sua classe AppConfig. Exemplo:
# myapp/apps.py
from django.apps import AppConfig
class MyappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
# Importe seus sinais aqui para garantir que sejam conectados
# O módulo myapp.signals conteria a lógica de conexão definida acima
import myapp.signals
Método 2: Usando o Decorador @receiver
Este é o método preferencial e mais limpo para conectar receptores. O decorador @receiver, fornecido por django.dispatch, simplifica a conexão de funções a sinais.
# myapp/signals.py (ou onde quer que seus receptores de sinais estejam)
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.contrib.auth import get_user_model
# Obtém o modelo de usuário ativo (pode ser um modelo personalizado)
User = get_user_model()
@receiver(pre_delete, sender=User)
def notificar_delecao_conta(sender, instance, **kwargs):
"""
Receptor que é executado antes que uma instância do modelo User seja excluída.
Pode ser usado para auditoria ou para prevenir exclusões indesejadas.
"""
print(f"AVISO: Tentativa de exclusão da conta do usuário: {instance.username} (ID: {instance.pk})")
# Lógica adicional pode ser adicionada aqui, como arquivar dados ou enviar um e-mail de alerta.
# Lembre-se de importar este módulo de sinais em seu AppConfig.ready()
Neste exemplo, a função notificar_delecao_conta será chamada sempre que uma instância do modelo de usuário padrão do Django estiver prestes a ser excluída.
Sinais Personalizados
Além dos sinais nativos, o Django permite que você defina e utilize seus próprios sinais personalizados, estendendo a arquitetura orientada a eventos da sua aplicação.
a. Definindo um Sinal Personalizado
Crie um novo arquivo, por exemplo, myapp/custom_signals.py, para definir seus sinais personalizados.
# myapp/custom_signals.py
import django.dispatch
# Define um sinal personalizado chamado 'transacao_concluida'.
# Ele espera três argumentos nomeados quando for enviado: transacao_id, conta_afetada, valor.
transacao_concluida = django.dispatch.Signal(providing_args=["transacao_id", "conta_afetada", "valor"])
b. Conectando-se ao Sinal Personalizado
Para "escutar" o sinal transacao_concluida, você conecta uma função receptora a ele. Isso pode ser feito usando .connect() ou o decorador @receiver, de forma similar aos sinais nativos.
# myapp/signals.py (ou onde quer que seus receptores estejam)
from .custom_signals import transacao_concluida # Importa o sinal que você definiu
from django.dispatch import receiver
@receiver(transacao_concluida)
def registrar_movimentacao_financeira(sender, transacao_id, conta_afetada, valor, **kwargs):
"""
Função receptora que reage ao sinal 'transacao_concluida'.
Registra detalhes da transação em um log ou sistema de auditoria.
"""
print(f"LOG FINANCEIRO: Transação {transacao_id} concluída. Conta: {conta_afetada}, Valor: {valor}. (Fonte: {sender})")
# Aqui você poderia, por exemplo, enviar um e-mail de notificação ou atualizar um saldo.
# Lembre-se de importar este módulo de sinais em seu AppConfig.ready()
c. Disparando o Sinal Personalizado
Diferentemente dos sinais nativos, que são disparados pelo próprio Django, os sinais personalizados precisam ser explicitamente disparados pelo seu código quando o evento correspondente ocorre.
# myapp/views.py ou myapp/models.py (onde a transação financeira acontece)
from myapp.custom_signals import transacao_concluida # Importa o sinal
def realizar_transferencia(id_origem, id_destino, montante):
# ... Lógica para processar a transferência de dinheiro ...
transacao_identificador = f"TXN-{abs(hash(f'{id_origem}-{id_destino}-{montante}'))}" # ID único
# Supondo que a transferência foi bem-sucedida
# Dispara o sinal para a conta de origem
transacao_concluida.send(
sender='SistemaBancario', # Quem está enviando o sinal
transacao_id=transacao_identificador,
conta_afetada=id_origem,
valor=-montante # Valor negativo para débito
)
# Dispara o sinal para a conta de destino
transacao_concluida.send(
sender='SistemaBancario',
transacao_id=transacao_identificador,
conta_afetada=id_destino,
valor=montante # Valor positivo para crédito
)
print(f"INFO: Sinais de 'transacao_concluida' disparados para a transação {transacao_identificador}.")
# Exemplo de uso:
# realizar_transferencia(1001, 2002, 500.00)
Ao utilizar sinais personalizados, você pode construir uma arquitetura de aplicação mais flexível e modular, onde diferentes partes do seu sistema podem reagir a eventos sem estarem diretamente acopladas à lógica que dispara esses eventos.