From 84d8a0e3b6b0cb4d9521f0281f9911e53009b5a5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 28 Apr 2026 21:19:32 +0200 Subject: [PATCH] Add S3 Storage... --- .env.example | 12 +++++++++ .github/copilot-instructions.md | 1 + nginx.conf | 6 +++-- proyecto/settings.py | 44 ++++++++++++++++++++++++++++++++- proyecto/urls.py | 2 +- requirements.txt | 2 ++ tienda/storage_backends.py | 23 +++++++++++++++++ 7 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 tienda/storage_backends.py diff --git a/.env.example b/.env.example index 9a50945..7cdfeed 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ SECRET_KEY=django-insecure-change-me DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1 +S3_ENABLE=False # PostgreSQL (por defecto habilitado; si POSTGRES_ENABLED=False se usa SQLite) POSTGRES_ENABLED=True @@ -14,6 +15,17 @@ POSTGRES_PORT=5432 # Redis REDIS_URL=redis://127.0.0.1:6379/1 +# S3 (activar con S3_ENABLE=True) +AWS_STORAGE_BUCKET_NAME= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_S3_REGION_NAME= +AWS_S3_ENDPOINT_URL= +AWS_S3_CUSTOM_DOMAIN= +AWS_S3_USE_SSL=True +AWS_QUERYSTRING_AUTH=False +AWS_DEFAULT_ACL=public-read + # Stripe STRIPE_PUBLISHABLE_KEY= STRIPE_SECRET_KEY= diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b3470d7..b9cdc65 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -35,6 +35,7 @@ Templates use Django's inheritance pattern: - **Image uploads**: Organized in `tienda/static/media/images/` via `upload_to='images/'` in ImageField - **Access**: Media files served automatically in development via Django's static file handler - **Image model**: Located in [tienda/models.py](tienda/models.py) with `ImageField(upload_to='images/')` +- **S3 mode**: if `S3_ENABLE=True` (case-insensitive), static and media switch to S3 storages instead of the local filesystem; Nginx should proxy the app only and the browser should load asset URLs from the bucket or CDN ## Shipping Restrictions - **Zona de envío**: Solo se vende/envía dentro de la provincia de Almería diff --git a/nginx.conf b/nginx.conf index a2a3e58..7e100bf 100644 --- a/nginx.conf +++ b/nginx.conf @@ -34,7 +34,9 @@ http { listen 80; server_name _; - # Archivos estáticos generados por collectstatic. + # Modo local: sirve static/media desde volúmenes montados. + # Si S3_ENABLE=True, estos bloques no se usan y el navegador debe + # cargar los assets directamente desde el bucket o CDN. location /static/ { alias /static/; expires 30d; @@ -42,7 +44,7 @@ http { access_log off; } - # Archivos subidos por usuarios. + # Archivos subidos por usuarios en modo local. location /media/ { alias /media/; expires 7d; diff --git a/proyecto/settings.py b/proyecto/settings.py index 2732df4..933895a 100644 --- a/proyecto/settings.py +++ b/proyecto/settings.py @@ -53,6 +53,21 @@ def env_int(name: str, default: int) -> int: return default return int(value) + +def env_str(name: str, default: str = '') -> str: + value = os.getenv(name) + if value is None: + return default + return value.strip() + + +def env_optional_str(name: str) -> str | None: + value = os.getenv(name) + if value is None: + return None + value = value.strip() + return value or None + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent load_dotenv(BASE_DIR / '.env') @@ -66,6 +81,7 @@ SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-#g((q@lvnkt(j6)2(gvtn0px)r # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env_bool('DEBUG', True) +S3_ENABLE = env_bool('S3_ENABLE', False) ALLOWED_HOSTS = env_list('ALLOWED_HOSTS', [ '192.168.1.142', @@ -87,9 +103,11 @@ INSTALLED_APPS = [ 'compressor', ] +if S3_ENABLE: + INSTALLED_APPS.append('storages') + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -98,6 +116,9 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +if not S3_ENABLE: + MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') + ROOT_URLCONF = 'proyecto.urls' TEMPLATES = [ @@ -211,6 +232,27 @@ STORAGES = { }, } +if S3_ENABLE: + AWS_STORAGE_BUCKET_NAME = env_str('AWS_STORAGE_BUCKET_NAME') or None + AWS_ACCESS_KEY_ID = env_optional_str('AWS_ACCESS_KEY_ID') + AWS_SECRET_ACCESS_KEY = env_optional_str('AWS_SECRET_ACCESS_KEY') + AWS_S3_REGION_NAME = env_optional_str('AWS_S3_REGION_NAME') + AWS_S3_ENDPOINT_URL = env_optional_str('AWS_S3_ENDPOINT_URL') + AWS_S3_CUSTOM_DOMAIN = env_optional_str('AWS_S3_CUSTOM_DOMAIN') + AWS_S3_USE_SSL = env_bool('AWS_S3_USE_SSL', True) + AWS_QUERYSTRING_AUTH = env_bool('AWS_QUERYSTRING_AUTH', False) + AWS_DEFAULT_ACL = env_str('AWS_DEFAULT_ACL', 'public-read') or None + AWS_S3_OBJECT_PARAMETERS = {} + + STORAGES = { + 'default': { + 'BACKEND': 'tienda.storage_backends.MediaStorage', + }, + 'staticfiles': { + 'BACKEND': 'tienda.storage_backends.StaticStorage', + }, + } + STATICFILES_FINDERS = [ 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', diff --git a/proyecto/urls.py b/proyecto/urls.py index 57c06ad..ed79491 100644 --- a/proyecto/urls.py +++ b/proyecto/urls.py @@ -28,5 +28,5 @@ urlpatterns = [ path('api/', api.urls) ] -if settings.DEBUG: +if settings.DEBUG and not settings.S3_ENABLE: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/requirements.txt b/requirements.txt index db7b304..0e56595 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ Django==6.0.4 django-appconf==1.2.0 django-redis==5.4.0 django_compressor==4.6.0 +django-storages[boto3]==1.14.6 gunicorn==25.1.0 idna==3.11 Jinja2==3.1.6 @@ -22,6 +23,7 @@ MarkupSafe==3.0.3 packaging==26.0 paypalrestsdk==1.13.3 pillow==12.2.0 +boto3==1.42.97 prompt_toolkit==3.0.52 pycparser==3.0 pyOpenSSL==26.0.0 diff --git a/tienda/storage_backends.py b/tienda/storage_backends.py new file mode 100644 index 0000000..1b040ac --- /dev/null +++ b/tienda/storage_backends.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from storages.backends.s3 import S3ManifestStaticStorage, S3Storage + + +class StaticStorage(S3ManifestStaticStorage): + location = 'static' + default_acl = 'public-read' + querystring_auth = False + file_overwrite = True + object_parameters = { + 'CacheControl': 'public, max-age=31536000, immutable', + } + + +class MediaStorage(S3Storage): + location = 'media' + default_acl = 'public-read' + querystring_auth = False + file_overwrite = False + object_parameters = { + 'CacheControl': 'public, max-age=604800', + } \ No newline at end of file