Sinais (Signals)

O Django incluí um “dispachador de sinal” que ajuda ao permitir que aplicações dissociadas sejam notificadas quando uma ação ocorre em qualquer lugar do framework. Em resumo, sinais permitem certos remetentes notificar um conjunto de receptores sobre alguma ação que tenha ocorrido. Eles são especialmente úteis quando muitas peças de código podem estar interessados nos mesmos eventos.

O Django fornece um conjunto de sinais embutidos que deixam o código do usuário ser notificado pelo próprio Django sobre certas ações. Estas incluem algumas notificações úteis:

Veja a documentação de sinais embutidos para uma lista completa, e uma explicação completa de cada sinal.

Você também pode definir e enviar seus próprios sinais; veja abaixo.

Ouvindo sinais

Para receber um sinal, você precisa registrar uma função receptora que será chamada quando o sinal é enviado. Vamos ver como isto funciona registrando um sinal que é chamado depois de cada requisição HTTP é finalizada. Nós conectaremo-nos ao sinal request_finished.

Signal.connect(receiver[, sender=None, weak=True, dispatch_uid=None])
Parameters:
  • receiver – The callback function which will be connected to this signal. See Funções receptoras for more information.
  • sender – Specifies a particular sender to receive signals from. See Conectando-se a sinais enviados por remetentes específicos for more information.
  • weak – Django stores signal handlers as weak references by default. Thus, if your receiver is a local function, it may be garbage collected. To prevent this, pass weak=False when you call the signal’s connect() method.
  • dispatch_uid – A unique identifier for a signal receiver in cases where duplicate signals may be sent. See Preventing duplicate signals for more information.

Let’s see how this works by registering a signal that gets called after each HTTP request is finished. We’ll be connecting to the request_finished signal.

Funções receptoras

Primeiro, nós precisamos definir uma função receptora. Um receptor pode ser qualquer função Python ou método:

def my_callback(sender, **kwargs):
    print "Request finished!"

Observe que a função recebe um argumento sender, junto com o argumento coringa (**kwargs); todo manipulador de sinal recebe estes argumentos.

Nós olharemos o remetente um pouco depois, mas agora olhe o argumento **kwargs. Todos os sinais enviam argumentos nomeados, e podem mudar seus argumentos nomeados a qualquer momento. Neste caso o request_finished, ele está documentado como se não enviasse argumentos, que significa que poderiámos ser tentados a escrever nosso manipulador do sinal como my_callback(sender).

Isso poderia estar errado -- de fato, o Django irá lançar um erro se você fizer isso. Isto porque, em qualquer ponto, argumentos poderiam ser adicionados ao sinal, e o receptor deve ser capaz de manipular estes argumentos.

Conectando funções receptoras

Agora, nós precisaremos conectar nosso receptor ao sinal:

from django.core.signals import request_finished

request_finished.connect(my_callback)

Agora, nossa função my_callback será chamada a cada vez que uma requisição for finalizada.

Onde este código deve ficar?

Você poder colocar o código manipulador e de registro de sinal aonde você quiser. Entretanto, você precisará assegurar-se de que o módulo já tenha sido importado anteriormente, para que este manipulador de sinal seja registrado antes que qualquer sinal seja enviado. Isto faz do models.py de sua aplicação, um bom lugar para colocar o regsitro de manipuladores de sinais.

Conectando-se a sinais enviados por remetentes específicos

Alguns sinais são enviados muitas vezes, mas você somente é interessado em receber um certo sub-conjunto destes sinais. Por exemplo, considere o sinal django.db.models.signals.pre_save enviado antes de um model ser salvo. Na maioria das vezes, você não precisa saber quando qualquer model foi salvo -- só quando um model específico é salvo.

Nestes casos, você pode registrá-lo para receber sinais enviados somente por um remetente em particular. No caso do django.db.models.signals.pre_save, o remetente será a classe model que estiver sendo salva, então você pode indicar que você somente quer sinais enviados por algum model:

from django.db.models.signals import pre_save
from myapp.models import MyModel

def my_handler(sender, **kwargs):
    ...

pre_save.connect(my_handler, sender=MyModel)

A função my_handler somente será chamada quando uma instância de MyModel for salva.

Sinais diferentes usam objetos diferentes como seus remetentes; você precisará consultar a documentação de sinais embutidos para detalhes de cada sinal em particular.

Preventing duplicate signals

In some circumstances, the module in which you are connecting signals may be imported multiple times. This can cause your receiver function to be registered more than once, and thus called multiples times for a single signal event.

If this behavior is problematic (such as when using signals to send an e-mail whenever a model is saved), pass a unique identifier as the dispatch_uid argument to identify your receiver function. This identifier will usually be a string, although any hashable object will suffice. The end result is that your receiver function will only be bound to the signal once for each unique dispatch_uid value.

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

Definindo e enviando sinais

Your applications can take advantage of the signal infrastructure and provide its own signals.

Defining signals

class Signal([providing_args=list])

Todos os sinais são instâncias de django.dispatch.Signal. O providing_args é uma lista de nomes de argumentos que o sinal fornecerá aos ouvintes.

Por exemplo:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

Este declara um sinal pizza_done que fornecerá aos receptores os argumentos toppings e size.

Lembre-se de que você está autorizado a alterar esta lista de argumentos a qualquer momento, então obter o direito de API, na primeira tentativa, não é necessário.

Enviando sinais

There are two ways to send send signals in Django.

Signal.send(sender, **kwargs)
Signal.send_robust(sender, **kwargs)

Para enviar um sinal, chame Signal.send(). Você deve prover o argumento sender, e pode fornecer muitos outros argumentos nomeados conforme sua vontade.

Por exemplo, aqui mostramos como nosso envio de sinal pizza_done, pode ficar:

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self, toppings=toppings, size=size)
        ...

Both send() and send_robust() return a list of tuple pairs [(receiver, response), ... ], representing the list of called receiver functions and their response values.

send() differs from send_robust() in how exceptions raised by receiver functions are handled. send() does not catch any exceptions raised by receivers; it simply allows errors to propagate. Thus not all receivers may be notified of a signal in the face of an error.

send_robust() catches all errors derived from Python's Exception class, and ensures all receivers are notified of the signal. If an error occurs, the error instance is returned in the tuple pair for the receiver that raised the error.

Disconnecting signals

Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None])

To disconnect a receiver from a signal, call Signal.disconnect(). The arguments are as described in Signal.connect().

The receiver argument indicates the registered receiver to disconnect. It may be None if dispatch_uid is used to identify the receiver.