From 6ed320f9f781c73bfee047478cf09751f5b78e34 Mon Sep 17 00:00:00 2001 From: Javier Gea Date: Fri, 21 Nov 2025 12:46:21 +0100 Subject: [PATCH] feat: listado y seguimiento de pedidos --- essenza/essenza/urls.py | 2 + essenza/order/tests.py | 206 ++++++++++++++++++ essenza/order/urls.py | 14 ++ essenza/order/views.py | 86 ++++++++ essenza/templates/order/order_list_admin.html | 185 ++++++++++++++++ essenza/templates/order/order_list_user.html | 188 ++++++++++++++++ essenza/templates/order/order_track.html | 197 +++++++++++++++++ 7 files changed, 878 insertions(+) create mode 100644 essenza/templates/order/order_list_admin.html create mode 100644 essenza/templates/order/order_list_user.html create mode 100644 essenza/templates/order/order_track.html diff --git a/essenza/essenza/urls.py b/essenza/essenza/urls.py index 79197362..708c6397 100644 --- a/essenza/essenza/urls.py +++ b/essenza/essenza/urls.py @@ -14,6 +14,8 @@ path("catalog/", CatalogView.as_view(), name="catalog"), path("catalog//", CatalogDetailView.as_view(), name="catalog_detail"), path("cart/", include("cart.urls")), + path("order/", include("order.urls")), + ] if settings.DEBUG: diff --git a/essenza/order/tests.py b/essenza/order/tests.py index e69de29b..e466c04e 100644 --- a/essenza/order/tests.py +++ b/essenza/order/tests.py @@ -0,0 +1,206 @@ +from django.test import TestCase, Client +from django.urls import reverse +from django.contrib.auth import get_user_model + +from product.models import Product, Category +from order.models import Order, OrderProduct, Status + +User = get_user_model() + + +class OrderListUserViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.client = Client() + + cls.user = User.objects.create_user( + username="user1", + email="user@test.com", + password="1234", + role="user" + ) + cls.other_user = User.objects.create_user( + username="user2", + email="user2@test.com", + password="1234", + role="user" + ) + + cls.product = Product.objects.create( + name="Producto A", + brand="Marca A", + description="Desc", + category=Category.MAQUILLAJE, + price="10.00", + stock=10, + is_active=True, + ) + + # Pedido visible (NO en preparación) del usuario + cls.order_user = Order.objects.create( + user=cls.user, + status=Status.ENVIADO, + address="Calle 1", + ) + OrderProduct.objects.create( + order=cls.order_user, + product=cls.product, + quantity=2 + ) + + # Pedido del usuario que NO debe salir (EN_PREPARACION) + cls.order_hidden = Order.objects.create( + user=cls.user, + status=Status.EN_PREPARACION, + address="Calle Oculta", + ) + OrderProduct.objects.create( + order=cls.order_hidden, + product=cls.product, + quantity=1 + ) + + # Pedido de otro usuario + cls.order_other = Order.objects.create( + user=cls.other_user, + status=Status.ENVIADO, + address="Otra calle", + ) + OrderProduct.objects.create( + order=cls.order_other, + product=cls.product, + quantity=1 + ) + + cls.url = reverse("order_list_user") + + def test_user_must_login(self): + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 302) # redirect a login + + def test_user_sees_only_his_non_preparacion_orders(self): + self.client.login(email="user@test.com", password="1234") + resp = self.client.get(self.url) + + self.assertEqual(resp.status_code, 200) + 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.assertContains(resp, "Producto A") + self.assertContains(resp, "Calle 1") + + +class OrderListAdminViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.client = Client() + + cls.admin = User.objects.create_user( + username="admin1", + email="admin@test.com", + password="1234", + role="admin", + is_staff=True + ) + + cls.user = User.objects.create_user( + username="user3", + email="user3@test.com", + password="1234", + role="user" + ) + + cls.product = Product.objects.create( + name="Prod", + brand="Brand", + description="d", + category=Category.PERFUME, + price="5.00", + stock=10, + is_active=True, + ) + + cls.order = Order.objects.create( + user=cls.user, + status=Status.ENVIADO, + address="Dir", + ) + OrderProduct.objects.create( + order=cls.order, + product=cls.product, + quantity=1 + ) + + cls.url = reverse("order_list_admin") + + def test_admin_must_login(self): + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 302) + + def test_admin_can_view_orders(self): + self.client.login(email="admin@test.com", password="1234") + resp = self.client.get(self.url) + + self.assertEqual(resp.status_code, 200) + self.assertTemplateUsed(resp, "order/order_list_admin.html") + self.assertContains(resp, "Prod") + self.assertContains(resp, "user3@test.com") + + +class OrderTrackViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.client = Client() + + cls.user = User.objects.create_user( + username="trackuser", + email="track@test.com", + password="1234", + role="user" + ) + + cls.product = Product.objects.create( + name="Producto Track", + brand="Marca Track", + description="Desc", + category=Category.CABELLO, + price="12.00", + stock=5, + is_active=True, + ) + + cls.order = Order.objects.create( + user=cls.user, + status=Status.ENVIADO, + address="Direccion de prueba", + ) + OrderProduct.objects.create( + order=cls.order, + product=cls.product, + quantity=1 + ) + + cls.url = reverse("order_track") # ⬅️ SIN args + + 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): + data = {"order_id": str(self.order.id), "email": "track@test.com"} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 200) + 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"} + resp = self.client.post(self.url, data) + + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, "No se ha encontrado ningún pedido") diff --git a/essenza/order/urls.py b/essenza/order/urls.py index e69de29b..a9e5edc6 100644 --- a/essenza/order/urls.py +++ b/essenza/order/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from .views import ( + OrderListAdminView, + OrderListUserView, + OrderTrackView +) + +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"), + +] + diff --git a/essenza/order/views.py b/essenza/order/views.py index e69de29b..907c8999 100644 --- a/essenza/order/views.py +++ b/essenza/order/views.py @@ -0,0 +1,86 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.views import View +from django.shortcuts import render, redirect +from django.db.models import Prefetch +from .models import Order, OrderProduct, Status + +# ======================================================= +# LISTADO DE PEDIDOS - ADMIN +# ======================================================= +class OrderListAdminView(LoginRequiredMixin, UserPassesTestMixin, View): + template_name = "order/order_list_admin.html" + + def test_func(self): + return self.request.user.is_authenticated and self.request.user.role == "admin" + + def handle_no_permission(self): + if not self.request.user.is_authenticated: + return redirect("login") + return redirect("dashboard") + + def get(self, request): + orders = ( + Order.objects.select_related("user") + .prefetch_related( + Prefetch("order_products", queryset=OrderProduct.objects.select_related("product")) + ) + .order_by("-placed_at") + ) + return render(request, self.template_name, {"orders": orders}) + + +# ======================================================= +# LISTADO DE PEDIDOS - USER +# ======================================================= +class OrderListUserView(LoginRequiredMixin, View): + template_name = "order/order_list_user.html" + + 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")) + ) + .order_by("-placed_at") + ) + return render(request, self.template_name, {"orders": orders}) + + +# ======================================================= +# SEGUIMIENTO SIN LOGIN +# ======================================================= +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}) + + 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) + ) + except (ValueError, Order.DoesNotExist): + error = "No se ha encontrado ningún pedido con esos datos." + + return render( + request, + self.template_name, + {"order": order, "searched": True, "error": error}, + ) diff --git a/essenza/templates/order/order_list_admin.html b/essenza/templates/order/order_list_admin.html new file mode 100644 index 00000000..126897f3 --- /dev/null +++ b/essenza/templates/order/order_list_admin.html @@ -0,0 +1,185 @@ +{% extends "base.html" %} +{% load humanize %} + +{% block title %}Listado de pedidos · Admin · Essenza{% endblock %} + +{% block content %} + + +
+
+
+

Pedidos (Admin)

+

Listado completo de pedidos realizados.

+
+ + {% if orders %} + {% for order in orders %} +
+
+
+
Pedido #{{ order.id }}
+
{{ order.placed_at|date:"d/m/Y H:i" }}
+
+ +
+
+ Cliente: {{ order.user.username }} ({{ order.user.email }}) +
+
ID usuario: {{ order.user.id }}
+
+ +
+ {% if order.status == "en_preparacion" %} + {{ order.get_status_display }} + {% elif order.status == "enviado" %} + {{ order.get_status_display }} + {% elif order.status == "entregado" %} + {{ order.get_status_display }} + {% else %} + {{ order.get_status_display }} + {% endif %} +
+
+ +
+
+ Productos +
    + {% for op in order.order_products.all %} +
  • {{ op.product.name }} × {{ op.quantity }} — {{ op.subtotal|floatformat:2 }} €
  • + {% empty %} +
  • -
  • + {% endfor %} +
+
+ +
+
+ Total: {{ order.total_price|floatformat:2 }} € +
+
+ Dirección: {{ order.address|default:"-" }} +
+
+
+
+ {% endfor %} + {% else %} +
+

No hay pedidos para mostrar.

+
+ {% endif %} +
+
+{% endblock %} diff --git a/essenza/templates/order/order_list_user.html b/essenza/templates/order/order_list_user.html new file mode 100644 index 00000000..4bff0203 --- /dev/null +++ b/essenza/templates/order/order_list_user.html @@ -0,0 +1,188 @@ +{% extends "base.html" %} +{% load humanize %} + +{% block title %}Mis pedidos · Essenza{% endblock %} + +{% block content %} + + +
+
+
+

Mis pedidos

+

Consulta tu historial de compras y su estado.

+
+ + {% if orders %} + {% for order in orders %} +
+
+
+ Pedido #{{ order.id }}
+ + {{ order.placed_at|date:"d/m/Y H:i" }} + +
+ + {# Estado bonito según tu Status #} + {% if order.status == "en_preparacion" %} + {{ order.get_status_display }} + {% elif order.status == "enviado" %} + {{ order.get_status_display }} + {% elif order.status == "entregado" %} + {{ order.get_status_display }} + {% else %} + {{ order.get_status_display }} + {% endif %} +
+ +
+
+ Productos +
    + {% for op in order.order_products.all %} +
  • {{ op.product.name }} × {{ op.quantity }} — {{ op.subtotal|floatformat:2 }} €
  • + {% empty %} +
  • -
  • + {% endfor %} +
+
+ +
+
+ Total: {{ order.total_price|floatformat:2 }} € +
+
+ Dirección: {{ order.address|default:"-" }} +
+
+
+
+ {% endfor %} + {% else %} +
+

No tienes pedidos todavía.

+

Cuando compres algo, aparecerá aquí ✨

+
+ {% endif %} +
+
+{% endblock %} diff --git a/essenza/templates/order/order_track.html b/essenza/templates/order/order_track.html new file mode 100644 index 00000000..eca8e0fb --- /dev/null +++ b/essenza/templates/order/order_track.html @@ -0,0 +1,197 @@ +{% extends "base.html" %} +{% load humanize %} + +{% block title %}Seguimiento de pedido · Essenza{% endblock %} + +{% block content %} + + +
+
+
+
+

Seguimiento de pedido

+

Introduce el número de pedido y tu email para ver el estado.

+
+ +
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+ + {% if searched %} + {% if error %} +
{{ error }}
+ {% elif order %} +
+
+
Pedido
+
#{{ order.id }}
+
+
+
Fecha
+
{{ order.placed_at|date:"d/m/Y H:i" }}
+
+
+
Dirección
+
{{ order.address|default:"-" }}
+
+
+
Estado
+
+ {% if order.status == "en_preparacion" %} + {{ order.get_status_display }} + {% elif order.status == "enviado" %} + {{ order.get_status_display }} + {% elif order.status == "entregado" %} + {{ order.get_status_display }} + {% else %} + {{ order.get_status_display }} + {% endif %} +
+
+
+
Total
+
{{ order.total_price|floatformat:2 }} €
+
+ +
+
Productos
+
    + {% for op in order.order_products.all %} +
  • {{ op.product.name }} × {{ op.quantity }} — {{ op.subtotal|floatformat:2 }} €
  • + {% empty %} +
  • -
  • + {% endfor %} +
+
+
+ {% endif %} + {% endif %} + +
+
+
+{% endblock %}