Crear la app cuentas
y configurarla en el proyecto
Objetivo
Construiremos paso a paso un sistema de autenticación de usuarios en Django, dentro del proyecto pf_gaming
. En esta primera parte, mostraré cómo configurar la app cuentas
y cómo integrarla correctamente.
Estructura inicial del proyecto
El proyecto base pf_gaming
contiene tres apps:
home
— para la página principal.juegos
— relacionada con los juegos.cuentas
— donde gestionaremos el registro, inicio y cierre de sesión.
Configuración del proyecto
pf_gaming/urls.py
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('home.urls')), path('juegos/', include('juegos.urls')), path('cuentas/', include('cuentas.urls')), ]
Aquí se incluye la app cuentas
mediante include('cuentas.urls')
, lo que permite definir sus propias URLs (registro, login, logout).
settings.py
(fragmentos relevantes)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', ... 'cuentas', ]
Se registra la app cuentas
en INSTALLED_APPS
.
LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'
Estas líneas indican a Django hacia dónde redirigir después del login o logout (en este caso, al inicio).
Registro, inicio de sesión y cierre de sesión en Django
En esta parte veremos cómo implementar un sistema básico de autenticación usando las herramientas que Django ya nos proporciona.
Archivo: cuentas/forms.py
⚠️ Este archivo no se crea automáticamente al iniciar una app, tienes que crearlo tú manualmente si quieres definir formularios personalizados en tu app.
# cuentas/forms.py from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class CustomUserCreationForm(UserCreationForm): email = forms.EmailField(required=True) class Meta: model = User fields = ("username", "email", "password1", "password2") def save(self, commit=True): user = super().save(commit=False) user.email = self.cleaned_data["email"] if commit: user.save() return user
Código completo explicado
from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User
¿Qué importa esto?
forms
: el módulo de Django para crear formularios.UserCreationForm
: formulario que ya viene en Django para crear usuarios con contraseña.User
: modelo de usuario por defecto de Django.
Clase: CustomUserCreationForm
class CustomUserCreationForm(UserCreationForm): email = forms.EmailField(required=True)
¿Qué hace esto?
- Hereda de
UserCreationForm
, que ya incluye los campos:username
password1
password2
- Añade el campo
email
manualmente como obligatorio (required=True
).
Esto es útil porque por defecto UserCreationForm
no incluye el campo de email, y muchas veces necesitas recoger ese dato durante el registro.
Clase interna Meta
class Meta: model = User fields = ("username", "email", "password1", "password2")
¿Qué hace esto?
- Indica que este formulario trabaja con el modelo
User
. - Define qué campos mostrar en el formulario y en qué orden.
- Aquí, se incluye el nuevo campo
email
junto a los campos que ya teníaUserCreationForm
.
Método save()
def save(self, commit=True): user = super().save(commit=False) user.email = self.cleaned_data["email"] if commit: user.save() return user
¿Qué hace este método?
super().save(commit=False)
:- Llama al método original
save()
deUserCreationForm
, pero no guarda aún el usuario en la base de datos (commit=False
). - Esto devuelve una instancia de
User
aún sin guardar.
- Llama al método original
user.email = self.cleaned_data["email"]
:- Asigna al usuario el email que el usuario escribió en el formulario.
cleaned_data
es un diccionario con los datos validados del formulario.
if commit: user.save()
:- Si
commit=True
(el valor por defecto), se guarda el usuario en la base de datos. - Si
commit=False
, el usuario se devuelve sin guardarse (esto puede ser útil si se quiere modificar más cosas antes de guardarlo).
- Si
return user
:- Devuelve el usuario recién creado (guardado o no, según
commit
).
- Devuelve el usuario recién creado (guardado o no, según
¿Para qué sirve todo esto?
- Permite tener un formulario de registro de usuarios que recoja también el correo electrónico y lo guarde.
- Reutilizable: puedes usar este formulario en cualquier vista o clase para manejar el registro.
- Está basado en herramientas ya existentes de Django (no parte de cero).
Vistas: registro, login y logout
Ubicación: cuentas/views.py
Código completo (views.py
)
Aquí tienes una explicación detallada de este bloque de código, que define la vista de registro (signup), el inicio de sesión (CustomLoginView) y el cierre de sesión (CustomLogoutView) en una aplicación Django.
from django.shortcuts import render, redirect from django.contrib.auth import login as auth_login from django.contrib import messages from django.contrib.auth.views import LoginView, LogoutView from django.urls import reverse_lazy from .forms import CustomUserCreationForm def signup(request): if request.method == 'POST': form = CustomUserCreationForm(request.POST) if form.is_valid(): user = form.save() auth_login(request, user) messages.warning(request, "¡Registro exitoso! Te doy la bienvenida a PF Gaming.") return redirect('home.index') else: form = CustomUserCreationForm() return render(request, 'cuentas/signup.html', {'form': form}) class CustomLoginView(LoginView): template_name = 'cuentas/login.html' def form_valid(self, form): messages.success(self.request, f"¡Hola {form.get_user().username}, has iniciado sesión!") return super().form_valid(form) def form_invalid(self, form): messages.error(self.request, "Error al iniciar sesión. Verifica tus credenciales.") return super().form_invalid(form) def get_success_url(self): return reverse_lazy('home.index') class CustomLogoutView(LogoutView): next_page = reverse_lazy('home.index') def post(self, request, *args, **kwargs): messages.info(request, "Has cerrado sesión correctamente.") return super().post(request, *args, **kwargs)
Vamos a desglosarlo paso por paso:
from django.shortcuts import render, redirect from django.contrib.auth import login as auth_login from django.contrib import messages from django.contrib.auth.views import LoginView, LogoutView from django.urls import reverse_lazy from .forms import CustomUserCreationForm
¿Qué importa este bloque?
render
: para renderizar plantillas HTML.redirect
: para redirigir a otra URL tras un evento (por ejemplo, tras registrarse).auth_login
: función para autenticar manualmente a un usuario.messages
: sistema de Django para mostrar mensajes (éxito, error, etc.).LoginView
,LogoutView
: vistas genéricas de Django para login/logout.reverse_lazy
: forma de obtener URLs sin evaluarlas de inmediato (útil en clases).CustomUserCreationForm
: el formulario personalizado de registro explicado antes.
Vista de registro de usuarios
def signup(request): if request.method == 'POST': form = CustomUserCreationForm(request.POST) if form.is_valid(): user = form.save() auth_login(request, user) messages.warning(request, "¡Registro exitoso! Te doy la bienvenida a PF Gaming.") return redirect('home.index') else: form = CustomUserCreationForm() return render(request, 'cuentas/signup.html', {'form': form})
¿Qué hace esta vista?
- Si el método es POST (es decir, el usuario ha enviado el formulario):
- Se instancia el formulario con los datos enviados:
CustomUserCreationForm(request.POST)
. - Se valida con
form.is_valid()
. - Si es válido:
- Se guarda el usuario con
form.save()
. - Se inicia sesión automáticamente con
auth_login(request, user)
. - Se muestra un mensaje de bienvenida (
messages.warning(...)
). - Se redirige a
'home.index'
.
- Se guarda el usuario con
- Se instancia el formulario con los datos enviados:
- Si el método es GET (es decir, el usuario entra por primera vez a la página):
- Se instancia el formulario vacío.
- Se renderiza el template
signup.html
, pasándole el formulario.
messages.warning(...)
: se usawarning
, pero podría sersuccess
,info
, etc. según el estilo que prefieras.
Clase: CustomLoginView
class CustomLoginView(LoginView): template_name = 'cuentas/login.html'
Esta clase hereda de LoginView
, que ya maneja toda la lógica de login. Solo personaliza algunos comportamientos:
Método form_valid(self, form)
def form_valid(self, form): messages.success(self.request, f"¡Hola {form.get_user().username}, has iniciado sesión!") return super().form_valid(form)
- Se ejecuta cuando el formulario de login es válido (usuario y contraseña correctos).
- Añade un mensaje de bienvenida personalizado con el nombre del usuario.
- Llama al método original (
super().form_valid
) para completar el login.
Método form_invalid(self, form)
def form_invalid(self, form): messages.error(self.request, "Error al iniciar sesión. Verifica tus credenciales.") return super().form_invalid(form)
- Se ejecuta cuando los datos del formulario no son válidos.
- Añade un mensaje de error y continúa el flujo normal del
LoginView
.
Redirección tras el login
def get_success_url(self): return reverse_lazy('home.index')
- Este método define la URL a la que se redirige el usuario después de iniciar sesión.
reverse_lazy('home.index')
: busca la URL de la vista llamada"home.index"
(seguramente la página principal).
Clase: CustomLogoutView
class CustomLogoutView(LogoutView): next_page = reverse_lazy('home.index')
- Hereda de
LogoutView
, que ya maneja toda la lógica de logout. next_page
indica a dónde redirigir después de cerrar sesión.
Método post(...)
def post(self, request, *args, **kwargs): messages.info(request, "Has cerrado sesión correctamente.") return super().post(request, *args, **kwargs)
- Se ejecuta cuando se envía un POST para cerrar sesión (por ejemplo, desde un botón con
method="post"
). - Añade un mensaje informativo.
- Llama al comportamiento por defecto de
LogoutView
.
Conclusión general
Este views.py
te proporciona:
- Una forma clara y simple de registrar usuarios.
- Login/logout con mensajes personalizados.
- Uso correcto de clases basadas en vistas (CBV) para login/logout.
- Separación de lógica para cada funcionalidad (registro, login, logout).
URLs
Ubicación: cuentas/urls.py
from django.urls import path from .views import signup, CustomLoginView, CustomLogoutView urlpatterns = [ path('signup/', signup, name='signup'), path('login/', CustomLoginView.as_view(), name='login'), path('logout/', CustomLogoutView.as_view(), name='logout'), ]
Este archivo enlaza las vistas con rutas claras y simples:
/cuentas/signup/
/cuentas/login/
/cuentas/logout/
Estas rutas ya están conectadas al proyecto a través de:
# pf_gaming/urls.py path('cuentas/', include('cuentas.urls')),
Formularios de login y registro en Django: cómo interactúan con el sistema de usuarios
Ahora, vamos a crear dos archivos de plantilla, uno para el registro (signup.html) y otro para el inicio de sesión (login.html).
En un proyecto Django que implementa autenticación personalizada o extendida, las plantillas login.html
y signup.html
cumplen una función clave: comunican al usuario final con el sistema interno de autenticación de Django. Aquí analizamos cómo lo hacen, centrándonos en lo que afecta directamente al sistema de usuarios.
Login – Plantilla login.html
Empecemos con el código completo del archivo, y te voy explicando las partes relacionadas con el sistema de usuarios.
{% extends 'main.html' %} {% block content %} <div class="container mt-5 d-flex justify-content-center"> <div class="col-md-6"> <h2 class="text-center mb-4">Iniciar sesión</h2> <form method="post" novalidate> {% csrf_token %} {% if form.errors %} <div class="alert alert-danger"> Usuario o contraseña incorrectos. </div> {% endif %} <div class="mb-3"> <label for="id_username" class="form-label">Nombre de usuario</label> <input type="text" name="username" autofocus required class="form-control" id="id_username"> </div> <div class="mb-3"> <label for="id_password" class="form-label">Contraseña</label> <input type="password" name="password" required class="form-control" id="id_password"> </div> <button type="submit" class="btn btn-primary w-100">Iniciar sesión</button> </form> </div> </div> {% endblock content %}
Este formulario se basa en la vista CustomLoginView
, que hereda de LoginView
. Django espera campos específicos (username
, password
) para procesar correctamente la autenticación. Veamos cómo lo gestiona la plantilla:
Inicio del formulario y CSRF
<form method="post" novalidate> {% csrf_token %} {% if form.errors %} <div class="alert alert-danger"> Usuario o contraseña incorrectos. </div> {% endif %}
method="post"
indica que el formulario se enviará por POST, obligatorio para formularios de autenticación.{% csrf_token %}
incluye un token anti-CSRF para proteger contra ataques maliciosos.form.errors
permite detectar si el formulario enviado tiene errores. Si Django detecta que el usuario o contraseña no son válidos, este bloque muestra un mensaje de error.
Campos esperados por Django para login
<div class="mb-3"> <label for="id_username" class="form-label">Nombre de usuario</label> <input type="text" name="username" autofocus required class="form-control" id="id_username"> </div>
<div class="mb-3"> <label for="id_password" class="form-label">Contraseña</label> <input type="password" name="password" required class="form-control" id="id_password"> </div>
- El atributo
name="username"
es imprescindible: Django lo reconoce como el identificador principal del usuario. - El campo de contraseña debe tener
name="password"
. - Ambos
name
deben coincidir con lo que esperaAuthenticationForm
, que es la clase de formulario que usaLoginView
por defecto (o cualquier formulario compatible).
Envío del formulario
<button type="submit" class="btn btn-primary w-100">Iniciar sesión</button>
Este botón envía el formulario. Si las credenciales son correctas, Django autentica al usuario y redirige según get_success_url()
definido en la vista.
Registro – Plantilla signup.html
{% extends 'main.html' %} {% block content %} <div class="container mt-5 d-flex justify-content-center"> <div class="col-md-6"> <h2 class="text-center mb-4">Registro de usuario</h2> <form method="post" novalidate> {% csrf_token %} <!-- Campo de nombre de usuario --> <div class="mb-3"> <label for="id_username" class="form-label">Nombre de usuario</label> <input type="text" name="username" class="form-control {% if form.username.errors %}is-invalid{% endif %}" id="id_username" value="{{ form.username.value|default_if_none:'' }}"> {% if form.username.errors %} <div class="invalid-feedback"> {% for error in form.username.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Introduce tu nombre de usuario.</div> {% endif %} </div> <!-- Campo de correo electrónico (personalizado en la plantilla) --> <div class="mb-3"> <label for="id_email" class="form-label">Correo electrónico</label> <input type="email" name="email" class="form-control {% if form.email.errors %}is-invalid{% endif %}" id="id_email" value="{{ form.email.value|default_if_none:'' }}"> {% if form.email.errors %} <div class="invalid-feedback"> {% for error in form.email.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Introduce un correo electrónico válido.</div> {% endif %} </div> <!-- Campo de contraseña --> <div class="mb-3"> <label for="id_password1" class="form-label">Contraseña</label> <input type="password" name="password1" class="form-control {% if form.password1.errors %}is-invalid{% endif %}" id="id_password1"> {% if form.password1.errors %} <div class="invalid-feedback"> {% for error in form.password1.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Crea una contraseña segura.</div> {% endif %} </div> <!-- Campo de confirmación de contraseña --> <div class="mb-3"> <label for="id_password2" class="form-label">Confirmar contraseña</label> <input type="password" name="password2" class="form-control {% if form.password2.errors %}is-invalid{% endif %}" id="id_password2"> {% if form.password2.errors %} <div class="invalid-feedback"> {% for error in form.password2.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Repite la contraseña.</div> {% endif %} </div> <button type="submit" class="btn btn-primary w-100">Registrarse</button> </form> </div> </div> {% endblock content %}
Este formulario usa el formulario personalizado CustomUserCreationForm
, que hereda de UserCreationForm
y añade un campo de email obligatorio.
Inicio del formulario
<form method="post" novalidate> {% csrf_token %}
Mismo patrón que en login: se usa POST y se protege con CSRF.
Campo de nombre de usuario
<div class="mb-3"> <label for="id_username" class="form-label">Nombre de usuario</label> <input type="text" name="username" class="form-control {% if form.username.errors %}is-invalid{% endif %}" id="id_username" value="{{ form.username.value|default_if_none:'' }}"> {% if form.username.errors %} <div class="invalid-feedback"> {% for error in form.username.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Introduce tu nombre de usuario.</div> {% endif %} </div>
- Este campo mapea con el campo
username
del modeloUser
de Django. - Se añaden validaciones visuales: si hay errores, se muestra retroalimentación en el mismo campo.
Campo de correo electrónico
<div class="mb-3"> <label for="id_email" class="form-label">Correo electrónico</label> <input type="email" name="email" class="form-control {% if form.email.errors %}is-invalid{% endif %}" id="id_email" value="{{ form.email.value|default_if_none:'' }}"> {% if form.email.errors %} <div class="invalid-feedback"> {% for error in form.email.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Introduce un correo electrónico válido.</div> {% endif %} </div>
- Este campo fue añadido en el formulario
CustomUserCreationForm
como obligatorio. - No está presente en el
UserCreationForm
por defecto, por lo que esta plantilla refleja ese cambio.
Campos de contraseña
<div class="mb-3"> <label for="id_password1" class="form-label">Contraseña</label> <input type="password" name="password1" class="form-control {% if form.password1.errors %}is-invalid{% endif %}" id="id_password1"> {% if form.password1.errors %} <div class="invalid-feedback"> {% for error in form.password1.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Crea una contraseña segura.</div> {% endif %} </div> <div class="mb-3"> <label for="id_password2" class="form-label">Confirmar contraseña</label> <input type="password" name="password2" class="form-control {% if form.password2.errors %}is-invalid{% endif %}" id="id_password2"> {% if form.password2.errors %} <div class="invalid-feedback"> {% for error in form.password2.errors %} {{ error }} {% endfor %} </div> {% else %} <div class="form-text">Repite la contraseña.</div> {% endif %} </div>
- Estos campos también son requeridos por
UserCreationForm
. - Django validará automáticamente que ambas contraseñas coincidan y que cumplan las reglas de seguridad configuradas.
Envío del formulario
<button type="submit" class="btn btn-primary w-100">Registrarse</button>
Una vez enviado, la vista signup()
valida los datos, crea el usuario, inicia sesión automáticamente y redirige.
Conclusión
Ambas plantillas están perfectamente adaptadas al sistema de autenticación de Django:
login.html
usa los nombres esperados porAuthenticationForm
.signup.html
trabaja con un formulario extendido que incluye email.- Se gestionan correctamente los tokens CSRF y los errores de formulario.
- Todos los
name
de los inputs coinciden con los campos esperados por los formularios definidos en Python.
Perfecto. Aquí tienes la Parte 3 (final), que completa la explicación de los archivos que me pasaste:
Experiencia de Usuario y Mensajes con Django
Esta sección cubre cómo estás usando el sistema de mensajes y la base de templates para dar al usuario una experiencia clara, estética y funcional. Todo esto está presente en los archivos que ya has incluido.
Sistema de mensajes de Django
¿Dónde aparece?
- En
views.py
:messages.success(...)
,messages.warning(...)
, etc. - En
templates/main.html
: el lugar donde se muestran.
¿Qué hace?
El módulo django.contrib.messages
te permite enviar mensajes desde las vistas que luego se renderizan automáticamente en las plantillas.
Ejemplos en las vistas:
messages.warning(request, "¡Registro exitoso! Te doy la bienvenida a PF Gaming.") messages.success(self.request, f"¡Hola {form.get_user().username}, has iniciado sesión!") messages.info(request, "Has cerrado sesión correctamente.") messages.error(self.request, "Error al iniciar sesión. Verifica tus credenciales.")
Renderizado en main.html
:
{% if messages %} <div class="container mt-3"> {% for message in messages %} <div class="alert alert-{{ message.tags }}"> {{ message }} </div> {% endfor %} </div> {% endif %}
Detalle clave:
- Usa
message.tags
→ se convierte enalert-success
,alert-error
, etc., que Bootstrap interpreta como colores y estilos.
Plantilla base: main.html
Este archivo es la base común para las demás páginas. Estructura general:
<!DOCTYPE html> <html data-bs-theme="dark"> <head> <title>{{ plantilla_principal.title }}</title> {% load static %} <!-- CSS personalizado --> <link rel="stylesheet" href="{% static 'css/styles.css' %}" /> <!-- Bootstrap --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous" /> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" /> <!-- Google Fonts --> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <!-- Botón de cambio de tema --> <div class="p-2 d-flex justify-content-end gap-2"> <button id="theme-toggle" class="btn btn-sm"></button> </div> <!-- Cabecera --> <header class="p-3 bg-body text-body border-bottom"> <div class="container"> <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"> <!-- Logo --> <a href="{% url 'home.index' %}" class="d-flex align-items-center mb-2 mb-lg-0 text-body text-decoration-none"> <img src="{% static 'img/logo.png' %}" alt="PF Gaming Logo" style="height: 100px; width: auto;" /> </a> <!-- Navegación --> <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"> <li><a href="{% url 'home.index' %}" class="nav-link px-2 text-secondary">Inicio</a></li> <li><a href="#" class="nav-link px-2 text-body">Características</a></li> <li><a href="#" class="nav-link px-2 text-body">Precios</a></li> <li><a href="#" class="nav-link px-2 text-body">FAQ</a></li> <li><a href="#" class="nav-link px-2 text-body">Acerca</a></li> </ul> <!-- Búsqueda --> <form method="GET" action="{% url 'juegos.index' %}" class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3 d-flex" role="search"> <input type="search" name="q" class="form-control form-control-dark me-2" placeholder="Buscar..." value="{{ request.GET.q|default:'' }}"> <button type="submit" class="btn btn-outline-primary"><i class="fas fa-search"></i></button> </form> <!-- Botones de usuario --> <div class="text-end"> {% if user.is_authenticated %} <form method="POST" action="{% url 'logout' %}" style="display: inline;"> {% csrf_token %} <button type="submit" class="btn btn-outline-primary me-2">Cerrar sesión</button> </form> {% else %} <a href="{% url 'login' %}" class="btn btn-outline-primary me-2">Iniciar sesión</a> <a href="{% url 'signup' %}" class="btn btn-warning">Registrarse</a> {% endif %} </div> </div> </div> </header> <!-- Mensajes de Django flotantes --> <div class="position-fixed top-0 end-0 p-3" style="z-index: 1100;"> {% if messages %} {% for message in messages %} <div class="toast align-items-center text-bg-{{ message.tags }} border-0 show mb-2" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="4000"> <div class="d-flex"> <div class="toast-body"> {{ message }} </div> <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Cerrar"></button> </div> </div> {% endfor %} {% endif %} </div> <!-- Contenido dinámico --> <main class="py-5 bg-body-secondary text-body"> <div class="container"> {% block content %}{% endblock content %} </div> </main> <!-- Pie de página --> <footer class="bg-body text-body pt-4 pb-2 border-top"> <div class="container"> <div class="row small"> <div class="col-md-4 mb-3"> <h6 class="text-uppercase">Sobre Nosotros</h6> <p class="mb-1">PF Gaming es tu tienda de confianza, compra fácil, barato y con el mejor servicio técnico.</p> </div> <div class="col-md-4 mb-3"> <h6 class="text-uppercase">Enlaces útiles</h6> <ul class="list-unstyled"> <li><a href="{% url 'home.index' %}" class="text-secondary text-decoration-none">Inicio</a></li> <li><a href="#" class="text-secondary text-decoration-none">Noticias</a></li> <li><a href="#" class="text-secondary text-decoration-none">Comunidad</a></li> <li><a href="#" class="text-secondary text-decoration-none">Contacto</a></li> <li><a href="{% url 'home.faq' %}" class="text-secondary text-decoration-none">FAQ</a></li> </ul> </div> <div class="col-md-4 mb-3"> <h6 class="text-uppercase">Contacto</h6> <p class="mb-1">contacto@pfgaming.com</p> <p class="mb-1">+34 900 123 456</p> <p class="mb-1">Av. Gamer 42, Madrid</p> </div> </div> <hr class="my-2" /> <div class="text-center small text-secondary"> <small>© {% now "Y" %} PF Gaming. Todos los derechos reservados.</small> </div> </div> </footer> <!-- Bootstrap JS bundle --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"> </script> <!-- Script personalizado --> <script src="{% static 'js/main.js' %}"></script> </body> </html>
¿Por qué es útil?
- Centraliza el layout: título, estilo, estructura.
- Las páginas como
login.html
osignup.html
extienden de aquí:{% extends "main.html" %}
Esto hace que todas las páginas compartan diseño, estilos y lógica como la de mensajes.
Manejar el cierre de sesión al salir del sitio web o temporizarlo
Para que la sesión en Django se cierre automáticamente cuando el usuario cierra el navegador, tienes que hacer que la cookie de sesión no tenga una expiración persistente. Es decir, que sea una cookie de sesión (transitoria), no una que Django guarde con fecha fija.
Cómo hacerlo
En tu archivo settings.py
, asegúrate de incluir esta configuración:
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
¿Qué hace esto?
- Al poner
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
, Django no fija un tiempo de expiración a la cookie de sesión. - Por tanto, el navegador eliminará la cookie cuando el usuario cierra todas las ventanas del navegador.
Detalles a tener en cuenta
- No cierra la sesión si el usuario solo recarga la pestaña o abre otra dentro del mismo navegador.
- Esto depende del navegador y sus políticas (algunos pueden restaurar sesiones si tienen la recuperación de pestañas activada).
- Si necesitas un control aún más estricto (por ejemplo, cerrar la sesión después de X minutos de inactividad), puedes añadir:
SESSION_COOKIE_AGE = 300 # 5 minutos (en segundos) SESSION_SAVE_EVERY_REQUEST = True
Esto hace que:
- La sesión expire en 5 minutos de inactividad.
- Se renueve el contador con cada request.
Verifica que funciona
- Añade en
settings.py
:
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
- Borra cookies del navegador o inicia en incógnito para probar correctamente.
- Inicia sesión, cierra el navegador, vuelve a abrir y ve si la sesión sigue activa.
Conclusión final
Ya has montado un sistema de autenticación completo en Django con:
- Registro de usuario.
- Inicio de sesión personalizado.
- Logout con redirección y mensaje.
- Formularios con validación.
- Sistema de mensajes flash.
- Templates reutilizables.
- Estilo Bootstrap integrado.
- Cierre automático de sesión al cerrar el navegador o temporizado.