DEV Community

Cover image for Desarrollo de Ecommerce con Django (parte 4)
Gabriel Villacis
Gabriel Villacis

Posted on • Edited on

Desarrollo de Ecommerce con Django (parte 4)

Introducción

En este tutorial aprenderás a:

  • Registrar un usuario: Se validarán los datos usando Django Forms y se procesará el registro vía AJAX con Axios.
  • Iniciar sesión: Se realizará la autenticación del usuario y, mediante AJAX, se mostrará el resultado (éxito o error) sin recargar la página.
  • Cerrar sesión: Se implementará una función de logout que, también mediante AJAX, cerrará la sesión del usuario.
  • Actualizar la plantilla base: Se mostrará el nombre de usuario activo en el encabezado si el usuario ha iniciado sesión.

A lo largo del tutorial se separará la lógica de validación (usando Django Forms) de la presentación (las plantillas HTML), y se desarrollará todo el código JavaScript en archivos externos para mantener el HTML limpio y organizado.


1. Formularios con Django Forms

Aunque la plantilla del formulario se construya manualmente, es recomendable centralizar la validación en Django. Crea o edita el archivo store/forms.py:

# store/forms.py
from django import forms
from django.contrib.auth.models import User

class SignupForm(forms.ModelForm):
    password = forms.CharField(
        widget=forms.PasswordInput, 
        min_length=8, max_length=12, 
        label="Contraseña"
    )
    confirm_password = forms.CharField(
        widget=forms.PasswordInput, 
        min_length=8, max_length=12, 
        label="Confirmar Contraseña"
    )

    class Meta:
        model = User
        fields = ['first_name', 'last_name', 'username', 'email', 'password']
        labels = {
            'first_name': 'Nombre'
        }

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get("password")
        confirm_password = cleaned_data.get("confirm_password")
        if password and confirm_password and password != confirm_password:
            self.add_error('confirm_password', "Las contraseñas no coinciden.")
Enter fullscreen mode Exit fullscreen mode

Importante: Aunque en la plantilla se construyen manualmente los campos, definir los labels y las validaciones en el formulario permite reutilizar esta lógica en el backend y mantener el código organizado.


2. Vistas para Registro, Inicio y Cierre de Sesión

Se separarán dos tipos de vistas:

  1. Vistas para renderizar las plantillas: Cuando el usuario navegue a las páginas de registro o inicio de sesión.
  2. Vistas para procesar los datos vía AJAX: Que recibirán los datos del formulario, realizarán las validaciones y devolverán respuestas en formato JSON.

2.1. Vistas para Renderizar las Plantillas

En store/views.py se utilizarán las funciones existentes para mostrar los formularios:

from django.shortcuts import render

def signup(request):
    return render(request, 'signup.html')

def signin(request):
    return render(request, 'signin.html')
Enter fullscreen mode Exit fullscreen mode

2.2. Vistas para Procesamiento AJAX

Agrega en store/views.py las siguientes funciones para procesar los datos enviados mediante AJAX:

import json
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.contrib.auth import authenticate, login, logout
from .forms import SignupForm

@require_POST
def ajax_signup(request):
    form = SignupForm(request.POST)
    if form.is_valid():
        user = form.save(commit=False)
        # Encriptamos la contraseña correctamente
        user.set_password(form.cleaned_data['password'])
        user.save()
        return JsonResponse({'success': True, 'message': 'Usuario registrado exitosamente.'})
    else:
        return JsonResponse({'success': False, 'errors': form.errors})

@require_POST
def ajax_signin(request):
    username = request.POST.get('username')
    password = request.POST.get('password')
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        return JsonResponse({'success': True, 'message': 'Inicio de sesión exitoso.'})
    else:
        return JsonResponse({'success': False, 'errors': {'__all__': "Credenciales incorrectas."}})

def ajax_logout(request):
    logout(request)
    return JsonResponse({'success': True, 'message': 'Sesión cerrada exitosamente.'})
Enter fullscreen mode Exit fullscreen mode

3. Configuración de URLs

En store/urls.py define las rutas para renderizar las plantillas y para las solicitudes AJAX. Por ejemplo:

# store/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # Rutas para renderizar las plantillas:
    path('signup/', views.signup, name='signup'),
    path('signin/', views.signin, name='signin'),

    # Rutas para procesar solicitudes AJAX:
    path('ajax/signup/', views.ajax_signup, name='ajax_signup'),
    path('ajax/signin/', views.ajax_signin, name='ajax_signin'),
    path('logout/', views.ajax_logout, name='logout'),
    # Otras rutas de la aplicación...
]
Enter fullscreen mode Exit fullscreen mode

Con esta configuración, cuando un usuario visite /signup/ o /signin/ se mostrará el formulario correspondiente; y al enviar el formulario, el código JavaScript realizará la petición a /ajax/signup/ o /ajax/signin/.


4. JavaScript Externo con Axios

Crearemos un archivo JavaScript externo para manejar las llamadas AJAX utilizando Axios. Esto permite mantener el código de interacción fuera de las plantillas HTML.

Crea el archivo static/js/auth.js con el siguiente contenido:

// static/js/auth.js

// Función para manejar el envío del formulario de registro vía AJAX
function submitSignupForm() {
    const form = document.getElementById('signup-form');
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        // Limpiar errores anteriores
        document.querySelectorAll('.error').forEach(el => el.innerText = '');
        const formData = new FormData(form);

        axios.post(signupUrl, formData, {
            headers: {
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRFToken': csrfToken
            }
        })
        .then(response => {
            if(response.data.success) {
                alert(response.data.message);
                window.location.href = signinUrl;
            } else {
                // Mostrar errores en cada campo
                const errors = response.data.errors;
                for(let field in errors) {
                    const errorElem = document.getElementById('error-' + field);
                    if (errorElem) {
                        errorElem.innerText = errors[field];
                    }
                }
            }
        })
        .catch(error => console.error('Error:', error));
    });
}

// Función para manejar el envío del formulario de inicio de sesión vía AJAX
function submitSigninForm() {
    const form = document.getElementById('signin-form');
    form.addEventListener('submit', function(e) {
        e.preventDefault();
        document.querySelectorAll('.error').forEach(el => el.innerText = '');
        const formData = new FormData(form);

        axios.post(signinUrl, formData, {
            headers: {
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRFToken': csrfToken
            }
        })
        .then(response => {
            if(response.data.success) {
                alert(response.data.message);
                window.location.href = homeUrl;
            } else {
                if(response.data.errors.__all__){
                    alert(response.data.errors.__all__);
                } else {
                    const errors = response.data.errors;
                    for(let field in errors) {
                        const errorElem = document.getElementById('error-' + field);
                        if (errorElem) {
                            errorElem.innerText = errors[field];
                        }
                    }
                }
            }
        })
        .catch(error => console.error('Error:', error));
    });
}

// Función para cerrar sesión vía AJAX
function logoutUser() {
    axios.get(logoutUrl, {
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
    })
    .then(response => {
        if(response.data.success){
            window.location.href = homeUrl;
        }
    })
    .catch(error => console.error('Error:', error));
}

// Inicialización: se ejecuta cuando la página carga
document.addEventListener('DOMContentLoaded', function() {
    if(document.getElementById('signup-form')) {
        submitSignupForm();
    }
    if(document.getElementById('signin-form')) {
        submitSigninForm();
    }
});
Enter fullscreen mode Exit fullscreen mode

Variables Globales:

Este archivo utiliza las variables signupUrl, signinUrl, logoutUrl, homeUrl y csrfToken. Estas deben definirse en la plantilla base para que Axios las utilice correctamente.


5. Actualización de las Plantillas HTML

5.1. Plantilla Base (base.html)

Actualiza store/templates/base.html para incluir Axios, el archivo JavaScript externo y para mostrar el nombre del usuario si hay sesión activa:

{% load static %}
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EELA Ecommerce - {% block title %}{% endblock %}</title>
    <meta name="keywords" content="Tienda en línea, Ecuador">
    <meta name="author" content="Gabriel Villacis">
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
    <link rel="shortcut icon" href="{% static 'img/favicon.png' %}" type="image/x-icon">
</head>
<body>
   <header class="main-header">
        <div class="container">
            <div id="brand">
                <h1>EELA Ecommerce</h1>
            </div>
            <nav>
                <ul>
                    <li><a href="{% url 'home' %}">Inicio</a></li>
                    <li><a href="{% url 'catalog' %}">Productos</a></li>
                    <li><a href="{% url 'cart' %}">Carrito</a></li>
                    <li><a href="{% url 'contact' %}">Contacto</a></li>
                    {% if user.is_authenticated %}
                        <li>Bienvenido, {{ user.username }}</li>
                        <li><button onclick="logoutUser()" class="btn btn-secondary">Cerrar Sesión</button></li>
                    {% else %}
                        <li><a href="{% url 'signup' %}">Registrarse</a></li>
                        <li><a href="{% url 'signin' %}">Iniciar Sesión</a></li>
                    {% endif %}
                </ul>
            </nav>
        </div>
   </header> 
   <main> 
        <div class="container">
            {% block main_content %}{% endblock %}
        </div>     
   </main>
   <footer>
        <p>&copy; 2025 EELA Ecommerce - Todos los derechos reservados</p>
   </footer>

    <!-- Incluir Axios desde un CDN -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- Definir variables globales para Axios -->
    <script>
        var ajaxSignupUrl = "{% url 'ajax_signup' %}";
        var ajaxSigninUrl = "{% url 'ajax_signin' %}";
        var signinUrl = "{% url 'signin' %}";
        var homeUrl = "{% url 'home' %}";
        var logoutUrl = "{% url 'logout' %}";
        var csrfToken = "{{ csrf_token }}";
    </script>
    <!-- Incluir el JavaScript de autenticación -->
    <script src="{% static 'js/auth.js' %}"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

5.2. Plantilla de Registro (signup.html)

Edita la plantilla store/templates/signup.html:

{% extends 'base.html' %}
{% block title %}Registrarse{% endblock %}
{% block main_content %}
    <h2>Registrarse</h2>
    <div class="form-container">
        <form id="signup-form">
            <div class="form-group">
                <label for="name">Nombre:</label>
                <input type="text" name="first_name" id="name" required maxlength="40">
                <div class="error" id="error-first_name"></div>
            </div>
            <div class="form-group">
                <label for="name">Apellido:</label>
                <input type="text" name="last_name" id="last_name" required maxlength="40">
                <div class="error" id="error-last_name"></div>
            </div>
            <div class="form-group">
                <label for="username">Usuario:</label>
                <input type="text" name="username" id="username" required minlength="10" maxlength="20">
                <div class="error" id="error-username"></div>
            </div>
            <div class="form-group">
                <label for="email">Correo electrónico:</label>
                <input type="email" name="email" id="email" required>
                <div class="error" id="error-email"></div>
            </div>
            <div class="form-group">
                <label for="password">Contraseña:</label>
                <input type="password" name="password" id="password" required minlength="8" maxlength="12">
                <div class="error" id="error-password"></div>
            </div>
            <div class="form-group">
                <label for="confirm_password">Confirmar Contraseña:</label>
                <input type="password" name="confirm_password" id="confirm_password" required minlength="8" maxlength="12">
                <div class="error" id="error-confirm_password"></div>
            </div>
            <input type="submit" value="Registrarse" class="btn">
            <input type="reset" value="Cancelar" class="btn btn-secondary">
        </form>
    </div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

5.3. Plantilla de Inicio de Sesión (signin.html)

Crea o edita store/templates/signin.html:

{% extends 'base.html' %}
{% block title %}Iniciar Sesión{% endblock %}
{% block main_content %}
    <h2>Iniciar Sesión</h2>
    <div class="form-container">
        <form id="signin-form">
            <div class="form-group">
                <label for="username">Usuario:</label>
                <input type="text" name="username" id="username" required minlength="10" maxlength="20">
                <div class="error" id="error-username"></div>
            </div>
            <div class="form-group">
                <label for="password">Contraseña:</label>
                <input type="password" name="password" id="password" required>
                <div class="error" id="error-password"></div>
            </div>
            <input type="submit" value="Iniciar Sesión" class="btn">
            <input type="reset" value="Cancelar" class="btn btn-secondary">
        </form>
    </div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Conclusión

En este tutorial se implementaron de manera integral las funcionalidades de registro, inicio de sesión y cierre de sesión en Django utilizando el modelo de usuario predeterminado, validaciones centralizadas en Django Forms y llamadas AJAX mediante Axios. Además, se movió el código JavaScript a un archivo externo para mejorar la organización y se actualizó la plantilla base para mostrar el nombre del usuario cuando existe una sesión activa.

Estos pasos permiten ofrecer una experiencia de usuario moderna y fluida, con validación robusta y comunicación asíncrona entre el front end y el back end. ¡Continúa ampliando y personalizando tu proyecto según las necesidades de tu ecommerce!

Top comments (0)