¿Cómo configurar un proyecto Django con django-storages y Amazon S3, pero con diferentes carpetas para archivos estáticos y archivos multimedia?

92

Estoy configurando un proyecto de Django que estaba usando el sistema de archivos del servidor para almacenar los archivos estáticos de las aplicaciones ( STATIC_ROOT) y los archivos cargados por el usuario ( MEDIA_ROOT).

Ahora necesito alojar todo ese contenido en el S3 de Amazon, así que he creado un depósito para esto. Utilizando django-storagescon el botobackend de almacenamiento, logré cargar las estadísticas recopiladas en el depósito S3:

MEDIA_ROOT = '/media/'
STATIC_ROOT = '/static/'

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
AWS_ACCESS_KEY_ID = 'KEY_ID...'
AWS_SECRET_ACCESS_KEY = 'ACCESS_KEY...'
AWS_STORAGE_BUCKET_NAME = 'bucket-name'
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'

Luego, tuve un problema: MEDIA_ROOTy STATIC_ROOTno se usan dentro del depósito, por lo que la raíz del depósito contiene tanto los archivos estáticos como las rutas cargadas por el usuario.

Entonces podría establecer:

S3_URL = 'http://s3.amazonaws.com/%s' % AWS_STORAGE_BUCKET_NAME
STATIC_URL = S3_URL + STATIC_ROOT
MEDIA_URL = 'S3_URL + MEDIA_ROOT

Y use esa configuración en las plantillas, pero no hay distinción de archivos estáticos / multimedia cuando se almacena en S3 con django-storages.

¿Cómo se puede hacer esto?

¡Gracias!

Armando Pérez Marqués
fuente
8
Porque solo hay una configuración para especificar el nombre del depósito ( AWS_STORAGE_BUCKET_NAME), y esa es la que se usa cuando se crea una instancia de la clase especificada en STATICFILES_STORAGE.
Armando Pérez Marqués

Respuestas:

126

Creo que lo siguiente debería funcionar y ser más simple que el método de Mandx, aunque es muy similar:

Crea un s3utils.pyarchivo:

from storages.backends.s3boto import S3BotoStorage

StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static')
MediaRootS3BotoStorage  = lambda: S3BotoStorage(location='media')

Entonces en tu settings.py:

DEFAULT_FILE_STORAGE = 'myproject.s3utils.MediaRootS3BotoStorage'
STATICFILES_STORAGE = 'myproject.s3utils.StaticRootS3BotoStorage'

Un ejemplo diferente pero relacionado (que en realidad he probado) se puede ver en los dos example_archivos aquí .

bradenm
fuente
1
Definitivamente más simple y mejor que mi versión. Aunque no lo he probado, también creo que funcionará. ¡Gracias! También estoy revisando su repositorio django-s3storage , parece una solución muy liviana si el proyecto usa S3 exclusivamente.
Armando Pérez Marqués
1
Y, si le gusta más el empaquetado, consulte django-s3-folder-storage . Lo acabo de encontrar, no puedo decir si es la misma solución pero empaquetada.
Armando Pérez Marqués
4
Esto no funciona para mí, los archivos multimedia se cargan en el / del depósito s3. Parece que la configuración de ubicación no se está respetando. django-storages == 1.1.6, django-extensions == 1.1.1, django = 1.4
Nathan Keller
3
Para mí, tenía más sentido tener depósitos separados y no me gusta tener una configuración fuera de mi módulo de configuración, por lo que mi solución terminó luciendo así: gist.github.com/antonagestam/6075199
antonagestam
1
Esta solución no funciona, por lo que puedo decir. Este debería ser el enfoque: gist.github.com/defrex/82680e858281d3d3e6e4
defrex
8

Actualmente estoy usando este código en un s3utilsmódulo separado :

from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import force_unicode

from storages.backends.s3boto import S3BotoStorage


def safe_join(base, *paths):
    """
    A version of django.utils._os.safe_join for S3 paths.

    Joins one or more path components to the base path component intelligently.
    Returns a normalized version of the final path.

    The final path must be located inside of the base path component (otherwise
    a ValueError is raised).

    Paths outside the base path indicate a possible security sensitive operation.
    """
    from urlparse import urljoin
    base_path = force_unicode(base)
    paths = map(lambda p: force_unicode(p), paths)
    final_path = urljoin(base_path + ("/" if not base_path.endswith("/") else ""), *paths)
    # Ensure final_path starts with base_path and that the next character after
    # the final path is '/' (or nothing, in which case final_path must be
    # equal to base_path).
    base_path_len = len(base_path) - 1
    if not final_path.startswith(base_path) \
       or final_path[base_path_len:base_path_len + 1] not in ('', '/'):
        raise ValueError('the joined path is located outside of the base path'
                         ' component')
    return final_path


class StaticRootS3BotoStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        super(StaticRootS3BotoStorage, self).__init__(*args, **kwargs)
        self.location = kwargs.get('location', '')
        self.location = 'static/' + self.location.lstrip('/')

    def _normalize_name(self, name):
        try:
            return safe_join(self.location, name).lstrip('/')
        except ValueError:
            raise SuspiciousOperation("Attempted access to '%s' denied." % name)


class MediaRootS3BotoStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        super(MediaRootS3BotoStorage, self).__init__(*args, **kwargs)
        self.location = kwargs.get('location', '')
        self.location = 'media/' + self.location.lstrip('/')

    def _normalize_name(self, name):
        try:
            return safe_join(self.location, name).lstrip('/')
        except ValueError:
            raise SuspiciousOperation("Attempted access to '%s' denied." % name)

Luego, en mi módulo de configuración:

DEFAULT_FILE_STORAGE = 'myproyect.s3utils.MediaRootS3BotoStorage'
STATICFILES_STORAGE = 'myproyect.s3utils.StaticRootS3BotoStorage'

Tengo que redefinir el _normalize_name()método privado para usar una versión "fija" de la safe_join()función, ya que el código original me da SuspiciousOperationexcepciones para rutas legales.

Estoy publicando esto para su consideración, si alguien puede dar una mejor respuesta o mejorar esta, será muy bienvenido.

Armando Pérez Marqués
fuente
7

Archivo: PROJECT_NAME / custom_storages.py

from django.conf import settings
from storages.backends.s3boto import S3BotoStorage

class StaticStorage(S3BotoStorage):
    location = settings.STATICFILES_LOCATION

class MediaStorage(S3BotoStorage):
    location = settings.MEDIAFILES_LOCATION

Archivo: PROJECT_NAME / settings.py

STATICFILES_LOCATION = 'static'
MEDIAFILES_LOCATION = 'media'

if not DEBUG:
    STATICFILES_STORAGE = 'PROJECT_NAME.custom_storages.StaticStorage'
    DEFAULT_FILE_STORAGE = 'PROJECT_NAME.custom_storages.MediaStorage'
    AWS_ACCESS_KEY_ID = 'KEY_XXXXXXX'
    AWS_SECRET_ACCESS_KEY = 'SECRET_XXXXXXXXX'
    AWS_STORAGE_BUCKET_NAME = 'BUCKET_NAME'
    AWS_HEADERS = {'Cache-Control': 'max-age=86400',}
    AWS_QUERYSTRING_AUTH = False

Y correr: python manage.py collectstatic

Oscar Enrique Lotero S
fuente
Si le da un nombre a este archivo en storages.pylugar de custom_storages.pyfrom __future__ import absolute_import
Querrá
2

Creo que la respuesta es bastante simple y se realiza de forma predeterminada. Esto me funciona en AWS Elastic Beanstalk con Django 1.6.5 y Boto 2.28.0:

STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)

TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
)

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID']
AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_KEY']

Las claves de AWS se pasan desde el archivo de configuración del contenedor y no tengo ninguna STATIC_ROOTo no las he STATIC_URLconfigurado. Además, no es necesario el s3utils.pyarchivo. Estos detalles son manejados por el sistema de almacenamiento automáticamente. El truco aquí es que necesitaba hacer referencia a esta ruta desconocida en mis plantillas de manera correcta y dinámica. Por ejemplo:

<link rel="icon" href="{% static "img/favicon.ico" %}">

Así es como me dirijo a mi favicon que vive localmente (antes del despliegue) en ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Por supuesto que tengo un local_settings.py archivo para acceder a estas cosas localmente en el entorno de desarrollo y tiene configuraciones STATIC y MEDIA. Tuve que experimentar y leer mucho para encontrar esta solución y funciona consistentemente sin errores.

Entiendo que necesita la separación estática y raíz y, considerando que solo puede proporcionar un depósito, señalaría que este método toma todas las carpetas en mi entorno local ~/Projects/my_app/project/my_app/static/y crea una carpeta en la raíz del depósito (es decir: S3bucket / img / como en el ejemplo anterior). Entonces obtienes separación de archivos. Por ejemplo, podría tener una mediacarpeta en la staticcarpeta y acceder a ella a través de plantillas con esto:

{% static "media/" %}

Espero que esto ayude. Vine aquí buscando la respuesta y presioné un poco más para encontrar una solución más simple que extender el sistema de almacenamiento. En cambio, leí la documentación sobre el uso previsto de Boto y descubrí que gran parte de lo que necesitaba estaba integrado de forma predeterminada. ¡Salud!

e.tompsy
fuente