diff --git a/proyecto/__init__.py b/proyecto/__init__.py index e69de29..a289a61 100644 --- a/proyecto/__init__.py +++ b/proyecto/__init__.py @@ -0,0 +1,2 @@ +from .celery import app as celery_app +__all__ = ('celery_app',) \ No newline at end of file diff --git a/proyecto/__pycache__/__init__.cpython-314.pyc b/proyecto/__pycache__/__init__.cpython-314.pyc index 68e01f0..3adf1a0 100644 Binary files a/proyecto/__pycache__/__init__.cpython-314.pyc and b/proyecto/__pycache__/__init__.cpython-314.pyc differ diff --git a/proyecto/__pycache__/settings.cpython-314.pyc b/proyecto/__pycache__/settings.cpython-314.pyc index 3c9047e..9f4f766 100644 Binary files a/proyecto/__pycache__/settings.cpython-314.pyc and b/proyecto/__pycache__/settings.cpython-314.pyc differ diff --git a/proyecto/settings.py b/proyecto/settings.py index 6e9f179..913eab2 100644 --- a/proyecto/settings.py +++ b/proyecto/settings.py @@ -10,26 +10,65 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/6.0/ref/settings/ """ +import logging +import os, sys from pathlib import Path + +def load_dotenv(dotenv_path: Path) -> None: + if not dotenv_path.exists(): + return + + for raw_line in dotenv_path.read_text(encoding='utf-8').splitlines(): + line = raw_line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + + key, value = line.split('=', 1) + key = key.strip() + value = value.strip().strip('"').strip("'") + os.environ.setdefault(key, value) + + +def env_bool(name: str, default: bool = False) -> bool: + value = os.getenv(name) + if value is None: + return default + return value.strip().lower() in {'1', 'true', 'yes', 'on'} + + +def env_list(name: str, default: list[str] | None = None) -> list[str]: + value = os.getenv(name) + if value is None: + return default or [] + return [item.strip() for item in value.split(',') if item.strip()] + + +def env_int(name: str, default: int) -> int: + value = os.getenv(name) + if value is None: + return default + return int(value) + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +load_dotenv(BASE_DIR / '.env') # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-#g((q@lvnkt(j6)2(gvtn0px)r2r(911)pv59i(6w)5e!_-^ao' +SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-#g((q@lvnkt(j6)2(gvtn0px)r2r(911)pv59i(6w)5e!_-^ao') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = env_bool('DEBUG', True) -ALLOWED_HOSTS = [ - "192.168.1.142", - "localhost", - "127.0.0.1" -] +ALLOWED_HOSTS = env_list('ALLOWED_HOSTS', [ + '192.168.1.142', + 'localhost', + '127.0.0.1', +]) # Application definition @@ -152,7 +191,7 @@ MEDIA_ROOT = BASE_DIR / 'tienda' / 'static' / 'media' CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'redis://127.0.0.1:6379/1', + 'LOCATION': os.getenv('REDIS_URL', 'redis://127.0.0.1:6379/1'), 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', } @@ -177,11 +216,114 @@ MESSAGE_TAGS = { # Login URL LOGIN_URL = '/tienda/login/' -STRIPE_PUBLISHABLE_KEY = 'pk_test_51SxmSYJ2DN4I0upQDdiPeda51nmpB0ZEWfkNFKHhWBG4knIgtRoC1d9iFRoxRNdJKiLlQsIddlebU06R9XCfiSZH00ffoirwPw' -STRIPE_SECRET_KEY = 'sk_test_51SxmSYJ2DN4I0upQZb42dWKuIKToZxkQeK3vsCdijcaUr17EMEyFcLdIAm5AVEvUs96MAxl4KnZ4Yncp5VykO4ej00MZGs6c1F' +STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY', '') +STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY', '') # PayPal Configuration (Sandbox) # Para obtener credenciales: https://sandbox.paypal.com/ -PAYPAL_CLIENT_ID = 'AX3TIklQ41456StP2puciDfkQ6oSWAQWNYB8H9ThDsU6C_VYhWqwDZ1w0dK-No38Aa9IqAbrZbE-1kHJ' # Reemplazar con tu Client ID de PayPal Sandbox -PAYPAL_CLIENT_SECRET = 'EIXny9EkiebiCnwkfmWJa7ufwHwdUCTeSZ5TiUZycBPREradcN7U0vBKCUlg-PYd3SeXTW33D0kZb5BT' # Reemplazar con tu Client Secret de PayPal Sandbox -PAYPAL_MODE = 'sandbox' # Cambiar a 'live' en producción +PAYPAL_CLIENT_ID = os.getenv('PAYPAL_CLIENT_ID', '') # Reemplazar con tu Client ID de PayPal Sandbox +PAYPAL_CLIENT_SECRET = os.getenv('PAYPAL_CLIENT_SECRET', '') # Reemplazar con tu Client Secret de PayPal Sandbox +PAYPAL_MODE = os.getenv('PAYPAL_MODE', 'sandbox') # Cambiar a 'live' en producción + + +SMTP_ENDPOINT = os.getenv('SMTP_ENDPOINT', 'smtp.email.eu-paris-1.oci.oraclecloud.com') +SMTP_PORT = env_int('SMTP_PORT', 587) +SECURITY = os.getenv('SECURITY', 'tls') +SMTP_USERNAME = os.getenv('SMTP_USERNAME', None) +SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', None) +SMTP_EMAIL = os.getenv("SMTP_EMAIL", None) +if SMTP_USERNAME is None or SMTP_PASSWORD is None or SMTP_EMAIL is None: + print("Se requieren credenciales SMTP") + sys.exit(1) + + + +AUTH_USER_MODEL = 'tienda.User' + + +DOMAIN = os.getenv("DOMAIN", "localhost") +PROTOCOL = os.getenv("PROTOCOL", "http") + + +LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO').upper() +LOG_DIR = Path(os.getenv('LOG_DIR', BASE_DIR / 'logs')) +LOG_DIR.mkdir(parents=True, exist_ok=True) +LOG_FILE = LOG_DIR / os.getenv('LOG_FILE', 'app.log') + + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '%(asctime)s | %(levelname)s | %(name)s | %(message)s', + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'standard', + 'level': LOG_LEVEL, + }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'formatter': 'standard', + 'filename': str(LOG_FILE), + 'maxBytes': 5 * 1024 * 1024, + 'backupCount': 5, + 'level': LOG_LEVEL, + 'encoding': 'utf-8', + }, + }, + 'loggers': { + 'tienda': { + 'handlers': ['console', 'file'], + 'level': LOG_LEVEL, + 'propagate': False, + }, + 'tienda.audit': { + 'handlers': ['console', 'file'], + 'level': LOG_LEVEL, + 'propagate': False, + }, + 'django': { + 'handlers': ['console'], + 'level': 'INFO', + 'propagate': False, + }, + 'email.system': { + 'handlers': ['console', 'file'], + 'level': LOG_LEVEL, + 'propagate': False + } + }, +} + + +logging.captureWarnings(True) + + + + + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = SMTP_ENDPOINT +EMAIL_PORT = SMTP_PORT +EMAIL_USE_TLS = (SECURITY == 'tls') # True si SECURITY es 'tls' +EMAIL_USE_SSL = (SECURITY == 'ssl') # True si SECURITY es 'ssl' +EMAIL_HOST_USER = SMTP_USERNAME +EMAIL_HOST_PASSWORD = SMTP_PASSWORD + +# El correo que se usará como remitente por defecto +DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", SMTP_EMAIL) + +# URL de Redis (asumiendo que corre en el puerto default 6379) +CELERY_BROKER_URL = 'redis://localhost:6379/0' + +# Opcional: para guardar el resultado de las tareas +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' + +# Configuraciones adicionales recomendadas +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' \ No newline at end of file diff --git a/tienda/__pycache__/admin.cpython-314.pyc b/tienda/__pycache__/admin.cpython-314.pyc index b298973..1b0d656 100644 Binary files a/tienda/__pycache__/admin.cpython-314.pyc and b/tienda/__pycache__/admin.cpython-314.pyc differ diff --git a/tienda/__pycache__/models.cpython-314.pyc b/tienda/__pycache__/models.cpython-314.pyc index d30e9f6..e656c0b 100644 Binary files a/tienda/__pycache__/models.cpython-314.pyc and b/tienda/__pycache__/models.cpython-314.pyc differ diff --git a/tienda/__pycache__/urls.cpython-314.pyc b/tienda/__pycache__/urls.cpython-314.pyc index a5269ea..ab71d24 100644 Binary files a/tienda/__pycache__/urls.cpython-314.pyc and b/tienda/__pycache__/urls.cpython-314.pyc differ diff --git a/tienda/__pycache__/vars.cpython-314.pyc b/tienda/__pycache__/vars.cpython-314.pyc index 3d22943..c41a8a3 100644 Binary files a/tienda/__pycache__/vars.cpython-314.pyc and b/tienda/__pycache__/vars.cpython-314.pyc differ diff --git a/tienda/__pycache__/views.cpython-314.pyc b/tienda/__pycache__/views.cpython-314.pyc index af53977..4047b40 100644 Binary files a/tienda/__pycache__/views.cpython-314.pyc and b/tienda/__pycache__/views.cpython-314.pyc differ diff --git a/tienda/admin.py b/tienda/admin.py index 3f42731..685160b 100644 --- a/tienda/admin.py +++ b/tienda/admin.py @@ -1,12 +1,12 @@ from django.contrib import admin -from .models import Category, Image, Product, Cart, CartItem, Order, OrderItem, OrderMessage +from .models import Category, Image, Product, Cart, CartItem, Order, OrderItem, OrderMessage, User, VerificationCode # Register your models here. admin.site.register(Category) admin.site.register(Image) admin.site.register(Product) - - +admin.site.register(User) +admin.site.register(VerificationCode) class CartItemInline(admin.TabularInline): model = CartItem extra = 0 diff --git a/tienda/migrations/0001_initial.py b/tienda/migrations/0001_initial.py index 428d4c3..71b71ea 100644 --- a/tienda/migrations/0001_initial.py +++ b/tienda/migrations/0001_initial.py @@ -1,6 +1,10 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:33 +# Generated by Django 6.0.1 on 2026-03-10 07:56 +import django.contrib.auth.models +import django.contrib.auth.validators import django.db.models.deletion +import django.utils.timezone +from django.conf import settings from django.db import migrations, models @@ -9,6 +13,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -17,10 +22,160 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200)), - ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subcategories', to='tienda.category')), + ], + ), + migrations.CreateModel( + name='Image', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='', max_length=200)), + ('image', models.ImageField(upload_to='images/')), + ('alt', models.CharField(blank=True, default='', max_length=255, verbose_name='Texto alternativo')), + ], + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('registration_status', models.CharField(choices=[('CR', 'Confirmation Required'), ('AC', 'Active'), ('BN', 'Banned')], default='CR', max_length=2)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], options={ - 'verbose_name_plural': 'Categories', + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Cart', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('session_key', models.CharField(blank=True, max_length=40, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('session_key', models.CharField(blank=True, max_length=40, null=True)), + ('total', models.FloatField(default=0)), + ('status', models.CharField(choices=[('paid', 'Pagado'), ('cancelled', 'Cancelado')], default='paid', max_length=20)), + ('payment_method', models.CharField(choices=[('stripe', 'Stripe'), ('paypal', 'PayPal'), ('manual', 'Manual')], default='manual', max_length=20)), + ('payment_reference', models.CharField(blank=True, default='', max_length=200)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('buyer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='OrderItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('product_name', models.CharField(default='', max_length=200)), + ('quantity', models.PositiveIntegerField(default=1)), + ('unit_price', models.FloatField(default=0)), + ('total_price', models.FloatField(default=0)), + ('status', models.CharField(choices=[('pending', 'Pendiente'), ('processing', 'En preparación'), ('shipped', 'Enviado')], default='pending', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.order')), + ('seller', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items_to_fulfill', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='OrderMessage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('order_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='tienda.orderitem')), + ('sender', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['created_at'], + }, + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='', max_length=200)), + ('description', models.TextField(default='')), + ('briefdesc', models.TextField(default='')), + ('price', models.FloatField(default=0)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tienda.category')), + ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='created_products', to=settings.AUTH_USER_MODEL)), + ('primary_image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='tienda.image')), + ('secondary_images', models.ManyToManyField(blank=True, related_name='products_secondary', to='tienda.image')), + ], + ), + migrations.AddField( + model_name='orderitem', + name='product', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tienda.product'), + ), + migrations.CreateModel( + name='ShippingAddress', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('full_name', models.CharField(max_length=200, verbose_name='Nombre completo')), + ('address_line_1', models.CharField(max_length=250, verbose_name='Dirección')), + ('address_line_2', models.CharField(blank=True, max_length=250, verbose_name='Dirección (línea 2)')), + ('city', models.CharField(max_length=100, verbose_name='Ciudad')), + ('postal_code', models.CharField(max_length=20, verbose_name='Código postal')), + ('country', models.CharField(default='España', max_length=100, verbose_name='País')), + ('phone', models.CharField(max_length=20, verbose_name='Teléfono')), + ('is_default', models.BooleanField(default=False, verbose_name='Dirección predeterminada')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shipping_addresses', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Dirección de envío', + 'verbose_name_plural': 'Direcciones de envío', + 'ordering': ['-is_default', '-created_at'], + }, + ), + migrations.AddField( + model_name='order', + name='shipping_address', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='tienda.shippingaddress'), + ), + migrations.CreateModel( + name='VerificationCode', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.TextField(default='')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_belongsto', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='CartItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('added_at', models.DateTimeField(auto_now_add=True)), + ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.cart')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tienda.product')), + ], + options={ + 'unique_together': {('cart', 'product')}, }, ), ] diff --git a/tienda/migrations/0002_alter_category_options_remove_category_parent.py b/tienda/migrations/0002_alter_category_options_remove_category_parent.py deleted file mode 100644 index a7abb93..0000000 --- a/tienda/migrations/0002_alter_category_options_remove_category_parent.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:38 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='category', - options={}, - ), - migrations.RemoveField( - model_name='category', - name='parent', - ), - ] diff --git a/tienda/migrations/0002_verificationcode_code_mode_and_more.py b/tienda/migrations/0002_verificationcode_code_mode_and_more.py new file mode 100644 index 0000000..8dfb6c3 --- /dev/null +++ b/tienda/migrations/0002_verificationcode_code_mode_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.1 on 2026-03-10 11:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tienda', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='verificationcode', + name='code_mode', + field=models.CharField(choices=[('VA', 'Verify Account'), ('RP', 'Reset Password')], default='VA', max_length=2), + ), + migrations.AlterField( + model_name='verificationcode', + name='code', + field=models.TextField(default='', unique=True), + ), + ] diff --git a/tienda/migrations/0003_image.py b/tienda/migrations/0003_image.py deleted file mode 100644 index 93963c8..0000000 --- a/tienda/migrations/0003_image.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0002_alter_category_options_remove_category_parent'), - ] - - operations = [ - migrations.CreateModel( - name='Image', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='', max_length=200)), - ('image', models.ImageField(upload_to='')), - ], - ), - ] diff --git a/tienda/migrations/0004_alter_image_image.py b/tienda/migrations/0004_alter_image_image.py deleted file mode 100644 index dbd9c16..0000000 --- a/tienda/migrations/0004_alter_image_image.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0003_image'), - ] - - operations = [ - migrations.AlterField( - model_name='image', - name='image', - field=models.ImageField(upload_to='images/'), - ), - ] diff --git a/tienda/migrations/0005_product.py b/tienda/migrations/0005_product.py deleted file mode 100644 index be53e93..0000000 --- a/tienda/migrations/0005_product.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:48 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0004_alter_image_image'), - ] - - operations = [ - migrations.CreateModel( - name='Product', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='', max_length=200)), - ('description', models.TextField(default='')), - ('price', models.FloatField(default=0)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tienda.category')), - ('primary_image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='tienda.image')), - ], - ), - ] diff --git a/tienda/migrations/0006_product_secondary_images.py b/tienda/migrations/0006_product_secondary_images.py deleted file mode 100644 index 062bf13..0000000 --- a/tienda/migrations/0006_product_secondary_images.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0.1 on 2026-01-23 09:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0005_product'), - ] - - operations = [ - migrations.AddField( - model_name='product', - name='secondary_images', - field=models.ManyToManyField(blank=True, related_name='products_secondary', to='tienda.image'), - ), - ] diff --git a/tienda/migrations/0007_product_briefdesc.py b/tienda/migrations/0007_product_briefdesc.py deleted file mode 100644 index 8a8325d..0000000 --- a/tienda/migrations/0007_product_briefdesc.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-06 07:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0006_product_secondary_images'), - ] - - operations = [ - migrations.AddField( - model_name='product', - name='briefdesc', - field=models.TextField(default=''), - ), - ] diff --git a/tienda/migrations/0008_cart_cartitem.py b/tienda/migrations/0008_cart_cartitem.py deleted file mode 100644 index ec6272c..0000000 --- a/tienda/migrations/0008_cart_cartitem.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-06 10:41 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0007_product_briefdesc'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Cart', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('session_key', models.CharField(blank=True, max_length=40, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='CartItem', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.PositiveIntegerField(default=1)), - ('added_at', models.DateTimeField(auto_now_add=True)), - ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.cart')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tienda.product')), - ], - options={ - 'unique_together': {('cart', 'product')}, - }, - ), - ] diff --git a/tienda/migrations/0009_product_creator.py b/tienda/migrations/0009_product_creator.py deleted file mode 100644 index c5cb169..0000000 --- a/tienda/migrations/0009_product_creator.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-06 10:48 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0008_cart_cartitem'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='product', - name='creator', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='created_products', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/tienda/migrations/0010_order_orderitem.py b/tienda/migrations/0010_order_orderitem.py deleted file mode 100644 index 003b14e..0000000 --- a/tienda/migrations/0010_order_orderitem.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-09 09:06 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0009_product_creator'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Order', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('session_key', models.CharField(blank=True, max_length=40, null=True)), - ('total', models.FloatField(default=0)), - ('status', models.CharField(choices=[('paid', 'Pagado'), ('cancelled', 'Cancelado')], default='paid', max_length=20)), - ('payment_method', models.CharField(choices=[('stripe', 'Stripe'), ('paypal', 'PayPal'), ('manual', 'Manual')], default='manual', max_length=20)), - ('payment_reference', models.CharField(blank=True, default='', max_length=200)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('buyer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='OrderItem', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('product_name', models.CharField(default='', max_length=200)), - ('quantity', models.PositiveIntegerField(default=1)), - ('unit_price', models.FloatField(default=0)), - ('total_price', models.FloatField(default=0)), - ('status', models.CharField(choices=[('pending', 'Pendiente'), ('processing', 'En preparación'), ('shipped', 'Enviado')], default='pending', max_length=20)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.order')), - ('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='tienda.product')), - ('seller', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items_to_fulfill', to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/tienda/migrations/0011_ordermessage.py b/tienda/migrations/0011_ordermessage.py deleted file mode 100644 index 17796b6..0000000 --- a/tienda/migrations/0011_ordermessage.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-09 09:12 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0010_order_orderitem'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='OrderMessage', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('message', models.TextField()), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('order_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='tienda.orderitem')), - ('sender', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['created_at'], - }, - ), - ] diff --git a/tienda/migrations/0012_image_alt_shippingaddress.py b/tienda/migrations/0012_image_alt_shippingaddress.py deleted file mode 100644 index d6e0f4e..0000000 --- a/tienda/migrations/0012_image_alt_shippingaddress.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 6.0.1 on 2026-02-16 11:57 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tienda', '0011_ordermessage'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='image', - name='alt', - field=models.CharField(blank=True, default='', max_length=255, verbose_name='Texto alternativo'), - ), - migrations.CreateModel( - name='ShippingAddress', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('full_name', models.CharField(max_length=200, verbose_name='Nombre completo')), - ('address_line_1', models.CharField(max_length=250, verbose_name='Dirección')), - ('address_line_2', models.CharField(blank=True, max_length=250, verbose_name='Dirección (línea 2)')), - ('city', models.CharField(max_length=100, verbose_name='Ciudad')), - ('postal_code', models.CharField(max_length=20, verbose_name='Código postal')), - ('country', models.CharField(default='España', max_length=100, verbose_name='País')), - ('phone', models.CharField(max_length=20, verbose_name='Teléfono')), - ('is_default', models.BooleanField(default=False, verbose_name='Dirección predeterminada')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shipping_addresses', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Dirección de envío', - 'verbose_name_plural': 'Direcciones de envío', - 'ordering': ['-is_default', '-created_at'], - }, - ), - ] diff --git a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc index 729551d..851e7f4 100644 Binary files a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc and b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/0002_alter_category_options_remove_category_parent.cpython-314.pyc b/tienda/migrations/__pycache__/0002_alter_category_options_remove_category_parent.cpython-314.pyc deleted file mode 100644 index cd30f79..0000000 Binary files a/tienda/migrations/__pycache__/0002_alter_category_options_remove_category_parent.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc b/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc new file mode 100644 index 0000000..21c9811 Binary files /dev/null and b/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/0003_image.cpython-314.pyc b/tienda/migrations/__pycache__/0003_image.cpython-314.pyc deleted file mode 100644 index 8954823..0000000 Binary files a/tienda/migrations/__pycache__/0003_image.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0004_alter_image_image.cpython-314.pyc b/tienda/migrations/__pycache__/0004_alter_image_image.cpython-314.pyc deleted file mode 100644 index 85b1d13..0000000 Binary files a/tienda/migrations/__pycache__/0004_alter_image_image.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0005_product.cpython-314.pyc b/tienda/migrations/__pycache__/0005_product.cpython-314.pyc deleted file mode 100644 index 1bc7953..0000000 Binary files a/tienda/migrations/__pycache__/0005_product.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0006_product_secondary_images.cpython-314.pyc b/tienda/migrations/__pycache__/0006_product_secondary_images.cpython-314.pyc deleted file mode 100644 index 03f7d36..0000000 Binary files a/tienda/migrations/__pycache__/0006_product_secondary_images.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0007_product_briefdesc.cpython-314.pyc b/tienda/migrations/__pycache__/0007_product_briefdesc.cpython-314.pyc deleted file mode 100644 index 0b36937..0000000 Binary files a/tienda/migrations/__pycache__/0007_product_briefdesc.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0008_cart_cartitem.cpython-314.pyc b/tienda/migrations/__pycache__/0008_cart_cartitem.cpython-314.pyc deleted file mode 100644 index 8df3186..0000000 Binary files a/tienda/migrations/__pycache__/0008_cart_cartitem.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0009_auto_20260206_1042.cpython-314.pyc b/tienda/migrations/__pycache__/0009_auto_20260206_1042.cpython-314.pyc deleted file mode 100644 index 68fa0b9..0000000 Binary files a/tienda/migrations/__pycache__/0009_auto_20260206_1042.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0009_product_creator.cpython-314.pyc b/tienda/migrations/__pycache__/0009_product_creator.cpython-314.pyc deleted file mode 100644 index 32a084f..0000000 Binary files a/tienda/migrations/__pycache__/0009_product_creator.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0010_order_orderitem.cpython-314.pyc b/tienda/migrations/__pycache__/0010_order_orderitem.cpython-314.pyc deleted file mode 100644 index 6d6fc1b..0000000 Binary files a/tienda/migrations/__pycache__/0010_order_orderitem.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0011_ordermessage.cpython-314.pyc b/tienda/migrations/__pycache__/0011_ordermessage.cpython-314.pyc deleted file mode 100644 index b0db5c8..0000000 Binary files a/tienda/migrations/__pycache__/0011_ordermessage.cpython-314.pyc and /dev/null differ diff --git a/tienda/migrations/__pycache__/0012_image_alt_shippingaddress.cpython-314.pyc b/tienda/migrations/__pycache__/0012_image_alt_shippingaddress.cpython-314.pyc deleted file mode 100644 index 31f3562..0000000 Binary files a/tienda/migrations/__pycache__/0012_image_alt_shippingaddress.cpython-314.pyc and /dev/null differ diff --git a/tienda/models.py b/tienda/models.py index 7eddd51..e391fac 100644 --- a/tienda/models.py +++ b/tienda/models.py @@ -1,6 +1,31 @@ from django.db import models -from django.contrib.auth.models import User +from django.contrib.auth.models import User, AbstractUser from .vars import VAT_RATE +import random, string +class User(AbstractUser): + class RegisterStatus(models.TextChoices): + CONFIRMATION_REQUIRED = "CR", "Confirmation Required" + ACTIVE = "AC", "Active" + BANNED = "BN", "Banned" + + registration_status = models.CharField( + max_length = 2, + choices = RegisterStatus.choices, + default = RegisterStatus.CONFIRMATION_REQUIRED + ) + +class VerificationCode(models.Model): + class VerificationModes(models.TextChoices): + VERIFY_ACCOUNT = "VA" + RESET_PASSWORD = "RP" + + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_belongsto", null=False, blank=False) + code = models.TextField(default = "", unique=True) + code_mode = models.CharField( + max_length=2, + choices = VerificationModes.choices, + default = VerificationModes.VERIFY_ACCOUNT + ) # Create your models here. class Category(models.Model): @@ -105,6 +130,7 @@ class Order(models.Model): ] buyer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='orders') + shipping_address = models.ForeignKey('ShippingAddress', on_delete=models.SET_NULL, null=True, blank=True, related_name='orders') session_key = models.CharField(max_length=40, null=True, blank=True) total = models.FloatField(default=0) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PAID) diff --git a/tienda/static/js/checkout.js b/tienda/static/js/checkout.js index bfed0d8..a5215d6 100644 --- a/tienda/static/js/checkout.js +++ b/tienda/static/js/checkout.js @@ -45,6 +45,14 @@ document.addEventListener("DOMContentLoaded", () => { console.log("Stripe initialized"); button.addEventListener("click", () => { + const shippingAddressSelect = document.getElementById("shipping-address"); + const selectedShippingAddress = shippingAddressSelect ? shippingAddressSelect.value : ""; + + if (!selectedShippingAddress) { + alert("Selecciona una dirección de envío para continuar."); + return; + } + console.log("Checkout button clicked"); button.disabled = true; button.innerHTML = "Procesando..."; @@ -55,7 +63,9 @@ document.addEventListener("DOMContentLoaded", () => { "Content-Type": "application/json", "X-CSRFToken": getCookie("csrftoken") }, - body: JSON.stringify({}) + body: JSON.stringify({ + shipping_address_id: selectedShippingAddress + }) }) .then((res) => { console.log("Session response status:", res.status); diff --git a/tienda/tasks.py b/tienda/tasks.py new file mode 100644 index 0000000..5a7de53 --- /dev/null +++ b/tienda/tasks.py @@ -0,0 +1,7 @@ +from celery import shared_task +from .utilities import send_email +from .vars import login_message + +@shared_task +def enviar_correo_bienvenida(email_usuario, nombre_usuario): + send_email(email_usuario, "Inicio de Sesión correcto", login_message.format(name = nombre_usuario)) \ No newline at end of file diff --git a/tienda/templates/tienda/base.html b/tienda/templates/tienda/base.html index e6a162d..e3586db 100644 --- a/tienda/templates/tienda/base.html +++ b/tienda/templates/tienda/base.html @@ -1,4 +1,5 @@ {% load static %} +{% load cache %} {% load compress %} @@ -69,6 +70,7 @@ {% block head %}{% endblock %}
+ {% cache 500 sidebar request.user.username %} + {% endcache %}