80

Я настраиваю проект Django, который использовал файловую систему сервера для хранения статических файлов приложений (STATIC_ROOT) и загруженных пользователем файлов (MEDIA_ROOT).Как настроить проект Django с django-хранилищами и Amazon S3, но с разными папками для статических файлов и медиафайлов?

Мне нужно сейчас разместить весь контент на S3 Amazon, поэтому я создал для этого ведро. Использование django-storages с внутренним интерфейсом хранения boto, мне удалось загрузить собранную статику на 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' 

Затем я получил проблему: MEDIA_ROOT и STATIC_ROOT не используются в ведре, так что корень ведра содержит как статические файлы и загруженные пользователем пути.

Так тогда я мог бы установить:

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

И использовать эти параметры в шаблонах, но нет никакого различия статического/медиа-файлов при сохранении в S3 с django-storages.

Как это можно сделать?

Спасибо!

+2

Почему бы не иметь два ведра? –

+8

Поскольку существует только одна настройка для указания имени ведра ('AWS_STORAGE_BUCKET_NAME'), и это тот, который используется, когда экземпляр класса, указанный в' STATICFILES_STORAGE', создается. –

ответ

117

Я думаю, что должно работать, и быть проще, чем метод Mandx, хотя это очень похоже:

Создать s3utils.py файл:

from storages.backends.s3boto import S3BotoStorage 

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

Тогда в вашем settings.py:

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

Другой, но связанный с ним пример (который я фактически протестировал) можно увидеть в двух файлах example_here.

+1

Определенно проще и лучше, чем моя версия. Хотя я не тестировал это, я также думаю, что это сработает. Благодаря! Я также проверяю репозиторий [django-s3storage] (https://github.com/mstarinc/django-s3storage/), кажется очень легким решением, если проект использует только S3. –

+1

И, если вы больше занимаетесь упаковкой, ознакомьтесь с [django-s3-folder-storage] (http://pypi.python.org/pypi/django-s3-folder-storage/). Я просто нашел его, не могу сказать, если это то же самое решение, но расфасовано. –

+0

Почему импортируется 'SimpleLazyObject'? – glarrain

7

В настоящее время я использую этот код в отдельном модуле s3utils:

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) 

Затем в моем модуле настройки:

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

я переопределить _normalize_name() частный метод, чтобы использовать " фиксированная "версия функции safe_join(), поскольку исходный код дает мне SuspiciousOperation исключения для юридических путей.

Я отправляю это на рассмотрение, если кто-то может дать лучший ответ или улучшить этот, он будет очень желанным.

2

Я думаю, что ответ довольно прост и сделан по умолчанию. Это работает для меня на AWS Elastic Beanstalk с Django 1.6.5 и 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'] 

АМС ключи передаются в из файла контейнера конфигурации и у меня нет STATIC_ROOT или STATIC_URL набор вообще. Кроме того, нет необходимости в файле s3utils.py. Эти данные автоматически обрабатываются системой хранения. Трюк здесь в том, что мне нужно было правильно и динамично ссылаться на этот неизвестный путь в моих шаблонах.Например:

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

Вот как я обращаюсь к моей фавиконка, который живет на местном уровне (предварительное развертывание) в ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Конечно, у меня есть отдельный файл local_settings.py для доступа к этому материалу локально в среде dev, и у него есть настройки STATIC и MEDIA. Мне пришлось много экспериментировать и читать, чтобы найти это решение, и оно работает последовательно без ошибок.

Я понимаю, что вам нужно разделение статического и корневого элементов, и учитывая, что вы можете предоставить только одно ведро, я бы указал, что этот метод принимает все папки в моей локальной среде под и создает папку в корневом каталоге (т.е. : S3bucket/img/как в примере выше). Таким образом, вы получаете разделение файлов. Например, вы могли бы иметь media папку в папке static и получить доступ к нему через шаблоны с этим:

{% static "media/" %} 

Я надеюсь, что это помогает. Я пришел сюда, ища ответ, и немного затруднил найти более простое решение, чем продлить систему хранения. Вместо этого я прочитал документацию о предполагаемом использовании Boto, и я обнаружил, что многие из того, что мне нужно, были встроены по умолчанию. Ура!

4

Файл: 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 

Файл: 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 

И бежать: python manage.py collectstatic

+0

Если вы, вероятно, называете этот файл' хранилищами .py' вместо 'custom_storages.py' Вы хотите использовать' from __future__ import absolute_import' –