martes, 5 de diciembre de 2023

20.- Creación de un sistema de autenticación de usuarios.

En el capítulo anterior, exploramos cómo crear un widget para el carrito de compras. Sin embargo, para que nuestra aplicación funcione correctamente, es crucial permitir que los usuarios se autentifiquen. Esto es necesario para mantener los artículos agregados al carrito de cada usuario mediante el uso de sesiones. En consecuencia, cada usuario debe registrarse e iniciar sesión para poder utilizar esta funcionalidad.

En esta entrega, aprenderemos cómo implementar un sólido sistema de autenticación con enlaces clásicos de registro y acceso. En primer lugar, los usuarios podrán registrarse a través de un formulario, y sus datos quedarán almacenados en nuestra base de datos. Una vez completado este proceso, podrán acceder a la aplicación mediante un formulario de inicio de sesión, permitiéndoles mantener su sesión abierta para futuras interacciones.

Para llevar a cabo esto, crearemos una nueva aplicación dedicada exclusivamente a la gestión de la autenticación, que llamaremos "Autentificacion".

Empezamos creando una nueva aplicación que gestione esto llamada "autenticacion".


$ python manage.py startapp Autentificacion


Recuerda que debes añadir la nueva aplicación al proyecto, añadiendo el nombre de la aplicación dentro de INSTALLED_APPS dentro del archivo settings.py de la aplicación del proyecto "PracticaDjango". Edita este archivo y añade la lista resaltada dentro de la lista de INSTALLED_APPS:


PracticaDjango/PracticaDjango/settings.py

# Application definition

INSTALLED_APPS = [
    # Nuestras aplicaciones
    'Proyecto_web_app.apps.ProyectoWebAppConfig',
    'Servicios.apps.ServiciosConfig',
    'Blog.apps.BlogConfig',
    'Contacto.apps.ContactoConfig',
    'Tienda.apps.TiendaConfig',
    'Carro.apps.CarroConfig',
    'Autentificacion.apps.AutentificacionConfig',
    # Aplicaciones de terceros
    'django_bootstrap5',
    # Mapa del Sitio

Django busca las plantillas por orden de aparición en INSTALLED_APPS. La aplicación django.contrib.admin incluye plantillas de autentificación estandar que anularemos al registrar nuestra aplicación de Autentificación antes que esta. 

Ejecuta el siguiente comando para sincronizar la base de datos con los modelos de las aplicaciones predeterminadas incluidas en la configuración de INSTALLED_APPS:

python manage.py migrate


Usando el framework de autentificación de Django.


Ahora vamos a crear un sistema de autentificación de nuestro proyecto usando este framework.

Django viene con un marco de autenticación incorporado que puede manejar la autenticación de usuarios, sesiones, permisos y grupos de usuarios. El sistema de autenticación incluye vistas para acciones comunes de usuarios, como iniciar sesión, cerrar sesión, cambiar la contraseña y restablecer la contraseña.

El marco de autenticación se encuentra en django.contrib.auth y es utilizado por otros paquetes contrib de Django. Recuerda que ya utilizamos el marco de autentificación, cuando construimos la Aplicación del Blog, para crear un superusuario para la misma y acceder al sitio de administración.

Cuando creamos un nuevo proyecto de Django utilizando el comando startproject, el marco de autenticación está incluido en la configuración predeterminada de nuestro proyecto. Consiste en la aplicación django.contrib.auth y las siguientes dos clases de middleware encontradas en la configuración MIDDLEWARE de nuestro proyecto:

AuthenticationMiddleware: Asocia usuarios con peticiones utilizando sesiones.

SessionMiddleware: Maneja la sesión actual entre peticiones.

Middleware son clases con diferentes métodos que se ejecutan globalmente durante la fase de petición o respuesta de una página web y que veremos más adelante.

El framework de autentificación de Django también incluye los siguientes modelos que están definidos en django.contrib.auth.models:

  • User: Un modelo de usuario con unos campos básicos que son username, password, email, firstname, last_name y is_active.
  • Group: un grupo donde englobar y categorizar a los usuarios.
  • Permission: banderas para permitir a los usuarios o grupos realizar ciertas acciones.

El framework también incluye vistas y formularios predeterminados que usaremos más adelante.


Creando la vista del login.



Vamos a comenzar creando una vista que permita a los usuarios logearse en la página web. Crearemos una vista que permita a los usuarios hacer lo siguiente:

  • Presentaremos al usuario un formulario para loguearse.
  • Obtendremos el nombre de usuario y su contraseña cuando remita el formulario.
  • Autentificaremos el usuario verificando que exista en los datos guardados en la base de datos de la aplicación.
  • Comprobaremos que el usuario está activo.
  • Logearemos al usuario en la página web y comenzará a navegar en una sesión autentificada.

Empezaremos creando del formulario del login. 

PracticaDjango/Autentificacion/forms.py

from django import forms

class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)

Este formulario se usará para autenticar a los usuarios contra la base de datos. Ten en cuenta que se utiliza el widget PasswordInput para renderizar el elemento HTML de la contraseña. Esto incluirá type="password" en el HTML para que el navegador lo trate como un campo de contraseña.

Edita el archivo views.py de la aplicación de cuenta y agrega el siguiente código:

PracticaDjango/Autentificacion/views.py

from django.http import HttpResponse
from django.shortcuts import render
from django.contrib.auth import authenticate, login
from .forms import LoginForm

def user_login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        
        if form.is_valid():
            cd = form.cleaned_data
            user = authenticate(request, username=cd['username'], password=cd['password'])
            if user is not None:
                if user.is_active:
                    login(request, user)
                    return HttpResponse('Authenticated successfully')
                else:
                    return HttpResponse('Disabled account')
            else:
                return HttpResponse('Invalid login')
    else:
        form = LoginForm()
        return render(request, 'Autentificacion/login.html', {'form': form})
Esto es lo que hace básicamente la vista:

Cuando se llama a la vista user_login con una petición GET, se instancia un nuevo formulario de login con form = LoginFom(). El formulario se pasa luego a la plantilla.

Cuando el usuario envía el formulario a través del método POST, se realizan las siguientes acciones:

  • Se crea una instancia del formulario con los datos remitidos, con form = LoginForm(request.POST).
  • Se valida el formulario con is_valid(). Si no es válido, los errores del formulario se mostrarán más tarde en la plantilla. (Por ejemplo, si el usuario no rellena uno de los campos).
  • Si los datos enviados son válidos, el usuario se autentifica contra la base de datos utilizando el método authenticate(). Este método toma el objeto de solicitud, los parámetros de nombre de usuario y contraseña, y devuelve el objeto "user" si el usuario se ha autenticado correctamente, o None en caso contrario. Si el usuario no ha sido autentificado correctamente, se devuelve una HttpResponse con un mensaje de inicio de sesión no válido.
  •  Si el usuario se autentifica correctamente, se verifica el estado del usuario accediendo al atributo is_active. Este es un atributo del modelo de Usuario de Django. Si el usuario no está activo, se devuelve una HttpResponse con un mensaje de cuenta desactivada
  • Si el usuario está activo, se inicia sesión en el sitio. El usuario se establece en la sesión llamando al método login(). Se devuelve un mensaje de "Autenticación exitosa".
  • Si el usuario está activo, se inicia sesión en el sitio. El usuario se establece en la sesión llamando al método login(). Se devuelve un mensaje de "Autentificación realizada con éxito".

Toma nota de la diferencia entre authenticate() y login(): authenticate() 
verifica las credenciales del usuario y devuelve un objeto user si son 
correctas; login() establece al usuario en la sesión actual.

Ahora crearemos los parámetros URL para esta vista.

Crea un nuevo archivo llamado urls.py en el directorio de la aplicación Autentificación y añade el siguiente código:

PracticaDjango/Autentificacion/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("login/", views.user_login, name="login"),
]

Edita el archivo principal urls.py que está localizado dentro de la aplicación PracticaDjango y añade el código resaltado:

PracticaDjango/PracticaDjango/urls.py

from django.contrib import admin
from django.urls import path, include

from django.conf import settings
from django.conf.urls.static import static

from django.contrib.sitemaps.views import sitemap
from .sitemaps import BlogSitemap, StaticSitemap

sitemaps = {
    'static':StaticSitemap, #add StaticSitemap to the dictionary
    'blog':BlogSitemap #add DynamicSitemap to the dictionary
}

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('Proyecto_web_app.urls')),
    path('servicios/', include('Servicios.urls')),
    path('blog/', include('Blog.urls')),
    path('contacto/', include('Contacto.urls')),
    path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
    path('tienda/', include('Tienda.urls')),
    path('carro/', include('Carro.urls')),
    path('cuenta/', include('Autentificacion.urls')),
]
urlpatterns+=static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


Ahora ya podemos acceder a la vista user_login a través de la URL.

Vamos a crear una plantilla para esta vista. Crea el directorio templates/Autentificación dentro de la aplicación Autentificación y crea un archivo llamado login.html. Añade el siguiente código:


PracticaDjango/Autentificacion/templates/Autentificacion/login.html

<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}

<!--Cargamos de nuevo el contenido estático porque sino por algún motivo
no reconoce de nuevo la etiqueta bootstrap_form-->
{% load django_bootstrap5 %}

<!-- Establecemos el titulo de la página -->
{% block title %}Log-in{% endblock %}

<!-- Definimos su contenido -->
{% block content %}

<!-- creamos el formulario -->
<form method="post" action="">
    <h1>Log-in</h1>
    <p>Por favor, use el siguiente formulario para logearse:</p>
    <form method="post">
        {% csrf_token %}
        <div class="container w-25 bg-primary rounded-1">
            {% bootstrap_form form %}
            <button type="submit" class="btn btn-primary">Log-in</button>
            <button type="reset" class="btn btn-secondary">Borrar</button>
        </div>
    </form>

</form>

{% endblock %}

Aún no tenemos ningún usuario en la base de datos, quitando el superusuario que creamos en capítulos anteriores al crear el Blog. Abre la dirección http://127.0.0.1:8000/admin/ en tu navegador. Accede al panel de administración con las credenciales que usaste cuando creaste el superusuario (en el capitulo de creación del blog, si no puedes crear uno con python manage.py createsuperuser). Al entrar verás los modelos de Usuarios y Grupos que utiliza el framework de autentificación de Django.

Tendrá un aspecto parecido a este:

el panel de administración de Django, usuarios y grupos

Haz click en la fila de usuarios y pulsa en añadir un nuevo usuario (+Add). En la siguiente pantalla Django te pedirá un nombre para el usuario y una contraseña.

formulario de usuario en el pandel de administración


Introduce los datos y pulsa el botón de guardar los datos (SAVE).

Luego en la siguiente pantalla "información personal" rellena los campos de nombre, apellido y correo electrónico.

Ahora abre la dirección http://127.0.0.1:8000/cuenta/login/ en tu navegador. Deberías ver la plantilla que creamos renderizada, que tendrá un aspecto parecido a este:

formulario de login

Vamos a ver si funciona. Para empezar introduce unas credenciales inválidas (un usuario inexistente o el que creaste antes con una contraseña errónea) y envía el formulario. 



mensaje de inicio de sesión no válido

Ahora introduce unas credenciales válidas (las del usuario que creaste anteriormente).  Deberías ver el siguiente mensaje de Autentificación correcta:


mensaje de exito en la autentificación


Usando las vistas de Autentificación de Django.


Django incluye varios formularios y vistas en el marco de autentificación que puedes usar de inmediato.

La vista de inicio de sesión que hemos creado es un buen ejercicio para comprender el proceso de autenticación de usuarios en Django. Sin embargo, en la mayoría de los casos puedes utilizar las vistas de autenticación predeterminadas de Django.

Django proporciona las siguientes vistas basadas en clases para manejar la autentificación. Todas ellas se encuentran en 'django.contrib.auth.views':


LoginView: Maneja un formulario de inicio de sesión e inicia sesión de un usuario.
LogoutView: Cierra sesión de un usuario.


Django proporciona las siguientes vistas para manejar cambios de contraseña:


PasswordChangeView: Maneja un formulario para cambiar la contraseña del usuario.
PasswordChangeDoneView: La vista de éxito a la que se redirige al usuario después de un cambio de contraseña exitoso.


Django también incluye las siguientes vistas para permitir a los usuarios restablecer su contraseña:


PasswordResetView: Permite a los usuarios restablecer su contraseña. Genera un enlace de un solo uso con un token y lo envía a la cuenta de correo electrónico del usuario.
PasswordResetDoneView: Informa a los usuarios que se les ha enviado un correo electrónico, incluyendo un enlace para restablecer su contraseña.
PasswordResetConfirmView: Permite a los usuarios establecer una nueva contraseña.
PasswordResetCompleteView: La vista de éxito a la que se redirige al usuario después de restablecer con éxito su contraseña.


Estas vistas pueden ahorrarte mucho tiempo al construir cualquier aplicación web con cuentas de usuario. Las vistas utilizan valores predeterminados que pueden ser reemplazados, como la ubicación de la plantilla a ser renderizada o el formulario a ser usado por la vista.


Puedes obtener más información sobre las vistas de autenticación integradas en https://docs.djangoproject.com/en/4.1/topics/auth/default/#all-authentication-views.


Vistas de Login y Logout.


Edita el archivo urls.py de la aplicación Autorización y añade el siguiente código:


PracticaDjango/Autentificacion/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # login url anterior
    # path("login/", views.user_login, name="login"),
    # login / logout url
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]


En el código anterior, hemos comentado el patrón de URL para la vista user_login que creamos previamente. Ahora usaremos la vista LoginView del marco de autentificación de Django. También hemos añadido un patrón de URL para la vista LogoutView.

Crea un nuevo directorio dentro del directorio templates/ de la aplicación Autentificacion y llámalo registration. Este es el camino por defecto donde las vistas de autentificación de Django esperan que estén tus plantillas de autentificación.

El módulo django.contrib.admin incluye plantillas de autentificación que se utilizan para el sitio de administración, como la plantilla de inicio de sesión. Al colocar la aplicación Autentificacion en la parte superior de la configuración INSTALLED_APPS al configurar el proyecto, aseguramos que Django use nuestras plantillas de autentificación en lugar de las definidas en cualquier otra aplicación.

Crea un nuevo archivo dentro del directorio templates/registration/ y nómbralo login.html, luego agrega el siguiente código:

PracticaDjango/Autentificacion/templates/registration/login.html

{% extends "Proyecto_web_app/base.html" %}

{% load django_bootstrap5 %}

{% block title %}Log-in{% endblock %}

{% block content %}

<h1>Log-in</h1>
{% if form.errors %}
<p>
    Tu nombre de usuario y/o contraseña no coinciden.
    Por favor, inténtalo de nuevo.
</p>
{% else %}
<p>Por favor, use el siguiente formulario para log-in:</p>
{% endif %}
<div class="login-form">
    <form action="{% url 'login' %}" method="post">
        {% csrf_token %}
        <div class="container w-25 bg-primary rounded-1">
            {% bootstrap_form form %}
            <input type="hidden" name="next" value="{{ next }}" />
            <p><input type="submit" value="Log-in"></p>
    </form>
</div>

{% endblock %}

Este template de inicio de sesión es bastante similar al que creamos anteriormente. Django utiliza por defecto el formulario AuthenticationForm ubicado en django.contrib.auth.forms. Este formulario intenta autenticar al usuario y genera un error de validación si el inicio de sesión no es exitoso. Utilizamos {% if form.errors %} en la plantilla para verificar si las credenciales proporcionadas son incorrectas.

Hemos añadido un elemento HTML <input> oculto para enviar el valor de una variable llamada "next". Esta variable se proporciona a la vista de inicio de sesión si pasas un parámetro llamado "next" en la solicitud, por ejemplo, accediendo a http://127.0.0.1:8000/cuenta/login/?next=/cuenta/.

El parámetro "next" debe ser una URL. Si se proporciona este parámetro, la vista de inicio de sesión de Django redirigirá al usuario a la URL dada después de un inicio de sesión exitoso.

Ahora, crearemos la plantilla logged_out.html dentro del directorio templates/registration/ y añade un código similar a este:

PracticaDjango/Autentificacion/templates/registration/logged_out.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Logged out{% endblock %}

{% block content %}

<h1>Logged out</h1>
<p>
    ¡Has cerrado la sesión correctamente!
    Puedes <a href="{% url 'login' %}">logearte de nuevo</a>.
</p>

{% endblock %}
Esta es la plantilla que Django mostrará al cerrar la sesión. 

Hemos añadido los patrones de URL y las plantillas para las vistas de inicio (login) y cierre de sesión (logout). Ahora, los usuarios pueden iniciar y cerrar sesión usando las vistas de autentificación de Django.


Registro de Usuarios y perfiles de Usuario.


Los usuarios del sitio ahora pueden iniciar sesión y  cerrar sesión. Sin embargo necesitamos crear una vista que permita a los visitantes crear una cuenta de usuario.


Registro de usuario


Creemos una vista sencilla para permitir el registro de usuarios en tu sitio web. Los campos que necesitaremos para crear un nuevo usuario son:

  • nombre de usuario
  • correo-electrónico
  • password1
  • password2 (para verificar que la contraseña se ha escrito correctamente)


Vamos a crear el formulario de registro usando UserCreationForm que nos va a permitir crear el formulario de registro una forma sencillísima. Lo vamos a tener que personalizar un poco ya que por defecto UserCreationForm no contiene el campo email y lo vamos a necesitar más tarde para restaurar la contraseña si el usuario la olvida.

Django tiene una forma sencilla de extender los formularios existentes para agregar nuevos campos. Lo primero que tenemos que hacer es crear un nuevo formulario que herede del "UserCreationForm" y luego agregar el campo adicional para el correo electrónico. 

Edita el archivo forms.py del directorio de la aplicación Autentificación que habiamos creado anteriormente y añade el código resaltado:

PracticaDjango/Autentificacion/forms.py

from django import forms

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)


class UserCreationWithEmailForm(UserCreationForm):
    email = forms.EmailField(label='Correo electrónico', required=True)

    class Meta:
        model = User
        fields = ("username", "email", "password1", "password2")


Luego en la vista, importamos esa clase "UserCreationWithEmailForm" y creamos el formulario para crear usuario y lo renderizamos pasando por parámetro el formulario que acabamos de crear a la plantilla.


PracticaDjango/Autentificacion/views.py

from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from .forms import LoginForm, UserCreationWithEmailForm

# Para crear la clase que nos servirá para registrar un nuevo usuario
from django.views.generic import View

#...
    
class registrar(View):

    def get(self, request):
        # creamos el formulario y se lo pasamos a la plantilla
        user_form = UserCreationWithEmailForm()
        return render(request, "Autentificacion/registro.html", {"user_form":user_form})

    def post(self, request):
        user_form = UserCreationWithEmailForm(request.POST)
        # si el formulario es válido
        if user_form.is_valid():
            # Creamos un nuevo usuario y lo guardamos en la base de datos.
            new_user = user_form.save()
            # con .save() se guarda en la base de datos el hash de la contraseña y el usuario.
            # Al crear una cuenta si el registro
            # tiene exito el usuario se logea automáticamente y se redirecciona 
            # a la pagina de inicio
            login(request, new_user)
            # una vez logueado hacemos la redirección a la pagina principal
            return redirect('Proyecto_web_app:home')
        return render(request, "Autentificacion/registro.html", {"user_form":user_form})


La vista para crear un nuevo usuario es bastante sencilla. Cuando se llama a la vista por primera vez, usando el método GET, se crea una instancia del formulario y se traslada a la plantilla para que se renderice. 

Una vez enviado el formulario, a través del método POST, se creara una instancia del formulario. Pero sus campos ya no estarán vacíos puesto que recogen la información facilitada en request.POST. El formulario puede fallar dependiendo de lo que introduzca el usuario por eso usamos el "if form.is_valid()". Si no fuera válido se vuelve a renderizar el formulario vacío de nuevo. Pero si es correcto se creara una instancia del nuevo usuario (su nombre y contraseña) que se guardará en la base de datos y se realizará el login. Una vez logeado volverá a la página principal.

Aquí pararemos un momento para comentar, que Django no guarda directamente la contraseña que introduce el usuario. En su lugar las almacena en forma de "hashes". El hash es el resultado de transformar la clave dada en otro valor. Se utiliza una función hash para generar un valor de longitud fija según un algoritmo matemático. Repito, no se guarda la contraseña sino el valor que resulta de aplicarle un algoritmo matemático. 

Por defecto, Django utiliza el algoritmo de hash PBKDF2 con un hash SHA256 para almacenar todas las contraseñas. Sin embargo, Django no solo verifica contraseñas existentes hasheadas con PBKDF2, sino que también admite la verificación de contraseñas almacenadas con otros algoritmos como PBKDF2SHA1, argon2, bcrypt y scrypt.

La configuración PASSWORD_HASHERS define los algoritmos de hash de contraseñas que el proyecto Django admite y que se puede colocar en setttings.py del proyecto. La siguiente lista muestra los hashers predeterminados:


PASSWORD_HASHERS = [

'django.contrib.auth.hashers.PBKDF2PasswordHasher',

'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',

'django.contrib.auth.hashers.Argon2PasswordHasher',

'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',

'django.contrib.auth.hashers.ScryptPasswordHasher',

]


Django utiliza la primera entrada de la lista, en este caso PBKDF2PasswordHasher, para hashear todas las contraseñas. Los demás hashers pueden ser utilizados por Django para verificar contraseñas existentes.

Puedes obtener más información sobre cómo Django almacena contraseñas y los hashers de contraseñas incluidos en https://docs.djangoproject.com/en/4.2/topics/auth/passwords/.

Seguimos. Antes de poder ver como queda el formulario y los tres campos que contiene (nombre, contraseña y repetición de la contraseña) tenemos que editar el archivo urls.py de la aplicación Autentificación y añadir el siguiente parámetro de URL para su vista:


PracticaDjango/Autentificacion/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    # login url anterior
    # path("login/", views.user_login, name="login"),
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('registrar/', views.registrar.as_view(), name="registrar")
]

Finalmente crearemos una nueva plantilla en templates/Autentificacion llamada registro.html que será la encargada de renderizar el formulario. Tendrá el siguiente código:


PracticaDjango/Autentificacion/templates/Autentificacion/registro.html

<!--Cargamos la plantilla base-->
{% extends "Proyecto_web_app/base.html" %}

{% load django_bootstrap5 %}

<!-- Establecemos el titulo de la página -->
{% block title %}Registro{% endblock %}

<!-- Definimos su contenido -->
{% block content %}

<!-- creamos el formulario -->
<form method="post" action="">
    {% csrf_token %}
    <div>&nbsp;</div>

    <div class="container w-50 bg-info rounded-1 pt-4">
        {% bootstrap_form user_form layout="horizontal" %}
        <!-- layout='horizontal' los nombres de los campos y sus etiquetas 
            se muestran en la misma línea -->
        <button type="submit" class="btn btn-primary">Registrate</button>
        <button type="reset" class="btn btn-secondary">Borrar</button>
    </div>

</form>

{% endblock %}

Ahora si ejecutamos el servidor y entramos en la dirección "http://127.0.0.1:8000/cuenta/registrar/" nos encontraremos lo siguiente:



formulario de registro de nuevo usuario



Lo que vamos a hacer a continuación es preparar un espacio en la parte superior derecha de la plantilla principal para que el usuario pueda logearse o registrarse, independientemente de la página en la que este. O si ya está logueado pueda cerrar su sesión.

Lo vamos a ubicar entre la etiqueta <h1> del titulo y la barra de navegación. Nos vamos para ello a la plantilla base

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

#...
<body>
    <header>
        <h1 class="display-4 text-center text-white bg-dark py-3">
            UnikGAME
        </h1>
        <div style="color: black; text-align: right; margin-right: 100px;">
            {% if user.is_authenticated %}
            Hola, {{user.username}}&nbsp;&nbsp;|&nbsp;&nbsp;
            <a href="{% url 'logout' %}">Cerrar Sesión</a>
            {% else %}
            <a href="{% url 'login' %}">Login</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
                href=""{% url 'registrar' %}"">Registrarse</a>
            {% endif %}
        </div>
        {% include "Proyecto_web_app/nav.html" %}        
    </header>
    <!-- Parte cambiante de las plantillas -->
...
Si el usuario esta autentificado entonces se mostrará un mensaje de bienvenida con su nombre en la parte superior derecha de la pantalla y también la opción de cerrar sesión. Si el usuario no lo está entonces se mostrará un enlace para o bien poder logearse o registrarse en caso de que no lo haya hecho. 

Si el usuario no esta logeado le aparecerá este menú.

menu para logearse o registrarse


Y si ya esta registrado este otro.

menu bienvenida y cerrar sesión



Características Avanzadas.


Ahora que lo básico esta hecho vamos a pulir algunos detalles. Por ejemplo, cuando ahora el usuario se logea o cierra la aplicación, no se le redirige a ninguna página en concreto. Cuando se crea un nuevo usuario si que se le redirige a la página principal porque asi lo hemos programado nosotros. Podemos sin embargo hacer esto a través de las opciones de Django:

Edita el archivo settings.py del proyecto y añade el siguiente código:

PracticaDjango/PracticaDjango/settings.py

#...

# Redirección URL
LOGIN_REDIRECT_URL = 'Proyecto_web_app:home'
LOGIN_URL = 'login'
LOGOUT_URL = 'logout'


Hemos definido las siguientes configuraciones:


LOGIN_REDIRECT_URL: Indica a Django a qué URL redirigir al usuario después de un inicio de sesión exitoso si no hay un parámetro "next" presente en la solicitud.

LOGIN_URL: La URL a la que se redirige al usuario para iniciar sesión (por ejemplo, vistas que utilizan el decorador login_required).

LOGOUT_URL: La URL a la que se redirige al usuario para cerrar sesión, si no se ha especificado otra cosa.

Hemos empleado los nombres de las URLs que definimos previamente con el atributo name de la función path() en los patrones de URL. También se pueden usar URLs codificadas directamente en lugar de nombres de URL para estas configuraciones.


Cambiar o Resetear la contraseña de Usuario.


Necesitamos que los usuarios puedan cambiar sus contraseñas después de logearse en nuestra página. Vamos a hacer uso de las facilidades que nos proporciona Django. 

Abre el archivo urls.py de la aplicación Autentificacion y añade los siguientes parámetros de URL:

PracticaDjango/Autentificacion/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

from django.urls import reverse_lazy

urlpatterns = [
    # login url anterior
    # path("login/", views.user_login, name="login"),
    path("login/", auth_views.LoginView.as_view(), name="login"),
    path("logout/", auth_views.LogoutView.as_view(), name="logout"),
    path("registrar/", views.registrar.as_view(), name="registrar"),
    # cambio de contraseña
    path(
        "password-change/",
        auth_views.PasswordChangeView.as_view(
            success_url=reverse_lazy("Autentificacion:password_change_done")
        ),
        name="password_change",
    ),
    path(
        "password-change/done/",
        auth_views.PasswordChangeDoneView.as_view(),
        name="password_change_done",
    ),
]


La vista PasswordChangeView manejará el formulario para cambiar la contraseña, y la vista PasswordChangeDoneView mostrará un mensaje de éxito después de que el usuario haya cambiado exitosamente su contraseña. 

INCISO. Si hubieramos usado app_name = "Autentificacion" en urls.py, cosa que no hemos hecho ni recomiendo hacer,  tendríamos que haber escrito la ruta como:

# Primero importar reverse_lazy
from django.urls import reverse_lazy

# Y luego espeficicar así el parámetro URL
path(
        "password-change/",
        auth_views.PasswordChangeView.as_view(
            success_url=reverse_lazy("Autentificacion:password_change_done")
        ),
        name="password_change",
    ),
app_name = "Autentificacion", provoca que nuestra URL use un namespace de la aplicación llamado "Autentificacion". PasswordChangeView no espera que haya un namespace de la aplicación cuando busca la vista nombrada como password_change_done. Esto provocaría un error cuando una vez enviado el formulario de cambio de contraseña se intentara mostrar la página de que la contraseña se ha cambiado con éxito. Sin embargo podemosdefinir explicitamente el atributo success_url dentro de PasswordChangeView. El parámetro success_url espera la URL a la que se redirigirá una vez que se haya cambiado la contraseña correctamente.

La función reverse_lazy("Autentificacion:password_change_done") busca la URL con nombre password_change_done que está definida en el espacio de nombres (namespace) llamado Autentificacion.

Puedes encontrar más información sobre namespace en: https://docs.djangoproject.com/en/4.2/topics/http/urls/#url-namespaces-and-included-urlconfs


SEGUIMOS. Creemos, ahora,  una plantilla para cada vista. Crea un nuevo archivo dentro del directorio templates/registration/ con el nombre de password_change_form.html y añade este código:


PracticaDjango/Autentificacion/templates/registration/password_change_form.html

{% extends "Proyecto_web_app/base.html" %}

{% load django_bootstrap5 %}

{% block title %}Cambia tu contraseña{% endblock %}

{% block content %}
<h1>Cambia tu contraseña</h1>
<p>Usa el siguiente formulario para cambiar la contraseña.</p>

<div class="login-form">
    <form action="" method="post">
        {% csrf_token %}
        <div class="container w-25 bg-primary rounded-1">
            {% bootstrap_form form %}
            <p><input type="submit" value="Cambiar"></p>
    </form>
</div>
{% endblock %}

La plantilla password_change_form.html incluye el formulario para cambiar la contraseña. 

Ahora crearemos otro archivo en el mismo directorio y lo llamaremos password_change_done.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_change_done.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Contraseña Cambiada{% endblock %}

{% block content %}
<h1>Contraseña Cambiada</h1>
<p>Tu contraseña se ha cambiado con éxito.</p>
{% endblock %}
La plantilla password_change_done.html contiene únicamente el mensaje de éxito que se mostrará cuando el usuario haya cambiado exitosamente su contraseña. Abre http://127.0.0.1:8000/cuenta/password-change/ en tu navegador. Si no has iniciado sesión, el navegador te redireccionará a la página de inicio de sesión. Después de autentificarte correctamente, verás la siguiente página para cambiar la contraseña:

formulario para cambiar contraseña


Si rellenas el formulario con tu contraseña actual y tu nueva contraseña y le das al botón de cambiar debería salirte la plantilla en la que te dicen que la contraseña se ha cambiado correctamente. Logeate y cierra sesión de nuevo usando tu nueva contraseña para comprobar que todo funciona como debiera.


Vista para resetear la contraseña.


¿Y si se nos olvida la contraseña? Pues que tendremos que resetearla para poder introducir una nueva. Vamos con ello. Edita el archivo urls.py de la aplicación de Autentificación y añade el siguiente código al final del archivo dentro de urlpatterns:

PracticaDjango/Autentificacion/urls.py

#...
# reset password urls
    path(
        "password-reset/", 
        auth_views.PasswordResetView.as_view(), 
        name="password_reset",
    ),
    path(
        "password-reset/done/",
        auth_views.PasswordResetDoneView.as_view(),
        name="password_reset_done",
    ),
    path(
        "password-reset/<uidb64>/<token>/",
        auth_views.PasswordResetConfirmView.as_view(),
        name="password_reset_confirm",
    ),
    path(
        "password-reset/complete/",
        auth_views.PasswordResetCompleteView.as_view(),
        name="password_reset_complete",
    ),

Añade un nuevo archivo en el directorio templates/registration de la aplicación de autentificación y llámalo password_reset_form.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_reset_form.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Resetea tu contraseña{% endblock %}

{% block content %}
<h1>¿Olvidaste tu contraseña?</h1>
<p>Introduce tu dirección de e-mail para obtener una nueva.</p>
<form method="post">
    {{ form.as_p }}
    <p><input type="submit" value="Enviar e-mail"></p>
    {% csrf_token %}
</form>
{% endblock %}
Ahora crea otro archivo en el mismo directorio y llámalo password_reset_email.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_reset_email.html

Alguien ha solicitado restablecer la contraseña de este email: {{ email }}. 
Sigue el siguiente enlace:
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
Tu nombre de usuario, en caso de que lo hayas olvidado es: {{ user.get_username }}
La plantilla password_reset_email.html se usará para renderizar el correo que se enviará a los usuarios para restablecer su contraseña. Incluye un token de restablecimiento proporcionado por la vista.

Crea otro archivo en el mismo directorio y llámalo password_reset_done.html. Añádele el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_reset_done.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Restablece tu contraseña{% endblock %}

{% block content %}

<h1>Restablecer tu contraseña.</h1>
<p>Te hemos enviado instrucciones al correo electrónico para restablecer la contraseña</p>
<p>Si no lo has recibido, asegúrate de haber usado el correo electrónico con el que 
    te registraste.</p>
{% endblock %}
Crea otra plantilla en el mismo directorio y llámalo password_reset_confirm.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_reset_confirm.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Restable tu contraseña{% endblock %}

{% block content %}

<h1>Restablece tu contraseña</h1>
{% if validlink %}
<p>Por favor, introduce tu nueva contraseña dos veces:</p>
<form method="post">
    {{ form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Cambiar mi contraseña" /></p>
</form>
{% else %}
<p>El enlace para restablecer la contraseña no era válido, posiblemente porque ya
    ha sido usado. Solicita un nuevo restablecimiento de contraseña.</p>
{% endif %}

{% endblock %}
En esta plantilla, confirmamos si el enlace para restablecer la contraseña es válido cuando se haga click en el enlace proporcionado. La vista PasswordResetConfimView comprueba la validez del token proporcionado en la URL y pasa la variable validlink a la plantilla. Si el enlace es válido, la contraseña del usuario se muestra en el formulario de reinicio. Los usuarios solo podrán restablecer la contraseña si tienen un enlace válido para ello.

Crea otra plantilla en el mismo directorio y llámala password_reset_complete.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/registration/password_reset_complete.html

{% extends "Proyecto_web_app/base.html" %}

{% block title %}Contraseña restablecida{% endblock %}

{% block content %}

<h1>Contraseña registrada.</h1>
<p>Tu contraseña ha sido registrada. Puedes <a href="{% url 'login'  %}">
    logearte ahora</a></p>

{% endblock %}
Finalmente, edita la plantilla templates/registration/login.html de la aplicación Autentificación y añade las líneas resaltadas.

PracticaDjango/Autentificacion/templates/registration/login.html

{% extends "Proyecto_web_app/base.html" %}

{% load django_bootstrap5 %}

{% block title %}Log-in{% endblock %}

{% block content %}

<h1>Log-in</h1>
{% if form.errors %}
<p>
    Tu nombre de usuario y/o contraseña no coinciden.
    Por favor, inténtalo de nuevo.
</p>
{% else %}
<p>Por favor, use el siguiente formulario para log-in:</p>
{% endif %}
<div class="login-form">
    <form action="{% url 'login' %}" method="post">
        {% csrf_token %}
        <div class="container w-25 bg-primary rounded-1">
            {% bootstrap_form form %}
            <input type="hidden" name="next" value="{{ next }}" />
            <p><input type="submit" value="Log-in"></p>
            <!-- {{ form.as_p }} -->
    </form>
    <p>
        <a href="{% url 'password_reset' %}" style="color:white">
            ¿Olvidaste tu contraseña?
        </a>
    </p>
</div>

{% endblock %}
Ahora abre la dirección htpp://127.0.01:8000/cuenta/login en tu navegador. La página del login debería incluir un link para poder resetear la contraseña.

la página de login incluyendo un enlace para resetear la contraseña


Haz clic en el link ¿Olvidaste tu contraseña?. Deberías ver la siguiente página:


formulariio para introducir el correo y resetear la contraseña



En este punto, necesitamos añadir un Simple Mail Transfer Protocol dentro del archivo de configuración del Proyecto (settings.py) para que Django pueda enviar Emails. Aprendimos como hacerlo en el capitulo "10.- Formulario de contacto. Envío de Emails y variables de entorno". Sin embargo durante el desarrollo de la aplicación, puedes configurar Django para que envíe los correos a la salida estandar utilizando este apartado dentro del archivo de configuración settings.py del proyecto:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Ahora vuelve al navegador e introduce la dirección de correo de un usuario que tengas creado. (Por eso pusimos el campo email cuando el usuario se registra). Luego haz clic en el botón Enviar Email. Deberías ver la siguiente página:


mensaje de correo enviado para restaurar contraseña



Echa un vistazo al shell de la consola donde estás ejecutando el servidor de desarrollo de Django. Verás el email que se ha enviado al usuario para restablecer la contraseña:


token para restablecer la contraseña



El email es renderizado usando la plantilla password_reset_email.html que creamos previamente. La URL para restablecer la contraseña incluye un token que ha sido generado por Django. Copia la URL desde el email, que debería tener un aspecto similar a http://127.0.0.1:8000/cuenta/reset/Mg/bypfqo-d8b1f54efabda9254fa3f37df2b967da/ y ábrelo en tu navegador. Debería ver la siguiente página:


formulario para introducir la nueva contraseña



La página para introducir una nueva contraseña usa la plantilla password_reset_confirm.html. Introduce una nueva contraseña y haz clic en Cambiar mi contraseña. Django creará un nuevo hash de la contraseña y la guardará en la base de datos. Verás la siguiente página indicando que la operación ha sido llevada a cabo con exito:


contraseña registrada con exito



Puede volver a logearte usando la nueva contraseña.

Cada token para establecer una nueva contraseña solo se puede utilizar una vez. Si abres el enlace que recibiste nuevamente, recibirás un mensaje que indica que el token no es válido.

Hemos integrado las vistas del marco de autenticación de Django en el proyecto. Estas vistas son adecuadas para la mayoría de los casos. Sin embargo, puedes crear tus propias vistas si necesitas un comportamiento diferente.

Django proporciona patrones de URL para las vistas de autenticación que son equivalentes a los que acabamos de crear. Reemplazaremos los patrones de URL de autenticación con los proporcionados por Django.

Comenta los patrones de URL de autenticación que agregaste al archivo urls.py de la aplicación de Autentificación e incluye django.contrib.auth.urls en su lugar, de la siguiente manera. ¡Verás que reducido se queda el archivo!. El nuevo código está resaltado en azul:

PracticaDjango/Autentificacion/urls.py

from django.urls import path, include
from django.contrib.auth import views as auth_views
from . import views

# from django.urls import reverse_lazy
# No usaremos app_name = "Autentificacion" porque dará problemas
# con el framework de autentificación

urlpatterns = [
    # login url anterior
    # path("login/", views.user_login, name="login"),
    # path("login/", auth_views.LoginView.as_view(), name="login"),
    # path("logout/", auth_views.LogoutView.as_view(), name="logout"),
    path("registrar/", views.registrar.as_view(), name="registrar"),
    # # cambio de contraseña
    # path(
    #     "password-change/",
    #     auth_views.PasswordChangeView.as_view(),
    #     name="password_change",
    # ),
    # path(
    #     "password-change/done/",
    #     auth_views.PasswordChangeDoneView.as_view(),
    #     name="password_change_done",
    # ),
    # # reset password urls
    # path(
    #     "password-reset/", 
    #     auth_views.PasswordResetView.as_view(), 
    #     name="password_reset",
    # ),
    # path(
    #     "password-reset/done/",
    #     auth_views.PasswordResetDoneView.as_view(),
    #     name="password_reset_done",
    # ),
    # path(
    #     "password-reset/<uidb64>/<token>/",
    #     auth_views.PasswordResetConfirmView.as_view(),
    #     name="password_reset_confirm",
    # ),
    # path(
    #     "password-reset/complete/",
    #     auth_views.PasswordResetCompleteView.as_view(),
    #     name="password_reset_complete",
    # ),
    path("", include('django.contrib.auth.urls')),
]

¡Se ha quedado reducido todo a dos líneas reales de path!

Y hasta aquí la creación de un sistema de autentificación de usuarios en la aplicación.

Puedes encontrar el código del capítulo en el siguiente enlace de GitHub.

No hay comentarios:

Publicar un comentario