Compare commits

..

2 Commits

Author SHA1 Message Date
elordenador 848a49c92d feat: add BlankToNoneCharField for handling empty strings in models and update Cart model to use it
fix: update view functions to require appropriate HTTP methods
2026-05-26 11:48:04 +02:00
elordenador ac9efaaf91 fix: update delete review URL to use review ID instead of product ID 2026-05-26 10:35:17 +02:00
4 changed files with 109 additions and 107 deletions
+33 -1
View File
@@ -11,6 +11,31 @@ import random, string
MAX_QUANTITY = 9999 MAX_QUANTITY = 9999
class BlankToNoneCharField(models.CharField):
"""Treat empty strings as None in Python, but store as empty strings in DB."""
def to_python(self, value):
value = super().to_python(value)
if value == "":
return None
return value
def from_db_value(self, value, expression, connection):
if value == "":
return None
return value
def get_prep_value(self, value):
if value is None or value == "":
return ""
return super().get_prep_value(value)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
path = "django.db.models.CharField"
return name, path, args, kwargs
def generate_transaction_code() -> str: def generate_transaction_code() -> str:
while True: while True:
code = f"{TRANSACTION_CODE_PREFIX}{get_random_string(TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET)}" code = f"{TRANSACTION_CODE_PREFIX}{get_random_string(TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET)}"
@@ -194,9 +219,16 @@ class StockReservationItem(models.Model):
class Cart(models.Model): class Cart(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
session_key = models.CharField(max_length=40, default="", blank=True) session_key = BlankToNoneCharField(max_length=40, default="", blank=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if self.session_key is None:
self.session_key = ""
super().save(*args, **kwargs)
if self.session_key == "":
self.session_key = None
def __str__(self): def __str__(self):
return f"Cart {self.id} - {self.user or self.session_key}" return f"Cart {self.id} - {self.user or self.session_key}"
+1 -1
View File
@@ -78,7 +78,7 @@
{% elif user_has_review %} {% elif user_has_review %}
<div class="ms-auto"> <div class="ms-auto">
<a href="{% url 'add_review' product.id %}" class="btn btn-sm btn-outline-primary">Editar mi valoración</a> <a href="{% url 'add_review' product.id %}" class="btn btn-sm btn-outline-primary">Editar mi valoración</a>
<form method="post" action="{% url 'delete_review' product.id %}" style="display:inline;"> <form method="post" action="{% url 'delete_review' user_review_id %}" style="display:inline;">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('¿Eliminar esta valoración?');">Eliminar</button> <button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('¿Eliminar esta valoración?');">Eliminar</button>
</form> </form>
+1 -1
View File
@@ -70,6 +70,6 @@ urlpatterns = [
path("reset-password", views.reset_password, name="reset_password"), path("reset-password", views.reset_password, name="reset_password"),
path("reset-password-phase2/<str:code>", views.reset_password_phase2, name="reset_password_phase2"), path("reset-password-phase2/<str:code>", views.reset_password_phase2, name="reset_password_phase2"),
path("producto/<int:product_id>/valorar/", views.add_review, name="add_review"), path("producto/<int:product_id>/valorar/", views.add_review, name="add_review"),
path("producto/<int:product_id>/valorar/eliminar/", views.delete_review, name="delete_review"), path("valoracion/<int:review_id>/eliminar/", views.delete_review, name="delete_review"),
path("api/producto/<int:product_id>/valoraciones/", views.product_reviews, name="product_reviews"), path("api/producto/<int:product_id>/valoraciones/", views.product_reviews, name="product_reviews"),
] ]
+74 -104
View File
@@ -19,7 +19,7 @@ from .vars import (
) )
from django.conf import settings from django.conf import settings
from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.http import require_POST from django.views.decorators.http import require_GET, require_POST, require_http_methods
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from decimal import Decimal, ROUND_HALF_UP from decimal import Decimal, ROUND_HALF_UP
@@ -204,6 +204,7 @@ def get_price_with_vat_decimal(price) -> Decimal:
rounding=ROUND_HALF_UP, rounding=ROUND_HALF_UP,
) )
@require_http_methods(["GET"])
def home(request: HttpRequest): def home(request: HttpRequest):
"""Página de inicio del sitio""" """Página de inicio del sitio"""
categorias = Category.objects.all() categorias = Category.objects.all()
@@ -219,7 +220,7 @@ def home(request: HttpRequest):
"total_sellers": total_sellers "total_sellers": total_sellers
}) })
@require_http_methods(["GET"])
def index(request: HttpRequest): def index(request: HttpRequest):
"""Página de productos (lista paginada)""" """Página de productos (lista paginada)"""
page = 1 page = 1
@@ -233,7 +234,7 @@ def index(request: HttpRequest):
categorias = Category.objects.all() categorias = Category.objects.all()
return render(request, "tienda/index.html", {"products": products, "categories": categorias}) return render(request, "tienda/index.html", {"products": products, "categories": categorias})
@require_http_methods(["GET", "POST"])
def login(request: HttpRequest): def login(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
form: UserLoginForm = UserLoginForm(request.POST) form: UserLoginForm = UserLoginForm(request.POST)
@@ -291,47 +292,7 @@ def login(request: HttpRequest):
form = UserLoginForm() form = UserLoginForm()
return render(request, "tienda/login.html", {"form": form}) return render(request, "tienda/login.html", {"form": form})
# @require_http_methods(["GET", "POST"])
# if user is not None:
# auth_login(request, user)
#
# # Configurar duración de sesión
# if not remember:
# request.session.set_expiry(0)
# else:
# request.session.set_expiry(1209600) # 14 días en segundos
#
# audit_logger.info(
# "LOGIN_SUCCESS user_id=%s email=%s ip=%s remember=%s",
# user.id,
# user.email,
# client_ip,
# bool(remember),
# )
# tasks.enviar_correo_bienvenida.delay(user.email, "{} {}".format(user.first_name, user.last_name))
# # result = send_email(user.email, "Inicio de sesión correcto", login_message.format(name = "{} {}".format(user.first_name, user.last_name)))
# messages.success(request, f"¡Bienvenido {user.first_name or user.username}!")
# return redirect("index")
# else:
# user1: User = User.objects.get(username=username)
# if user1.registration_status == User.RegisterStatus.BANNED:
# audit_logger.warning(
# "LOGIN FAILED email=%s reason=user_banned ip=%s",
# email,
# client_ip,
# )
# messages.error(request, "Error, La cuenta esta bloqueada")
# return render(request, "tienda/login.html")
# audit_logger.warning(
# "LOGIN_FAILED email=%s reason=invalid_credentials ip=%s",
# email,
# client_ip,
# )
# messages.error(request, "Correo electrónico o contraseña incorrectos.")
# return render(request, "tienda/login.html")
#
# return render(request, "tienda/login.html")
def register(request: HttpRequest): def register(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
form = UserRegisterForm(request.POST) form = UserRegisterForm(request.POST)
@@ -375,7 +336,7 @@ def register(request: HttpRequest):
form = UserRegisterForm() form = UserRegisterForm()
return render(request, "tienda/register.html", {"form":form}) return render(request, "tienda/register.html", {"form":form})
@require_http_methods(["GET"])
def logout(request: HttpRequest): def logout(request: HttpRequest):
user_id = request.user.id if request.user.is_authenticated else None user_id = request.user.id if request.user.is_authenticated else None
email = request.user.email if request.user.is_authenticated else None email = request.user.email if request.user.is_authenticated else None
@@ -385,7 +346,7 @@ def logout(request: HttpRequest):
messages.success(request, "Has cerrado sesión exitosamente.") messages.success(request, "Has cerrado sesión exitosamente.")
return redirect("index") return redirect("index")
@require_http_methods(["GET"])
def producto(request: HttpRequest, id: int): def producto(request: HttpRequest, id: int):
"""Vista de detalle del producto con cacheo en Redis (5 minutos)""" """Vista de detalle del producto con cacheo en Redis (5 minutos)"""
cache_key = f'product_{id}' cache_key = f'product_{id}'
@@ -418,6 +379,7 @@ def producto(request: HttpRequest, id: int):
"user_review_id": user_review_id "user_review_id": user_review_id
}) })
@require_http_methods(["GET"])
def categoria(request: HttpRequest, id: int): def categoria(request: HttpRequest, id: int):
page = 1 page = 1
if "page" in request.GET: if "page" in request.GET:
@@ -432,6 +394,7 @@ def categoria(request: HttpRequest, id: int):
# Funciones auxiliares para el carrito # Funciones auxiliares para el carrito
def get_or_create_cart(request): def get_or_create_cart(request):
"""Obtiene o crea un carrito para el usuario actual o sesión""" """Obtiene o crea un carrito para el usuario actual o sesión"""
if request.user.is_authenticated: if request.user.is_authenticated:
@@ -647,7 +610,7 @@ def _get_selected_shipping_address(request: HttpRequest):
return ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first() return ShippingAddress.objects.filter(id=shipping_address_id, user=request.user).first()
@require_GET
def create_order_from_cart(request, payment_method, payment_reference="", shipping_address=None, stock_reservation=None): def create_order_from_cart(request, payment_method, payment_reference="", shipping_address=None, stock_reservation=None):
"""Crea un pedido a partir del carrito actual, validando y descontando stock.""" """Crea un pedido a partir del carrito actual, validando y descontando stock."""
cart = get_or_create_cart(request) cart = get_or_create_cart(request)
@@ -837,7 +800,7 @@ def add_to_cart(request: HttpRequest, product_id: int):
messages.error(request, "Producto no encontrado.") messages.error(request, "Producto no encontrado.")
return redirect('index') return redirect('index')
@require_GET
def view_cart(request: HttpRequest): def view_cart(request: HttpRequest):
"""Muestra el contenido del carrito""" """Muestra el contenido del carrito"""
cart = get_or_create_cart(request) cart = get_or_create_cart(request)
@@ -850,7 +813,7 @@ def view_cart(request: HttpRequest):
"stock_issues": stock_issues, "stock_issues": stock_issues,
}) })
@require_POST
def update_cart_item(request: HttpRequest, item_id: int): def update_cart_item(request: HttpRequest, item_id: int):
"""Actualiza la cantidad de un item del carrito""" """Actualiza la cantidad de un item del carrito"""
try: try:
@@ -887,7 +850,7 @@ def update_cart_item(request: HttpRequest, item_id: int):
messages.error(request, "Cantidad no válida.") messages.error(request, "Cantidad no válida.")
return redirect('view_cart') return redirect('view_cart')
@require_POST
def remove_from_cart(request: HttpRequest, item_id: int): def remove_from_cart(request: HttpRequest, item_id: int):
"""Elimina un producto del carrito""" """Elimina un producto del carrito"""
try: try:
@@ -904,7 +867,7 @@ def remove_from_cart(request: HttpRequest, item_id: int):
return redirect('view_cart') return redirect('view_cart')
@require_POST
def clear_cart(request: HttpRequest): def clear_cart(request: HttpRequest):
"""Vacía todo el carrito""" """Vacía todo el carrito"""
cart = get_or_create_cart(request) cart = get_or_create_cart(request)
@@ -914,7 +877,7 @@ def clear_cart(request: HttpRequest):
messages.success(request, "Carrito vaciado.") messages.success(request, "Carrito vaciado.")
return redirect('view_cart') return redirect('view_cart')
@require_GET
@login_required @login_required
def mis_productos(request: HttpRequest): def mis_productos(request: HttpRequest):
"""Muestra los productos creados por el usuario autenticado""" """Muestra los productos creados por el usuario autenticado"""
@@ -925,7 +888,7 @@ def mis_productos(request: HttpRequest):
"total_productos": productos.count() "total_productos": productos.count()
}) })
@require_GET
@login_required @login_required
def pedidos_vendedor(request: HttpRequest): def pedidos_vendedor(request: HttpRequest):
"""Muestra los pedidos asignados al vendedor autenticado""" """Muestra los pedidos asignados al vendedor autenticado"""
@@ -939,13 +902,10 @@ def pedidos_vendedor(request: HttpRequest):
"total_pedidos": total_pedidos_por_enviar "total_pedidos": total_pedidos_por_enviar
}) })
@login_required @login_required
@require_POST
def cambiar_estado_pedido(request: HttpRequest, item_id: int): def cambiar_estado_pedido(request: HttpRequest, item_id: int):
"""Cambia el estado de un pedido asignado al vendedor""" """Cambia el estado de un pedido asignado al vendedor"""
if request.method != "POST":
messages.error(request, "Acción no permitida.")
return redirect("pedidos_vendedor")
order_item = get_object_or_404(OrderItem, id=item_id, seller=request.user) order_item = get_object_or_404(OrderItem, id=item_id, seller=request.user)
nuevo_estado = request.POST.get("estado") nuevo_estado = request.POST.get("estado")
@@ -959,13 +919,10 @@ def cambiar_estado_pedido(request: HttpRequest, item_id: int):
return redirect("pedidos_vendedor") return redirect("pedidos_vendedor")
@login_required @login_required
@require_POST
def enviar_mensaje_pedido(request: HttpRequest, item_id: int): def enviar_mensaje_pedido(request: HttpRequest, item_id: int):
"""Envía un mensaje al comprador sobre un pedido""" """Envía un mensaje al comprador sobre un pedido"""
if request.method != "POST":
messages.error(request, "Acción no permitida.")
return redirect("pedidos_vendedor")
order_item = get_object_or_404(OrderItem, id=item_id, seller=request.user) order_item = get_object_or_404(OrderItem, id=item_id, seller=request.user)
mensaje = request.POST.get("mensaje", "").strip() mensaje = request.POST.get("mensaje", "").strip()
@@ -983,7 +940,7 @@ def enviar_mensaje_pedido(request: HttpRequest, item_id: int):
messages.success(request, "Mensaje enviado correctamente.") messages.success(request, "Mensaje enviado correctamente.")
return redirect("pedidos_vendedor") return redirect("pedidos_vendedor")
@require_http_methods(["GET","POST"])
@login_required @login_required
def crear_producto(request: HttpRequest): def crear_producto(request: HttpRequest):
if request.method == "POST": if request.method == "POST":
@@ -1013,6 +970,7 @@ def crear_producto(request: HttpRequest):
form = ProductForm() form = ProductForm()
return render(request, "tienda/crear_producto.html", {"form":form}) return render(request, "tienda/crear_producto.html", {"form":form})
@require_http_methods(["GET","POST"])
@login_required @login_required
def editar_producto(request: HttpRequest, id: int): def editar_producto(request: HttpRequest, id: int):
"""Edita un producto del usuario autenticado""" """Edita un producto del usuario autenticado"""
@@ -1071,10 +1029,11 @@ def editar_producto(request: HttpRequest, id: int):
"producto": producto "producto": producto
}) })
@login_required @login_required
@require_http_methods(["GET", "POST"])
def borrar_producto(request: HttpRequest, id: int): def borrar_producto(request: HttpRequest, id: int):
"""Borra un producto del usuario autenticado""" """Borra un producto del usuario autenticado"""
if request.method != "POST": if request.method != "POST":
messages.error(request, "Acción no permitida.") messages.error(request, "Acción no permitida.")
return redirect("mis_productos") return redirect("mis_productos")
@@ -1086,7 +1045,7 @@ def borrar_producto(request: HttpRequest, id: int):
messages.success(request, f"Producto '{nombre}' eliminado correctamente.") messages.success(request, f"Producto '{nombre}' eliminado correctamente.")
return redirect("mis_productos") return redirect("mis_productos")
@require_http_methods(["GET","POST"])
@login_required @login_required
def gestionar_imagenes(request: HttpRequest, id: int): def gestionar_imagenes(request: HttpRequest, id: int):
"""Gestiona las imágenes secundarias de un producto""" """Gestiona las imágenes secundarias de un producto"""
@@ -1114,13 +1073,10 @@ def gestionar_imagenes(request: HttpRequest, id: int):
"form": form "form": form
}) })
@require_POST
@login_required @login_required
def eliminar_imagen_secundaria(request: HttpRequest, product_id: int, image_id: int): def eliminar_imagen_secundaria(request: HttpRequest, product_id: int, image_id: int):
"""Elimina una imagen secundaria de un producto""" """Elimina una imagen secundaria de un producto"""
if request.method != "POST":
messages.error(request, "Acción no permitida.")
return redirect("gestionar_imagenes", id=product_id)
producto = get_object_or_404(Product, id=product_id, creator=request.user) producto = get_object_or_404(Product, id=product_id, creator=request.user)
image = get_object_or_404(Image, id=image_id) image = get_object_or_404(Image, id=image_id)
@@ -1135,6 +1091,7 @@ def eliminar_imagen_secundaria(request: HttpRequest, product_id: int, image_id:
messages.success(request, "Imagen eliminada correctamente.") messages.success(request, "Imagen eliminada correctamente.")
return redirect("gestionar_imagenes", id=product_id) return redirect("gestionar_imagenes", id=product_id)
@require_GET
@login_required @login_required
def checkout(request: HttpRequest): def checkout(request: HttpRequest):
cart = get_or_create_cart(request) cart = get_or_create_cart(request)
@@ -1156,20 +1113,19 @@ def checkout(request: HttpRequest):
"paypal_client_id": settings.PAYPAL_CLIENT_ID, "paypal_client_id": settings.PAYPAL_CLIENT_ID,
}) })
@require_GET
@csrf_exempt @csrf_exempt
def stripe_config(request): def stripe_config(request):
if request.method == "GET": stripe_config = {
stripe_config = { "publicKey": settings.STRIPE_PUBLISHABLE_KEY
"publicKey": settings.STRIPE_PUBLISHABLE_KEY }
} return JsonResponse(stripe_config, safe=False)
return JsonResponse(stripe_config, safe=False)
@login_required @login_required
@csrf_protect @csrf_protect
@require_POST
def create_checkout_session(request: HttpRequest): def create_checkout_session(request: HttpRequest):
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
shipping_address = _get_selected_shipping_address(request) shipping_address = _get_selected_shipping_address(request)
@@ -1239,7 +1195,7 @@ def create_checkout_session(request: HttpRequest):
logger.exception("STRIPE_CHECKOUT_SESSION_ERROR user_id=%s error=%s", request.user.id, str(e)) logger.exception("STRIPE_CHECKOUT_SESSION_ERROR user_id=%s error=%s", request.user.id, str(e))
return JsonResponse({"error": "Error al crear la sesión de pago. Por favor inténtalo de nuevo."}, status=500) return JsonResponse({"error": "Error al crear la sesión de pago. Por favor inténtalo de nuevo."}, status=500)
@require_GET
@login_required @login_required
def checkout_success(request: HttpRequest): def checkout_success(request: HttpRequest):
payment_reference = request.session.get('stripe_session_id', "") payment_reference = request.session.get('stripe_session_id', "")
@@ -1266,7 +1222,7 @@ def checkout_success(request: HttpRequest):
messages.success(request, "Pago realizado correctamente. ¡Gracias por tu compra!") messages.success(request, "Pago realizado correctamente. ¡Gracias por tu compra!")
return render(request, "tienda/checkout_success.html", {"order": order}) return render(request, "tienda/checkout_success.html", {"order": order})
@require_GET
@login_required @login_required
def checkout_cancel(request: HttpRequest): def checkout_cancel(request: HttpRequest):
_cancel_active_stock_reservations_for_request(request) _cancel_active_stock_reservations_for_request(request)
@@ -1274,7 +1230,7 @@ def checkout_cancel(request: HttpRequest):
messages.info(request, "Pago cancelado. Puedes intentarlo de nuevo cuando quieras.") messages.info(request, "Pago cancelado. Puedes intentarlo de nuevo cuando quieras.")
return render(request, "tienda/checkout_cancel.html", {}) return render(request, "tienda/checkout_cancel.html", {})
@require_GET
def search(request: HttpRequest): def search(request: HttpRequest):
"""Vista para buscar productos""" """Vista para buscar productos"""
query = request.GET.get('q', '').strip() query = request.GET.get('q', '').strip()
@@ -1295,7 +1251,7 @@ def search(request: HttpRequest):
"categories": categories "categories": categories
}) })
@require_GET
def search_suggestions(request: HttpRequest): def search_suggestions(request: HttpRequest):
"""API AJAX que retorna sugerencias de búsqueda en JSON""" """API AJAX que retorna sugerencias de búsqueda en JSON"""
query = request.GET.get('q', '').strip() query = request.GET.get('q', '').strip()
@@ -1318,13 +1274,11 @@ def search_suggestions(request: HttpRequest):
@require_POST
@login_required @login_required
def create_paypal_payment(request: HttpRequest): def create_paypal_payment(request: HttpRequest):
"""Crea un pago con PayPal y redirige a PayPal""" """Crea un pago con PayPal y redirige a PayPal"""
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
shipping_address = _get_selected_shipping_address(request) shipping_address = _get_selected_shipping_address(request)
if shipping_address is None: if shipping_address is None:
@@ -1436,6 +1390,7 @@ def create_paypal_payment(request: HttpRequest):
return JsonResponse({"error": f"Error al crear el pago"}, status=500) return JsonResponse({"error": f"Error al crear el pago"}, status=500)
@require_GET
@login_required @login_required
def paypal_execute(request: HttpRequest): def paypal_execute(request: HttpRequest):
"""Ejecuta el pago de PayPal después de la aprobación""" """Ejecuta el pago de PayPal después de la aprobación"""
@@ -1504,15 +1459,13 @@ def paypal_execute(request: HttpRequest):
# ==================== STRIPE PAYMENT INTENTS ==================== # ==================== STRIPE PAYMENT INTENTS ====================
@login_required @login_required
@require_POST
@csrf_protect @csrf_protect
def crear_payment_intent(request: HttpRequest): def crear_payment_intent(request: HttpRequest):
""" """
Crea un Stripe PaymentIntent para el carrito actual. Crea un Stripe PaymentIntent para el carrito actual.
Acepta JSON: { shipping_address_id, saved_payment_method_id (opcional), save_card (bool) } Acepta JSON: { shipping_address_id, saved_payment_method_id (opcional), save_card (bool) }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
payload = json.loads(request.body.decode("utf-8") or "{}") payload = json.loads(request.body.decode("utf-8") or "{}")
except (json.JSONDecodeError, UnicodeDecodeError): except (json.JSONDecodeError, UnicodeDecodeError):
@@ -1588,15 +1541,13 @@ def crear_payment_intent(request: HttpRequest):
@login_required @login_required
@require_POST
@csrf_protect @csrf_protect
def confirmar_pago_tarjeta(request: HttpRequest): def confirmar_pago_tarjeta(request: HttpRequest):
""" """
Verificar que el PaymentIntent fue exitoso y crear el pedido. Verificar que el PaymentIntent fue exitoso y crear el pedido.
Acepta JSON: { payment_intent_id, payment_method_id (si nueva tarjeta) } Acepta JSON: { payment_intent_id, payment_method_id (si nueva tarjeta) }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
payload = json.loads(request.body.decode("utf-8") or "{}") payload = json.loads(request.body.decode("utf-8") or "{}")
except (json.JSONDecodeError, UnicodeDecodeError): except (json.JSONDecodeError, UnicodeDecodeError):
@@ -1663,14 +1614,13 @@ def confirmar_pago_tarjeta(request: HttpRequest):
@login_required @login_required
@csrf_protect @csrf_protect
@require_POST
def crear_orden_paypal(request: HttpRequest): def crear_orden_paypal(request: HttpRequest):
""" """
Crea una orden de PayPal con el total del carrito actual (Orders API v2). Crea una orden de PayPal con el total del carrito actual (Orders API v2).
Acepta JSON: { shipping_address_id } Acepta JSON: { shipping_address_id }
Retorna: { id: paypal_order_id } Retorna: { id: paypal_order_id }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
shipping_address = _get_selected_shipping_address(request) shipping_address = _get_selected_shipping_address(request)
if shipping_address is None: if shipping_address is None:
@@ -1717,13 +1667,12 @@ def crear_orden_paypal(request: HttpRequest):
@login_required @login_required
@csrf_protect @csrf_protect
@require_POST
def capturar_orden_paypal(request: HttpRequest): def capturar_orden_paypal(request: HttpRequest):
""" """
Captura una orden de PayPal aprobada y crea el pedido en nuestra BD. Captura una orden de PayPal aprobada y crea el pedido en nuestra BD.
Acepta JSON: { orderID } Acepta JSON: { orderID }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
payload = json.loads(request.body.decode("utf-8") or "{}") payload = json.loads(request.body.decode("utf-8") or "{}")
@@ -1806,7 +1755,7 @@ def capturar_orden_paypal(request: HttpRequest):
# ==================== MÉTODOS DE PAGO DEL USUARIO ==================== # ==================== MÉTODOS DE PAGO DEL USUARIO ====================
@require_GET
@login_required @login_required
def metodos_pago(request: HttpRequest): def metodos_pago(request: HttpRequest):
"""Lista los métodos de pago guardados del usuario.""" """Lista los métodos de pago guardados del usuario."""
@@ -1819,6 +1768,7 @@ def metodos_pago(request: HttpRequest):
@login_required @login_required
@require_GET
def agregar_tarjeta(request: HttpRequest): def agregar_tarjeta(request: HttpRequest):
"""Página para añadir una nueva tarjeta usando Stripe SetupIntent.""" """Página para añadir una nueva tarjeta usando Stripe SetupIntent."""
return render(request, "tienda/agregar_tarjeta.html", { return render(request, "tienda/agregar_tarjeta.html", {
@@ -1827,14 +1777,13 @@ def agregar_tarjeta(request: HttpRequest):
@login_required @login_required
@require_POST
@csrf_protect @csrf_protect
def crear_setup_intent(request: HttpRequest): def crear_setup_intent(request: HttpRequest):
""" """
Crea un Stripe SetupIntent y retorna el client_secret para que el frontend Crea un Stripe SetupIntent y retorna el client_secret para que el frontend
pueda montar el Card Element y confirmar sin realizar un cobro. pueda montar el Card Element y confirmar sin realizar un cobro.
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
stripe.api_key = settings.STRIPE_SECRET_KEY stripe.api_key = settings.STRIPE_SECRET_KEY
customer_id = _get_or_create_stripe_customer(request.user) customer_id = _get_or_create_stripe_customer(request.user)
@@ -1853,13 +1802,12 @@ def crear_setup_intent(request: HttpRequest):
@login_required @login_required
@csrf_protect @csrf_protect
@require_POST
def confirmar_setup_intent(request: HttpRequest): def confirmar_setup_intent(request: HttpRequest):
""" """
Tras la confirmación del SetupIntent en el frontend, guarda la tarjeta. Tras la confirmación del SetupIntent en el frontend, guarda la tarjeta.
Acepta JSON: { payment_method_id, setup_intent_id } Acepta JSON: { payment_method_id, setup_intent_id }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
payload = json.loads(request.body.decode("utf-8") or "{}") payload = json.loads(request.body.decode("utf-8") or "{}")
@@ -1924,10 +1872,11 @@ def confirmar_setup_intent(request: HttpRequest):
@login_required @login_required
@require_http_methods(["GET", "POST"])
def eliminar_metodo_pago(request: HttpRequest, id: int): def eliminar_metodo_pago(request: HttpRequest, id: int):
"""Elimina un método de pago guardado del usuario.""" """Elimina un método de pago guardado del usuario."""
if request.method != "POST": if request.method != "POST":
messages.error(request, "Acción no permitida.")
return redirect("metodos_pago") return redirect("metodos_pago")
metodo = get_object_or_404(SavedPaymentMethod, id=id, user=request.user) metodo = get_object_or_404(SavedPaymentMethod, id=id, user=request.user)
@@ -1946,6 +1895,7 @@ def eliminar_metodo_pago(request: HttpRequest, id: int):
@login_required @login_required
@require_GET
def agregar_paypal(request: HttpRequest): def agregar_paypal(request: HttpRequest):
"""Página para guardar una cuenta de PayPal como método de pago (usa un pago de verificación de 0.01 €).""" """Página para guardar una cuenta de PayPal como método de pago (usa un pago de verificación de 0.01 €)."""
return render(request, "tienda/agregar_paypal.html", { return render(request, "tienda/agregar_paypal.html", {
@@ -1954,14 +1904,13 @@ def agregar_paypal(request: HttpRequest):
@login_required @login_required
@require_POST
@csrf_protect @csrf_protect
def crear_orden_paypal_setup(request: HttpRequest): def crear_orden_paypal_setup(request: HttpRequest):
""" """
Crea una orden PayPal de 0.01 € para verificar/guardar la cuenta. Crea una orden PayPal de 0.01 € para verificar/guardar la cuenta.
Retorna { id: paypal_order_id } Retorna { id: paypal_order_id }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
paypal_order = _paypal_create_order(Decimal("0.01")) paypal_order = _paypal_create_order(Decimal("0.01"))
return JsonResponse({"id": paypal_order.get("id")}) return JsonResponse({"id": paypal_order.get("id")})
@@ -1972,13 +1921,12 @@ def crear_orden_paypal_setup(request: HttpRequest):
@login_required @login_required
@csrf_protect @csrf_protect
@require_POST
def capturar_orden_paypal_setup(request: HttpRequest): def capturar_orden_paypal_setup(request: HttpRequest):
""" """
Captura la orden de verificación de PayPal y guarda la cuenta del usuario. Captura la orden de verificación de PayPal y guarda la cuenta del usuario.
Acepta JSON: { orderID } Acepta JSON: { orderID }
""" """
if request.method != "POST":
return JsonResponse({"error": "Método no permitido"}, status=405)
try: try:
payload = json.loads(request.body.decode("utf-8") or "{}") payload = json.loads(request.body.decode("utf-8") or "{}")
@@ -2029,6 +1977,7 @@ def capturar_orden_paypal_setup(request: HttpRequest):
# ==================== PORTAL DE USUARIO ==================== # ==================== PORTAL DE USUARIO ====================
@login_required @login_required
@require_GET
def portal_usuario(request: HttpRequest): def portal_usuario(request: HttpRequest):
"""Dashboard del portal de usuario""" """Dashboard del portal de usuario"""
# Obtener estadísticas del usuario # Obtener estadísticas del usuario
@@ -2050,7 +1999,7 @@ def portal_usuario(request: HttpRequest):
"recent_messages": recent_messages, "recent_messages": recent_messages,
}) })
@require_GET
@login_required @login_required
def mis_compras(request: HttpRequest): def mis_compras(request: HttpRequest):
"""Lista completa de compras del usuario autenticado""" """Lista completa de compras del usuario autenticado"""
@@ -2063,6 +2012,7 @@ def mis_compras(request: HttpRequest):
@login_required @login_required
@require_GET
def mis_recibos(request: HttpRequest): def mis_recibos(request: HttpRequest):
"""Lista de recibos (pedidos pagados) del usuario autenticado""" """Lista de recibos (pedidos pagados) del usuario autenticado"""
receipts = Order.objects.filter( receipts = Order.objects.filter(
@@ -2077,6 +2027,7 @@ def mis_recibos(request: HttpRequest):
@login_required @login_required
@require_http_methods(["GET","POST"])
def editar_perfil(request: HttpRequest): def editar_perfil(request: HttpRequest):
"""Edita la información del perfil del usuario""" """Edita la información del perfil del usuario"""
if request.method == "POST": if request.method == "POST":
@@ -2107,6 +2058,7 @@ def editar_perfil(request: HttpRequest):
@login_required @login_required
@require_http_methods(["GET","POST"])
def cambiar_contrasena(request: HttpRequest): def cambiar_contrasena(request: HttpRequest):
"""Cambia la contraseña del usuario""" """Cambia la contraseña del usuario"""
if request.method == "POST": if request.method == "POST":
@@ -2138,6 +2090,7 @@ def cambiar_contrasena(request: HttpRequest):
@login_required @login_required
@require_GET
def direcciones_usuario(request: HttpRequest): def direcciones_usuario(request: HttpRequest):
"""Lista las direcciones de entrega del usuario""" """Lista las direcciones de entrega del usuario"""
direcciones = ShippingAddress.objects.filter(user=request.user) direcciones = ShippingAddress.objects.filter(user=request.user)
@@ -2148,6 +2101,7 @@ def direcciones_usuario(request: HttpRequest):
@login_required @login_required
@require_http_methods(["GET", "POST"])
def crear_direccion(request: HttpRequest): def crear_direccion(request: HttpRequest):
"""Crea una nueva dirección de entrega""" """Crea una nueva dirección de entrega"""
if request.method == "POST": if request.method == "POST":
@@ -2187,6 +2141,7 @@ def crear_direccion(request: HttpRequest):
@login_required @login_required
@require_http_methods(["GET", "POST"])
def editar_direccion(request: HttpRequest, id: int): def editar_direccion(request: HttpRequest, id: int):
"""Edita una dirección de entrega existente""" """Edita una dirección de entrega existente"""
direccion = get_object_or_404(ShippingAddress, id=id, user=request.user) direccion = get_object_or_404(ShippingAddress, id=id, user=request.user)
@@ -2236,6 +2191,7 @@ def editar_direccion(request: HttpRequest, id: int):
@login_required @login_required
@require_http_methods(["GET", "POST"])
def eliminar_direccion(request: HttpRequest, id: int): def eliminar_direccion(request: HttpRequest, id: int):
"""Elimina una dirección de entrega""" """Elimina una dirección de entrega"""
if request.method != "POST": if request.method != "POST":
@@ -2249,6 +2205,7 @@ def eliminar_direccion(request: HttpRequest, id: int):
@login_required @login_required
@require_GET
def mensajes_comprador(request: HttpRequest): def mensajes_comprador(request: HttpRequest):
"""Muestra los mensajes recibidos de vendedores""" """Muestra los mensajes recibidos de vendedores"""
# Obtener todos los order items del comprador con mensajes # Obtener todos los order items del comprador con mensajes
@@ -2265,6 +2222,7 @@ def mensajes_comprador(request: HttpRequest):
@require_GET
def verify(request: HttpRequest, code: str): def verify(request: HttpRequest, code: str):
obj = None obj = None
try: try:
@@ -2282,27 +2240,35 @@ def verify(request: HttpRequest, code: str):
return HttpResponse("<h1>Error</h1><p>No existe el codigo de verificación</p>") return HttpResponse("<h1>Error</h1><p>No existe el codigo de verificación</p>")
@require_GET
def rgpd(request: HttpRequest): def rgpd(request: HttpRequest):
return render(request, "tienda/rgpd.html", {}) return render(request, "tienda/rgpd.html", {})
@require_GET
def devoluciones(request: HttpRequest): def devoluciones(request: HttpRequest):
return render(request, "tienda/devoluciones.html", {}) return render(request, "tienda/devoluciones.html", {})
@require_GET
def aviso_legal(request: HttpRequest): def aviso_legal(request: HttpRequest):
return render(request, "tienda/aviso_legal.html", {}) return render(request, "tienda/aviso_legal.html", {})
@require_GET
def terminos(request: HttpRequest): def terminos(request: HttpRequest):
return render(request, "tienda/terminos.html", {}) return render(request, "tienda/terminos.html", {})
@require_GET
def cookies(request: HttpRequest): def cookies(request: HttpRequest):
return render(request, "tienda/cookies.html", {}) return render(request, "tienda/cookies.html", {})
@require_GET
def sobre_nosotros(request: HttpRequest): def sobre_nosotros(request: HttpRequest):
return render(request, "tienda/sobre_nosotros.html", {}) return render(request, "tienda/sobre_nosotros.html", {})
@require_GET
def ayuda(request: HttpRequest): def ayuda(request: HttpRequest):
return render(request, "tienda/ayuda.html", {}) return render(request, "tienda/ayuda.html", {})
@require_http_methods(["GET", "POST"])
def reset_password(request: HttpRequest): def reset_password(request: HttpRequest):
if request.method == "GET": if request.method == "GET":
form = ResetPasswordForm() form = ResetPasswordForm()
@@ -2314,6 +2280,7 @@ def reset_password(request: HttpRequest):
messages.info(request, "Si tienes una cuenta con ese correo electronico, se ha enviado un correo con un enlace") messages.info(request, "Si tienes una cuenta con ese correo electronico, se ha enviado un correo con un enlace")
return render(request, "tienda/index.html", {}) return render(request, "tienda/index.html", {})
@require_http_methods(["GET", "POST"])
def reset_password_phase2(request: HttpRequest, code: str): def reset_password_phase2(request: HttpRequest, code: str):
try: try:
ver_code = VerificationCode.objects.get(code=code) ver_code = VerificationCode.objects.get(code=code)
@@ -2343,6 +2310,7 @@ def reset_password_phase2(request: HttpRequest, code: str):
@login_required @login_required
@require_http_methods(["GET", "POST"])
def add_review(request: HttpRequest, product_id: int): def add_review(request: HttpRequest, product_id: int):
product = get_object_or_404(Product, id=product_id) product = get_object_or_404(Product, id=product_id)
@@ -2403,6 +2371,7 @@ def add_review(request: HttpRequest, product_id: int):
}) })
@require_GET
def product_reviews(request: HttpRequest, product_id: int): def product_reviews(request: HttpRequest, product_id: int):
product = get_object_or_404(Product, id=product_id) product = get_object_or_404(Product, id=product_id)
reviews = product.reviews.select_related("user").prefetch_related("images").all() reviews = product.reviews.select_related("user").prefetch_related("images").all()
@@ -2427,6 +2396,7 @@ def product_reviews(request: HttpRequest, product_id: int):
}) })
@require_POST
@login_required @login_required
def delete_review(request: HttpRequest, review_id: int): def delete_review(request: HttpRequest, review_id: int):
review = get_object_or_404(Review, id=review_id, user=request.user) review = get_object_or_404(Review, id=review_id, user=request.user)