Una acción típica que se va a repetir en, prácticamente, cada listado que mostremos, es la de añadir un buscador [1]. Un buscador típico incluirá un pequeño formulario en la misma página de listado:

<form method="get" action="">
    <input type="text" name="q" value="{{ q }}" />
    <input type="submit" value="Search" />
</form>

Nos interesaría no tener que ir copiando y pengando este código en cada listado. Aunque sea un código que no vaya a cambiar, viola el principio de DRY.

Una mejor solución pasa por crear un templatetag, en el fichero my_tags.py dentro del directorio templatetags:

@register.inclusion_tag('search_form.html', takes_context=True)
def display_search_form(context):
    return {
        'q': context['q'],
    }

search_forml.html es la plantilla HTML que contiene el formulario mostrado arriba. Mediante el decorador register.inclusion_tag permitimos que a la plantilla HTML le llegue la variable q del contexto, que contiene la búsqueda.

Y luego, en la plantilla del listado, incluimos el templatetag

{% load my_tags %}

Allá donde queramos que aparezca el buscador incluiremos lo siguiente:

{% display_search_form %}

Sólo queda ver el contenido de la vista que muestra el listado. En particular, deberemos recoger la variable q que nos puede llegar por GET. Algo así:

q = request.GET.get("q", "")

Antes de modificar la consulta para filtrar los resultados que concuerden con nuestra búsqueda, hay diferentes aspectos que deberíamos tener en cuenta referentes a las búsquedas.

Lo primero es sobre qué vamos a buscar, es decir, sobre qué campos del modelo que vamos a buscar. Pero también podríamos tener una serie de palabras clave asociadas y guardadas en otro modelo qué también nos gustaría que se tuvieran en cuenta en la búsqueda. O podríamos buscar, para cada clave foránea, en los campos de ese modelo.

Lo siguiente es cómo interpretar esa búsqueda. Podríamos buscar una coincidencia exacta de todo lo que hemos buscado, que debería coincidir con, o estar contenida en, el contenido de un campo. También podríamos realizar una búsqueda más elaborada, separando la búsqueda en palabras. o añadir modificadores, estilo Google, para, por ejemplo, excluir palabras.

Utilizaremos el código publicado por Julien Phalip para realizar esta búsqueda de forma que nos sirva para lo que pretendemos. El método de Julien separa las palabras de la búsqueda para montar un query que utiliza para filtrar el resultado según la lista de campos proporcionada. Nos basaremos en este método para extender la búsqueda a todos los campos del modelo y los campos de los modelos referenciados por las claves foráneas de éste.

def get_full_query(query_string, model):
    """ Returns a query to search in every field of the given model """
    fields = []
    for f in model._meta.fields:
        if not f.rel:
            fields.append(f.name)
        else:
            rel_fields = [ "%s__%s" % (f.name, fr.name) for fr in f.rel.to._meta.fields if not fr.rel ]
            fields.extend(rel_fields)
    return get_query(query_string, fields)

model._meta.fields devuelve, como su nombre indica, un listado con los campos del modelo. Cada campo tiene, entre otros, los atributos name, con el nombre del campo, y rel, que, en el caso de una clave foránea, contiene el modelo al que hace referencia.

En la vista del listado tendremos:

queryset = MyTable._default_manager.all() # [2]
if q:
    query = get_full_query(q, MyTable)
    queryset = queryset.filter(query)

Este queryset contiene el listado que le pasamos a la plantilla:

return render_to_response("my_list.html",
    {
        "object_list": queryset,
        "q": q,
    },
    context_instance=RequestContext(request))

» [1] Existen aplicaciones para realizar búsquedas, como haystack o django-sphinx. » [2] Según James Bennett, en "Django practical projects", utilizar _default_manager en lugar de objects es una buena práctica, ya que podría ser que el modelo tuviera un manager personalizado. Utilizar _default_manager siempre es seguro.


Entradas relacionadas


Published

Category

dev

Tags

Contacto