El siguiente artículo es la última entrega de la serie de exploración de vistas basadas en clases (CBV) de Django, donde presentaremos cómo implementar funciones comúnmente utilizadas en el trabajo, como paginación, búsqueda y ordenación, utilizando ListView. Si hasta la séptima parte hemos revisado desde lo básico de CBV hasta el uso de Mixins, en este artículo veremos cómo combinar todo para gestionar de manera limpia las requisitos generales que encontramos en el desarrollo web real.

Para ir al artículo anterior, ¡haz clic en el siguiente enlace!

Exploración de vistas basadas en clases (CBV) serie ⑦ - Uso de Mixins y gestión de permisos


“¡Ampliar ListView para implementar potentes funciones de listado y mejorar la experiencia del usuario!”


1. ListView, mucho más que una simple lista

ListView es una Vista Genérica muy útil para mostrar listas de datos. Sin embargo, en aplicaciones web reales, además de simplemente mostrar listas, son esenciales la paginación para administrar eficientemente grandes cantidades de datos, la búsqueda para encontrar información rápidamente y la ordenación según preferencias de usuario.

En este capítulo, veremos cómo ampliar ListView para implementar fácilmente y de manera elegante estas necesidades prácticas aprovechando las ventajas de CBV.


2. Implementar Paginación

Mostrar grandes cantidades de datos de una sola vez no es bueno para la experiencia del usuario. La paginación reduce la carga de carga del usuario al dividir los datos en varias páginas y ayuda a encontrar la información deseada más fácilmente.

2.1 Configuración básica de paginación

ListView proporciona una función de paginación incorporada. Solo necesitas establecer el atributo paginate_by en la clase de vista.

Ejemplo de views.py

from django.views.generic import ListView
from .models import Article

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10 # Muestra 10 objetos Article por página
  • paginate_by = 10: Si se establece así, ListView divide automáticamente los resultados en páginas de 10 y pasa objetos relacionados con la paginación (paginator, page_obj, is_paginated) al contexto de la plantilla.

2.2 Implementar la UI de paginación en la plantilla

En la plantilla, puedes usar page_obj que se ha pasado al contexto para mostrar enlaces de número de página y implementar botones de página anterior/siguiente.

Ejemplo de articles/article_list.html

<ul>
    {% for article in articles %}
        <li>{{ article.title }} - {{ article.created_at }}</li>
    {% empty %}
        <li>No hay artículos publicados aún.</li>
    {% endfor %}
</ul>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; Primero</a>
            <a href="?page={{ page_obj.previous_page_number }}>Anterior</a>
        {% endif %}

        <span class="current">
            Página {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}>Siguiente</a>
            <a href="?page={{ paginator.num_pages }}>Último &raquo;</a>
        {% endif %}
    </span>
</div>
  • page_obj: Este objeto contiene los datos de la página actual. Proporciona atributos como has_previous, has_next, previous_page_number, next_page_number, y number.
  • paginator: Este objeto contiene información sobre todas las páginas. Proporciona atributos como num_pages (el número total de páginas).

2.3 Mantener la conexión de URLs

Al generar enlaces de paginación, puedes notar que se usa un parámetro de consulta ?page=2. ListView reconoce automáticamente este parámetro y muestra los datos de la página correspondiente.


Encontrar información deseada rápidamente en un sitio web es muy importante. Vamos a implementar la función de búsqueda ampliando ListView.

3.1 Definir el formulario

Primero, definimos un sencillo formulario para que el usuario ingrese los términos de búsqueda.

Ejemplo de forms.py

from django import forms

class ArticleSearchForm(forms.Form):
    search_term = forms.CharField(label='Término de búsqueda', required=False)

3.2 Modificar ListView

Sobre escribimos el método get_queryset() de ListView para implementar la función de búsqueda.

Ejemplo de views.py

from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET) # Obtener datos del formulario de GET

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                # Filtrar artículos con el término de búsqueda en el título o en el contenido
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = self.form # Pasar el formulario a la plantilla
        return context
  • get_queryset(): Este método se sobreescribe para modificar el queryset base.
  • Se crea una instancia del formulario, y al pasar la validación se obtienen los términos de búsqueda de cleaned_data.
  • Se utilizan Q objetos para filtrar los artículos que contienen el término de búsqueda en el título (title) o en el contenido (content); __icontains verifica la inclusión sin distinguir mayúsculas y minúsculas.
  • get_context_data(): Se agrega la instancia del formulario al contexto para que pueda ser renderizada en la plantilla.

3.3 Mostrar el formulario de búsqueda en la plantilla y mantener resultados

Agregamos el formulario de búsqueda a la plantilla y organizamos la URL de manera que se puedan mantener resultados mientras se utiliza la paginación.

articles/article_list.html

<form method="get">
    {{ search_form }}
    <button type="submit">Buscar</button>
</form>

<ul>
    {% for article in articles %}
        <li>{{ article.title }} - {{ article.created_at }}</li>
    {% empty %}
        <li>No se encontraron resultados.</li>
    {% endfor %}
</ul>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}">&laquo; Primero</a>
            <a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">Anterior</a>
        {% endif %}

        <span class="current">
            Página {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">Siguiente</a>
            <a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}">Último &raquo;</a>
        {% endif %}
    </span>
</div>
  • Se establece el method del formulario como get para pasar los términos de búsqueda como parámetros de consulta de URL.
  • Al crear enlaces de página, se incluye el término de búsqueda actual (search_form.search_term.value) en los parámetros de URL para que la paginación mantenga los resultados de búsqueda. Se utiliza |default:'' para usar una cadena vacía si no hay término de búsqueda.

Ejemplo de pantalla de ListView – Página de lista que incluye UI de búsqueda y ordenación


4. Añadir la función de ordenación

Vamos a añadir la función de ordenación para que los usuarios puedan ver los datos en el orden que deseen.

4.1 Agregar un formulario de selección de orden (opcional)

También puedes agregar un formulario desplegable para seleccionar el criterio de ordenación. Sin embargo, se puede manejar de manera simple también solo con parámetros de URL. Aquí describiremos el método que utiliza parámetros de URL.

4.2 Modificar ListView

Modificamos el método get_queryset() para aplicar order_by().

Ejemplo de views.py (incluyendo la función de búsqueda)

from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm

class ArticleListView(ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET)
        ordering = self.request.GET.get('ordering', '-created_at') # Orden predeterminado: más reciente primero

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )

        queryset = queryset.order_by(ordering)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = self.form
        context['ordering'] = self.request.GET.get('ordering', '-created_at') # Pasar el criterio actual de ordenación a la plantilla
        return context
  • self.request.GET.get('ordering', '-created_at'): Obtiene el valor de ordering de los parámetros de consulta de la URL. Si no hay valor, ordena por defecto por -created_at (más reciente primero).
  • queryset = queryset.order_by(ordering): Utiliza el valor de ordering para ordenar el queryset. Colocar - delante del nombre del campo del modelo ordenará en orden inverso.

4.3 Proporcionar enlaces de ordenación en la plantilla

En la plantilla, se proporcionan enlaces que permiten ordenar por cada campo.

Ejemplo de articles/article_list.html

<form method="get">
    {{ search_form }}
    <button type="submit">Buscar</button>
</form>

<table>
    <thead>
        <tr>
            <th><a href="?ordering=title&search_term={{ search_form.search_term.value|default:'' }}">Título</a></th>
            <th><a href="?ordering=-created_at&search_term={{ search_form.search_term.value|default:'' }}">Fecha de creación</a></th>
            </tr>
    </thead>
    <tbody>
        {% for article in articles %}
            <tr>
                <td>{{ article.title }}</td>
                <td>{{ article.created_at }}</td>
            </tr>
        {% empty %}
            <tr><td colspan="2">No se encontraron resultados.</td></tr>
        {% endfor %}
    </tbody>
</table>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">&laquo; Primero</a>
            <a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Anterior</a>
        {% endif %}

        <span class="current">
            Página {{ page_obj.number }} / {{ paginator.num_pages }}
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Siguiente</a>
            <a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">Último &raquo;</a>
        {% endif %}
    </span>
</div>
  • Ofrecemos enlaces que incluyen los parámetros de ordering en cada encabezado de la tabla.
  • Al crear enlaces de página, se incluyen los parámetros de búsqueda y el criterio de orden actual para mantener el estado de orden mientras se realiza la paginación.

Diagrama de proceso de búsqueda/ordenación – Visualización del flujo de ampliación de ListView


5. Combinar funcionalidades con Mixins

Utilizando Mixins, podemos gestionar de manera más limpia las funciones de paginación, búsqueda y ordenación. Por ejemplo, podemos separar la función de búsqueda en un Mixin.

Ejemplo de mixins.py

from django.db.models import Q
from .forms import ArticleSearchForm

class ArticleSearchMixin:
    def get_queryset(self):
        queryset = super().get_queryset()
        self.form = ArticleSearchForm(self.request.GET)

        if self.form.is_valid():
            search_term = self.form.cleaned_data.get('search_term')
            if search_term:
                queryset = queryset.filter(
                    Q(title__icontains=search_term) | Q(content__icontains=search_term)
                )
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_form'] = getattr(self, 'form', ArticleSearchForm()) # Usa el formulario predeterminado si no hay formulario
        return context

Ejemplo de views.py (aplicando Mixin)

from django.views.generic import ListView
from .models import Article
from .mixins import ArticleSearchMixin

class ArticleListView(ArticleSearchMixin, ListView):
    model = Article
    template_name = 'articles/article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

    def get_queryset(self):
        queryset = super().get_queryset()
        ordering = self.request.GET.get('ordering', '-created_at')
        queryset = queryset.order_by(ordering)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['ordering'] = self.request.GET.get('ordering', '-created_at')
        return context
  • Se ha creado ArticleSearchMixin para separar la lógica relacionada con la búsqueda.
  • ArticleListView hereda tanto de ArticleSearchMixin como de ListView, lo que le permite tener ambas funcionalidades de búsqueda y listado.

6. Ventajas y limitaciones de CBV (conclusión de la serie)

A lo largo de 8 partes de esta serie de exploración de CBV, hemos examinado por qué hacer la transición de vistas basadas en funciones (FBV) a vistas basadas en clases (CBV), la estructura y el uso básico de CBV, el uso de diversas Vistas Genéricas, y cómo extender funcionalidades utilizando Mixins.

Principales ventajas de CBV:

  • Reutilización de código: Con Vistas Genéricas y Mixins, puedes reducir el código repetitivo y hacer un desarrollo más eficiente.
  • Código estructurado: El código basado en clases facilita la separación y gestión clara de la lógica.
  • Escalabilidad: Gracias a la herencia y los Mixins, puedes extender fácilmente la funcionalidad existente y agregar nuevas características.
  • Mantenimiento: Como el código está modularizado, es más fácil de mantener y reduce el impacto de los cambios en el código total.
  • Aumento de la productividad en desarrollo: Usando las diversas Vistas Genéricas y Mixins que ofrece Django, puedes desarrollar rápidamente.

Limitaciones y consideraciones de CBV:

  • Curva de aprendizaje inicial: Puede ser necesario comprender la estructura basada en clases en comparación con FBV.
  • Herencia excesiva: Usar demasiados Mixins puede dificultar el seguimiento del flujo del código. Es importante usar Mixins de manera apropiada.
  • Sobre ingeniería para lógica simple: En páginas o funcionalidades muy sencillas, FBV puede ser más conciso. Es recomendable elegir el método de vista adecuado según la situación.

En conclusión, Django CBV es una herramienta muy potente y útil para desarrollar aplicaciones web que requieren funcionalidades complejas y diversas. Comprender y utilizar adecuadamente las ventajas de CBV te permitirá escribir códigos más eficientes y mantenibles.

¡Gracias por leer la serie de exploración de vistas basadas en clases (CBV)! Espero que esta serie te haya ayudado a comprender mejor Django CBV y a aplicarlo en tus proyectos reales.


Ver de nuevo el artículo anterior

  1. Exploración de vistas basadas en clases (CBV) serie #1 – Razones para la transición de FBV a CBV y la mentalidad del desarrollador
  2. Exploración de vistas basadas en clases (CBV) serie #2 – Comprender la clase View básica de Django
  3. Exploración de vistas basadas en clases (CBV) serie #3 – Simplificando el procesamiento de formularios con FormView
  4. Exploración de vistas basadas en clases (CBV) serie #4 – Uso de ListView y DetailView
  5. Exploración de vistas basadas en clases (CBV) serie #5 – Implementación de CRUD con CreateView, UpdateView y DeleteView
  6. Exploración de vistas basadas en clases (CBV) serie #6 – Uso de TemplateView y RedirectView
  7. Exploración de vistas basadas en clases (CBV) serie #7 – Uso de Mixins y gestión de permisos

“¡Mejora funciones de listado más ricas y amigables para el usuario ampliando ListView, y!

¡Domina CBV para llevar tus habilidades de desarrollo en Django al siguiente nivel!”