diff --git a/essenza/db.sqlite3 b/essenza/db.sqlite3 index a07469d..836a999 100644 Binary files a/essenza/db.sqlite3 and b/essenza/db.sqlite3 differ diff --git a/essenza/essenza/settings.py b/essenza/essenza/settings.py index 82d2acc..662ee0e 100644 --- a/essenza/essenza/settings.py +++ b/essenza/essenza/settings.py @@ -46,6 +46,7 @@ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -63,6 +64,7 @@ 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.request', + 'django.template.context_processors.i18n', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], @@ -106,7 +108,7 @@ # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'es' TIME_ZONE = 'UTC' diff --git a/essenza/essenza/urls.py b/essenza/essenza/urls.py index 49f832d..e08e7df 100644 --- a/essenza/essenza/urls.py +++ b/essenza/essenza/urls.py @@ -3,6 +3,8 @@ from django.http import HttpResponse from info.views import info_view from product.views import EscaparateView +import user + def home(request): html = """ @@ -45,15 +47,20 @@ def home(request): transition: background-color 0.3s; } .info-button:hover { background-color: #a35a34; } - .login-button { - position: absolute; - top: 60%; - left: 50%; - transform: translate(-50%, -50%); + + .button-container { + margin-top: 30px; /* Espacio desde el texto de arriba */ + display: flex; + flex-direction: column; /* Apila los botones verticalmente */ + align-items: center; /* Centra los botones horizontalmente */ + gap: 20px; /* Espacio automático entre cada botón */ + } + + .action-button { padding: 15px 35px; background-color: #c06b3e; color: white; - font-size: 20px; + font-size: 15px; font-weight: bold; border: none; border-radius: 10px; @@ -61,11 +68,17 @@ def home(request): box-shadow: 0 4px 8px rgba(0,0,0,0.2); text-decoration: none; transition: background-color 0.3s, transform 0.2s; + + display: block; + width: 300px; /* Ancho fijo para que se vean uniformes */ + box-sizing: border-box; /* Para que el padding no afecte el ancho */ } - .login-button:hover { + + .action-button:hover { background-color: #a35a34; - transform: translate(-50%, -50%) scale(1.05); + transform: scale(1.05); /* Efecto de zoom simple */ } + @@ -74,7 +87,11 @@ def home(request):

Tu espacio online de cosmética natural, belleza y cuidado personal.

Explora nuestros productos, descubre nuevas fragancias y disfruta de la experiencia Essenza 🌸

- Iniciar sesión +
+ Registro + Iniciar sesión + Continuar como invitado +
""" @@ -86,5 +103,4 @@ def home(request): path("user/", include("user.urls")), path('admin/', admin.site.urls), path('escaparate/', EscaparateView.as_view(), name='escaparate') -] - +] \ No newline at end of file diff --git a/essenza/templates/product/escaparate.html b/essenza/templates/product/escaparate.html index d76efec..5fbbd77 100644 --- a/essenza/templates/product/escaparate.html +++ b/essenza/templates/product/escaparate.html @@ -105,18 +105,26 @@ - -
+
ESSENZA
-
- {% csrf_token %} - -
-
- - + {% if user.is_authenticated %} + +
+ {% csrf_token %} + +
+ + {% else %} + + + Log in + + + {% endif %} +
+ \ No newline at end of file diff --git a/essenza/templates/user/register.html b/essenza/templates/user/register.html new file mode 100644 index 0000000..1ff83b2 --- /dev/null +++ b/essenza/templates/user/register.html @@ -0,0 +1,176 @@ +{% load static %} + + + + + Registro · Essenza + + + + +
+

ESSENZA

+
+
+ {% csrf_token %} + + + {{ form.first_name }} + + + {{ form.last_name }} + + + {{ form.username }} + + + {{ form.email }} + + + {{ form.password1 }} + + + {{ form.password2 }} + + + {{ form.foto }} + + {% if form.errors %} +
+ {% for field in form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} + {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} + + + + +
+
+
+ + \ No newline at end of file diff --git a/essenza/user/forms.py b/essenza/user/forms.py index ac41549..70b77fa 100644 --- a/essenza/user/forms.py +++ b/essenza/user/forms.py @@ -1,5 +1,6 @@ -# user/forms.py from django import forms +from django.contrib.auth.forms import UserCreationForm +from .models import Usuario class LoginForm(forms.Form): email = forms.CharField( @@ -10,3 +11,28 @@ class LoginForm(forms.Form): label="Contraseña", widget=forms.PasswordInput(attrs={"placeholder": "Introduce tu contraseña"}) ) + +class RegisterForm(UserCreationForm): + + first_name = forms.CharField( + label="Nombre", + required=True + ) + last_name = forms.CharField( + label="Apellidos", + required=True + ) + email = forms.EmailField( + label="Correo electrónico", + required=True + ) + foto = forms.ImageField( + label="Foto (Opcional)", + required=False + ) + + class Meta(UserCreationForm.Meta): + + model = Usuario + + fields = UserCreationForm.Meta.fields + ('first_name', 'last_name', 'email', 'foto') diff --git a/essenza/user/models.py b/essenza/user/models.py index 76688e1..7a92bfb 100644 --- a/essenza/user/models.py +++ b/essenza/user/models.py @@ -9,9 +9,9 @@ class Usuario(AbstractUser): foto = models.ImageField(upload_to='images/', null=True, blank=True) role = models.CharField(max_length=10, choices=Role.choices, default=Role.USER) - email = models.EmailField(unique=True) # <-- aseguramos emails únicos + email = models.EmailField(unique=True) - USERNAME_FIELD = 'email' # <-- se usará email para login + USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] def __str__(self): diff --git a/essenza/user/tests.py b/essenza/user/tests.py index f6d5c17..52594ea 100644 --- a/essenza/user/tests.py +++ b/essenza/user/tests.py @@ -1,11 +1,12 @@ -from django.test import TestCase - -# Create your tests here. -# user/tests/test_login.py +import tempfile from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile +import io +import os + User = get_user_model() @@ -20,7 +21,7 @@ def setUp(self): email=self.email, password=self.password ) - self.login_url = reverse("user:login") + self.login_url = reverse("login") self.home_url = reverse("home") self.escaparate_url = reverse("escaparate") @@ -58,3 +59,114 @@ def test_login_with_invalid_password_shows_error(self): resp = self.client.post(self.login_url, data) self.assertEqual(resp.status_code, 200) self.assertContains(resp, "Usuario o contraseña incorrectos") + + +class RegisterViewTests(TestCase): + def setUp(self): + self.register_url = reverse("register") + self.escaparate_url = reverse("escaparate") + self.initial_user_count = User.objects.count() + + # Datos para un nuevo usuario de prueba + self.valid_data = { + 'first_name': 'Juan', + 'last_name': 'Perez', + 'username': 'nuevo_usuario', + 'email': 'nuevo@ejemplo.com', + 'password1': 'PasswordSeguro123', + 'password2': 'PasswordSeguro123', + } + + #1. Comprueba que la página de registro carga correctamente + def test_get_register_page_returns_200(self): + resp = self.client.get(self.register_url) + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, "Crear cuenta") + self.assertContains(resp, "ESSENZA") + + #2. Registro con datos válidos y redirige al escaparate (302) + def test_successful_registration_redirects_and_creates_user(self): + data = self.valid_data.copy() + resp = self.client.post(self.register_url, data, follow=False) + + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp["Location"], self.escaparate_url) + self.assertEqual(User.objects.count(), self.initial_user_count + 1) + + new_user = User.objects.get(email=data['email']) + self.assertTrue(new_user.check_password(data['password1'])) # La contraseña está hasheada + + + #3. Registro con email duplicado muestra error + def test_registration_with_duplicate_email_shows_error(self): + User.objects.create_user(username='test', email=self.valid_data['email'], password='test') # Usuario previo creado con mismo email + data = self.valid_data.copy() + resp = self.client.post(self.register_url, data) #Intento de registro con el mismo email + + self.assertEqual(User.objects.count(), self.initial_user_count + 1) #Solo se añade el usuario creado antes + self.assertContains(resp, "Ya existe Usuario con este Email.", html=True) + + + #4. Registro con contraseñas que no coinciden muestra error + def test_registration_with_mismatched_passwords_shows_error(self): + data = self.valid_data.copy() + data['password2'] = 'diferente123' + resp = self.client.post(self.register_url, data) + + self.assertEqual(User.objects.count(), self.initial_user_count) + self.assertContains(resp, "Los dos campos de contraseña no coinciden.") + + + #5. Registro con campo 'first_name' vacío muestra error (required=True) + def test_registration_missing_first_name_shows_error(self): + data = self.valid_data.copy() + data['first_name'] = '' + resp = self.client.post(self.register_url, data) + + self.assertEqual(User.objects.count(), self.initial_user_count) + self.assertContains(resp, "Este campo es obligatorio.") + + + #6. Registro con subida de foto válida + def test_registration_with_valid_photo(self): + # Creo una foto JPEG en memoria + try: + from PIL import Image + buf = io.BytesIO() + img = Image.new('RGB', (1, 1), color=(255, 0, 0)) + img.save(buf, format='JPEG') + image_data = buf.getvalue() + except Exception: + self.skipTest('Pillow is required to create a test JPEG image') + + photo = SimpleUploadedFile( + name='test_photo.jpg', + content=image_data, + content_type='image/jpeg' + ) + + data = self.valid_data.copy() + data['foto'] = photo + resp = self.client.post(self.register_url, data, follow=False) + + self.assertEqual(resp.status_code, 302) + new_user = User.objects.get(email=data['email']) + self.assertTrue(new_user.foto.name.startswith('images/test_photo')) + + # Elimina la foto creada + if new_user.foto: + if os.path.exists(new_user.foto.path): + os.remove(new_user.foto.path) + + + #7. Registro sin campo 'foto' (opcional) es exitoso + def test_registration_without_photo_is_successful(self): + data = self.valid_data.copy() + if 'foto' in data: + del data['foto'] + + resp = self.client.post(self.register_url, data, follow=False) + + self.assertEqual(resp.status_code, 302) + new_user = User.objects.get(email=data['email']) + self.assertFalse(new_user.foto) \ No newline at end of file diff --git a/essenza/user/urls.py b/essenza/user/urls.py index fce363f..26825a4 100644 --- a/essenza/user/urls.py +++ b/essenza/user/urls.py @@ -1,13 +1,12 @@ from django.contrib import admin from django.urls import include, path from django.http import HttpResponse -from user import views +import user.views as views -app_name = "user" urlpatterns = [ + path('register/', views.RegisterView.as_view(), name='register'), path('login/', views.LoginView.as_view(), name='login'), path('logout/', views.LogoutView.as_view(), name='logout'), - ] diff --git a/essenza/user/views.py b/essenza/user/views.py index 8cf1432..25774b6 100644 --- a/essenza/user/views.py +++ b/essenza/user/views.py @@ -1,8 +1,8 @@ from django.shortcuts import render, redirect from django.views import View from django.contrib.auth import authenticate, login, logout -from .forms import LoginForm - +from .forms import LoginForm, RegisterForm +from .models import Usuario class LoginView(View): form_class = LoginForm @@ -35,14 +35,10 @@ def post(self, request, *args, **kwargs): return render(request, self.template_name, {'form': form}) - class LogoutView(View): - """Cierra la sesión y borra la cookie de sesión.""" - def get(self, request): logout(request) response = redirect('home') - # 🔥 borra cookie de sesión en el navegador response.delete_cookie('sessionid') return response @@ -50,4 +46,21 @@ def post(self, request): logout(request) response = redirect('home') response.delete_cookie('sessionid') - return response \ No newline at end of file + return response + +class RegisterView(View): + form_class = RegisterForm + template_name = 'user/register.html' + + def get(self, request, *args, **kwargs): + form = self.form_class() + return render(request, self.template_name, {'form': form}) + + def post(self, request, *args, **kwargs): + form = self.form_class(request.POST, request.FILES) + + if form.is_valid(): + user = form.save() + return redirect('escaparate') + + return render(request, self.template_name, {'form': form}) \ No newline at end of file