Decoradores y mixins de autenticación

Intermedio
Django
Django
Actualizado: 18/04/2026

Decoradores para vistas funcionales

@login_required

El decorador @login_required redirige al usuario a settings.LOGIN_URL si no está autenticado:

from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, get_object_or_404
from .models import Pedido

@login_required
def mis_pedidos(request):
    pedidos = Pedido.objects.filter(usuario=request.user)
    return render(request, 'pedidos/mis_pedidos.html', {'pedidos': pedidos})

# Con URL de login personalizada
@login_required(login_url='/usuarios/acceder/')
def area_privada(request):
    return render(request, 'privado.html')

Diagrama conceptual de Decoradores y mixins de autenticación

@permission_required

@login_required
@permission_required('catalogo.add_producto', raise_exception=True)
def crear_producto(request):
    """Con raise_exception=True devuelve 403 en lugar de redirigir al login."""
    ...

@permission_required(['blog.add_articulo', 'blog.publicar_articulo'])
def crear_y_publicar(request):
    """Requiere ambos permisos."""
    ...

@user_passes_test

Para condiciones de autorización personalizadas:

from django.contrib.auth.decorators import user_passes_test

def es_editor_activo(usuario):
    return usuario.is_authenticated and usuario.groups.filter(name='Editor').exists()

@user_passes_test(es_editor_activo, login_url='/sin-acceso/')
def panel_editorial(request):
    ...

# Verificar múltiples condiciones
def puede_ver_dashboard(user):
    return user.is_authenticated and (user.is_staff or user.has_perm('app.view_dashboard'))

@user_passes_test(puede_ver_dashboard)
def dashboard_admin(request):
    ...

Mixins para vistas basadas en clases

LoginRequiredMixin

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView

class MisDirecciones(LoginRequiredMixin, ListView):
    template_name = 'usuarios/mis_direcciones.html'
    login_url = '/login/'               # Por defecto: settings.LOGIN_URL
    redirect_field_name = 'siguiente'   # Nombre del parámetro en la URL de login

    def get_queryset(self):
        return Direccion.objects.filter(usuario=self.request.user)

PermissionRequiredMixin

from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic.edit import CreateView, UpdateView

class ProductoCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
    model = Producto
    fields = ['nombre', 'precio', 'categoria']
    permission_required = 'catalogo.add_producto'

    def handle_no_permission(self):
        from django.contrib import messages
        messages.error(self.request, 'No tienes permiso para realizar esta acción.')
        return super().handle_no_permission()

class ProductoUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    model = Producto
    fields = ['nombre', 'precio']
    permission_required = ('catalogo.change_producto',)  # Tupla también válida

    def get_permission_required(self):
        """Permisos dinámicos según el objeto."""
        if self.get_object().es_destacado:
            return ('catalogo.change_producto', 'catalogo.destacar_producto')
        return ('catalogo.change_producto',)

UserPassesTestMixin

from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic.edit import DeleteView

class ArticuloDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Articulo
    success_url = reverse_lazy('articulo-list')

    def test_func(self):
        """Solo el autor o un admin puede eliminar el artículo."""
        articulo = self.get_object()
        return (
            self.request.user == articulo.autor or
            self.request.user.is_staff or
            self.request.user.has_perm('blog.delete_articulo')
        )

    def handle_no_permission(self):
        messages.error(self.request, 'Solo el autor puede eliminar este artículo.')
        return redirect('articulo-detail', pk=self.kwargs['pk'])

LoginRequiredMiddleware (Django 5.1+)

Desde Django 5.1, hay un middleware que hace que todas las vistas requieran autenticación por defecto, y las que no lo requieren se marcan explícitamente:

# settings.py
MIDDLEWARE = [
    # ...
    'django.contrib.auth.middleware.LoginRequiredMiddleware',
]
LOGIN_URL = '/login/'

Con este middleware activo, las vistas públicas se marcan con el decorador @login_not_required:

from django.contrib.auth.decorators import login_not_required

@login_not_required
def pagina_inicio(request):
    return render(request, 'inicio.html')

@login_not_required
def catalogo_publico(request):
    productos = Producto.objects.filter(activo=True, publico=True)
    return render(request, 'catalogo.html', {'productos': productos})

Para CBV públicas:

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_not_required

@method_decorator(login_not_required, name='dispatch')
class CatalogoPublicoView(ListView):
    model = Producto
    template_name = 'catalogo/publico.html'

LoginRequiredMiddleware invierte la lógica: en lugar de proteger vistas individualmente, todas están protegidas y se desprotegen explícitamente. Es especialmente útil en aplicaciones donde casi todas las vistas requieren autenticación.

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

Usar @login_required para requerir autenticación en vistas funcionales. Aplicar @permission_required para exigir permisos específicos en vistas funcionales. Implementar LoginRequiredMixin y PermissionRequiredMixin en vistas basadas en clases. Crear lógica de autorización personalizada con UserPassesTestMixin. Configurar LoginRequiredMiddleware de Django 5.1 para proteger todas las vistas por defecto.