web-dev-qa-db-fra.com

Comment configurer un projet Django avec Django-storages et Amazon S3, mais avec différents dossiers pour les fichiers statiques et les fichiers multimédias?

Je configure un projet Django qui utilisait le système de fichiers du serveur pour stocker les fichiers statiques des applications (STATIC_ROOT) et les fichiers téléchargés par l'utilisateur (MEDIA_ROOT).

J'ai besoin maintenant d'héberger tout ce contenu sur Amazon S3, j'ai donc créé un compartiment pour cela. En utilisant Django-storages avec le backend de stockage boto, j'ai réussi à télécharger les statistiques collectées dans le compartiment 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'

Ensuite, j'ai eu un problème: le MEDIA_ROOT et STATIC_ROOT ne sont pas utilisés dans le compartiment, donc la racine du compartiment contient à la fois les fichiers statiques et les chemins téléchargés par l'utilisateur.

Alors je pourrais définir:

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

Et utilisez ces paramètres dans les modèles, mais il n'y a aucune distinction de fichiers statiques/multimédias lors du stockage dans S3 avec Django-storages.

Comment cela peut-il se faire?

Merci!

89

Je pense que ce qui suit devrait fonctionner et être plus simple que la méthode de Mandx, bien qu'elle soit très similaire:

Créer un s3utils.py fichier:

from storages.backends.s3boto import S3BotoStorage

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

Ensuite, dans votre settings.py:

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

Un exemple différent mais connexe (que j'ai testé) peut être vu dans les deux example_ fichiers ici .

125
bradenm

J'utilise actuellement ce code dans un module s3utils Séparé:

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)

Ensuite, dans mon module de paramètres:

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

J'ai dû redéfinir la méthode privée _normalize_name() pour utiliser une version "fixe" de la fonction safe_join(), car le code d'origine me donne SuspiciousOperation exceptions pour les chemins légaux.

Je poste ceci pour examen, si quelqu'un peut donner une meilleure réponse ou améliorer celle-ci, ce sera le bienvenu.


Fichier: 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

Fichier: 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

Et courir: python manage.py collectstatic

6

Je pense que la réponse est assez simple et faite par défaut. Cela fonctionne pour moi sur AWS Elastic Beanstalk avec Django 1.6.5 et 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']

Les clés AWS sont transmises depuis le fichier de configuration du conteneur et je n'ai pas de STATIC_ROOT ou STATIC_URL défini du tout. De plus, pas besoin de s3utils.py fichier. Ces informations sont gérées automatiquement par le système de stockage. L'astuce ici est que j'avais besoin de référencer ce chemin inconnu correctement et dynamiquement dans mes modèles. Par exemple:

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

Voilà comment j'adresse mon favicon qui vit localement (pré-déploiement) dans ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Bien sûr, j'ai un local_settings.py fichier pour accéder à ce truc localement dans l'environnement de développement et il a des paramètres STATIQUE et MÉDIA. J'ai dû faire beaucoup d'expérimentation et de lecture pour trouver cette solution et cela fonctionne de manière cohérente sans erreur.

Je comprends que vous avez besoin de la séparation statique et racine et considérant que vous ne pouvez fournir qu'un seul compartiment, je voudrais souligner que cette méthode prend tous les dossiers de mon environnement local sous ~/Projects/my_app/project/my_app/static/et crée un dossier à la racine du bucket (ie: S3bucket/img/comme dans l'exemple ci-dessus). Vous obtenez donc la séparation des fichiers. Par exemple, vous pourriez avoir un dossier media dans le dossier static et y accéder via un modèle avec ceci:

{% static "media/" %}

J'espère que ça aide. Je suis venu ici à la recherche de la réponse et j'ai poussé un peu plus fort pour trouver une solution plus simple que d'étendre le système de stockage. Au lieu de cela, j'ai lu la documentation sur l'utilisation prévue de Boto et j'ai constaté qu'une grande partie de ce dont j'avais besoin était intégrée par défaut. À votre santé!

2
e.thompsy