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

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
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.