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')

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