Filtrado por categorías y búsqueda por palabras clave

En este capítulo aprenderás a manejar los parámetros de consulta de URL, implementar la navegación de filtrado por categorías y la función de búsqueda por palabras clave.


Parámetros de consulta de URL

Los parámetros de consulta son la parte de la URL después del signo de interrogación, por ejemplo/?category=django&q=入门。

En las funciones de vista de Django, a través derequest.GETobtener estos parámetros.

Ejemplo

def index(request):
    # request.GET es un objeto similar a un diccionario
    # .get('clave', 'valor_predeterminado') obtiene el parámetro de forma segura, devolviendo el valor predeterminado si no existe
    category_slug = request.GET.get('category', '')   # Filtrado por categoría
    keyword = request.GET.get('q', '')                 # Palabra clave de búsqueda
    print(f'Categoría: {category_slug}, Búsqueda: {keyword}')

Filtrado encadenado de ORM y objetos Q

El filtrado y la búsqueda requieren combinar dinámicamente las condiciones de consulta.

Ejemplo

# Ruta del archivo: blog/views.py, actualizar la función index
from django.shortcuts import render
from django.db.models import Q           # Los objetos Q se utilizan para combinar condiciones de consulta complejas
from .models import Post, Category

def index(request):
    """Página de inicio del blog: muestra la lista de artículos, admite filtrado por categoría + búsqueda por palabras clave"""
    posts = Post.objects.all().order_by('-created_at')

    # 1. Obtener los parámetros de consulta
    category_slug = request.GET.get('category', '')
    keyword = request.GET.get('q', '')

    # 2. Filtrar por categoría
    if category_slug:
        # category__slug: accede al campo slug de la tabla de categorías a través de la clave externa (doble guion bajo)
        posts = posts.filter(category__slug=category_slug)

    # 3. Buscar por palabra clave (título o resumen)
    if keyword:
        # Q(title__icontains=...) | Q(summary__icontains=...): combinación OR
        # icontains: consulta de contención que no distingue entre mayúsculas y minúsculas
        posts = posts.filter(
            Q(title__icontains=keyword) | Q(summary__icontains=keyword)
        )

    # 4. Obtener todas las categorías (para renderizar la navegación de filtros)
    categories = Category.objects.all()

    context = {
        'posts': posts,
        'categories': categories,
        'category_slug': category_slug,   # Pasar a la plantilla para resaltar la selección actual
        'keyword': keyword,               # Mostrar de nuevo la palabra clave de búsqueda
        'title': 'Blog EJEMPLO - Inicio',
    }
    return render(request, 'blog/index.html', context)

Explicación de los métodos de consulta del ORM

MétodoSignificadoEjemplo
field__exactCoincidencia exactatitle__exact='Django'
field__iexactCoincidencia exacta ignorando mayúsculas/minúsculastitle__iexact='django'
field__containsContiene (distingue mayúsculas/minúsculas)title__contains='入门'
field__icontainsContiene (ignora mayúsculas/minúsculas)title__icontains='django'
field__gte / lteMayor o igual que / Menor o igual quecreated_at__gte='2024-01-01'
field__in=[...]En una listacategory__in=[1, 2, 3]

Doble guion bajo__Es la convención central de Django ORM: se utiliza para acceder a los campos de modelos relacionados, o para especificar el modo de consulta de un campo. Por ejemplocategory__slugRepresenta atravesar la clave externa hasta el campo slug de la tabla Category.


Actualizar la plantilla de la página de inicio

Añadir la navegación de filtrado por categorías y el cuadro de búsqueda sobre la lista de artículos.

Ejemplo

<!-- Ruta del archivo: blog/templates/blog/index.html -->
{% extends 'blog/base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h2 class="section-title">Artículos recientes</h2>

<!-- Cuadro de búsqueda -->
<div class="search-bar">
    <!-- Formulario method="get": después de enviarlo, los parámetros aparecen en la URL (/search/?q=xxx) -->
    <form method="get" action="{% url 'index' %}">
        <input
           type="text"
           name="q"
           value="{{ keyword }}"
           placeholder="Buscar título o resumen del artículo..."
           class="search-input"
       />
        {% if keyword %}
        <a href="{% url 'index' %}" class="clear-btn">✕</a>
        {% endif %}
    </form>
</div>

<!-- Navegación de filtrado por categorías -->
<div class="category-bar">
    <!-- Botón "Todos": sin parámetro category -->
    <a href="{% url 'index' %}"
      class="{% if not category_slug %}active{% endif %}">Todos</a>

    {% for cat in categories %}
    <a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
      class="{% if category_slug == cat.slug %}active{% endif %}">
        {{ cat.name }}
    </a>
    {% endfor %}
</div>

<!-- Estadísticas de resultados del filtro -->
<p class="result-info">
Total: {{ posts.count }} artículos
{% if keyword %}, buscando «{{ keyword }}»{% endif %}
</p>

{% if posts %}
    <div class="article-grid">
        {% for post in posts %}
        <a href="{% url 'post_detail' post.pk %}" class="card-link">
            <div class="article-card">
                <div class="card-content">
                    <span class="card-category">{{ post.category.name }}</span>
                    <h3>{{ post.title }}</h3>
                    <p>{{ post.summary|truncatechars:80 }}</p>
                    <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
                </div>
            </div>
        </a>
        {% endfor %}
    </div>
{% else %}
    <p class="empty-tip">No se encontraron artículos que coincidan</p>
{% endif %}
{% endblock %}

Estilos de la navegación de filtros y del cuadro de búsqueda

Ejemplo

/* Añadir en el bloque style de base.html */
.search-bar {
    margin-bottom: 20px;
}

.search-input {
    width: 100%;
    padding: 12px 16px;
    border: 2px solid #eee;
    border-radius: 8px;
    font-size: 15px;
    outline: none;
    transition: border-color 0.2s;
}

.search-input:focus {
    border-color: #2c3e50;
}

.clear-btn {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: #999;
    text-decoration: none;
    font-size: 18px;
}

.category-bar {
    display: flex;
    gap: 10px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.category-bar a {
    padding: 6px 16px;
    border: 1px solid #ddd;
    border-radius: 20px;
    text-decoration: none;
    color: #333;
    font-size: 14px;
    transition: all 0.2s;
}

.category-bar a.active {
    background: #2c3e50;
    color: #fff;
    border-color: #2c3e50;
}

.category-bar a:hover {
    border-color: #2c3e50;
}

.result-info {
    color: #999;
    font-size: 14px;
    margin-bottom: 16px;
}

.card-link {
    text-decoration: none;
    color: inherit;
}

Los enlaces de los botones de categoría conservan al mismo tiempo elcategoryykeywordparámetro:?category={{ cat.slug }}&q={{ keyword }}. De este modo, si el usuario primero busca y luego hace clic en una categoría, la condición de búsqueda no se pierde y ambas condiciones se aplican a la vez.


Manos a la obra: probar el filtrado y la búsqueda

Inicia el servidor y verifica los siguientes puntos:

  • Al hacer clic en el botón de categoría, solo se muestran los artículos de esa categoría
  • Introduce una palabra clave para filtrar en tiempo real los títulos y resúmenes
  • Categoría + búsqueda combinados: los resultados filtrados cumplen ambas condiciones a la vez
  • El botón "Todos" borra el filtro de categoría y vuelve a mostrar todos los artículos

Resumen del capítulo

En este capítulo has aprendido las formas de manejar los parámetros de consulta de URL en Django: request.GET.get() para obtener parámetros, filtrado encadenado de ORM + objetos Q para implementar consultas complejas, y concatenación dinámica de parámetros URL en las plantillas para implementar enlaces de filtrado.

Las funciones de filtrado por categoría y búsqueda permiten a los usuarios encontrar rápidamente los artículos que les interesan.

Otras extensiones