Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a307cd2
Merge pull request #62 from WBR4892/release/2.0
WVH7591 Nov 17, 2025
abd2b9b
feat: buscador
YRM2963 Nov 17, 2025
42d4c03
Merge branch 'feature/navegation' of https://github.eii.us.es/WBR4892…
YRM2963 Nov 17, 2025
6fb8131
Merge pull request #65 from WBR4892/hotfix/v2.0.1
WVH7591 Nov 17, 2025
f2a1c7b
Funcionalidad CRUD carrito de compra implementada
Nov 18, 2025
94b6a72
feat: CRUD carrito compras
YRM2963 Nov 19, 2025
70c802a
test: test cart order
YRM2963 Nov 19, 2025
4d06140
Merge branch 'develop' of https://github.eii.us.es/WBR4892/PGPI-G1.11…
YRM2963 Nov 20, 2025
f96a7f1
fix: admin view
YRM2963 Nov 20, 2025
3dc5b48
fix: estilo detalle de producto en admin
WVH7591 Nov 20, 2025
6592845
Merge pull request #63 from WBR4892/feature/navegation
WVH7591 Nov 20, 2025
3c344dc
Merge branch 'develop' of https://github.eii.us.es/WBR4892/PGPI-G1.11…
YRM2963 Nov 20, 2025
cdf3e29
fix: buscador
YRM2963 Nov 20, 2025
9c206e1
Merge branch 'feature/navegation' of https://github.eii.us.es/WBR4892…
YRM2963 Nov 20, 2025
fbc410a
Merge pull request #71 from WBR4892/feature/CRUDcart
WVH7591 Nov 20, 2025
f7ae03f
Merge pull request #72 from WBR4892/feature/navegation
WVH7591 Nov 20, 2025
c27e0c6
Borrador checkout y cambios de variables de entorno y demás
WVH7591 Nov 20, 2025
6cdba86
Borrador provisional con cambios
WVH7591 Nov 20, 2025
5a026a5
Creacion del modelo de carrito Co-authored-by: pakillodecm <pakillode…
Nov 20, 2025
0c70236
Corrección de tests
WVH7591 Nov 20, 2025
6dc70cc
Merge pull request #73 from WBR4892/feature/cart
WVH7591 Nov 20, 2025
4828823
Merge branch 'develop' of https://github.eii.us.es/WBR4892/PGPI-G1.11…
WVH7591 Nov 21, 2025
5b73648
fix: filtros de busqueda
YRM2963 Nov 21, 2025
af26577
fix: redirect admin to stock
YRM2963 Nov 21, 2025
8648168
Merge pull request #74 from WBR4892/feature/navegation
KBC8043 Nov 21, 2025
b9b2da4
Merge pull request #75 from WBR4892/fix/admin_manage
KBC8043 Nov 21, 2025
6ed320f
feat: listado y seguimiento de pedidos
KBC8043 Nov 21, 2025
cd3f4ea
Merge pull request #76 from WBR4892/feature/order_list
WVH7591 Nov 21, 2025
f86425d
Funcionalidad de checkout terminada
Nov 21, 2025
abbb91f
Correccion de conflictos con la rama de feature/order_list
Nov 21, 2025
4979eb2
Arreglos sobre mostrar pedidos de un usuario
Nov 21, 2025
71b5425
Arreglos varios checkout
WVH7591 Nov 21, 2025
3419d97
Arreglos varios
WVH7591 Nov 21, 2025
6d5c9aa
Lista de pedidos arreglada, información de pedido y redirects correctos
KBC8043 Nov 22, 2025
a596d38
Correccion de un test
Nov 22, 2025
734acab
Merge pull request #78 from WBR4892/feature/order_list
WVH7591 Nov 22, 2025
0d465c1
Merge branch 'develop' of https://github.eii.us.es/WBR4892/PGPI-G1.11…
Nov 22, 2025
e4dfc1b
Correcciones varias
WVH7591 Nov 22, 2025
b1af579
Merge pull request #79 from WBR4892/feature/checkout
WVH7591 Nov 22, 2025
b219e62
Arreglo del filtrado: correccion del estilo de las tarjetas de productos
Nov 22, 2025
6b16313
Merge pull request #81 from WBR4892/fix/filter
WVH7591 Nov 22, 2025
c2fc8c5
Funcionalidad gestión de usuario
WVH7591 Nov 22, 2025
7539890
Corrección de condiciones user/admin
Nov 22, 2025
aac2670
Merge pull request #82 from WBR4892/feature/user_management
WBR4892 Nov 22, 2025
d177d2e
Funcionalidad y test completos implementados de la gestión de ventas …
Nov 23, 2025
86e8ee6
Pequeños cambios en la estética
Nov 23, 2025
87dfe69
Merge pull request #83 from WBR4892/feature/gestionVentas
WVH7591 Nov 23, 2025
91467b8
Correccion tests
WVH7591 Nov 23, 2025
5012d35
Merge pull request #84 from WBR4892/feature/gestionVentas
WBR4892 Nov 23, 2025
bb0f7bc
merge branch 'develop' of https://github.com/pc-git/PGPI-G1.11 into d…
YRM2963 Nov 23, 2025
a6a3284
Merge branch 'develop' of https://github.eii.us.es/WBR4892/PGPI-G1.11…
YRM2963 Nov 23, 2025
b9e1931
docs: iteracion 3
YRM2963 Nov 23, 2025
53f078a
Informes de seguimiento, registros (cambios, decisiones, incidencias)…
Nov 24, 2025
f856df2
MS PROJECT de la iteracion 3
Nov 24, 2025
ada9479
Merge pull request #85 from WBR4892/docs/iteracion3
WBR4892 Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ __pycache__/

# Entorno virtual
venv/
.env/
.env
.venv

# Archivos de base de datos
Expand Down
Binary file not shown.
Binary file modified docs/Iteración 1/REGISTRO DE CAMBIOS_v1.0.pdf
Binary file not shown.
Binary file added docs/Iteración 1/SEGUIMIENTO DE COSTES 1.pdf
Binary file not shown.
Binary file not shown.
Binary file added docs/Iteración 1/VALOR GANADO 1.pdf
Binary file not shown.
Binary file added docs/Iteración 2/KANBAN SEGUIMIENTO IT.2.pdf
Binary file not shown.
Binary file added docs/Iteración 2/SEGUIMIENTO DE COSTES 2.pdf
Binary file not shown.
Binary file not shown.
Binary file added docs/Iteración 2/VALOR GANADO 2.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added docs/Iteración 3/REGISTRO DE CAMBIOS_v2.0.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added docs/Iteración 3/SEGUIMIENTO DE COSTES 3.pdf
Binary file not shown.
Binary file not shown.
Binary file added docs/Iteración 3/SPRINT RETROSPECTIVE 3.pdf
Binary file not shown.
Binary file added docs/Iteración 3/SPRINT REVIEW 3.pdf
Binary file not shown.
Binary file added docs/Iteración 3/Sprint Backlog 3.pdf
Binary file not shown.
Binary file added docs/Iteración 3/VALOR GANADO 3.pdf
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added essenza/_sample_assets/profile_pics/user4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added essenza/_sample_assets/profile_pics/user5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added essenza/cart/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions essenza/cart/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.contrib import admin

# Register your models here.
from .models import Cart, CartProduct

# Register your models here.

admin.site.register(Cart)
admin.site.register(CartProduct)
6 changes: 6 additions & 0 deletions essenza/cart/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CartConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'cart'
34 changes: 34 additions & 0 deletions essenza/cart/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.db import models


class Cart(models.Model):
user = models.ForeignKey(
"user.Usuario", on_delete=models.CASCADE, related_name="cart"
)

@property
def total_price(self):
total = 0
for product in self.cart_products.all():
total += product.subtotal
return total

def __str__(self):
return f"Cart {self.id} by {self.user.email}"


class CartProduct(models.Model):
cart = models.ForeignKey(
"cart.Cart", on_delete=models.CASCADE, related_name="cart_products"
)
product = models.ForeignKey(
"product.Product", on_delete=models.CASCADE, related_name="product_carts"
)
quantity = models.IntegerField()

@property
def subtotal(self):
return self.quantity * self.product.price

def __str__(self):
return f"{self.quantity} of {self.product.name} in cart {self.cart.id}"
204 changes: 204 additions & 0 deletions essenza/cart/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from product.models import Category, Product

from cart.models import Cart, CartProduct

# Usamos get_user_model() porque usas un usuario personalizado (user.Usuario)
User = get_user_model()


class CartTests(TestCase):
def setUp(self):
self.client = Client()

# 1. Crear Usuario
self.user = User.objects.create_user(
username="user1",
email="test@example.com",
password="password123",
first_name="Test",
last_name="User",
)

# 2. Crear Producto
# Usamos las choices reales de tu modelo
self.product = Product.objects.create(
name="Producto Test",
description="Descripción de prueba",
category=Category.MAQUILLAJE,
brand="Marca Test",
price=10.00,
stock=50,
is_active=True,
)

# 3. URLs (Sin namespace 'order' según tu urls.py actual)
self.url_detail = reverse("cart_detail")
self.url_add = reverse("add_to_cart", args=[self.product.pk])
self.url_update = reverse("update_cart_item", args=[self.product.pk])
self.url_remove = reverse("remove_from_cart", args=[self.product.pk])

# ---------------------------------------------------------
# BLOQUE 1: DETALLE DEL CARRITO (GET)
# ---------------------------------------------------------

def test_cart_detail_authenticated_empty(self):
"""Usuario logueado sin carrito previo. Debe cargar vacío sin fallar."""
self.client.force_login(self.user)
response = self.client.get(self.url_detail)
self.assertEqual(response.status_code, 200)
# Tu vista pasa 'cart_products' vacío si falla el try/except o no hay carrito
self.assertEqual(len(response.context.get("cart_products", [])), 0)

def test_cart_detail_authenticated_with_items(self):
"""Usuario logueado con carrito en DB."""
self.client.force_login(self.user)

# Setup DB
cart = Cart.objects.create(user=self.user)
CartProduct.objects.create(cart=cart, product=self.product, quantity=2)

response = self.client.get(self.url_detail)

self.assertEqual(response.status_code, 200)
# Verifica que lee de la DB
self.assertEqual(len(response.context["cart_products"]), 1)
self.assertEqual(response.context["total_price"], 20.00) # 2 * 10.00

def test_cart_detail_anonymous_session(self):
"""Usuario anónimo con datos en sesión."""
self.client.logout()

# Inyectar sesión
session = self.client.session
session["cart_session"] = {
str(self.product.pk): {"quantity": 3, "price": "10.00"}
}
session.save()

response = self.client.get(self.url_detail)

self.assertEqual(response.status_code, 200)
# Tu vista pasa 'cart_products' también para anónimos (lo vi en tu código)
self.assertEqual(len(response.context["cart_products"]), 1)
self.assertEqual(response.context["total_price"], 30.00) # 3 * 10.00

# ---------------------------------------------------------
# BLOQUE 2: AÑADIR AL CARRITO (POST)
# ---------------------------------------------------------

def test_add_item_authenticated(self):
"""Añadir ítem crea Cart y CartProduct en DB."""
self.client.force_login(self.user)

response = self.client.post(self.url_add, {"quantity": 1})
response = self.client.post(self.url_add, {"quantity": 3})

self.assertRedirects(response, self.url_detail)

# Verificar DB
cart = Cart.objects.get(user=self.user)
cp = CartProduct.objects.get(cart=cart, product=self.product)
self.assertEqual(cp.quantity, 4)

def test_add_item_anonymous(self):
"""Añadir ítem guarda en Sesión."""
self.client.logout()

response = self.client.post(self.url_add, {"quantity": 1})

self.assertRedirects(response, self.url_detail)

session = self.client.session
self.assertIn("cart_session", session)
self.assertEqual(session["cart_session"][str(self.product.pk)]["quantity"], 1)

def test_add_item_out_of_stock(self):
"""No se debe poder añadir productos sin stock."""
self.product.stock = 0
self.product.save()

self.client.force_login(self.user)

# Debería redirigir al catálogo (o donde definas 'catalog') y mostrar error
# Como no sé tu URL 'catalog', verificamos que NO se creó el CartProduct
self.assertFalse(CartProduct.objects.filter(product=self.product).exists())

# ---------------------------------------------------------
# BLOQUE 3: ACTUALIZAR (POST) - AQUI ESTÁ EL PELIGRO
# ---------------------------------------------------------

def test_auth_update_item(self):
"""
Verifica update para logueados.
NOTA: Este test está 'trucado' para que pase con tu bug actual.
"""
self.client.force_login(self.user)
cart = Cart.objects.create(user=self.user)

# TRUCO: Forzamos que el ID del CartProduct sea igual al del Product
# para que tu vista rota (pk=product_id) lo encuentre.
cp = CartProduct(
id=self.product.pk, cart=cart, product=self.product, quantity=1
)
cp.save()

response = self.client.post(self.url_update, {"quantity": 5})

self.assertRedirects(response, self.url_detail)
cp.refresh_from_db()
self.assertEqual(cp.quantity, 5)

def test_anon_update_item(self):
"""Verifica update para anónimos (Sesión)."""
self.client.logout()
# Añadimos primero
self.client.post(self.url_add, {"quantity": 1})

# Actualizamos
response = self.client.post(self.url_update, {"quantity": 4})

self.assertRedirects(response, self.url_detail)
session = self.client.session
self.assertEqual(session["cart_session"][str(self.product.pk)]["quantity"], 4)

# ---------------------------------------------------------
# BLOQUE 4: ELIMINAR (POST)
# ---------------------------------------------------------

def test_auth_remove_item(self):
"""
Verifica borrado para logueados.
NOTA: También trucado por tu bug.
"""
self.client.force_login(self.user)
cart = Cart.objects.create(user=self.user)

# TRUCO: ID CartProduct == ID Product
cp = CartProduct(
id=self.product.pk, cart=cart, product=self.product, quantity=1
)
cp.save()

response = self.client.post(self.url_remove)

self.assertRedirects(response, self.url_detail)
self.assertFalse(CartProduct.objects.filter(pk=cp.pk).exists())

def test_anon_remove_item(self):
"""Verifica borrado para anónimos."""
self.client.logout()
# Setup sesión
session = self.client.session
session["cart_session"] = {
str(self.product.pk): {"quantity": 1, "price": "10.00"}
}
session.save()

response = self.client.post(self.url_remove)

self.assertRedirects(response, self.url_detail)
session = self.client.session
self.assertNotIn(str(self.product.pk), session["cart_session"])
18 changes: 18 additions & 0 deletions essenza/cart/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.urls import path

from .views import AddToCartView, CartDetailView, RemoveFromCartView, UpdateCartItemView

urlpatterns = [
path("", CartDetailView.as_view(), name="cart_detail"),
path("add/<int:product_id>/", AddToCartView.as_view(), name="add_to_cart"),
path(
"update/<int:product_id>/",
UpdateCartItemView.as_view(),
name="update_cart_item",
),
path(
"remove/<int:product_id>/",
RemoveFromCartView.as_view(),
name="remove_from_cart",
),
]
Loading