Implementando un campo de búsqueda y filtros con formularios en Django
Índice de contenido
- Qué significa filtrar datos en Django
- Cuándo usar Q() para consultas más flexibles
- Crear un formulario para filtrar listados
- Campos de texto y selección de categoría
- Mantener valores del filtro activos tras la búsqueda
- ⚙️ Implementar los filtros en la vista (views.py)
- Aplicar condicionales simples para filtros opcionales
- Ejemplo completo de vista con Paginator
- Mejorar la experiencia del usuario
- ⚠️ Errores comunes y buenas prácticas
- Conclusión y próximos pasos
- ❓FAQs
- Profundizar el el filtrado en la vista
La mayoría de las aplicaciones que creamos o con las que interactuamos tienen muchísimos datos. Por tanto, no hace falta decir que es necesario tener una forma de buscar y filtrar los datos que necesitamos de forma rápida y eficaz. Sin más preámbulos, veamos las diversas formas en que podemos implementar la búsqueda en Django desde la búsqueda Q simple hasta la búsqueda de texto completo. Hagamos el componente de búsqueda.
Recuerda que nos quedamos en que aprendimos a enviar correos electrónicos en Django.
Qué significa filtrar datos en Django
Cómo funcionan los QuerySets y el método .filter()
En Django, casi todo parte de los QuerySets, que representan una colección de objetos de la base de datos. Para filtrar un listado, usamos el método filter(), que devuelve solo los registros que cumplen ciertas condiciones.
products = Product.objects.filter(category__contains='Laptops')Ese ejemplo devuelve todos los productos cuya categoría contiene “Laptops”. Es sencillo, pero limitado: distingue mayúsculas y minúsculas. Para hacerlo más flexible, podemos usar icontains.
Cuándo usar Q() para consultas más flexibles
Cuando necesitas combinar varios campos (por ejemplo, nombre, descripción o categoría), entra en juego Q(), un potente objeto que permite construir consultas complejas con operadores lógicos.
from django.db.models import Q
products = Product.objects.filter(
Q(name__icontains='laptop') |
Q(description__icontains='laptop') |
Q(category__icontains='laptop')
)Esta es la forma más simple de crear una búsqueda por texto en múltiples columnas sin depender aún de Postgres o búsqueda avanzada.
Crear un formulario para filtrar listados
Usar el método GET y un formulario sin action
Cuando queremos filtrar datos, lo ideal es usar peticiones GET. Así los filtros se reflejan en la URL y son fácilmente compartibles o recargables.
En mi caso, suelo dejar el action vacío, de modo que el formulario se envíe a sí mismo:
<form method="get" class="row">
<div class="col-auto">
<input type="text" name="search" class="form-control" placeholder="Search">
</div>
<div class="col-auto">
<select name="category_id" class="form-select">
<option value="">Categories</option>
{% for c in categories %}
<option value="{{ c.id }}">{{ c.title }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Search</button>
</div>
<div class="col-auto">
<a href="{% url 'elements:index' %}" class="btn btn-outline-secondary">Clean</a>
</div>
</form>La opción de limpiar el filtro es particularmente útil para la usabilidad de nuestro formulario, que apunte al listado, con eso, mediante la redirección se limpia automáticamente.
Un diseño más discreto, algo así. La idea del diseño es que este botón se vea más de segundo plano, y ya se entiende visualmente que el que está más pintado (más destacado) es el de primer plano.
Se usa un sistema de filas, mediante los rows de Bootstrap o Tailwind para que se alinen en tanta columnas como necesitemos
Campos de texto y selección de categoría
Aquí filtramos por nombre (campo de texto) y categoría (menú desplegable). Si quieres añadir más filtros, basta con replicar el patrón para cada campo del modelo.
Mantener valores del filtro activos tras la búsqueda
Algo que muchos olvidan: mantener el valor del campo después de enviar el formulario.
Yo lo resuelvo así:
<input type="text" name="search" class="form-control" value="{{ search }}">Y para las categorías:
<option
{% if category_id == c.id %} selected {% endif %}
value="{{ c.id }}">
{{ c.title }}
</option>Esto mejora mucho la experiencia del usuario, especialmente cuando la lista está paginada.
⚙️ Implementar los filtros en la vista (views.py)
Obtener parámetros de búsqueda con request.GET
En Django, acceder a los valores enviados es tan fácil como usar request.GET.get():
search = request.GET.get('search') if request.GET.get('search') else ''
category_id = request.GET.get('category_id')
category_id = int(category_id) if category_id else ''Aplicar condicionales simples para filtros opcionales
Los filtros opcionales no son magia, son simples condicionales, es así de simple, y esta es la magia de los filtros del lado del cliente.
Se usan peticiones GET, ya que, es consulta lo que estamos haciendo más no cambiando el modelo de datos. Por lo tanto, puedes agregar más filtros cambiar los actuales con tan solo agregar o cambiar los condicionales actuales y sus campos de formularios respectivos implementados anteriormente.
elements = Element.objects
if search:
elements = elements.filter(title__icontains=search)
if category_id:
elements = elements.filter(category_id=category_id)
elements = elements.filter(type=2).all()Ejemplo completo de vista con Paginator
Finalmente, paginamos y enviamos los datos al template, manteniendo los valores de los filtros:
from django.core.paginator import Paginator
paginator = Paginator(elements, 10)
page_number = request.GET.get('page')
return render(request, 'elements/index.html', {
'elements': paginator.get_page(page_number),
'search': search,
'category_id': category_id,
'categories': categories
})Mejorar la experiencia del usuario
Agregar un botón para limpiar los filtros
Siempre añado un botón Clean o Reset que permite reiniciar los filtros. Es tan simple como esto:
<div class="col-auto">
<a href="{% url 'elements:index' %}" class="btn btn-outline-secondary">Clean</a>
</div>Diseño y estilo del formulario de búsqueda
Para lograr un diseño más limpio, suelo aplicar un estilo discreto al botón secundario.
El objetivo es que el usuario entienda visualmente cuál es la acción principal (buscar) y cuál la secundaria (limpiar).
⚠️ Errores comunes y buenas prácticas
- No olvidar convertir IDs a enteros
- category_id = int(category_id) evita errores al filtrar claves foráneas.
- Evitar duplicar consultas filter()
- Usar el mismo objeto elements y encadenar los filtros.
- Cuándo usar icontains en lugar de contains
- icontains ignora mayúsculas/minúsculas y mejora la usabilidad de la búsqueda.
Conclusión y próximos pasos
Filtrar un listado en Django no requiere librerías adicionales: basta con usar request.GET, filter(), y un poco de lógica.
En mi caso, este enfoque ha sido suficiente incluso en proyectos de producción.
Si más adelante necesitas búsquedas más potentes, puedes integrar SearchVector y SearchQuery de PostgreSQL, o librerías como django-filter.
❓FAQs
¿Cómo aplicar múltiples filtros en Django?
Encadena varios .filter() o combina condiciones con Q().
¿Cómo limpiar los filtros?
Incluye un enlace que recargue la vista sin parámetros GET.
¿Cómo mantener los filtros al paginar?
Pasa search y category_id en el contexto y muéstralos en el formulario.
Profundizar el el filtrado en la vista
<!-- search -->
<div class="search_form">
<form class="search" method="GET" action="{% url 'products:search' %}">
<label>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"
viewBox="0 0 32 32">
<path
d="M32 30.586l-10.845-10.845c1.771-2.092 2.845-4.791 2.845-7.741
0-6.617-5.383-12-12-12s-12 5.383-12 12c0 6.617 5.383 12
12 12 2.949 0 5.649-1.074 7.741-2.845l10.845 10.845
1.414-1.414zM12 22c-5.514 0-10-4.486-10-10s4.486-10
10-10c5.514 0 10 4.486 10 10s-4.486 10-10 10z"></path>
</svg>
<input name="q" placeholder="Search" />
</label>
</form>
</div>El componente es fácil de entender, así que vamos a diseñarlo a continuación.
.search > label {
background-color: #fff;
padding: 0.5rem;
display: flex;
align-items: center;
gap: 1rem;
border-radius: 0.5rem;
border:1px solid #000;
}
.search svg {
fill: #000;
width: 1.2rem;
height: 1.2rem;
margin-left: 0.5rem;
}
.search input {
width: 100%;
background: transparent;
border: none;
outline: none;
color: #000;
font-size: 13px;
}Fácilmente y sin demasiada complejidad, digamos que tiene una tienda en línea y desea filtrar productos por su nombre o categoría, entonces puede hacer esto.
Products.objects.filter(category__contains='Laptops')Lo que devolverá el conjunto de consultas anterior son todos los productos de la categoría Computadoras portátiles. Esa es una forma muy restrictiva de búsqueda porque tiene que coincidir exactamente con las computadoras portátiles en mayúsculas, no necesito explicar cuán limitante es eso.
Podemos mejorar esto marginalmente usando la búsqueda de campos icontains, pero todo lo que hace es hacer que el conjunto de consultas no distinga entre mayúsculas y minúsculas. Podríamos mejorar esto aún más usando funciones de base de datos, pero todavía no es suficiente, así que no entraré en ello; sin embargo, es una lectura interesante.
Digno de mención es el objeto Q que te ayudará a realizar una consulta un poco más compleja. Veamos un ejemplo y lo explicaré.
from .models import Product
from django.db.models import Q
from django.shortcuts import render
def search(request):
q = request.GET.get('q') if request.GET.get('q') != None else ''
products = Product.objects.get(
Q(category__icontains=q) |
Q(name__icontains=q) |
Q(description__icontains=q)
)
context = {
"products":products
}
return render(request, "search.html", context)Lo primero que hacemos es importar el modelo sobre el que queremos ejecutar consultas en nuestro caso el modelo Producto, luego importamos el objeto Q que es el que vamos a usar para realizar consultas, y finalmente la función de renderizado. Luego tenemos una función de búsqueda que manejará todas las solicitudes de búsqueda y tenemos una variable q que simplemente obtiene lo que el usuario ingresó como términos de búsqueda en la barra de búsqueda.
Finalmente, construimos nuestra consulta. Digamos que el usuario ingresa laptop como término de búsqueda que evaluará la consulta anterior, obtenga el producto donde la categoría es como laptop o la descripción es como laptop o el nombre como laptop, que no distinguirá entre mayúsculas y minúsculas. Luego colocamos el resultado de esa consulta o la falta de ella en un diccionario de contexto y lo pasamos a la función de renderizado junto con la plantilla de búsqueda. Si bien esto es sin duda una mejora, necesita que los usuarios de la aplicación conozcan mucha información de antemano, como las categorías que podría tener, los nombres de los productos, etc., lo cual no es el caso.
Para resolver ese problema necesitamos una búsqueda de texto completo y tenemos suerte de que Django funcione bien con Postgres y Postgres tiene un motor de búsqueda de texto completo incorporado. Lea más sobre esto aquí. Django, por lo tanto, proporciona un módulo de búsqueda de Postgres aquí mismo django.contrib.postgres.search y esto es lo que vamos a utilizar para resolver algunos de los problemas anteriores para mejorar nuestra implementación de búsqueda.
Introduzcamos la búsqueda, es fácil y común buscar un solo término en una sola columna de la base de datos. Vea el ejemplo a continuación, pero para hacer esto necesitamos tener 'django.contrib.postgres' en las Aplicaciones instaladas.
from .models import Product
from django.contrib.postres import search
from django.shortcuts import render
def product_search(request):
q = request.GET.get('q') if request.GET.get('q') != None else ''
products = Product.objects.filter(category__search=q)
context = {
"products":products
}
return render(request, "search.html", context)Entonces, mejoramos la vista que hicimos anteriormente que usaba la búsqueda Q, pero eso aún es limitante ya que solo podemos buscar en una columna, mejoremos eso introduciendo SearchVector, y de esa manera podemos ejecutar nuestra búsqueda en más de una columna. Vea el ejemplo a continuación
from .models import Product
from django.contrib.postgres import search
from django.contrib.postgres.search import SearchVector
from django.shortcuts import render
def product_search(request):
q = request.GET.get('q') if request.GET.get('q') != None else ''
products = Product.objects.annotate(search=SearchVector('category', 'name', 'description')).filter(search=q)
context = {
"products":products
}
return render(request, "search.html", context)SearchVector nos permite ejecutar nuestras consultas en más de una columna, pero aún es limitante ya que solo podemos usar consultas de una palabra. Mejoremos esto introduciendo SearchQuery, que le permite usar más de una palabra en su término de búsqueda. Esto es fantástico porque puedes utilizar SearchQuery con varias configuraciones, lo que lo hace muy útil para buscar información en un sitio. Veamos un ejemplo y lo explico.
from .models import Product
from django.contrib.postres import search
from django.contrib.postgres.search import SearchVector, SearchQuery
from django.shortcuts import render
def product_search(request):
q = request.GET.get('q') if request.GET.get('q') != None else ''
products = Product.objects.annotate(
search=SearchVector("name", "description", "category"))
.filter(search=SearchQuery(q))
context = {
"products":products
}
return render(request, "search.html", context)Hay algunas adiciones aquí, lo más importante es que hemos importado SearchQuery, que hemos agregado a nuestro conjunto de consultas y lo que eso hace es, y nos permite buscar usando más de un término. En su configuración predeterminada, search_type será simple y tratará los términos de búsqueda como palabras clave separadas, por lo que SearchQuery('hp laptop') se tratará como dos palabras clave separadas. Si configuramos search_type en una frase como SearchQuery(“hp laptop”, search_type=’phrase’), las dos palabras se tratarán como una frase. Hay otras configuraciones que puede usar con SearchQuery; consulte la documentación aquí.
Con todas las técnicas de búsqueda anteriores, devolvemos como resultado que es posible cualquier coincidencia entre el vector y la consulta. ¿Qué sucede si desea ordenar los resultados de alguna manera según su relevancia? Luego, Postgres proporciona una forma de clasificar los resultados. Consulte esta documentación para eso.
No considero esto como una guía completa para la introducción a la búsqueda, sino más bien como una forma de abrirle el apetito para comenzar a buscar en Django, por lo que le imploro que consulte las distintas partes de la documentación que tengo. adjunto en todas partes, pero espero que facilite la comprensión de algunas cosas que tal vez no resulten naturales a primera vista.
Siguiente paso, aprende a crear tus primeras pruebas unitarias y de integración en Django.
Acepto recibir anuncios de interes sobre este Blog.
Veremos como implementar un campo de búsqueda y filtrado en Django desde el formulario de tipo GET hasta llegar a la vista, y sus condicionales y consultas avanzadas.