El Blog de Trespams

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

Consultes dinàmiques a Django

Estic mirant de fer un poc més fàcil la integració de jqGrid amb Django, integrar la paginació ha estat força senzill ja que jqGrid i Django fan servir un conjunt de variables semblant: plana actual, total de registres i llista de registres.

jqGrid passa aquests paràmetres per GET quan feim un camvi de plana, per tant podem aprofitar l'objecte paginator de Django per fer la paginació com si fos una aplicació HTML clàssica, sols que en lloc d'HTML retornarem JSON (o XML).

La cosa es complica un poc més quan es tracta de cercar. jqGrid fa servir quatre paràmetres: _search, que es posa a true quan s'ha activat la cerca, searchField que conté el camp pel qual es cerca. SearchField retorna el contingut de la variable index de colModels si existeix o bé el nom. Llavors tenim també searchOper que ens informa del tipus de cerca que es vol fer. Per exemple, passa eq per indicar la coincidència exacta, ew per indicar acaba amb i cn correspon a la sentència IN d'SQL. Per acabar searchString conté el valor del que estam cercant.

Així doncs es tracta de montar un filtre de manera dinàmica amb Django a partir dels valors que ens envia el jqGrid. El primer que hem de fer és fer la taula d'equivalències que mapejarà cada operació de jqGrid amb una operació equivalent de Django.

Per exemple:

django_equivalences = { 'eq': '%s__iexact', 'lt': '%s__lt', 'le': '%s__lte'}

Això éns permet convertir fàcilment una expressió d'igualtat per a un camp amb l'equivalent Django

field = self.request.GET.get('searchField')
op = request.GET.get('searchOper') % field

Amb això ens trobam amb dos problemes: què feim amb les condicions del tipus "no és igual a" o "no comença amb" i què feim amb les expressions "in". I si m'apurau amb un problema més, com passam aquestes condicions que constriuim (o construirem) dinàmicament a Django. Perquè Django espera construccions del tipus Entry.objects.filter(id__gt=4) i nosaltres el que farem és construir-les.

Si ens fixam en la documentació veurem que el problema dels inclosos i dels exclosos està resolt per filter i excludes. Amb la mateixa construcció i generant la consulta amb excludes en lloc de filter ja ho tenim. Sols necessitam doncs que el mapeig a més de la plantilla ens retorni el tipus a que correspon filter o bé excludes.

django_equivalences = { 'eq': ('filter', '%s__iexact'), 'ne': ('exclude','%s__iexact'), 'lt': ('filter', '%s__lt')}

Suposant que la nostra classe es diu Entry podríem fer quelcom semblant a això:

searchField = request.GET.get('searchField')
op = self.request.GET.get('searchOper')
value = request.GET.get('searchString')
tipo, filtro = django_equivalences[op]
query = {filtro%searchField:value}
record_list = Entry.objects.filter(**query)
if tipo == 'filter' \ else Entry.objects.exclude(**query)

Fitxem-nos com hem fet la construcció dinàmica. En lloc de crear el codi el que feim és crear un diccionary query que té com a clau l'operació de filtratge damunt el camp que jqGrid ens passa i com a valor, el que volem cercar.

Ens queda encar un petit detall, què passa amb l'in, a Django mapejaria com icontains i espera una llista de valors com a paràmetre. Està clar que ho podríem tractar com un cas particular, però anem-ho a fer divertit, el que farem serà afegir una funció com a valor del mapeig que ens dirà el que hem de fer amb el valor, llevat de in i ni (includes i not includes) no s'ha de fer res, millor dit la funció ha de retornar el mateix valor que li passam (us sona la funció identitat?) i en cas contrari convertirem el valor que ens passi a una llista de valors mitjançant el mètode split(',').

Així doncs el nostre mapeix seria:

identity = lambda x:x
django_equivalences = { 'eq': ('filter', '%s__iexact', identity), 'ne': ('exclude','%s__iexact',identity), 'lt': ('filter', '%s__lt',identity), 'le': ('filter', '%s__lte',identity), 'gt': ('filter', '%s__gt',identity), 'ge': ('filter', '%s__gte',identity), 'bw': ('filter', '%s__istartswith',identity), 'bn': ('exclude','%s__istartswith',identity), 'in': ('filter', '%s__in',lambda x: x.split(',')), 'ni': ('exclude','%s__in',lambda x: x.split(',')), 'ew': ('filter', '%s__iendswith',identity), 'en': ('exclude','%s__iendswith', identity), 'cn': ('filter', '%s__icontains', identity), 'nc': ('exclude','%s__icontains', identity) }
field = request.GET.get('searchField')
op = self.request.GET.get('searchOper')
value = request.GET.get('searchString')
try:
tipo, filtro, f = django_equivalences[op]
except KeyError:
tipo, filtro, f = django_equivalences['eq']
filtro = str(filtro % field)
query = {filtro:f(value)}
record_list = Entry.objects.filter(**query)
if tipo == 'filter' \ else Entry.objects.exclude(**query)

Com veis un exercici interessant perquè hi ha un poc de tot: funció lambda, us dels diccionaris, ús de les funcions com a objectes i pas de paràmetres a una funció mitjançant un diccionari.

blog comments powered by Disqus