Archivos estáticos y multimedia#

Configuración para servir archivos estáticos y medios cargados por el usuario en producción.

Descripción general#

Construbot maneja dos tipos de archivos:

  1. Archivos estáticos: CSS, JavaScript, imágenes (parte del código de la aplicación)

  2. Archivos multimedia: cargas de usuarios (PDF, imágenes, documentos)

En producción, estos deben servirse de manera diferente que en desarrollo en términos de rendimiento y escalabilidad.

Estrategia de archivos estáticos#

CDN (CloudFront/CloudFlare)#

Para mayor tráfico, proporcione archivos estáticos a través de una CDN:

Configuración de AWS CloudFront:

# Create S3 bucket for static files
aws s3 mb s3://construbot-static-yourdomain

# Sync static files to S3
aws s3 sync staticfiles/ s3://construbot-static-yourdomain/static/

# Create CloudFront distribution
aws cloudfront create-distribution \\
  --origin-domain-name construbot-static-yourdomain.s3.amazonaws.com \\
  --default-root-object index.html

Actualizar configuración:

# In .env
STATIC_URL=https://d111111abcdef8.cloudfront.net/static/

Beneficios:

  • Distribución global

  • Menor latencia

  • Descargar tráfico de los servidores de aplicaciones

  • Costos de ancho de banda reducidos

Estrategia de archivos multimedia#

Espacios DigitalOceánicos#

Alternativa a S3:

# In .env
USE_S3=True  # Spaces uses S3-compatible API
AWS_ACCESS_KEY_ID=your-spaces-key
AWS_SECRET_ACCESS_KEY=your-spaces-secret
AWS_STORAGE_BUCKET_NAME=construbot-media
AWS_S3_ENDPOINT_URL=https://nyc3.digitaloceanspaces.com
AWS_S3_REGION_NAME=nyc3

Más barato que S3 para un uso elevado de ancho de banda.

Almacenamiento local (pequeñas implementaciones)#

Para implementaciones pequeñas sin almacenamiento en la nube:

# In .env
USE_S3=False

Configure Nginx para servir archivos multimedia:

location /media/ {
    alias /app/media/;
    expires 1y;
    add_header Cache-Control "public";
}

Montar volumen de medios en docker-compose.yml:

django:
  volumes:
    - media_volume:/app/media

nginx:
  volumes:
    - media_volume:/app/media:ro

Se requiere estrategia de respaldo - rsync al servidor remoto o S3.

Configuración de archivos estáticos#

Referencia de configuración#

base.py:

# Static files (CSS, JavaScript, Images)
STATIC_ROOT = str(ROOT_DIR / 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = [
    str(APPS_DIR / 'static'),
]

producción.py:

# WhiteNoise
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# Or with CDN:
STATIC_URL = 'https://cdn.yourdomain.com/static/'

Recopilar archivos estáticos#

Durante la implementación:

docker compose run --rm django python manage.py collectstatic --no-input

Lo que sucede:

  1. Reúne todos los archivos estáticos de aplicaciones y STATICFILES_DIRS

  2. Los procesa (minificación, hash)

  3. Copias a STATIC_ROOT (staticfiles/)

  4. Crea el manifiesto staticfiles.json

Sistema de manifiesto:

{
  "version": "1.0",
  "paths": {
    "css/main.css": "css/main.abc123.css",
    "js/app.js": "js/app.def456.js"
  }
}

Beneficios:

  • Eliminación de caché (nombres de archivos únicos)

  • Encabezados de caché de futuro lejano

  • Compresión automática (gzip/brotli)

Archivos estáticos personalizados#

Agregar CSS/JS personalizado:

# Place in
construbot/static/css/custom.css
construbot/static/js/custom.js

En plantillas:

{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
<script src="{% static 'js/custom.js' %}"></script>

Recordar después de los cambios:

docker compose run --rm django python manage.py collectstatic --no-input

Configuración de archivos multimedia#

Referencia de configuración#

base.py:

# Media files (user uploads)
MEDIA_ROOT = str(APPS_DIR / 'media')
MEDIA_URL = '/media/'

producción.py con S3:

if USE_S3:
    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/' if AWS_S3_CUSTOM_DOMAIN \\
                else f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/'

Manejo de carga de archivos#

En modelos (ejemplo):

from django.db import models

class Document(models.Model):
    file = models.FileField(upload_to='documents/%Y/%m/%d/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

Subir a S3:

# In views or forms
document = Document.objects.create(file=request.FILES['file'])
# File automatically uploaded to S3

URL del archivo:

# Get file URL
url = document.file.url
# Returns: https://bucket.s3.amazonaws.com/documents/2024/01/15/file.pdf

URL firmadas (archivos privados)#

Para acceso seguro a archivos:

from django.core.files.storage import default_storage

# Generate signed URL (expires in 1 hour)
url = default_storage.url('path/to/file.pdf', expire=3600)

Configurar los ajustes de S3:

# In production.py
AWS_QUERYSTRING_AUTH = True  # Use signed URLs
AWS_S3_FILE_OVERWRITE = False  # Don't overwrite files
AWS_DEFAULT_ACL = None  # Use bucket ACL

Mejoramiento#

Optimización de imagen#

Instalar almohada (ya incluida):

from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile

def optimize_image(image_field):
    img = Image.open(image_field)

    # Resize if too large
    if img.width > 1920:
        img.thumbnail((1920, 1920), Image.LANCZOS)

    # Convert to RGB if needed
    if img.mode != 'RGB':
        img = img.convert('RGB')

    # Save with optimization
    output = BytesIO()
    img.save(output, format='JPEG', quality=85, optimize=True)
    output.seek(0)

    return ContentFile(output.read())

Compresión#

Archivos estáticos (WhiteNoise maneja automáticamente):

  • compresión gzip

  • Compresión Brotli (si está instalada)

Compresión manual:

# Install brotli
pip install brotli

# In production.py
WHITENOISE_COMPRESSION = True  # Default

Almacenamiento en caché#

Almacenamiento en caché de archivos estáticos:

location /static/ {
    alias /app/staticfiles/;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Almacenamiento en caché de archivos multimedia:

location /media/ {
    alias /app/media/;
    expires 30d;
    add_header Cache-Control "public";
}

Configuración de CDN#

Nube Frontal para S3#

Crear distribución:

aws cloudfront create-distribution \\
  --origin-domain-name construbot-media-yourdomain.s3.amazonaws.com \\
  --comment "Construbot media files" \\
  --default-cache-behavior '{
    "TargetOriginId": "S3-construbot-media",
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": {"Items": ["GET", "HEAD"], "Quantity": 2},
    "CachedMethods": {"Items": ["GET", "HEAD"], "Quantity": 2},
    "ForwardedValues": {
      "QueryString": false,
      "Cookies": {"Forward": "none"}
    },
    "MinTTL": 0,
    "DefaultTTL": 86400,
    "MaxTTL": 31536000
  }'

Actualizar la configuración de Django:

# In .env
AWS_S3_CUSTOM_DOMAIN=d111111abcdef8.cloudfront.net

nubeflare#

Configuración:

  1. Agregar dominio a CloudFlare

  2. Actualizar servidores de nombres

  3. Habilitar el almacenamiento en caché para rutas estáticas/media

  4. Configurar reglas de página

Regla de página para archivos estáticos:

URL: *yourdomain.com/static/*
Cache Level: Cache Everything
Edge Cache TTL: 1 year

Copia de seguridad y recuperación#

Versiones S3#

# Enable versioning
aws s3api put-bucket-versioning \\
  --bucket construbot-media-yourdomain \\
  --versioning-configuration Status=Enabled

# List versions
aws s3api list-object-versions --bucket construbot-media-yourdomain

# Restore previous version
aws s3api copy-object \\
  --copy-source construbot-media-yourdomain/path/to/file.pdf?versionId=xxx \\
  --bucket construbot-media-yourdomain \\
  --key path/to/file.pdf

Políticas de ciclo de vida de S3#

Eliminar archivos antiguos automáticamente:

{
  "Rules": [
    {
      "Id": "DeleteOldVersions",
      "Status": "Enabled",
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 90
      }
    },
    {
      "Id": "TransitionToGlacier",
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 365,
          "StorageClass": "GLACIER"
        }
      ]
    }
  ]
}
aws s3api put-bucket-lifecycle-configuration \\
  --bucket construbot-media-yourdomain \\
  --lifecycle-configuration file://lifecycle.json

Copia de seguridad local#

Sincronizar S3 con local:

# Backup media files locally
aws s3 sync s3://construbot-media-yourdomain /backup/media/

# Schedule with cron
0 3 * * * aws s3 sync s3://construbot-media-yourdomain /backup/media/

Solución de problemas#

Los archivos estáticos no se cargan#

# Recollect
docker compose run --rm django python manage.py collectstatic --clear --no-input

# Check STATIC_ROOT
ls -la staticfiles/

# Verify STATIC_URL in settings
docker compose run --rm django python manage.py shell
>>> from django.conf import settings
>>> print(settings.STATIC_URL)

Error al cargar el archivo#

Verifique los permisos de S3:

# Test AWS credentials
aws s3 ls s3://construbot-media-yourdomain

# Check Django settings
docker compose run --rm django python manage.py shell
>>> from django.conf import settings
>>> print(settings.DEFAULT_FILE_STORAGE)
>>> print(settings.AWS_STORAGE_BUCKET_NAME)

Carga de prueba:

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
path = default_storage.save('test.txt', ContentFile(b'test'))
print(path)

Errores CORS#

Actualización S3 CORS:

aws s3api put-bucket-cors \\
  --bucket construbot-media-yourdomain \\
  --cors-configuration file://cors.json

Permita su dominio en cors.json AllowedOrigins.

Ver también#