Django incluye un sistema de autenticación robusto y completo que maneja cuentas de usuario, grupos, permisos y sesiones basadas en cookies. Es uno de los puntos fuertes del framework y te permite implementar funcionalidades de seguridad sin escribir código desde cero.
El sistema de autenticación se incluye automáticamente en INSTALLED_APPS
:
'django.contrib.auth'
'django.contrib.contenttypes'
También necesitas el middleware de autenticación en MIDDLEWARE
.
Django proporciona un modelo User con los siguientes campos principales:
username
- Nombre de usuario únicoemail
- Dirección de correo electrónicofirst_name
- Nombrelast_name
- Apellidois_active
- Usuario activois_staff
- Acceso al adminis_superuser
- Todos los permisosdate_joined
- Fecha de registroDjango proporciona varias formas de trabajar con usuarios. Veamos las operaciones más comunes:
# En una vista o shell de Django
from django.contrib.auth.models import User
# Crear un usuario normal
user = User.objects.create_user(
username='nuevo_usuario',
email='usuario@email.com',
password='mi_password_seguro'
)
# Crear un superusuario
superuser = User.objects.create_superuser(
username='admin',
email='admin@email.com',
password='password_admin'
)
# Desde la línea de comandos
# python manage.py createsuperuser
# Obtener usuario por username
user = User.objects.get(username='nombre_usuario')
# Verificar si existe un usuario
if User.objects.filter(username='nombre_usuario').exists():
print("El usuario existe")
# Obtener todos los usuarios activos
usuarios_activos = User.objects.filter(is_active=True)
# Obtener usuarios staff
staff_users = User.objects.filter(is_staff=True)
# Cambiar información del usuario
user = User.objects.get(username='nombre_usuario')
user.email = 'nuevo_email@email.com'
user.first_name = 'Nuevo Nombre'
user.save()
# Cambiar contraseña
user.set_password('nueva_password')
user.save()
# Activar/desactivar usuario
user.is_active = False
user.save()
set_password()
para hashear automáticamente las contraseñas.
Django proporciona vistas genéricas para login y logout, pero también puedes crear las tuyas propias.
# urls.py
from django.urls import path, include
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
# Usar vistas genéricas de Django
path('login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
# O usar vistas personalizadas
path('mi-login/', views.mi_login, name='mi_login'),
path('mi-logout/', views.mi_logout, name='mi_logout'),
]
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
def mi_login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
messages.success(request, f'¡Bienvenido {user.first_name}!')
return redirect('dashboard') # Redirigir a página principal
else:
messages.error(request, 'Credenciales inválidas')
return render(request, 'registration/login.html')
def mi_logout(request):
logout(request)
messages.info(request, 'Sesión cerrada exitosamente')
return redirect('home')
<!-- templates/registration/login.html -->
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="text-center">Iniciar Sesión</h3>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">Usuario</label>
<input type="text" class="form-control" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Contraseña</label>
<input type="password" class="form-control" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Ingresar</button>
</form>
<div class="text-center mt-3">
<a href="{% url 'registro' %}">¿No tienes cuenta? Regístrate</a>
</div>
</div>
</div>
</div>
</div>
</div>
# settings.py
# Redirección después del login exitoso
LOGIN_REDIRECT_URL = '/' # o 'dashboard'
# Redirección después del logout
LOGOUT_REDIRECT_URL = '/'
# URL de login para decoradores @login_required
LOGIN_URL = '/login/'
Implementar un sistema de registro para que nuevos usuarios puedan crear sus cuentas.
# forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegistroForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
if commit:
user.save()
return user
# views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib import messages
from .forms import RegistroForm
def registro(request):
if request.method == 'POST':
form = RegistroForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user) # Login automático después del registro
messages.success(request, f'¡Bienvenido {user.first_name}! Tu cuenta ha sido creada.')
return redirect('dashboard')
else:
form = RegistroForm()
return render(request, 'registration/registro.html', {'form': form})
<!-- templates/registration/registro.html -->
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="text-center">Crear Cuenta</h3>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Nombre</label>
{{ form.first_name }}
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Apellido</label>
{{ form.last_name }}
</div>
</div>
<div class="mb-3">
<label class="form-label">Usuario</label>
{{ form.username }}
</div>
<div class="mb-3">
<label class="form-label">Email</label>
{{ form.email }}
</div>
<div class="mb-3">
<label class="form-label">Contraseña</label>
{{ form.password1 }}
</div>
<div class="mb-3">
<label class="form-label">Confirmar Contraseña</label>
{{ form.password2 }}
</div>
<button type="submit" class="btn btn-success w-100">Registrarse</button>
</form>
</div>
</div>
</div>
</div>
</div>
Django tiene un sistema de permisos granular que te permite controlar exactamente qué puede hacer cada usuario.
add_modelname
- Agregar objetoschange_modelname
- Modificar objetosdelete_modelname
- Eliminar objetosview_modelname
- Ver objetosLos permisos se crean automáticamente para cada modelo.
Los grupos permiten:
# Verificar permisos
user = request.user
# Verificar un permiso específico
if user.has_perm('myapp.add_post'):
# El usuario puede agregar posts
pass
# Verificar múltiples permisos
if user.has_perms(['myapp.add_post', 'myapp.change_post']):
# El usuario tiene ambos permisos
pass
# Asignar permisos a un usuario
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get_for_model(Post)
permission = Permission.objects.get(
codename='add_post',
content_type=content_type,
)
user.user_permissions.add(permission)
# Remover permisos
user.user_permissions.remove(permission)
# Crear un grupo
from django.contrib.auth.models import Group, Permission
# Crear grupo
editores = Group.objects.create(name='Editores')
# Asignar permisos al grupo
permisos = Permission.objects.filter(
codename__in=['add_post', 'change_post', 'view_post']
)
editores.permissions.set(permisos)
# Agregar usuario al grupo
user.groups.add(editores)
# Verificar si usuario pertenece a un grupo
if user.groups.filter(name='Editores').exists():
# Usuario es editor
pass
# Obtener todos los grupos de un usuario
grupos_usuario = user.groups.all()
# models.py
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
permissions = [
("can_publish", "Can publish posts"),
("can_feature", "Can feature posts on homepage"),
]
# Usar permisos personalizados
if user.has_perm('myapp.can_publish'):
# El usuario puede publicar posts
pass
Django proporciona decoradores que facilitan la implementación de control de acceso en tus vistas.
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
@login_required
def dashboard(request):
"""Vista que requiere que el usuario esté autenticado"""
return render(request, 'dashboard.html')
@login_required(login_url='/mi-login/')
def vista_especial(request):
"""Especifica una URL de login personalizada"""
return render(request, 'vista_especial.html')
from django.contrib.auth.decorators import permission_required
@permission_required('myapp.add_post')
def crear_post(request):
"""Solo usuarios con permiso 'add_post' pueden acceder"""
return render(request, 'crear_post.html')
@permission_required(['myapp.add_post', 'myapp.change_post'])
def gestionar_posts(request):
"""Requiere múltiples permisos"""
return render(request, 'gestionar_posts.html')
@permission_required('myapp.delete_post', raise_exception=True)
def eliminar_post(request, post_id):
"""Lanza excepción 403 en lugar de redirigir al login"""
# Lógica para eliminar post
pass
from django.contrib.auth.decorators import user_passes_test
def is_staff_user(user):
return user.is_staff
def is_owner_or_staff(user):
return user.is_staff or user.groups.filter(name='Propietarios').exists()
@user_passes_test(is_staff_user)
def admin_panel(request):
"""Solo usuarios staff pueden acceder"""
return render(request, 'admin_panel.html')
@user_passes_test(is_owner_or_staff, login_url='/acceso-denegado/')
def panel_propietarios(request):
"""Con URL de redirección personalizada"""
return render(request, 'panel_propietarios.html')
<!-- Verificar si usuario está autenticado -->
{% if user.is_authenticated %}
<p>Bienvenido, {{ user.first_name }}!</p>
<a href="{% url 'logout' %}">Cerrar sesión</a>
{% else %}
<a href="{% url 'login' %}">Iniciar sesión</a>
{% endif %}
<!-- Verificar permisos específicos -->
{% if perms.myapp.add_post %}
<a href="{% url 'crear_post' %}" class="btn btn-primary">
Crear nuevo post
</a>
{% endif %}
<!-- Verificar si usuario es staff -->
{% if user.is_staff %}
<a href="{% url 'admin_panel' %}">Panel de administración</a>
{% endif %}
<!-- Verificar grupos -->
{% if 'Editores' in user.groups.all|join:',' %}
<div class="alert alert-info">Tienes permisos de editor</div>
{% endif %}
@login_required
+ @permission_required
asegura que el usuario esté autenticado Y tenga los permisos necesarios.
# Crear proyecto
django-admin startproject blog_seguro
cd blog_seguro
python manage.py startapp blog
# Aplicar migraciones iniciales
python manage.py migrate
python manage.py createsuperuser
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=False)
class Meta:
permissions = [
("can_publish", "Can publish posts"),
("can_feature", "Can feature posts"),
]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post_detail', kwargs={'pk': self.pk})
# blog/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import Post
class RegistroForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
}
# blog/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.core.paginator import Paginator
from .models import Post
from .forms import RegistroForm, PostForm
def home(request):
posts = Post.objects.filter(is_published=True).order_by('-created_at')
paginator = Paginator(posts, 5)
page = request.GET.get('page')
posts = paginator.get_page(page)
return render(request, 'blog/home.html', {'posts': posts})
def registro(request):
if request.method == 'POST':
form = RegistroForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
messages.success(request, '¡Cuenta creada exitosamente!')
return redirect('home')
else:
form = RegistroForm()
return render(request, 'registration/registro.html', {'form': form})
@login_required
def crear_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
messages.success(request, 'Post creado exitosamente!')
return redirect('mis_posts')
else:
form = PostForm()
return render(request, 'blog/crear_post.html', {'form': form})
@login_required
def mis_posts(request):
posts = Post.objects.filter(author=request.user).order_by('-created_at')
return render(request, 'blog/mis_posts.html', {'posts': posts})
@permission_required('blog.can_publish')
def publicar_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
post.is_published = True
post.save()
messages.success(request, f'Post "{post.title}" publicado exitosamente!')
return redirect('admin_posts')
# blog/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('registro/', views.registro, name='registro'),
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('crear-post/', views.crear_post, name='crear_post'),
path('mis-posts/', views.mis_posts, name='mis_posts'),
path('publicar//', views.publicar_post, name='publicar_post'),
]
# blog_seguro/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]