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ísticasFBV (vista de función)CBV (vista de clase)
Cantidad de códigoFlexible, pero la lógica común debe repetirseSolo se necesitan unas pocas líneas de código al heredar vistas genéricas
LegibilidadFlujo lineal, lógica claraLa cadena de herencia requiere seguimiento, pero una vez que estás familiarizado, se entiende de un vistazo
ReutilizaciónMediante encapsulación en funcionesMediante composición con Mixins (una característica distintiva de Django)
Escenarios aplicablesPáginas con lógica simple y flujo claroPáginas con patrones como CRUD (crear, leer, actualizar, eliminar)
Distribución de métodos HTTPManual: 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

# Ruta del archivo: blog/views.py
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

# Ruta del archivo: blog/views.py (agregado)
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

# Ruta del archivo: blog/urls.py
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

VistaNombre de plantilla por defectoPropósito
ListViewnombre_modelo_list.htmlMuestra la lista de objetos
DetailViewnombre_modelo_detail.htmlMuestra los detalles de un solo objeto
CreateViewnombre_modelo_form.htmlCrea un nuevo objeto (con formulario)
UpdateViewnombre_modelo_form.htmlActualiza un objeto existente
DeleteViewnombre_modelo_confirm_delete.htmlConfirma la eliminación del objeto
TemplateViewDebe especificarseRenderiza 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.contrib.auth.mixins import LoginRequiredMixin
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