Refactorización de vistas basadas en clases (CBV)
En este capítulo aprenderás las vistas basadas en clases (CBV) de Django, y refactorizarás la página de inicio y la página de detalles con un código más conciso.
FBV vs CBV
Hasta ahora, todas nuestras vistas están escritas con funciones (Function-Based View, FBV).
Django también proporcionavistas basadas en clases (Class-Based View, CBV), que usan clases para encapsular la lógica de las vistas.
| Características | FBV (vista de función) | CBV (vista de clase) |
|---|---|---|
| Cantidad de código | Flexible, pero la lógica común debe repetirse | Solo se necesitan unas pocas líneas de código al heredar vistas genéricas |
| Legibilidad | Flujo lineal, lógica clara | La cadena de herencia requiere seguimiento, pero una vez que estás familiarizado, se entiende de un vistazo |
| Reutilización | Mediante encapsulación en funciones | Mediante composición con Mixins (una característica distintiva de Django) |
| Escenarios aplicables | Páginas con lógica simple y flujo claro | Páginas con patrones como CRUD (crear, leer, actualizar, eliminar) |
| Distribución de métodos HTTP | Manual: if request.method == 'POST' | Distribución automática a los métodos get() / post() |
No hay una ventaja absoluta entre ambos; los proyectos Django suelencombinar FBV y CBV: lógica compleja con FBV, CRUD estándar con CBV.
ListView — Refactorización de la página de listado
La vista index de la página de inicio, en esencia, es:Consultar la lista de artículos → renderizar la plantilla。
Este tipo de lógica con patrones, DjangoListViewya la tiene escrita por ti.
Ejemplo
from django.views.generic import ListView
from django.db.models import Q
from .models import Post, Category
class PostListView(ListView):
"""Página de inicio del blog: hereda ListView, muestra la lista de artículos"""
model = Post # Especificar el modelo
template_name = 'blog/index.html' # Especificar la plantilla (por defecto: blog/post_list.html)
context_object_name = 'posts' # Nombre de variable usado en la plantilla (por defecto: object_list)
paginate_by = 12 # 12 elementos por página (paginación)
ordering = ['-created_at'] # Ordenación
def get_queryset(self):
"""Sobrescribir el método de consulta: admite filtro por categoría + búsqueda por palabras clave"""
queryset = super().get_queryset()
# Filtro por categoría
self.category_slug = self.request.GET.get('category', '')
if self.category_slug:
queryset = queryset.filter(category__slug=self.category_slug)
# Búsqueda por palabras clave
self.keyword = self.request.GET.get('q', '')
if self.keyword:
queryset = queryset.filter(
Q(title__icontains=self.keyword) |
Q(summary__icontains=self.keyword)
)
return queryset
def get_context_data(self, **kwargs):
"""Sobrescribir el método de contexto: inyectar datos adicionales en la plantilla"""
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['category_slug'] = self.category_slug
context['keyword'] = self.keyword
context['title'] = 'EJEMPLO Blog - Inicio'
return context
Comparando la cantidad de código antes y después de la refactorización: FBV alrededor de 40 líneas → CBV alrededor de 30 líneas, y la lógica está claramente dividida en diferentes métodos.
DetailView — Refactorización de la página de detalles
Ejemplo
from django.views.generic import DetailView
class PostDetailView(DetailView):
"""Página de detalles del artículo: hereda DetailView"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
# pk_url_kwarg: nombre del parámetro en la URL para pasar la clave primaria (por defecto es pk, aquí se declara explícitamente)
pk_url_kwarg = 'pk'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'{self.object.title} - Blog de EJEMPLO'
return context
Actualizar la configuración de rutas
La escritura de rutas para CBV es ligeramente diferente a la de FBV: se necesita llamar a.as_view()。
Ejemplo
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
# CBV: agregar .as_view() después de la clase para convertirla en una función de vista
path('', views.PostListView.as_view(), name='index'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
# Las demás vistas no cambian
path('register/', views.register, name='register'),
path('login/', auth_views.LoginView.as_view(
template_name='blog/login.html',
redirect_authenticated_user=True
), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]
¿Por qué CBV necesita usar
.as_view()? La configuración de URL de Django espera un objeto invocable (función)..as_view()Convierte la vista de clase en un objeto invocable que cumple con la firma de una función de vista de Django; internamente se encarga de distribuir automáticamente a los métodos get() o post() de la clase según el método HTTP (GET/POST).
Resumen de las vistas genéricas comunes
| Vista | Nombre de plantilla por defecto | Propósito |
|---|---|---|
| ListView | nombre_modelo_list.html | Muestra la lista de objetos |
| DetailView | nombre_modelo_detail.html | Muestra los detalles de un solo objeto |
| CreateView | nombre_modelo_form.html | Crea un nuevo objeto (con formulario) |
| UpdateView | nombre_modelo_form.html | Actualiza un objeto existente |
| DeleteView | nombre_modelo_confirm_delete.html | Confirma la eliminación del objeto |
| TemplateView | Debe especificarse | Renderiza una plantilla estática (sin consulta de datos) |
Las vistas genéricas de Django cubren aproximadamente el 80% de los escenarios de desarrollo web. Si encuentras un caso que no cumple con los requisitos, vuelve a FBV.
LoginRequiredMixin
CBV no puede usar@login_requireddecoradores (eso es para funciones); en su lugar, usaLoginRequiredMixin。
Ejemplo
from django.views.generic import ListView
class FavoriteListView(LoginRequiredMixin, ListView):
"""Lista de favoritos: requiere inicio de sesión para acceder"""
model = Post
template_name = 'blog/favorites.html'
context_object_name = 'posts'
ordering = ['-created_at']
# LoginRequiredMixin: los usuarios no autenticados serán redirigidos automáticamente a settings.LOGIN_URL
def get_queryset(self):
# Solo muestra los artículos favoritos del usuario actual
return self.request.user.favorite_posts.all().order_by('-created_at')
El orden de herencia de los Mixins es muy importante:
LoginRequiredMixinDebe escribirse delante deListViewListView. El orden de resolución de la herencia múltiple en Python es de izquierda a derecha; LoginRequiredMixin primero verifica el estado de inicio de sesión y, solo después de pasar, continúa con la lógica de ListView.
¿Cuándo usar CBV y cuándo usar FBV?
Regla empírica:
- FBV: lógica compleja, múltiples ramas condicionales, vistas con flujo no estándar (como toggle_favorite)
- CBV: operaciones CRUD estándar, vistas cuya lógica se puede dividir claramente en get/post (como la página de lista y la de detalles)
Operaciones como toggle_favorite (alternar favorito) tienen lógica simple pero un flujo diferente al CRUD convencional; FBV es más adecuado.
Para páginas estándar de visualización de datos como index y post_detail, CBV puede reducir significativamente el código repetitivo.
Resumen del capítulo
En este capítulo aprendiste el uso principal de las vistas basadas en clases de Django: ListView para mostrar listas, DetailView para mostrar detalles, get_queryset/get_context_data para personalizar consultas y contexto, as_view() para habilitar en rutas, y LoginRequiredMixin como reemplazo de login_required.
El código de la página de inicio y de la página de detalles después de la refactorización es más corto y tiene una estructura más clara.
Otras extensiones