Add S3 Storage...

This commit is contained in:
2026-04-28 21:19:32 +02:00
parent d8f6838f0c
commit 84d8a0e3b6
7 changed files with 86 additions and 4 deletions
+12
View File
@@ -2,6 +2,7 @@
SECRET_KEY=django-insecure-change-me SECRET_KEY=django-insecure-change-me
DEBUG=True DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1 ALLOWED_HOSTS=localhost,127.0.0.1
S3_ENABLE=False
# PostgreSQL (por defecto habilitado; si POSTGRES_ENABLED=False se usa SQLite) # PostgreSQL (por defecto habilitado; si POSTGRES_ENABLED=False se usa SQLite)
POSTGRES_ENABLED=True POSTGRES_ENABLED=True
@@ -14,6 +15,17 @@ POSTGRES_PORT=5432
# Redis # Redis
REDIS_URL=redis://127.0.0.1:6379/1 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
STRIPE_PUBLISHABLE_KEY= STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY= STRIPE_SECRET_KEY=
+1
View File
@@ -35,6 +35,7 @@ Templates use Django's inheritance pattern:
- **Image uploads**: Organized in `tienda/static/media/images/` via `upload_to='images/'` in ImageField - **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 - **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/')` - **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 ## Shipping Restrictions
- **Zona de envío**: Solo se vende/envía dentro de la provincia de Almería - **Zona de envío**: Solo se vende/envía dentro de la provincia de Almería
+4 -2
View File
@@ -34,7 +34,9 @@ http {
listen 80; listen 80;
server_name _; 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/ { location /static/ {
alias /static/; alias /static/;
expires 30d; expires 30d;
@@ -42,7 +44,7 @@ http {
access_log off; access_log off;
} }
# Archivos subidos por usuarios. # Archivos subidos por usuarios en modo local.
location /media/ { location /media/ {
alias /media/; alias /media/;
expires 7d; expires 7d;
+43 -1
View File
@@ -53,6 +53,21 @@ def env_int(name: str, default: int) -> int:
return default return default
return int(value) 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'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / '.env') 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env_bool('DEBUG', True) DEBUG = env_bool('DEBUG', True)
S3_ENABLE = env_bool('S3_ENABLE', False)
ALLOWED_HOSTS = env_list('ALLOWED_HOSTS', [ ALLOWED_HOSTS = env_list('ALLOWED_HOSTS', [
'192.168.1.142', '192.168.1.142',
@@ -87,9 +103,11 @@ INSTALLED_APPS = [
'compressor', 'compressor',
] ]
if S3_ENABLE:
INSTALLED_APPS.append('storages')
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@@ -98,6 +116,9 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
if not S3_ENABLE:
MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
ROOT_URLCONF = 'proyecto.urls' ROOT_URLCONF = 'proyecto.urls'
TEMPLATES = [ 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 = [ STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+1 -1
View File
@@ -28,5 +28,5 @@ urlpatterns = [
path('api/', api.urls) 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) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+2
View File
@@ -14,6 +14,7 @@ Django==6.0.4
django-appconf==1.2.0 django-appconf==1.2.0
django-redis==5.4.0 django-redis==5.4.0
django_compressor==4.6.0 django_compressor==4.6.0
django-storages[boto3]==1.14.6
gunicorn==25.1.0 gunicorn==25.1.0
idna==3.11 idna==3.11
Jinja2==3.1.6 Jinja2==3.1.6
@@ -22,6 +23,7 @@ MarkupSafe==3.0.3
packaging==26.0 packaging==26.0
paypalrestsdk==1.13.3 paypalrestsdk==1.13.3
pillow==12.2.0 pillow==12.2.0
boto3==1.42.97
prompt_toolkit==3.0.52 prompt_toolkit==3.0.52
pycparser==3.0 pycparser==3.0
pyOpenSSL==26.0.0 pyOpenSSL==26.0.0
+23
View File
@@ -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',
}