Lab 4: Sistema de usuarios

Introducción

En este lab vamos a aprender como implementar el sistema de usuarios de Django. Con esto nuestros usuarios podran registrarse, iniciar sesion, cerrar sesion, recuperar su contraseña. Tambien vamos a poder gestionar nuestros usuarios mediante la pagina de administrador y agregar tipos de usuarios personalizados.

Modelos de datos para usuarios

Para empezar debemos abrir nuestro proyecto y agregar 3 nuevos modelos. Primero el modelo base Profile, y a partir de este crearemos Colaborador y Cliente. Para poder integrar nuestros usuarios personalizados con el sistema de usuarios de Django debemos importar la siguiente clase en main/models.py:

from django.contrib.auth.models import User

Luego definimos el modelo base Profile y usaremos el campo especial OneToOneField para relacionarlo con la clase User que hemos importado antes:

class Profile(models.Model):
    # Relacion con el modelo User de Django
    user = models.OneToOneField(User, on_delete=models.CASCADE)

Finalmente agregamos los demas atributos igual que en el lab anterior:

    ...
    # Atributos adicionales para el usuario
    documento_identidad = models.CharField(max_length=8)
    fecha_nacimiento = models.DateField()
    estado = models.CharField(max_length=3)
    ## Opciones de genero
    MASCULINO = 'MA'
    FEMENINO = 'FE'
    NO_BINARIO = 'NB'
    GENERO_CHOICES = [
        (MASCULINO, 'Masculino'),
        (FEMENINO, 'Femenino'),
        (NO_BINARIO, 'No Binario')
    ]
    genero = models.CharField(max_length=2, choices=GENERO_CHOICES)

    def __str__(self):
        return self.user.get_username()

Para el atributo genero hemos hecho algo nuevo, hemos creado algunas variables que se mostraran como opciones para nuestros usuarios a la hora de registrarse.

Ahora crearemos Cliente, este debe estar integrado con el modelo Profile:

class Cliente(models.Model):
    # Relacion con el modelo Perfil
    user_profile = models.OneToOneField(Profile, on_delete=models.CASCADE)

    # Atributos especificos del Cliente
    preferencias = models.ManyToManyField(to='Categoria')

    def __str__(self):
        return f'Cliente: {self.user_profile.user.get_username()}'

Finalmente crearemos el modelo Colaborador:

class Colaborador(models.Model):
    # Relacion con el modelo Perfil
    user_profile = models.OneToOneField(Profile, on_delete=models.CASCADE)

    # Atributos especificos del Colaborador
    reputacion = models.FloatField()
    cobertura_entrega = models.ManyToManyField(to='Localizacion')

    def __str__(self):
        return f'Colaborador: {self.user_profile.user.get_username()}'

No te olvides de migrar: Luego de modificar el archivo models.py debemos utilizar los comandos makemigrations y migrate para que nuestros cambios hagan efecto.

Quiero espiar a mis usuarios!

Para visualizar nuestros modelos desde la página de administrador debemos añadir el siguiente código al archivo main/admin.py:

# Importar clases Cliente y Colaborador
from .models import Cliente, Colaborador, Profile

class ClienteInline(admin.TabularInline):
    model=Cliente

class ColaboradorInline(admin.TabularInline):
    model=Colaborador

class ProfileAdmin(admin.ModelAdmin):
    inlines = [
        ClienteInline,
        ColaboradorInline
    ]

admin.site.register(Cliente)
admin.site.register(Colaborador)
admin.site.register(Profile, ProfileAdmin)

Ahora crearemos el formulario para registro

Django nuevamente nos da una base para comenzar, pero tendremos que agregar todos los atributos personalizados que tenemos de las clases Profile, Colaborador y Cliente. Para crear un formulario debemos crear el archivo main/forms.py, dentro primero importaremos lo siguiente:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import Localizacion, Categoria

Ahora vamos a crear una clase UserForm que tendra como super clase a UserCreationForm que acabamos de importar, esto nos dara agunas funcionalidades utiles como validacion de usurios duplicados, contrasenas, etc. En esta clase agregaremos todos los atributos de User y Profile que necesitamos obtener de nuestros usuarios:

class UserForm(UserCreationForm):
    # django.contrib.auth.User attributes
    first_name = forms.CharField(max_length=150, required=False)
    last_name = forms.CharField(max_length=150, required=False)
    email = forms.EmailField(max_length=150)

    # Profile attributes
    documento_identidad = forms.CharField(max_length=8)
    fecha_nacimiento = forms.DateField()
    estado = forms.CharField(max_length=3)
    ## Opciones de genero
    MASCULINO = 'MA'
    FEMENINO = 'FE'
    NO_BINARIO = 'NB'
    GENERO_CHOICES = [
        (MASCULINO, 'Masculino'),
        (FEMENINO, 'Femenino'),
        (NO_BINARIO, 'No Binario')
    ]
    genero = forms.ChoiceField(choices=GENERO_CHOICES)

Tambien necesitamos agregar los datos de los modelo Cliente y Colaborador:

class UserForm(UserCreationForm):
  ...
  # Cliente attributes
  is_cliente = forms.BooleanField(required=False)
  preferencias = forms.ModelChoiceField(queryset=Categoria.objects.all(), required=False)

  # Colaborador attributes
  is_colaborador = forms.BooleanField(required=False)
  reputacion = forms.FloatField(required=False)
  cobertura_entrega = forms.ModelChoiceField(queryset=Localizacion.objects.all(), required=False)

Hay dos intrusos! He anadido dos datos de ayuda: is_cliente e is_colaborador, mas adelante usaremos estas variables para saber que instancias debemos crear y guardar en nuestra base de datos.

ModelChoiceField: Este campo permite utilizar los objetos de otro modelo de datos como opciones a seleccionar por el usuario. Muy util!

Finalmente dentro de la clase UserForm usaremos la clase Meta, esta clase nos permite indicarle que modelo de datos esta asociado a este formulario y tambien personalizarlo. Por ejemplo el atributo field permite definir el orden en el que los usuarios veran los campos a llenar del formulario:

class Meta:
    model = User
    fields = ['username',
    'first_name',
    'last_name',
    'email',
    'documento_identidad',
    'fecha_nacimiento',
    'estado',
    'genero',
    'is_cliente',
    'preferencias',
    'is_colaborador',
    'reputacion',
    'cobertura_entrega',
    ]

¡Ahora hemos terminado con los modelos de datos y sus formularios!

Veamos las vistas (pun intented)

Primero debemos importar algunas herramientas dentro de main/views.py:

from django.views.generic import FormView, TemplateView
from django.urls import reverse_lazy
from django.contrib.auth import login

# Importamos forms.py
from .forms import *

#Importamos las clases recien creadas
from .models import *

Primero vamos a actualizar nuestra vista home y pasaremos de usar una funcion a una clase (porque somos cheveres):

 class HomePageView(TemplateView):

    template_name = "main/home.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['latest_products'] = Producto.objects.all()[:5]

        return context

Ahora vamos a crear nuestra vista para mostrar el formulario de registro de usuarios y recibir los datos de los usuarios:

class RegistrationView(FormView):
    template_name = 'registration/register.html'
    form_class = UserForm
    success_url = reverse_lazy('home')

Esto seria suficiente si no tuvieramos los campos adicionales para Profile, Cliente y Colaborador, por ello necesitamos personalizar el metodo form_valid que se activa cuando nuestro usuario se registra con datos correctos:

class RegistrationView(FormView):
  ...
  def form_valid(self, form):
    # This methos is called when valid from data has been POSTed
    # It should return an HttpResponse

    # Create User
    username = form.cleaned_data['username']
    first_name = form.cleaned_data['first_name']
    last_name = form.cleaned_data['last_name']
    email = form.cleaned_data['email']
    password = form.cleaned_data['password1']

    user = User.objects.create_user(username=username, first_name=first_name, last_name=last_name, email=email, password=password)
    user.save()

Aqui hemos grabado a nuestro usuario, ahora guardemos el Profile asociado:

class RegistrationView(FormView):
  ...

  def form_valid(self, form):
    ...
    # Create Profile
    documento_identidad = form.cleaned_data['documento_identidad']
    fecha_nacimiento = form.cleaned_data['fecha_nacimiento']
    estado = form.cleaned_data['estado']
    genero = form.cleaned_data['genero']

    user_profile = Profile.objects.create( user=user, documento_identidad=documento_identidad, fecha_nacimiento=fecha_nacimiento, estado=estado, genero=genero)
    user_profile.save()

Luego, debemos verificar si es Cliente y guardarlo, aqui hay un nuevo tipo de dato set esto hace referencia a un conjunto de instancias de otro modelo, en este caso el atributo preferencias es un conjunto de instacias de Categoria.

class RegistrationView(FormView):
  ...

  def form_valid(self, form):
    ...
    # Create Cliente if needed
    is_cliente = form.cleaned_data['is_cliente']
    if is_cliente:
        cliente = Cliente.objects.create(user_profile=user_profile)

        # Handle special attribute
        preferencias = form.cleaned_data['preferencias']
        preferencias_set = Categoria.objects.filter(pk=preferencias.pk)
        cliente.preferencias.set(preferencias_set)

        cliente.save()

Hacemos lo mismo para Colaborador:

class RegistrationView(FormView):
  ...

  def form_valid(self, form):
    ...
    # Create Colaborador if needed
    is_colaborador = form.cleaned_data['is_colaborador']
    if is_colaborador:
        reputacion = form.cleaned_data['reputacion']
        colaborador = Colaborador.objects.create(user_profile=user_profile, reputacion=reputacion)

        # Handle special attribute
        cobertura_entrega = form.cleaned_data['cobertura_entrega']
        cobertura_entrega_set = Localizacion.objects.filter(pk=cobertura_entrega.pk)
        colaborador.cobertura_entrega.set(cobertura_entrega_set)

        colaborador.save()

Finalmente, logueamos al usuario y lo redirigimos a la url indicada en el atributo success_url que pusimos al inicio de la clase:

class RegistrationView(FormView):
  ...

  def form_valid(self, form):
    ...
    # Login the user
    login(self.request, user)

    return super().form_valid(form)

Si creo o modifico vistas debo actualizar mis urls!

En el archivo main/urls.py vamos a actulizar la vista para la pagina de inicio. Debemos borrar path(‘’, views.home, name=’home’) y agregaremos las siguientes url:

...
urlpatterns = [
    path('', views.HomePageView.as_view(), name='home'),
    ...
    path('registro/', views.RegistrationView.as_view(), name='register'),
]

Luego en el archivo urls.py de tu aplicacion base (en mi caso es linioexp/urls.py) debemos agregar las urls de Django Auth:

urlpatterns = [
    ...
    path('accounts/', include('django.contrib.auth.urls')),
]

Un par de templates y nos vamos!

Primero crearemos un nuevo archivo en main/templates/base.html. Este archivo contendrá el siguiente código:

<!DOCTYPE html>
<html>

<head>
    <!-- Usaremos utf-8 como formato de codificación -->
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- Título de nuestra página -->
    <title>Linio Expres - Compras por internet</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
    {% block content %}{% endblock %}
</body>

Actualicemos main/templates/main/home.html con lo que hemos aprendido hoy:

{% extends "base.html" %}

{% block content %}
    {% if user.is_authenticated %}
    <h3> Hola {{ user.get_username }} </h3>
    <p>
      <a href="{% url 'logout' %}">Cierra Sesion</a>
    </p>
    {% else %}
    <h3> Hola </h3>
    <p>
      <a href="{% url 'login' %}">Inicia Sesion</a> o
      <a href="{% url 'register' %}">Registrate</a>
    </p>
    {% endif %}

    <hr>
    <h5> Ultimos productos </h5>
    <ul>
      {% for producto in latest_products %}
        <li>
          <a href="{% url 'product-detail' producto.pk %}">
            {{ producto.nombre }}
          </a>
          - {{ producto.precio }}
        </li>
      {% empty %}
        <li>Aun no hay productos disponibles.</li>
      {% endfor %}
    </ul>
    <hr>
    <ul>
      <li><a href="{% url 'product-list' %}">Ver Lista de Productos Completa</a></li>
    </ul>
{% endblock %}

Con user.is_authenticated y user.get_username vamos a personalizar el saludo a nuestros visitantes.

Django Auth nos permite crear un template en main/templates/registration/login.html donde automaticamente mostrara un formulario para iniciar sesion:

{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login">
<input type="hidden" name="next" value="{% url 'home'%}">
</form>

{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>

{% endblock %}

y tambien creemos main/templates/registration/register.html donde mostraremos el formulario de registrro:

{% extends "base.html" %}

{% block content %}

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Registrate">
</form>

{% endblock %}

Ultimos detalles

LOGOUT_REDIRECT_URL = '/'

Probemos las nuevas funciones!

Siguiente Lab

  1. Se evaluará la presentación del proyecto funcionando hasta lo avanzado en este Lab (y de preferencia más avances).

  2. Aprenderemos a implementar el carrito de compras (órdenes de compra) y procesar una compra.

< Anterior lab
Siguiente lab >