From 551057b06774ce8e3e6d1ce045af7edf9c2c6212 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 8 May 2026 09:42:44 +0200 Subject: [PATCH 1/2] Rewrite all forms to use Django Forms with validation - Add ProductEditForm, EditProfileForm, ChangePasswordForm, ShippingAddressForm - Add ResetPasswordForm, ResetPasswordPhase2Form - Update views to use new Django Forms - Add form validation tests (terms required, password mismatch, etc) - Update templates to use Django Forms {{ form.as_p }} --- tienda/forms.py | 354 ++++++++ tienda/templates/tienda/editar_direccion.html | 50 +- tienda/templates/tienda/editar_perfil.html | 27 +- tienda/templates/tienda/editar_producto.html | 62 +- tienda/templates/tienda/reset_password.html | 6 +- .../tienda/reset_password_phase2.html | 11 +- tienda/tests.py | 181 ++++ tienda/views.py | 805 ++++++++---------- 8 files changed, 918 insertions(+), 578 deletions(-) create mode 100644 tienda/forms.py diff --git a/tienda/forms.py b/tienda/forms.py new file mode 100644 index 0000000..0cb5655 --- /dev/null +++ b/tienda/forms.py @@ -0,0 +1,354 @@ +from django import forms +from django.core.exceptions import ValidationError +from .models import Category + + +class ProductForm(forms.Form): + name = forms.CharField( + label="Nombre del Producto", + max_length=200, + required = True, + widget=forms.TextInput( + attrs = { + 'class': 'form-control', + 'placeholder': 'Ej: iPhone 15 Pro Max' + } + ) + ) + briefdesc = forms.CharField( + label="Descripción Breve", + max_length=250, + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control', + 'placeholder': 'Una descripción corta para mostrar en las tarjetas de producto' + } + ) + ) + description = forms.CharField( + widget=forms.Textarea(attrs={"rows": "5", "class": "form-control"}), + max_length=5000, + label="Descripción completa", + required = True + ) + price = forms.FloatField( + label="Precio (en €)", + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control', + 'placeholder': '15.99' + } + ) + ) + stock = forms.IntegerField( + label="Stock Disponible", + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control' + } + ) + ) + category = forms.ModelChoiceField( + queryset=Category.objects.all(), + label="Categoría", + required=True, + widget=forms.Select(attrs={'class': 'form-control'}) + ) + + primary_image = forms.ImageField( + label="Imagen Principal", + required = False, + widget = forms.ClearableFileInput( + attrs = { + 'class': 'form-control', + 'accept': 'image/*' + } + ) + ) + + +class ProductEditForm(forms.Form): + name = forms.CharField( + label="Nombre del Producto", + max_length=200, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Ej: iPhone 15 Pro Max'}) + ) + briefdesc = forms.CharField( + label="Descripción Breve", + max_length=250, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Una descripción corta'}) + ) + description = forms.CharField( + widget=forms.Textarea(attrs={"rows": "5", "class": "form-control"}), + max_length=5000, + label="Descripción completa", + required=True + ) + price = forms.FloatField( + label="Precio (en €)", + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '15.99'}) + ) + stock = forms.IntegerField( + label="Stock Disponible", + required=True, + widget=forms.TextInput(attrs={'class': 'form-control'}) + ) + category = forms.ModelChoiceField( + queryset=Category.objects.all(), + label="Categoría", + required=True, + widget=forms.Select(attrs={'class': 'form-control'}) + ) + primary_image = forms.ImageField( + label="Imagen Principal (opcional)", + required=False, + widget=forms.ClearableFileInput(attrs={'class': 'form-control', 'accept': 'image/*'}) + ) + + +class SecondaryImageForm(forms.Form): + image = forms.ImageField( + label="Seleccionar Imagen", + required = True, + widget = forms.ClearableFileInput( + attrs = { + 'class': 'form-control', + 'accept': 'image/*' + } + ) + ) + alt = forms.CharField( + label="Texto Alternativo", + max_length=255, + required = False, + widget = forms.TextInput( + attrs = { + 'class': 'form-control', + 'placeholder': 'Descripción opcional de la imagen' + } + ) + ) + + +class UserLoginForm(forms.Form): + email = forms.EmailField( + label = "Correo Electrónico", + max_length=255, + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control', + 'placeholder': 'Correo Electronico de tu cuenta...' + } + ) + ) + password = forms.CharField( + label="Contraseña", + max_length = 255, + required = True, + widget = forms.PasswordInput( + attrs = { + 'class': 'form-control', + 'placeholder': 'Contraseña del usuario' + } + ) + ) + remember = forms.BooleanField( + required = False, + label = "Recuerdame", + widget = forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + +class UserRegisterForm(forms.Form): + name = forms.CharField( + label = "Nombre Completo", + max_length = 255, + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control' + } + ) + ) + email = forms.EmailField( + label = "Correo Electrónico", + max_length = 255, + required = True, + widget = forms.TextInput( + attrs = { + 'class': 'form-control' + } + ) + ) + password = forms.CharField( + label = "Contraseña", + max_length = 255, + required = True, + widget = forms.PasswordInput( + attrs = { + 'class': 'form-control' + } + ) + ) + password_confirm = forms.CharField( + label = "Verificar Contraseña", + max_length = 255, + required = True, + widget = forms.PasswordInput( + attrs = { + 'class': 'form-control' + } + ) + ) + terms = forms.BooleanField( + required = True, + label = "Acepto los terminos y condiciones", + widget = forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + def clean(self): + cleaned_data = super().clean() + password = cleaned_data.get("password") + password_confirm = cleaned_data.get("password_confirm") + if password and password_confirm and password != password_confirm: + raise ValidationError("Las contraseñas no coinciden.") + + +class EditProfileForm(forms.Form): + first_name = forms.CharField( + label="Nombre", + max_length=150, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control'}) + ) + last_name = forms.CharField( + label="Apellidos", + max_length=150, + required=False, + widget=forms.TextInput(attrs={'class': 'form-control'}) + ) + email = forms.EmailField( + label="Correo Electrónico", + max_length=254, + required=True, + widget=forms.EmailInput(attrs={'class': 'form-control'}) + ) + + +class ChangePasswordForm(forms.Form): + current_password = forms.CharField( + label="Contraseña Actual", + max_length=128, + required=True, + widget=forms.PasswordInput(attrs={'class': 'form-control'}) + ) + new_password = forms.CharField( + label="Nueva Contraseña", + max_length=128, + required=True, + widget=forms.PasswordInput(attrs={'class': 'form-control'}) + ) + confirm_password = forms.CharField( + label="Confirmar Nueva Contraseña", + max_length=128, + required=True, + widget=forms.PasswordInput(attrs={'class': 'form-control'}) + ) + + def clean(self): + cleaned_data = super().clean() + new_password = cleaned_data.get("new_password") + confirm_password = cleaned_data.get("confirm_password") + if new_password and confirm_password and new_password != confirm_password: + raise ValidationError("Las contraseñas no coinciden.") + if new_password and len(new_password) < 8: + raise ValidationError("La contraseña debe tener al menos 8 caracteres.") + + +class ShippingAddressForm(forms.Form): + full_name = forms.CharField( + label="Nombre Completo", + max_length=255, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Juan Pérez García'}) + ) + address_line_1 = forms.CharField( + label="Dirección", + max_length=255, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Calle Mayor 123'}) + ) + address_line_2 = forms.CharField( + label="Dirección (línea 2)", + max_length=255, + required=False, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Piso, puerta, etc.'}) + ) + city = forms.CharField( + label="Población", + max_length=100, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Almería'}) + ) + postal_code = forms.CharField( + label="Código Postal", + max_length=5, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '04001'}) + ) + country = forms.CharField( + label="País", + max_length=100, + required=False, + initial="España", + widget=forms.TextInput(attrs={'class': 'form-control', 'readonly': True}) + ) + phone = forms.CharField( + label="Teléfono", + max_length=20, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '612 345 678'}) + ) + is_default = forms.BooleanField( + label="Establecer como dirección predeterminada", + required=False, + widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + +class ResetPasswordForm(forms.Form): + email = forms.EmailField( + label="Correo Electrónico", + max_length=254, + required=True, + widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': 'tu@email.com'}) + ) + + +class ResetPasswordPhase2Form(forms.Form): + password = forms.CharField( + label="Nueva Contraseña", + max_length=128, + required=True, + widget=forms.PasswordInput(attrs={'class': 'form-control'}) + ) + verify_password = forms.CharField( + label="Confirmar Contraseña", + max_length=128, + required=True, + widget=forms.PasswordInput(attrs={'class': 'form-control'}) + ) + + def clean(self): + cleaned_data = super().clean() + password = cleaned_data.get("password") + verify_password = cleaned_data.get("verify_password") + if password and verify_password and password != verify_password: + raise ValidationError("Las contraseñas no coinciden.") \ No newline at end of file diff --git a/tienda/templates/tienda/editar_direccion.html b/tienda/templates/tienda/editar_direccion.html index d5bc6e3..b4d4855 100644 --- a/tienda/templates/tienda/editar_direccion.html +++ b/tienda/templates/tienda/editar_direccion.html @@ -21,52 +21,7 @@
{% csrf_token %} -
- - -
-
- - -
-
- - -
-
-
- - - - {% for town in almeria_municipalities %} - - {% endfor %} - -
Selecciona o escribe un municipio de la provincia de Almería.
-
- El pueblo/ciudad debe pertenecer a la provincia de Almería. -
-
-
- - -
Solo aceptamos códigos postales de Almería (04xxx).
-
-
-
- - -
-
- - -
-
- - -
+ {{ form.as_p }}
Cancelar @@ -80,7 +35,6 @@