El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Senyals a Django

El mecanisme de senyalització de Django ens permet desacoblar les nostres aplicacions, ja que permet notificar d'accions que es produeixen a tot una sèrie de receptors o subscriptors d'aquestes senyals.

Podem distingir dos tipus de senyals a Django:

  • Les senyals que venen predefinides a bastiment i que podem trobar a la documentacio de Django. En aquest cas Django se n'encarrega de llançar l'avís de que una acció concreta s'ha produït i nosaltres com a programadors podem generar codi per tal de subscriure'ns a aquest tipus de missatges i disposar-ho tot per a que s'executi el codi que ha de respondre a aquestes accions.

  • Les senyals definides per l'usuari. Es un cas més general que l'anterior. Django ens permet definir les nostres pròpies senyals i enviar-les. En aquest cas doncs, podem definir tant la senyal com el tractament que se'n faci.

Escoltant senyals

Per a escoltar una senyal necessitam fer dues coses:

  1. Definir la funció que rebrà la senyal amb el codi que s'ha d'executar.

  2. Connectar la funció amb la senyal, és a dir subscriure's a la senyal.

Les funcions que haurem de definir han de ser de la forma:

 

def nom_funcio (sender, kwargs):
           "Aqui va la documentacio"
           # aqui el codi
           pass
     

Hem de tenir en compte que el nombre d'arguments que pot transmetre una senyal per definició pot canviar en qualsevol moment. Els arguments es passen per nom, i la nostra funció ha d'estar preparada per gestionar aquest possible canvi d'arguments.

Per a connectar la senyal amb la nostra funció importarem la senyal a la que ens vulguem subscriure i ens hi connectarem amb el mètode connect de la pròpia senyal.

Per exemple per connectar-nos a la senyal que s'emet quan una petició http ha acabat bastaria fer:

 

    from django.core.signals import request_finished
    request_finished.connect(nom_funcio)

Les senyals tenen un paràmetre per defecte, el sender, que ens permet subscriure'ns sols a aquelles senyals que provenguin d'instàncies d'una classe determinada.

Definint les nostres senyals

Per a definir les nostres senyals hem fer dos passos:

  1. Definir la senyal i els arguments que passarà

  2. Allà on ens interessi enviar la senyal amb els arguments que hem definit

Pel primer pas basta importar django.dispatch i crear una instància de Signal amb els paràmetres que hem definit.

 

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

Enviar la senyal és encara més senzill, basta cridar al mètode send amb els paràmetres que hem definit, sense oblidar-nos d'establir la variable sender amb l'objecte que ha enviat la senyal.

Suposem que volem que el sistema ens avisi automàticament cada cop que algú esborra un usuari de la base de dades.

Per a mostrar-ho farem ús del senyal pre_delete que es troba a django.db.models.signals. Per al registre crearem una taula a la BD que guardarà l'usuari que se va esborrar i el seu nom.

La senal pre_delete s'envia quan s'entra dins el mètode delete del model, com a arguments té el sender que és la classe que envia la senyal i instance que recull l'objecte que s'eliminarà de la base de dades.

Nosaltres ens volem registrar sols a les senyals que impliquin eliminar usuaris de l'aplicació, així que sols ens subscriurem a les senyals que provenguin de django.contrib.auth.models.User

El nostre model seria

 

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Autor: aaloy

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import pre_delete

class Eliminat(models.Model):
user = models.CharField(max_length=30)
created = models.DateTimeField(editable=False, auto_now_add=True, blank=False,
    db_index = True)

def __unicode__(self):
    return self.user 

def registra(sender, **kwargs):
    instancia = kwargs['instance']
    usuari = Eliminat(user=instancia.username)
    usuari.save()

pre_delete.connect(registra, sender=User)

Per a veure com podem utilitzar les nostres pròpies senyals, crearem una senyal que registrarla la IP i el nom de la vista.

Per això crearem una nova classe, per exemple LogIp, que mantindrà el registre i connectarem el senyal amb la funció registre_ip

Hem de tenir en compte que es un exemple acadèmic, té poc sentit fe aquest tipus de coses, és a dir, definir una senyal dins el mateix model i enviant-la a la vista. Té molt més sentit que el senyal s'envii en resposta a una acció dins el propi model o a una acció particular de la vista i que una aplicació externa s'hi subscrigui.

 

class LogIp (models.Model):
    "Logs the ips and calling functions"
    name = models.CharField(max_length= 200)
    ip = models.IPAddressField()
    created = models.DateTimeField(editable=False, auto_now_add=True, blank=False,
        db_index = True)

in_view = django.dispatch.Signal(providing_args=['ip', 'name'])

def registra_ip(sender, **kwargs):
    ip = kwargs['ip']
    name = kwargs['name']
    log = LogIp(name= name, ip=ip)
    log.save()

in_view.connect(registra_ip)

in_view és la nostra senyal, com a paràmetres hem posat ip i name, això vol dir que la funció que es registri com a escoltadora els pot fer servir i ha de ser capaç de admetre'ls. Per una altra banda també ens diu que allà on enviem la senyal amb el send haurem de proporcionar també aquests paràmetres.

Si llançam el senyal a la vista quedaria com

 

def list(request):
    "Lists the deleted users"
    deleted = Eliminat.objects.all()
    in_view.send(sender=chivato.views.list, ip=request.META['REMOTE_ADDR'], 
    name=request.get_full_path())
    return render_to_response('list.html', {'deleted':deleted})

Amb això cada cop que algú des de el seu navegador accedeixi a la funció quedarà registrada la url d'accés i la seva ip.

Tot el codi font amb un projecte complet llest per funcionar, sols heu de canviar las rutes al properties.py a appfuse o directament

svn checkout http://appfusedjango.googlecode.com/svn/trunk/signals

blog comments powered by Disqus