Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 1 addition & 11 deletions essenza/cart/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View
from product.models import Product
Expand All @@ -10,7 +9,7 @@ class CartDetailView(View):
"""
Muestra el carrito.
- Si es usuario logueado: Lee de la base de datos
- Si es anónimo: Lee de la sesión.
- Si es anónimo: Lee de la sesión
"""

template_name = "cart/cart_detail.html"
Expand Down Expand Up @@ -72,7 +71,6 @@ def post(self, request, product_id):
product = get_object_or_404(Product, pk=product_id)

if product.stock <= 0:
messages.error(request, f"Lo sentimos, '{product.name}' está agotado.")
return redirect("catalog")

try:
Expand Down Expand Up @@ -102,11 +100,6 @@ def post(self, request, product_id):
else:
cart_product.quantity += quantity
cart_product.save()
msg = f"Se ha añadido otra unidad de {product.name}."
else:
msg = f"{product.name} añadido al carrito."

messages.success(request, msg)

# Si el usuario no está logueado, guardamos en sesión
else:
Expand All @@ -119,17 +112,14 @@ def post(self, request, product_id):
return redirect("cart_detail")
else:
cart_session[product_id_str]["quantity"] += quantity
msg = f"Se ha añadido otra unidad de {product.name}."
else:
cart_session[product_id_str] = {
"quantity": quantity,
"price": str(product.price),
}
msg = f"{product.name} añadido al carrito."

request.session["cart_session"] = cart_session
request.session.modified = True
messages.success(request, msg)

return redirect("cart_detail")

Expand Down
41 changes: 36 additions & 5 deletions essenza/essenza/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
https://docs.djangoproject.com/en/5.2/ref/settings/
"""

import os
from pathlib import Path

from dotenv import load_dotenv

load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

Expand All @@ -20,12 +25,14 @@
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-7+c*kj699pt34%5ub-x04i3%nlbhc@y+7sdew3+7!z5h-z1k_v"
SECRET_KEY = os.getenv(
"SECRET_KEY", "django-insecure-7+c*kj699pt34%5ub-x04i3%nlbhc@y+7sdew3+7!z5h-z1k_v"
)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv("DEBUG", "False") == "True"

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]


# Application definition
Expand All @@ -47,6 +54,7 @@

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
Expand Down Expand Up @@ -112,8 +120,7 @@

LANGUAGE_CODE = "es"

TIME_ZONE = "UTC"

TIME_ZONE = "Europe/Madrid"
USE_I18N = True

USE_TZ = True
Expand All @@ -126,6 +133,9 @@
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"

# Configuración para que Whitenoise sirva los estáticos comprimidos y rápido
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

Expand All @@ -140,3 +150,24 @@
# es el modelo de autenticación oficial.
# -----------------------------------------------------------------
AUTH_USER_MODEL = "user.Usuario"

# -----------------------------------------------------------------
# CONFIGURACIÓN DE STRIPE Y DOMINIO (Leen del .env)
# -----------------------------------------------------------------
STRIPE_PUBLIC_KEY = os.getenv("STRIPE_PUBLIC_KEY")
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY")
DOMAIN_URL = os.getenv(
"DOMAIN_URL", "http://127.0.0.1:8000"
) # Default a localhost si falla

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True

# Leemos las credenciales del archivo .env (o las pones aquí directamente entre comillas si prefieres)
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")

# El remitente que aparecerá en los correos
DEFAULT_FROM_EMAIL = "Essenza <noreply@essenza.com>"
1 change: 0 additions & 1 deletion essenza/essenza/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
path("catalog/<int:pk>/", CatalogDetailView.as_view(), name="catalog_detail"),
path("cart/", include("cart.urls")),
path("order/", include("order.urls")),

]

if settings.DEBUG:
Expand Down
110 changes: 69 additions & 41 deletions essenza/load_samples.bat
Original file line number Diff line number Diff line change
@@ -1,48 +1,76 @@
@echo off
REM ---------------------------------------------------------
REM IMPORTANTE: Este archivo borra todos los datos de tu BD local (y la crea con los datos de sampleo).
REM Las imágenes de sampleo se copian a la carpeta 'media/'.
REM También instala las dependencias necesarias definidas en 'requirements.txt' (si aun no lo están).
REM IMPORTANTE: Este archivo RESTAURA TOTALMENTE la BD del proyecto.
REM 1. Verifica entorno virtual.
REM 2. Instala dependencias.
REM 3. Borra BD y Media.
REM 4. Recrea BD y copia assets de sampleo.
REM ---------------------------------------------------------

echo --- Instalando dependencias (pip)...
pip install -r requirements.txt && (

echo --- Borrando TODOS los datos de la BD...
python manage.py flush --noinput && (

echo.
echo --- Aplicando migraciones...
python manage.py migrate --noinput && (

echo.
echo --- Copiando imagenes de sampleo a 'media/'...
REM XCOPY [origen] [destino] /E /I /Y
REM /E = Copia subdirectorios (incluso vacíos)
REM /I = Si el destino no existe, asume que es un directorio
REM /Y = Suprime la pregunta de "sobreescribir archivo"
XCOPY _sample_assets media /E /I /Y && (

echo.
echo --- Cargando datos de USER...
python manage.py loaddata user/sample/sample.json && (

echo.
echo --- Cargando datos de PRODUCT...
python manage.py loaddata product/sample/sample.json && (

echo.
echo --- Cargando datos de ORDER...
python manage.py loaddata order/sample/sample.json && (

echo.
echo --- !Proceso completado! La base de datos esta lista. ---
)
)
)
)
)
)
IF "%VIRTUAL_ENV%"=="" (
echo.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
echo ERROR: No se detecta un entorno virtual activo.
echo Por favor, activa tu '.venv' antes de ejecutar este script.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
pause
exit /b 1
)

echo.
echo --- Instalando dependencias (pip)...
pip install -r requirements.txt
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Borrando TODOS los datos de la BD...
python manage.py flush --noinput
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Aplicando migraciones...
python manage.py migrate --noinput
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Copiando imagenes de sampleo a 'media/'...
REM XCOPY [origen] [destino] /E /I /Y
REM /E = Copia subdirectorios (incluso vacíos)
REM /I = Si el destino no existe, asume que es un directorio
REM /Y = Suprime la pregunta de "sobreescribir archivo"
XCOPY _sample_assets media /E /I /Y
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Cargando datos de USER...
python manage.py loaddata user/sample/sample.json
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Cargando datos de PRODUCT...
python manage.py loaddata product/sample/sample.json
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo --- Cargando datos de ORDER...
python manage.py loaddata order/sample/sample.json
IF %ERRORLEVEL% NEQ 0 GOTO :ERROR

echo.
echo ========================================================
echo !PROCESO COMPLETADO CON EXITO!
echo Los datos de sampleo se han cargado en la base de datos.
echo ========================================================
GOTO :END

:ERROR
echo.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
echo ERROR -> El script se detuvo porque un comando ha fallado.
echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
pause
exit /b 1

:END

@echo on
50 changes: 46 additions & 4 deletions essenza/order/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import random
import string

from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone

Expand All @@ -11,21 +15,59 @@ class Status(models.TextChoices):

class Order(models.Model):
user = models.ForeignKey(
"user.Usuario", on_delete=models.CASCADE, related_name="orders"
"user.Usuario",
on_delete=models.SET_NULL,
related_name="orders",
null=True,
blank=True,
)
address = models.CharField(max_length=255, null=True, blank=True)
email = models.EmailField(max_length=255)
address = models.CharField(max_length=255)
placed_at = models.DateTimeField(default=timezone.now)
status = models.CharField(choices=Status.choices, default=Status.EN_PREPARACION)

tracking_code = models.CharField(
max_length=8,
unique=True,
editable=False, # No se puede editar manualmente
verbose_name="Localizador",
)

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

def save(self, *args, **kwargs):
"""
Sobrescribimos el método save para generar el tracking_code
automáticamente antes de guardar si aún no tiene uno.
"""

if not self.tracking_code:
self.tracking_code = self._generate_unique_tracking_code()

if not self.user and self.email:
User = get_user_model()
existing_user = User.objects.filter(email=self.email).first()

if existing_user:
self.user = existing_user
super().save(*args, **kwargs)

def _generate_unique_tracking_code(self):
"""Genera un código único de 8 caracteres alfanuméricos."""
chars = string.ascii_uppercase + string.digits
while True:
code = "".join(random.choices(chars, k=8))
# Verifica que no exista para evitar duplicados
if not Order.objects.filter(tracking_code=code).exists():
return code

def __str__(self):
return f"Order {self.id} by {self.user.email}"
return f"Order {self.id} [{self.tracking_code}] - {self.email}"


class OrderProduct(models.Model):
Expand All @@ -42,4 +84,4 @@ def subtotal(self):
return self.quantity * self.product.price

def __str__(self):
return f"{self.quantity} of {self.product.name} in order {self.order.id}"
return f"{self.quantity} of {self.product.name} in order {self.order.tracking_code}"
Loading