Django filtro de query usando un string


Es común que queramos pasar directamente un string al filtro de una consulta, por ejemplo, desde una url. Dado que el parámetro de filter es un diccionario, lo podemos hacer mediante el operador de desempaquetado de diccionarios **.

Recordemos que para desempaquetar una lista usamos el operador * y para desempaquetar un diccionario el **:

Ejemplo de *


>>> pepe=(1,10,)
>>> range(pepe)
Traceback (most recent call last):
File "", line 1, in
TypeError: range() integer end argument expected, got tuple.
>>> range(*pepe)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Ejemplo de **



>>> pepe={ "venus" : "blanco", "tierra": "azul", "marte": "rojo" }
>>> '{tierra}'.format(pepe)
Traceback (most recent call last):
File "", line 1, in
KeyError: 'tierra'
>>> '{tierra}'.format(**pepe)
'azul'
>>>


En ambos ejemplos observamos que al usar * o ** pasamos con el tipo correcto. Pero ¿Cómo aprovecharlo para pasar directamente un filtro a la consulta?. Yo lo hago de la siguiente forma:


# un string
filtro="nacionalidad__exact@ESP"
# lo parseamos en clave-valor
f=filtro.split("@")
# pasamos como diccionario
p=Persona.objects.filter(**{f[0]:f[1]})
# el efecto es el mismo que hacer
p=Persona.objects.filter(nacionalidad__exact='ESP')

Y ahora veamos cómo implementarlo.

En la página web:

$('a.pais').click(function() {
   var pais=$(this).attr("pais");
   $('#{{entidad}}').load("?filtro="+escape('nacionalidad__exact@'+pais)+"&type=ajax");
});


Notemos que en la url pasamos,
  1. Un interrogante: Hará un get sobre la página actual.
  2. Un nombre de parámetro (filtro). Si queremos añadir más parámetros los separamos con &
  3. El valor del parametro poniendo un igual después del nombre, por ejemplo,  filtro=nacionalidad__exact@ESP 
En la parte del servidor lo que hacemos es procesar directamente el filtro pasando de string a diccionario así:


def get_filtro( self, request):
  """ aplica el filtro y devuelve el modelo filtrado """
   filtro=request.GET.get('filtro', False)
   if filtro:
  try:
     f=filtro.split('@')
     return Persona.objects.filter(**{f[0]:f[1]})
   except Exception, e:
    logger.error('desempaquetando filtro '+str(e))

   return Persona.objects.all()


La ventaja de este método es que es muy sencillo crear toda clase de filtros directamente en la página web teniendo un solo código que los gestiona en el servidor. 

Una última puntualización: Si no todos los usuarios pueden ver todo el modelo i.e. ver todas las personas Persona.objects.all() hay que ir con cuidado ya que es muy sencillo manipular a mano la url para que nos devuelva cualquier registro. Por ejemplo:

?filtro=moroso__exact@1

Nos devolvería todos los morosos ;-)