From 6d5c9aa6bcdd3352c15c4177c90c96483046bfe0 Mon Sep 17 00:00:00 2001 From: Javier Gea Date: Sat, 22 Nov 2025 14:25:50 +0100 Subject: [PATCH] =?UTF-8?q?Lista=20de=20pedidos=20arreglada,=20informaci?= =?UTF-8?q?=C3=B3n=20de=20pedido=20y=20redirects=20correctos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- essenza/order/tests.py | 134 +++++++++++++++--- essenza/order/urls.py | 4 +- essenza/order/views.py | 87 +++++++++--- essenza/templates/order/order_detail.html | 111 +++++++++++++++ essenza/templates/order/order_list_admin.html | 22 +++ essenza/templates/order/order_list_user.html | 23 ++- essenza/templates/order/order_track.html | 31 +++- 7 files changed, 366 insertions(+), 46 deletions(-) create mode 100644 essenza/templates/order/order_detail.html diff --git a/essenza/order/tests.py b/essenza/order/tests.py index e466c04..f79f55e 100644 --- a/essenza/order/tests.py +++ b/essenza/order/tests.py @@ -8,6 +8,10 @@ User = get_user_model() +# ============================================================ +# TESTS: LISTADO DE PEDIDOS DEL USUARIO +# ============================================================ + class OrderListUserViewTests(TestCase): @classmethod def setUpTestData(cls): @@ -36,31 +40,31 @@ def setUpTestData(cls): is_active=True, ) - # Pedido visible (NO en preparación) del usuario - cls.order_user = Order.objects.create( + # Pedido 1 del usuario + cls.order1 = Order.objects.create( user=cls.user, - status=Status.ENVIADO, + status=Status.EN_PREPARACION, address="Calle 1", ) OrderProduct.objects.create( - order=cls.order_user, + order=cls.order1, product=cls.product, quantity=2 ) - # Pedido del usuario que NO debe salir (EN_PREPARACION) - cls.order_hidden = Order.objects.create( + # Pedido 2 del usuario + cls.order2 = Order.objects.create( user=cls.user, - status=Status.EN_PREPARACION, - address="Calle Oculta", + status=Status.ENVIADO, + address="Calle 2", ) OrderProduct.objects.create( - order=cls.order_hidden, + order=cls.order2, product=cls.product, quantity=1 ) - # Pedido de otro usuario + # Pedido de otro usuario (NO debe salir) cls.order_other = Order.objects.create( user=cls.other_user, status=Status.ENVIADO, @@ -76,9 +80,10 @@ def setUpTestData(cls): def test_user_must_login(self): resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 302) # redirect a login + self.assertEqual(resp.status_code, 302) - def test_user_sees_only_his_non_preparacion_orders(self): + def test_user_sees_only_his_orders(self): + """Debe ver TODOS sus pedidos, incluyendo EN_PREPARACION.""" self.client.login(email="user@test.com", password="1234") resp = self.client.get(self.url) @@ -86,12 +91,21 @@ def test_user_sees_only_his_non_preparacion_orders(self): self.assertTemplateUsed(resp, "order/order_list_user.html") orders = resp.context["orders"] - self.assertEqual(orders.count(), 1) - self.assertEqual(orders.first().id, self.order_user.id) + self.assertEqual(orders.count(), 2) # 🔥 YA NO FILTRAMOS NADA + + # Los IDs deben coincidir + returned_ids = set(o.id for o in orders) + expected_ids = {self.order1.id, self.order2.id} + self.assertEqual(returned_ids, expected_ids) - self.assertContains(resp, "Producto A") self.assertContains(resp, "Calle 1") + self.assertContains(resp, "Calle 2") + + +# ============================================================ +# TESTS: LISTADO DE PEDIDOS DEL ADMIN +# ============================================================ class OrderListAdminViewTests(TestCase): @classmethod @@ -150,6 +164,11 @@ def test_admin_can_view_orders(self): self.assertContains(resp, "user3@test.com") + +# ============================================================ +# TESTS: SEGUIMIENTO DE PEDIDO +# ============================================================ + class OrderTrackViewTests(TestCase): @classmethod def setUpTestData(cls): @@ -183,20 +202,20 @@ def setUpTestData(cls): quantity=1 ) - cls.url = reverse("order_track") # ⬅️ SIN args + cls.url = reverse("order_track") def test_track_get_returns_form(self): resp = self.client.get(self.url) self.assertEqual(resp.status_code, 200) self.assertTemplateUsed(resp, "order/order_track.html") - def test_track_post_valid_shows_order(self): + def test_track_post_valid_redirects_to_detail(self): data = {"order_id": str(self.order.id), "email": "track@test.com"} - resp = self.client.post(self.url, data) + resp = self.client.post(self.url, data, follow=True) self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "order/order_detail.html") self.assertContains(resp, "Direccion de prueba") - self.assertContains(resp, self.order.get_status_display()) def test_track_post_invalid_shows_error(self): data = {"order_id": "9999", "email": "track@test.com"} @@ -204,3 +223,80 @@ def test_track_post_invalid_shows_error(self): self.assertEqual(resp.status_code, 200) self.assertContains(resp, "No se ha encontrado ningún pedido") + + + +# ============================================================ +# TESTS: DETALLE DE PEDIDO +# ============================================================ + +class OrderDetailViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.client = Client() + + cls.user = User.objects.create_user( + username="user_detail", + email="user_detail@test.com", + password="1234", + role="user" + ) + cls.other_user = User.objects.create_user( + username="other_detail", + email="other_detail@test.com", + password="1234", + role="user" + ) + cls.admin = User.objects.create_user( + username="admin_detail", + email="admin_detail@test.com", + password="1234", + role="admin", + is_staff=True + ) + + cls.product = Product.objects.create( + name="Prod Detalle", + brand="Brand", + description="d", + category=Category.MAQUILLAJE, + price="7.00", + stock=10, + is_active=True, + ) + + cls.order = Order.objects.create( + user=cls.user, + status=Status.ENVIADO, + address="Calle detalle", + ) + OrderProduct.objects.create( + order=cls.order, + product=cls.product, + quantity=2 + ) + + cls.url = reverse("order_detail", args=[cls.order.id]) + + def test_user_must_login(self): + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 302) + + def test_owner_can_view_detail(self): + self.client.login(email="user_detail@test.com", password="1234") + resp = self.client.get(self.url) + + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "order/order_detail.html") + self.assertContains(resp, "Prod Detalle") + + def test_other_user_cannot_view_detail(self): + self.client.login(email="other_detail@test.com", password="1234") + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 403) + + def test_admin_can_view_any_detail(self): + self.client.login(email="admin_detail@test.com", password="1234") + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "order/order_detail.html") diff --git a/essenza/order/urls.py b/essenza/order/urls.py index a9e5edc..1dad78c 100644 --- a/essenza/order/urls.py +++ b/essenza/order/urls.py @@ -2,13 +2,15 @@ from .views import ( OrderListAdminView, OrderListUserView, - OrderTrackView + OrderTrackView, + OrderDetailView, ) urlpatterns = [ path("admin/listado/", OrderListAdminView.as_view(), name="order_list_admin"), path("mis-pedidos/", OrderListUserView.as_view(), name="order_list_user"), path("seguimiento/", OrderTrackView.as_view(), name="order_track"), + path("pedido//", OrderDetailView.as_view(), name="order_detail"), ] diff --git a/essenza/order/views.py b/essenza/order/views.py index 907c899..efcb448 100644 --- a/essenza/order/views.py +++ b/essenza/order/views.py @@ -3,6 +3,11 @@ from django.shortcuts import render, redirect from django.db.models import Prefetch from .models import Order, OrderProduct, Status +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django.urls import reverse + + # ======================================================= # LISTADO DE PEDIDOS - ADMIN @@ -38,7 +43,6 @@ class OrderListUserView(LoginRequiredMixin, View): def get(self, request): orders = ( Order.objects.filter(user=request.user) - .exclude(status=Status.EN_PREPARACION) # carrito / en preparación NO .prefetch_related( Prefetch("order_products", queryset=OrderProduct.objects.select_related("product")) ) @@ -54,33 +58,72 @@ class OrderTrackView(View): template_name = "order/order_track.html" def get(self, request): - # Solo formulario - return render(request, self.template_name, {"order": None, "searched": False}) + # Solo muestra el formulario vacío + return render(request, self.template_name, {"searched": False}) def post(self, request): order_id = request.POST.get("order_id", "").strip() email = request.POST.get("email", "").strip().lower() - order = None - error = None - if not order_id or not email: - error = "Debes introducir el número de pedido y el email." - else: - try: - order_pk = int(order_id) - order = ( - Order.objects.select_related("user") - .prefetch_related( - Prefetch("order_products", queryset=OrderProduct.objects.select_related("product")) - ) - .get(pk=order_pk, user__email__iexact=email) + return render( + request, + self.template_name, + { + "searched": True, + "error": "Debes introducir el número de pedido y el email.", + }, + ) + + try: + order_pk = int(order_id) + order = ( + Order.objects + .select_related("user") + .prefetch_related( + Prefetch("order_products", queryset=OrderProduct.objects.select_related("product")) ) - except (ValueError, Order.DoesNotExist): - error = "No se ha encontrado ningún pedido con esos datos." + .get(pk=order_pk, user__email__iexact=email) + ) + except (ValueError, Order.DoesNotExist): + return render( + request, + self.template_name, + { + "searched": True, + "error": "No se ha encontrado ningún pedido con esos datos.", + }, + ) + + return redirect(f"{reverse('order_detail', kwargs={'pk': order.pk})}?from=track") + + - return render( - request, - self.template_name, - {"order": order, "searched": True, "error": error}, +class OrderDetailView(View): + template_name = "order/order_detail.html" + + def get(self, request, pk): + order = ( + Order.objects + .select_related("user") + .prefetch_related( + Prefetch("order_products", queryset=OrderProduct.objects.select_related("product")) + ) + .filter(pk=pk) + .first() ) + + if not order: + raise Http404("Pedido no encontrado") + + # ✅ Permitir anónimos SOLO si vienen del seguimiento + if not request.user.is_authenticated: + if request.GET.get("from") == "track": + return render(request, self.template_name, {"order": order}) + return redirect("login") + + # 🛡 Permisos normales + if request.user.role != "admin" and order.user != request.user: + raise PermissionDenied + + return render(request, self.template_name, {"order": order}) diff --git a/essenza/templates/order/order_detail.html b/essenza/templates/order/order_detail.html new file mode 100644 index 0000000..4bee5f6 --- /dev/null +++ b/essenza/templates/order/order_detail.html @@ -0,0 +1,111 @@ +{% extends "base.html" %} +{% load humanize %} + +{% block title %}Pedido #{{ order.id }} · Essenza{% endblock %} + +{% block content %} + + +
+ +
+

Pedido #{{ order.id }}

+
+ +
+

Estado: {{ order.get_status_display }}

+

Fecha: {{ order.placed_at|date:"d/m/Y H:i" }}

+

Dirección: {{ order.address|default:"Sin dirección" }}

+
+ +

Productos

+ +
+ {% for op in order.order_products.all %} +
+ {% if op.product.photo %} + {{ op.product.name }} + {% else %} + No image + {% endif %} + +
+

{{ op.product.name }}

+

Cantidad: {{ op.quantity }}

+

Precio: {{ op.product.price }} €

+

Subtotal: {{ op.subtotal }} €

+
+
+ {% endfor %} +
+ +
+ Total: {{ order.total_price }} € +
+ + ← Volver + + +
+ +{% endblock %} diff --git a/essenza/templates/order/order_list_admin.html b/essenza/templates/order/order_list_admin.html index 126897f..63f5822 100644 --- a/essenza/templates/order/order_list_admin.html +++ b/essenza/templates/order/order_list_admin.html @@ -100,6 +100,24 @@ color: #6a5f57; } + /* ✅ NUEVO: botón detalle */ + .detail-btn { + display: inline-block; + margin-top: 10px; + padding: 8px 14px; + border-radius: 10px; + background: #c06b3e; + color: white; + font-size: 13px; + font-weight: 700; + text-decoration: none; + transition: all .2s ease; + } + .detail-btn:hover { + background: #a35a34; + transform: translateY(-1px); + } + .empty-box { background: #fff; border: 1px dashed #e7d9cf; @@ -171,6 +189,10 @@

Pedidos (Admin)

Dirección: {{ order.address|default:"-" }}
+ + + Ver detalle del pedido + diff --git a/essenza/templates/order/order_list_user.html b/essenza/templates/order/order_list_user.html index 4bff020..4f5ad8d 100644 --- a/essenza/templates/order/order_list_user.html +++ b/essenza/templates/order/order_list_user.html @@ -108,6 +108,24 @@ color: #6a5f57; } + /* ✅ NUEVO: botón detalle user */ + .detail-btn { + display: inline-block; + margin-top: 10px; + padding: 8px 14px; + border-radius: 10px; + background: #c06b3e; + color: white; + font-size: 13px; + font-weight: 700; + text-decoration: none; + transition: all .2s ease; + } + .detail-btn:hover { + background: #a35a34; + transform: translateY(-1px); + } + .empty-box { background: #fff; border: 1px dashed #e7d9cf; @@ -142,7 +160,6 @@

Mis pedidos

- {# Estado bonito según tu Status #} {% if order.status == "en_preparacion" %} {{ order.get_status_display }} {% elif order.status == "enviado" %} @@ -173,6 +190,10 @@

Mis pedidos

Dirección: {{ order.address|default:"-" }}
+ + + Ver detalle del pedido + diff --git a/essenza/templates/order/order_track.html b/essenza/templates/order/order_track.html index eca8e0f..82868b3 100644 --- a/essenza/templates/order/order_track.html +++ b/essenza/templates/order/order_track.html @@ -50,10 +50,8 @@ padding: 10px 12px; border-radius: 10px; border: 1px solid #ddd; - box-sizing: border-box; - outline: none; - transition: all .2s ease; background: #fafafa; + transition: all .2s ease; } input[type="text"]:focus, input[type="email"]:focus{ background:#fff; @@ -118,6 +116,23 @@ padding-left:18px; } .products-list li{ margin:4px 0; } + + .detail-btn { + display: inline-block; + margin-top: 14px; + padding: 10px 18px; + background: #c06b3e; + color: #fff; + border-radius: 10px; + font-size: 14px; + font-weight: 700; + text-decoration: none; + transition: .2s ease; + } + .detail-btn:hover { + background: #a35a34; + transform: translateY(-1px); + }
@@ -146,18 +161,22 @@

Seguimiento de pedido

{{ error }}
{% elif order %}
+
Pedido
#{{ order.id }}
+
Fecha
{{ order.placed_at|date:"d/m/Y H:i" }}
+
Dirección
{{ order.address|default:"-" }}
+
Estado
@@ -172,6 +191,7 @@

Seguimiento de pedido

{% endif %}
+
Total
{{ order.total_price|floatformat:2 }} €
@@ -187,6 +207,11 @@

Seguimiento de pedido

{% endfor %}
+ + + Ver detalle completo del pedido + +
{% endif %} {% endif %}