80

Je suis en train de configurer 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).Comment configurer un projet Django avec django-storages et Amazon S3, mais avec des dossiers différents pour les fichiers statiques et les fichiers multimédias?

Je dois maintenant 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 statics collectés sur le godet 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, je suis un problème: le MEDIA_ROOT et STATIC_ROOT ne sont pas utilisés dans le seau, de sorte que la racine du godet 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 utiliser ces paramètres dans les modèles, mais il n'y a pas de distinction de fichiers statiques/médias lors de l'enregistrement dans S3 avec django-storages.

Comment cela peut-il être fait?

Merci!

+2

Pourquoi ne pas avoir deux seaux? –

+8

Parce qu'il existe un seul paramètre pour spécifier le nom du compartiment ('AWS_STORAGE_BUCKET_NAME'), et c'est celui utilisé lorsqu'une instance de la classe spécifiée dans' STATICFILES_STORAGE' est instanciée. –

Répondre

117

Je pense que les éléments suivants devraient travailler et être plus simple que la méthode de Mandx, bien qu'il soit très similaire:

Créer un fichier s3utils.py:

from storages.backends.s3boto import S3BotoStorage 

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

Puis dans votre settings.py:

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

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

+1

Certainement plus simple et meilleur que ma version. Bien que je n'ai pas testé cela, je pense aussi que cela fonctionnera. Merci! Je vérifie aussi votre dépôt [django-s3storage] (https://github.com/mstarinc/django-s3storage/), semble une solution très légère si le projet utilise S3 exclusivement. –

+1

Et, si vous êtes plus dans l'emballage, consultez [django-s3-folder-storage] (http://pypi.python.org/pypi/django-s3-folder-storage/). Je viens de le trouver, je ne peux pas dire si c'est la même solution mais préemballée. –

+0

Pourquoi 'SimpleLazyObject' est-il importé? – glarrain

7

J'utilise actuellement ce code dans un s3utils séparé Module:

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 Paramètres:

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

je suis arrivé à redéfinir la méthode privée _normalize_name() d'utiliser un " corrigé "version de la fonction safe_join(), puisque le code d'origine me donne SuspiciousOperation exceptions pour les voies légales.

Je poste ceci pour considération, si n'importe qui peut donner une meilleure réponse ou améliorer celui-ci, il sera très bienvenu.

2

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 2.28.0 Boto:

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 touches AWS sont transmises depuis le fichier de configuration du conteneur et je ne STATIC_ROOT ou STATIC_URL ensemble du tout. En outre, pas besoin du fichier s3utils.py. Ces détails sont gérés automatiquement par le système de stockage. L'astuce ici est que j'avais besoin de référencer ce chemin inconnu dans mes templates correctement et dynamiquement.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 fichier séparé local_settings.py pour accéder à ce matériel localement dans l'environnement de développement et il a des paramètres STATIQUE et MEDIA. J'ai dû faire beaucoup d'expérimentation et de lecture pour trouver cette solution et cela fonctionne sans erreur. Je comprends que vous avez besoin de la séparation statique et racine et compte tenu que vous ne pouvez fournir qu'un seul seau, je signale que cette méthode prend tous les dossiers dans mon environnement local sous et crée un dossier dans la racine du seau (c.-à-d. : S3bucket/img/as 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 templating avec ceci:

{% static "media/" %} 

J'espère que cela 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 trouvé que beaucoup de ce dont j'avais besoin était intégré par défaut. À votre santé!

4

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

+0

Si vous nommez ce fichier' stockages .py' au lieu de 'custom_storages.py' Vous aurez besoin d'utiliser' from __future__ import absolute_import' –

Questions connexes