Los buscadores son unos de los principales protagonistas de la web. Casos de éxito como Google y Yahoo, han construido sus empresas multimillonarias alrededor de las búsquedas. Casi todos los sitios obtienen un gran porcentaje de tráfico viniendo desde y hacia sus páginas de búsqueda. A menudo, la diferencia entre el éxito y el fracaso de un sitio, lo determina la calidad de su búsqueda. Así que sería mejor que agreguemos un poco de búsqueda a nuestro pequeño sitio de libros, ¿no?
Comenzaremos agregando la vista para la búsqueda a nuestro URLconf
(mysite.urls
). Recuerda que esto se hace agregando algo como
(r'^search/$', 'mysite.books.views.search')
al conjunto de URL patterns
(patrones).
A continuación, escribiremos la vista search
en nuestro módulo de vistas
(mysite.books.views
):
from django.db.models import Q
from django.shortcuts import render_to_response
from models import Book
def search(request):
query = request.GET.get('q', '')
if query:
qset = (
Q(title__icontains=query) |
Q(authors__first_name__icontains=query) |
Q(authors__last_name__icontains=query)
)
results = Book.objects.filter(qset).distinct()
else:
results = []
return render_to_response("books/search.html", {
"results": results,
"query": query
})
Aquí han surgido algunas cosas que todavía no hemos vimos. La primera, ese
request.GET
. Así es cómo accedes a los datos del GET desde Django;
Los datos del POST se acceden de manera similar, a través de un objeto
llamado request.POST
. Estos objetos se comportan exactamente como
los diccionarios estándar de Python, y tienen además otras
características que se explican en el apéndice H.
Así que la línea:
query = request.GET.get('q', '')
busca un parámetro del GET llamado q
y devuelve una cadena de texto
vacía si este parámetro no fue suministrado. Observa que estamos
usando el método get()
de request.GET
, algo potencialmente
confuso. Este método get()
es el mismo que posee cualquier
diccionario de Python. Lo estamos usando aquí para ser precavidos:
no es seguro asumir que request.GET
tiene una clave 'q'
, así
que usamos get('q', '')
para proporcionar un valor por defecto,
que es ''
(el string vacío). Si hubiéramos intentado acceder a la
variable simplemente usando request.GET['q']
, y q
no hubiese
estado disponible en los datos del GET, se habría lanzado un
KeyError
.
Segundo, ¿qué es ese Q
? Los objetos Q
se utilizan para ir
construyendo consultas complejas — en este caso, estamos buscando los
libros que coincidan en el título o en el nombre con la
consulta. Técnicamente, estos objetos Q
consisten de un QuerySet
,
y puede leer más sobre esto en el apéndice C.
En estas consultas, icontains
es una búsqueda en la que no se
distinguen mayúsculas de minúsculas (case-insensitive), y que
internamente usa el operador LIKE
de SQL en la base de datos.
Dado que estamos buscando en campos de muchos-a-muchos, es posible que
un libro se obtenga más de una vez (por ej: un libro que tiene dos
autores, y los nombres de ambos concuerdan con la consulta). Al
agregar .distinct()
en el filtrado, se eliminan los resultados
duplicados.
Todavía no hay una plantilla para esta vista. Esto lo solucionará:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>Search{% if query %} Results{% endif %}</title>
</head>
<body>
<h1>Search</h1>
<form action="." method="GET">
<label for="q">Search: </label>
<input type="text" name="q" value="{{ query|escape }}">
<input type="submit" value="Search">
</form>
{% if query %}
<h2>Results for "{{ query|escape }}":</h2>
{% if results %}
<ul>
{% for book in results %}
<li>{{ book|escape }}</l1>
{% endfor %}
</ul>
{% else %}
<p>No books found</p>
{% endif %}
{% endif %}
</body>
</html>
A esta altura, lo que esto hace debería ser obvio. Sin embargo, hay algunos detalles que vale la pena resaltar:
- action s
.
en el formulario, esto significa "la URL actual". Esta es una buena práctica estándar: no utilices vistas distintas para la página que contiene el formulario y para la página con los resultados; usa una página única para las dos cosas. - Volvemos a insertar el texto de la consulta en el
<input>
. Esto permite a los usuarios refinar fácilmente sus búsquedas sin tener que volver a teclear todo nuevamente. En todo lugar que aparece
query
ybook
, lo pasamos por el filtroescape
para asegurarnos de que cualquier búsqueda potencialmente maliciosa sea descartada antes de que se inserte en la página¡Es vital hacer esto con todo el contenido suministrado por el usuario! De otra forma el sitio se abre a ataques de cross-site scripting (XSS). El Capítulo 19 explica XSS y la seguridad con más detalle.
- En cambio, no necesitamos preocuparnos por el contenido malicioso en las búsquedas de la base de datos — podemos pasar directamente la consulta a la base de datos. Esto es posible gracias a que la capa de base de datos de Django se encarga de manejar este aspecto de la seguridad por ti.
Ahora ya tenemos la búsqueda funcionando. Se podría mejorar más el sitio colocando el formulario de búsqueda en cada página (esto es, en la plantilla base). Dejaremos esto de tarea para el hogar.
A continuación veremos un ejemplo más complejo. Pero antes de hacerlo, discutamos un tema más abstracto: el "formulario perfecto".