Vistas genéricas de listado y detalle

Intermedio
Django
Django
Actualizado: 18/04/2026

ListView: listados con paginación

ListView es una vista genérica de Django que renderiza un listado de objetos de un modelo. Con pocos atributos de clase se obtiene paginación, ordenación y contexto listo para usar en la plantilla:

# views.py
from django.views.generic import ListView
from .models import Producto

class ProductoListView(ListView):
    model = Producto
    template_name = 'catalogo/producto_list.html'
    context_object_name = 'productos'
    paginate_by = 12
    ordering = ['-fecha_creacion']

    def get_queryset(self):
        """Permite filtrar o anotar el queryset base."""
        qs = super().get_queryset().select_related('categoria')
        categoria = self.request.GET.get('categoria')
        if categoria:
            qs = qs.filter(categoria__slug=categoria)
        return qs

    def get_context_data(self, **kwargs):
        """Añade datos adicionales al contexto de la plantilla."""
        context = super().get_context_data(**kwargs)
        context['categorias'] = Categoria.objects.all()
        context['categoria_activa'] = self.request.GET.get('categoria', '')
        return context

Diagrama conceptual de Vistas genéricas de listado y detalle

Configuración de URLs:

# urls.py
from django.urls import path
from .views import ProductoListView

urlpatterns = [
    path('productos/', ProductoListView.as_view(), name='producto-list'),
]

Plantilla para ListView

<!-- catalogo/producto_list.html -->
{% for producto in productos %}
    <div class="producto-card">
        <h3>{{ producto.nombre }}</h3>
        <p>{{ producto.precio }} €</p>
    </div>
{% empty %}
    <p>No hay productos disponibles.</p>
{% endfor %}

<!-- Controles de paginación -->
{% if is_paginated %}
<nav aria-label="Paginación">
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">Anterior</a>
    {% endif %}
    <span>Página {{ page_obj.number }} de {{ page_obj.paginator.num_pages }}</span>
    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">Siguiente</a>
    {% endif %}
</nav>
{% endif %}

Django gestiona automáticamente el parámetro ?page=N en la URL cuando paginate_by está configurado.

DetailView: páginas de detalle

DetailView recupera un único objeto por su clave primaria (pk) o slug y lo pasa a la plantilla:

# views.py
from django.views.generic import DetailView
from .models import Producto

class ProductoDetailView(DetailView):
    model = Producto
    template_name = 'catalogo/producto_detail.html'
    context_object_name = 'producto'
    slug_field = 'slug'        # Campo del modelo a usar como slug
    slug_url_kwarg = 'slug'    # Nombre del parámetro en la URL

    def get_queryset(self):
        return super().get_queryset().select_related('categoria', 'proveedor')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['productos_relacionados'] = Producto.objects.filter(
            categoria=self.object.categoria
        ).exclude(pk=self.object.pk)[:4]
        return context
# urls.py
urlpatterns = [
    path('productos/', ProductoListView.as_view(), name='producto-list'),
    path('productos/<int:pk>/', ProductoDetailView.as_view(), name='producto-detail'),
    path('productos/<slug:slug>/', ProductoDetailView.as_view(), name='producto-detail-slug'),
]

Paginator manual

Para casos más personalizados, la clase Paginator de Django permite controlar la paginación en vistas funcionales:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
from .models import Producto

def lista_productos(request):
    productos_lista = Producto.objects.filter(activo=True).order_by('-fecha_creacion')
    paginator = Paginator(productos_lista, 12)  # 12 productos por página

    pagina = request.GET.get('page', 1)
    try:
        productos = paginator.page(pagina)
    except PageNotAnInteger:
        productos = paginator.page(1)
    except EmptyPage:
        productos = paginator.page(paginator.num_pages)

    return render(request, 'catalogo/producto_list.html', {
        'productos': productos,
        'paginator': paginator,
    })

Personalizar el template_name

Por convención, si no se específica template_name, Django busca automáticamente <app>/<model>_list.html para ListView y <app>/<model>_detail.html para DetailView. Conocer esta convención permite reducir la configuración al mínimo:

class ProductoListView(ListView):
    model = Producto
    paginate_by = 12
    # Django busca automáticamente: catalogo/producto_list.html

Las vistas genéricas de listado y detalle eliminan el código repetitivo de las operaciones más comunes, manteniendo la flexibilidad para personalizar el queryset y el contexto cuando sea necesario.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Implementar ListView para mostrar colecciones de objetos con paginación integrada. Usar DetailView para mostrar el detalle de un objeto por su clave primaria o slug. Personalizar el contexto que se pasa a la plantilla sobreescribiendo get_context_data. Configurar el queryset de la vista para filtrar o anotar los resultados. Aplicar paginación manual con la clase Paginator para casos más personalizados.