domingo, 10 de diciembre de 2023

20.1.- Extendiendo el modelo User. (user model, modelo de usuario personalizado, mensajes, logearse con email )

Extendiendo el modelo User.

Cuando estas trabajando con cuentas de usuarios, a menudo te encuentras con que el modelo User del framework de autentificación de Django es más que suficiente en la mayor parte de los casos. Sin embargo, el modelo estandar de usuario de Django viene con un conjunto limitado de campos. 

Una forma sencilla de extender el modelo de Usuario es creando un modelo de perfil que contenga una relación uno a uno con el modelo de Usuario de Django, y cualquier campo adicional. Una relación uno a uno es similar a un campo ForeignKey con el parámetro unique=True. El lado inverso de la relación es una relación implícita uno a uno con el modelo relacionado en lugar de un gestor para múltiples elementos. Desde cada lado de la relación, se accede a un único objeto relacionado.

Vamos a suponer que queremos añadir al usuario dos campos nuevos, que no están reflejados en el modelo estándar como son la fecha de nacimiento y un avatar.

Pero antes, para que se vea mejor, vamos a crear una nueva vista que muestre una especie de panel de control cuando el usuario este logeado en su cuenta y donde pueda ver sus datos personales.

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

PracticaDjango/Autentificacion/views.py

# Para que solo se pueda acceder a la vista si el usuario esta logeado
from django.contrib.auth.decorators import login_required
#...

@login_required
def dashboard(request):
    return render(request, "Autentificacion/dashboard.html")

Con esto hemos creado la vista "dashboard" y añadido el decorador @login_required. Si el usuario está autentificado, se ejecuta la vista del decorador. Si no lo está le redirige al URL de inicio de sesión con la URL solicitada originalmente a través de un parámetro GET denominado "next". Al hacer esto, la vista de inicio de sesión redirige a los usuarios a la URL a la que intentaban acceder después de que se hayan logeado correctamente.

Recuerda que agregamos un elemento HTML <input> oculto llamdo "next" en la plantilla de template/registration/login.html precisamente con esta finalidad.

A continuación, necesitamos crear una plantilla para la vista "dashboard".

Crea un nuevo archivo llamado dashboard.html dentro de templates/Autentificacion y añade el siguiente código:

PracticaDjango/Autentificacion/templates/Autentificacion/dashboard.html

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

{% block title %}Panel de Control{% endblock %}

{% block content %}

<h1>Panel de Control</h1>
<p>Bienvenido a tu panel de control.</p>

{% endblock %}

Edita el archivo urls.py de la aplicación de autentificación y añade el nuevo código 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

urlpatterns = [
    path("registrar/", views.registrar.as_view(), name="registrar"),
    path("", include('django.contrib.auth.urls')),
    path('', views.dashboard, name='dashboard'),
]

Lo que vamos a hacer ahora es añadir un icono de Bootstrap a la derecha de donde aparece el nombre de usuario, si este está logeado para que al pulsar en el se acceda directamente a esta vista. Para ello edita el archivo base.html de la aplicación Proyecto_web_app y añade este código:

PracticaDjango/Proyecto_web_app/templates/Proyecto_web_app/base.html

...
<body>
    <header>
        <h1 class="container-fluid text-center text-white bg-dark py-5">UnikGAME TIENDA VIRTUAL</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>&nbsp; 
            | &nbsp;&nbsp; 
            <a href="{% url 'dashboard' %}">
            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-person" viewBox="0 0 16 16">
                <path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"/>
            </svg>
            </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>

    <div class="fondo">
...

Si estas logeado y accedes a esta dirección web con el navegador http://127.0.0.1:8000/cuenta/ deberías ver una página similar a esta:


Pagina con los datos del usuario o  panel de control

Bien, vamos ahora a añadir unas nuevas características al usuario como son la fecha de nacimiento y un avatar. Edita el archivo models.py de la aplicación de Autentificacion y añade el siguiente código:

PracticaDjango/Autentificacion/models.py

from django.db import models
from django.conf import settings

# Create your models here.


class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    fecha_nacimiento = models.DateField(blank=True, null=True)
    avatar = models.ImageField(upload_to="users/%Y/%m/%d/", blank=True)

    def __str__(self):
        return f"Profile of {self.user.username}"

Para mantener tu código, utiliza el método get_user_model() para recuperar el modelo de usuario y la configuración AUTH_USER_MODEL para referirte a él al definir la relación de un modelo con el modelo de usuario, en lugar de referirte directamente al modelo de usuario de autenticación. Puedes obtener más información al respecto en https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#django.contrib.auth.get_user_model.

El campo one-to-one 'user' se usará para asociar perfiles con usuarios. Con on_delete=models.CASCADE, forzamos la eliminación del objeto de perfil relacionado cuando se elimina un objeto de usuario.

El campo 'fecha_nacimiento' es un DateField. Hemos hecho este campo opcional con blank=True, y permitimos valores nulos con null=True.

El campo 'avatar' es un ImageField. Hemos hecho este campo opcional con blank=True. Un campo ImageField gestiona el almacenamiento de archivos de imagen. Valida que el archivo proporcionado sea una imagen válida, almacena el archivo de imagen en el directorio indicado con el parámetro upload_to y guarda la ruta relativa al archivo en el campo relacionado de la base de datos. Un campo ImageField se traduce por defecto a una columna VARCHAR(100) en la base de datos. Se almacenará una cadena en blanco si se deja vacío el valor.

En nuestra aplicación ya lo tenemos implementado, pues lo hemos usado varias veces para subir imágenes, puedes encontrar como configurar y subir imágenes en el capítulo "14.- Creación del primer servicio. Subir y visualizar una imagen en la base de datos."

Como siempre que se toca un archivo models.py hay que realizar las migraciones:

python manage.py makemigrations

python manage.py migrate


Edita el archivo admin.py de la aplicación Autentificacion y registra el modelo Perfil en el panel de administración de Django añadiendo el siguiente código:

PracticaDjango/Autentificacion/admin.py

from django.contrib import admin
from .models import Profile

# Register your models here.

@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
    list_display = ['user', 'fecha_nacimiento', 'avatar']
    raw_id_fields = ['user']

Ejecuta el servidor de desarrollo con el comando python manage,py runserver y navega a la dirección del panel de administración de Django "http://127.0.0.1:8000/admin". Luego haz clic en añadir un perfil. Deberías ver algo como esto:


página para insertar un avatar



Crea manualmente un objeto de Perfil para cada usuario que tengas registrado.

Lo siguiente es permitir a los usuarios que puedan editar su perfil en la página web.

Edita el archivo forms.py de la aplicación de Autentificacion y añade el siguiente código resaltado:

PracticaDjango/Autentificacion/forms.py

#...
from .models import Profile

#...
class UserEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email']

class ProfileEditForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['fecha_nacimiento', 'avatar']

Estos formularios permiten:

  • UserEditForm: Este formulario permite a los usuarios modificar su nombre, apellido y email que son atributos que ya existen en el perfil estandar del usuario (user) de Django.
  • ProfileEditForm: Permite editar los datos de su perfil que están guardados en el perfil personalizado que hemos creado. Podrán editar su fecha de nacimiento y subir una imagen para su avatar personalizado.
Edita el archivo views.py de la aplicación de Autentificación y añade el siguiente código resaltado:

PracticaDjango/Autentificacion/views.py

# ...
from .models import Profile

# ...
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
            # Crea el objeto Profile
            Profile.objects.create(user=new_user)
            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}
        )

Cuando el usuario se registra en el sitio, se creará un objeto Profile que será asociado con el objeto User creado.

Ahora, permitiremos a los usuarios que puedan cambiar su perfil.

Edita el archivo views.py de la aplicación Autentificacion y añade el siguiente código resaltado:

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, UserEditForm, ProfileEditForm
from django.contrib.auth.decorators import login_required

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

# from django.contrib.auth.forms import UserCreationForm

from .models import Profile

#...
@login_required
def edit(request):
    try:
        profile = request.user.profile
    except Profile.DoesNotExist:
        profile = None

    if request.method == "POST":
        user_form = UserEditForm(instance=request.user, data=request.POST)
        profile_form = ProfileEditForm(
            instance=request.user.profile, data=request.POST, files=request.FILES
        )
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(instance=request.user.profile)
    return render(
        request,
        "Autentificacion/edit.html",
        {"user_form": user_form, "profile_form": profile_form},
    )

Hemos añadido la nueva vista de edición para permitir a los usuarios editar su información personal. Hemos agregado el decorador login_required a la vista porque solo los usuarios autentificados podrán editar sus perfiles. Para esta vista, usamos dos formularios de modelo: UserEditForm para almacenar los datos del modelo de usuario integrado y ProfileEditForm para almacenar los datos personales adicionales en el modelo personalizado de perfil. Para validar los datos enviados, llamamos al método is_valid() de ambos formularios. Si ambos formularios contienen datos válidos, guardamos ambos formularios llamando al método save() para actualizar los objetos correspondientes en la base de datos.

Agrega el siguiente patrón de URL al archivo urls.py de la aplicación de cuenta:

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 = [
    path("registrar/", views.registrar.as_view(), name="registrar"),
    path("", include('django.contrib.auth.urls')),
    path('', views.dashboard, name='dashboard'),
    path('edit/', views.edit, name='edit'),
]
Finalmente, crea una plantilla para esta vista en template/Autentificacion y llámalo edit.html. Añade el siguiente código:

PracticaDjango/Autentificacion/templates/Autentificacion/edit.html

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

{% block title %}Edita tu Cuenta{% endblock %}

{% block content %}
<h1>Edita tu cuenta.</h1>
<p>Puedes editar tu cuenta usando el siguiente formulario:</p>
<form method="post" enctype="multipart/form-data">
    {{ user_form.as_p }}
    {{ profile_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Guardar Cambios"></p>
</form>

{% endblock %}

En el código anterior, hemos añadido enctype="multipart/form-data" al elemento HTML <form> para habilitar la carga de archivos. Utilizamos un formulario HTML para enviar tanto el formulario user_form como el formulario profile_form.

Abre la URL http://127.0.0.1:8000/cuenta/registrar/ y registra un nuevo usuario. Luego, inicia sesión con el nuevo usuario y abre la URL http://127.0.0.1:8000/cuenta/edit/. Deberías ver la siguiente página:


página para editar la cuenta de usuario


Ahora puedes agregar la información del perfil y guardar los cambios.

Vamos a editar la plantilla del panel para incluir enlaces a las páginas de edición de perfil y cambio de contraseña.

Abre la plantilla templates/Autentificacion/dashboard.html y agrega las siguientes líneas resaltadas en negrita:

PracticaDjango/Autentificacion/templates/Autentificacion/dashboard.html

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

{% block title %}Panel de Control{% endblock %}

{% block content %}

<h1>Panel de Control</h1>

<p>Bienvenido a tu panel de control. Tu puedes <a href="{% url 'edit' %}">
        editar tu pérfil</a> o <a href="{% url 'password_change' %}">
        cambiar tu contraseña</a>.</p>

{% endblock %}
Los usuarios ahora pueden acceder al formulario para editar su perfil desde el panel. Abre http://127.0.0.1:8000/cuenta/ en tu navegador y prueba el nuevo enlace para editar el perfil de un usuario. El panel debería parecer así ahora:


Contenido de la página de dashboard



Usando un modelo de usuario personalizado


Django también ofrece una manera de sustituir el modelo de Usuario con un modelo personalizado. La clase de Usuario debe heredar de la clase AbstractUser de Django, la cual provee la implementación completa del usuario por defecto como un modelo abstracto. Puedes leer más sobre este método en https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#substituting-a-custom-user-model.

Usar un modelo de usuario personalizado te dará más flexibilidad, pero también podría resultar en una integración más difícil con aplicaciones que interactúan directamente con el modelo de usuario de autenticación de Django.


Usando el framework de mensajes


Cuando los usuarios interactúan con la plataforma, hay muchos casos en los que puede que desees informarles sobre el resultado de acciones específicas. Django tiene un framework de mensajes integrado que te permite mostrar notificaciones de una sola vez a tus usuarios.

El framework de mensajes se encuentra en django.contrib.messages y está incluido en la lista default INSTALLED_APPS del archivo settings.py cuando creas nuevos proyectos usando python manage.py startproject. El archivo de configuración también contiene el middleware django.contrib.messages.middleware.MessageMiddleware en la configuración MIDDLEWARE.

El framework de mensajes proporciona una manera sencilla de añadir mensajes a los usuarios. Por defecto, los mensajes se almacenan en una cookie (en caso de que falle, se almacenan en la sesión) y se muestran y eliminan en la siguiente solicitud del usuario. Puedes utilizar el framework de mensajes en tus vistas importando el módulo 'messages' y añadiendo nuevos mensajes con atajos simples, de la siguiente manera:


from django.contrib import messages
messages.error(request, 'Something went wrong')


Puedes crear nuevos mensajes usando el método add_message() o cualquiera de los siguientes métodos abreviados:

success(): Mensajes de éxito para mostrar cuando una acción fue exitosa

info(): Mensajes informativos

warning(): Una falla aún no ha ocurrido pero puede ser inminente

error(): Una acción no fue exitosa o ocurrió una falla

debug(): Mensajes de depuración que serán eliminados o ignorados en un entorno de producción

Agreguemos mensajes al proyecto. El framework de mensajes se aplica de manera global al proyecto. Usaremos la plantilla base para mostrar cualquier mensaje disponible al cliente. Esto nos permitirá notificar al cliente sobre los resultados de cualquier acción en cualquier página.

Como hemos usado un context_proccesor cuando construimos la cesta de la compra solo tendremos que añadir el código a la vista. Ya que si lo agregaramos también a la plantilla se nos duplicará el mensaje. 

El marco de mensajes incluye el procesador de contexto django.contrib.messages.context_processors.messages, que agrega una variable de mensajes al contexto de la solicitud. Puedes encontrarlo en la lista context_processors en la configuración TEMPLATES de tu proyecto. Puedes usar la variable de mensajes en plantillas para mostrar todos los mensajes existentes al usuario.

Un procesador de contexto o context_proccesor es una función en Python que toma el objeto de solicitud como argumento y devuelve un diccionario que se agrega al contexto de la solicitud. 

Modifiquemos la vista de edición para usar el marco de mensajes.

Edita el archivo views.py de la aplicación de cuenta y agrega las siguientes líneas resaltadas en azul:

PracticaDjango/Autentificacion/views.py

#...
# Para mostrar mensajes
from django.contrib import messages

#...
@login_required
def edit(request):
    if request.method == "POST":
        user_form = UserEditForm(instance=request.user, data=request.POST)
        profile_form = ProfileEditForm(
            instance=request.user.profile, data=request.POST, files=request.FILES
        )
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Actualización del Perfil realizada correctamente.')
        else:
            messages.error(request, 'Error actualizando tu Perfil.')
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(instance=request.user.profile)
    return render(
        request,
        "Autentificacion/edit.html",
        {"user_form": user_form, "profile_form": profile_form},
    )
Si no hubiésemos utilizado el context_processor deberías añadir este código a la plantilla para renderizar el mensaje. Por ejemplo podrías abrir la plantilla templates/base.html de la aplicación de cuenta y agrega el siguiente código resaltado en negrita donde quieras que aparezca el mensaje:

Úsalo si no tienes context_proccesor en el proyecto

<ul class="messages">
        {% for message in messages %}
        <li class="{{ message.tags }}">
            {{ message|safe }}
            <a href="#" class="close">x</a>
        </li>
        {% endfor %}
</ul>

Un mensaje de éxito se genera cuando los usuarios actualizan exitosamente su perfil. Si alguno de los formularios contiene datos inválidos, en cambio se genera un mensaje de error.

Abre http://127.0.0.1:8000/cuenta/edit/ en tu navegador y edita el perfil del usuario. Deberías ver el siguiente mensaje cuando el perfil se actualiza exitosamente:


mensaje de exito en el cambio de perfil


Ingresa una fecha inválida en el campo de Fecha de nacimiento y envía el formulario nuevamente. Deberías ver el siguiente mensaje:


el mensaje de que el cambio de perfil no ha sido exitoso


Generar mensajes para informar a tus usuarios sobre los resultados de sus acciones es realmente sencillo. También puedes agregar fácilmente mensajes a otras vistas.

Puedes obtener más información sobre el marco de mensajes en https://docs.djangoproject.com/en/4.2/ref/contrib/messages/.


Construyendo un backend de autentificación personalizado


Django te permite autentificar usuarios contra diferentes fuentes. El ajuste AUTHENTICATION_BACKENDS incluye una lista de backends de autenticación disponibles en el proyecto. El valor predeterminado de este ajuste es el siguiente:

['django.contrib.auth.backends.ModelBackend']

El ModelBackend predeterminado autentifica a los usuarios contra la base de datos utilizando el modelo de Usuario (User) de django.contrib.auth. Esto es adecuado para la mayoría de los proyectos web. Sin embargo, puedes crear backends personalizados para autentificar a tus usuarios contra otras fuentes, como un directorio LDAP (Protocolo Ligero de Acceso a Directorios) u otro sistema.

Puedes obtener más información sobre la personalización de la autenticación en https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#other-authentication-sources.

Cada vez que se utiliza la función authenticate() de django.contrib.auth, Django intenta autentificar al usuario contra cada uno de los backends definidos en AUTHENTICATION_BACKENDS uno por uno, hasta que uno de ellos autentique con éxito al usuario. Solo si todos los backends fallan en autenticar, el usuario no será autentificado.

Django proporciona una manera sencilla de definir tus propios backends de autentificación. Un backend de autenticación es una clase que proporciona los siguientes dos métodos:

• authenticate(): Toma el objeto de solicitud (request) y las credenciales del usuario como parámetros. Debe devolver un objeto de usuario que coincida con esas credenciales si son válidas, o None en caso contrario. El parámetro request es un objeto HttpRequest, o None si no se proporciona a la función authenticate().

• get_user(): Toma un parámetro de ID de usuario y debe devolver un objeto de usuario.

Crear un backend de autentificación personalizado es tan sencillo como escribir una clase en Python que implemente ambos métodos. Creemos un backend de autentificación para permitir que los usuarios se autentifiquen en el sitio, utilizando su dirección de correo electrónico en lugar de su nombre de usuario.

Crea un archivo nuevo dentro del directorio de la aplicación de Autentificación y llámalo autentificacion.py. Añade el siguiente código a él:

PracticaDjango/Autentificacion/autentificacion.py

from django.contrib.auth.models import User

class EmailAuthBackend:
    """
    Autentificación usando una dirección de email
    """
    def authenticate(self, request, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(password):
                return user
            return None
        except (User.DoesNotExist, User.MultipleObjectsReturned):
            return None
    
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None


El código anterior es un backend de autentificación simple. El método authenticate() recibe un objeto de solicitud y los parámetros opcionales de nombre de usuario y contraseña. Podríamos usar diferentes parámetros, pero usamos nombre de usuario y contraseña para que nuestro backend funcione de inmediato con las vistas del marco de autenticación. El código anterior funciona de la siguiente manera:

• authenticate(): Se recupera el usuario con la dirección de correo electrónico proporcionada, y se verifica la contraseña utilizando el método check_password() integrado del modelo de usuario. Este método maneja el hash de la contraseña para comparar la contraseña dada con la contraseña almacenada en la base de datos. Se capturan dos excepciones diferentes del QuerySet: DoesNotExist y MultipleObjectsReturned. La excepción DoesNotExist se genera si no se encuentra ningún usuario con la dirección de correo electrónico proporcionada. La excepción MultipleObjectsReturned se genera si se encuentran varios usuarios con la misma dirección de correo electrónico. Modificaremos las vistas de registro y edición más tarde para evitar que los usuarios utilicen una dirección de correo electrónico existente.

• get_user(): Se obtiene un usuario a través del ID proporcionado en el parámetro user_id. Django utiliza el backend que autentifico al usuario para recuperar el objeto Usuario durante la sesión del usuario. pk es una abreviatura de clave primaria, que es un identificador único para cada registro en la base de datos. Cada modelo de Django tiene un campo que sirve como su clave primaria. De forma predeterminada, la clave primaria es el campo id generado automáticamente. La clave primaria también puede referirse como pk en el ORM de Django. Puedes encontrar más información sobre campos de clave primaria automática en https://docs.djangoproject.com/en/4.2/topics/db/models/#automatic-primary-key-fields.

Edita el archivo settings.py de tu proyecto y agrega el siguiente código:

PracticaDjango/PracticaDjango/settings.py

AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'Autentificacion.autentificacion.EmailAuthBackend',
]
En la configuración anterior, mantenemos el ModelBackend predeterminado que se utiliza para autenticar con el nombre de usuario y la contraseña e incluimos nuestro propio backend de autenticación basado en correo electrónico, EmailAuthBackend.

Abre http://127.0.0.1:8000/cuenta/login/ en tu navegador. Recuerda que Django intentará autenticar al usuario contra cada uno de los backends, por lo que ahora deberías poder iniciar sesión sin problemas usando tu nombre de usuario o cuenta de correo electrónico.

La autenticación de las credenciales de usuario se verificará utilizando ModelBackend, y si no se devuelve ningún usuario, las credenciales se verificarán utilizando EmailAuthBackend.

Sin embargo tenemos que modificar algo en la vista de registro. El problema que me he encontrado es que cuando registras un nuevo usuario, los datos quedan guardados correctamente en la base de datos PERO cuando intenta logearlo automáticamente se genera el siguiente error:

ValueError at /cuenta/registrar/

You have multiple authentication backends configured and therefore must provide the `backend` argument or set the `backend` attribute on the user.

Para solucionarlo tenemos que indicarle a Django, pero solo en esta vista, explicitamente el backend que queremos usar. Para ello abrimos el archivo views.py de la aplicación de Autentitificación y añadimos a la vista registrar el siguiente código resaltado en azul:

PracticaDjango/Autentificacion/views.py

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
            # Crea el objeto Profile
            Profile.objects.create(user=new_user)
            login(request, new_user, backend='django.contrib.auth.backends.ModelBackend')
            # 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}
        )

El orden de los backends listados en la configuración AUTHENTICATION_BACKENDS es importante. Si las mismas credenciales son válidas para varios backends, Django se detendrá en el primer backend que autentique con éxito al usuario. Esto significa que, si tienes varios backends de autenticación y las credenciales proporcionadas son válidas para más de uno de esos backends, Django usará el primero de la lista que pueda autenticar al usuario con éxito y no continuará verificando los otros backends.


Prevención de usuarios que usan un correo electrónico existente


El modelo de usuario (User) del marco de autenticación no evita crear usuarios con la misma dirección de correo electrónico. Si dos o más cuentas de usuario comparten la misma dirección de correo electrónico, no podremos discernir qué usuario se está autenticando. Ahora que los usuarios pueden iniciar sesión utilizando su dirección de correo electrónico, debemos evitar que los usuarios se registren con una dirección de correo electrónico existente.

Ahora cambiaremos el formulario de registro de usuario para evitar que varios usuarios se registren con la misma dirección de correo electrónico.

Edita el archivo forms.py de la aplicación de Autentificacion, y añade las siguientes líneas resaltadas en azul a la clase UserCreationWithEmailForm:

PracticaDjango/Autentificacion/forms.py

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

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

    def clean_email(self):
        data = self.cleaned_data['email']
        if User.objects.filter(email=data).exists():
            raise forms.ValidationError('Este email ya está en uso.')
        return data
#...

Hemos añadido validación para el campo de correo electrónico que impide que los usuarios se registren con una dirección de correo electrónico existente. Construimos un conjunto de consultas (QuerySet) para buscar usuarios existentes con la misma dirección de correo electrónico. Verificamos si hay algún resultado con el método exists(). El método exists() devuelve Verdadero (True) si el conjunto de consultas contiene algún resultado, y Falso (False) en caso contrario.

IMPORTANTE:

Este método se ejecuta cuando el formulario es validado llamando a su método is_valid(). Puedes proporcionar un método clean_<nombrecampo>() para cualquiera de los campos de tu formulario para limpiar el valor o generar errores de validación específicos para un campo en particular. Los formularios también incluyen un método clean() general para validar el formulario completo, lo cual es útil para validar campos que dependen entre sí. En este caso, utilizamos la validación específica del campo clean_email() en lugar de anular el método clean() del formulario. Esto evita anular otras verificaciones específicas del campo que el ModelForm obtiene a partir de las restricciones establecidas en el modelo (por ejemplo, validar que el nombre de usuario sea único).

Ahora, agrega las siguientes líneas resaltadas en negrita a la clase UserEditForm:

PracticaDjango/Autentificacion/forms.py

class UserEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'email']
    
    def clean_email(self):
        data = self.cleaned_data['email']
        qs = User.objects.exclude(id=self.instance.id).filter(email=data)
        if qs.exists():
            raise forms.ValidationError(' Email ya en uso.')
        return data
En este caso, hemos añadido validación para el campo de correo electrónico que evita que los usuarios cambien su dirección de correo electrónico existente a una dirección de correo electrónico existente de otro usuario. Excluimos al usuario actual del conjunto de consultas (QuerySet). De lo contrario, la dirección de correo electrónico actual del usuario se consideraría una dirección de correo electrónico existente y el formulario no se validaría.


Puedes encontrar el código de este capítulo en este enlace de GITHUB.


No hay comentarios:

Publicar un comentario