Jak skonfigurować projekt Django z magazynami django i Amazon S3, ale z różnymi folderami na pliki statyczne i pliki multimedialne?

92

Konfiguruję projekt Django, który korzystał z systemu plików serwera do przechowywania plików statycznych aplikacji ( STATIC_ROOT) i plików przesłanych przez użytkownika ( MEDIA_ROOT).

Muszę teraz hostować całą tę zawartość na Amazon S3, więc stworzyłem do tego wiadro. Korzystając django-storagesz botozaplecza pamięci, udało mi się przesłać zebrane statystyki do zasobnika 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'

Następnie pojawił się problem: MEDIA_ROOTi STATIC_ROOTnie są używane w zasobniku, więc katalog główny zasobnika zawiera zarówno pliki statyczne, jak i ścieżki przesłane przez użytkownika.

Mogłem więc ustawić:

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

I użyj tych ustawień w szablonach, ale nie ma rozróżnienia między plikami statycznymi / multimedialnymi podczas przechowywania w S3 z django-storages.

Jak to zrobić?

Dzięki!

Armando Pérez Marqués
źródło
8
Ponieważ istnieje tylko jedno ustawienie określające nazwę zasobnika ( AWS_STORAGE_BUCKET_NAME) i jest ono używane, gdy tworzona jest instancja klasy określonej w STATICFILES_STORAGE.
Armando Pérez Marqués

Odpowiedzi:

126

Myślę, że poniższe powinno działać i być prostsze niż metoda Mandxa, chociaż jest bardzo podobna:

Utwórz s3utils.pyplik:

from storages.backends.s3boto import S3BotoStorage

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

Następnie w settings.py:

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

Inny, ale pokrewny przykład (który faktycznie przetestowałem) można zobaczyć w dwóch example_plikach tutaj .

bradenm
źródło
1
Zdecydowanie prostsze i lepsze niż moja wersja. Chociaż tego nie testowałem, myślę, że to zadziała. Dzięki! Sprawdzam również twoje repozytorium django-s3storage , wydaje się bardzo lekkim rozwiązaniem, jeśli projekt używa wyłącznie S3.
Armando Pérez Marqués
1
A jeśli bardziej interesujesz się pakowaniem, sprawdź django-s3-folder-storage . Właśnie go znalazłem, nie mogę powiedzieć, czy to to samo rozwiązanie, ale w opakowaniu.
Armando Pérez Marques,
4
U mnie to nie działa, pliki multimedialne są przesyłane do / z wiadra s3. Wygląda na to, że ustawienie lokalizacji nie jest przestrzegane. django-storages == 1.1.6, django-extensions == 1.1.1, django = 1.4
Nathan Keller
3
Dla mnie bardziej sensowne było posiadanie oddzielnych zasobników i nie podoba mi się konfiguracja poza moim modułem ustawień, więc moje rozwiązanie wyglądało tak: gist.github.com/antonagestam/6075199
antonagestam
1
To rozwiązanie nie działa, z tego, co wiem. Takie powinno być podejście: gist.github.com/defrex/82680e858281d3d3e6e4
defrex
8

Obecnie używam tego kodu w oddzielnym s3utilsmodule:

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)

Następnie w moim module ustawień:

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

Musiałem przedefiniować _normalize_name()metodę prywatną, aby używać „ustalonej” wersji safe_join()funkcji, ponieważ oryginalny kod daje mi SuspiciousOperationwyjątki dla legalnych ścieżek.

Przesyłam to do rozważenia, jeśli ktoś może udzielić lepszej odpowiedzi lub poprawić tę, będzie to bardzo mile widziane.

Armando Pérez Marqués
źródło
7

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

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

I biegnij: python manage.py collectstatic

Oscar Enrique Lotero S.
źródło
Jeśli zdarzy ci się nazwać ten plik storages.pyzamiast custom_storages.pyBędziesz chciał użyćfrom __future__ import absolute_import
Aaron McMillin
2

Myślę, że odpowiedź jest dość prosta i domyślna. To działa dla mnie na AWS Elastic Beanstalk z Django 1.6.5 i 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']

Klucze AWS są przekazywane z pliku konfiguracyjnego kontenera i nie mam ich wcale STATIC_ROOTlub nie mam ich wcale STATIC_URL. Nie ma też potrzeby posiadania s3utils.pypliku. Te szczegóły są obsługiwane automatycznie przez system magazynowania. Sztuczka polega na tym, że musiałem poprawnie i dynamicznie odnosić się do tej nieznanej ścieżki w moich szablonach. Na przykład:

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

W ten sposób zwracam się do mojej ulubionej ikony, która znajduje się lokalnie (przed wdrożeniem) w ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Oczywiście mam osobny local_settings.pyplik umożliwiający dostęp do tych rzeczy lokalnie w środowisku deweloperskim i ma on ustawienia STATIC i MEDIA. Musiałem dużo eksperymentować i czytać, aby znaleźć to rozwiązanie i działa konsekwentnie bez błędów.

Rozumiem, że potrzebujesz separacji statycznej i głównej, a biorąc pod uwagę, że możesz zapewnić tylko jeden zasobnik, zwróciłbym uwagę, że ta metoda bierze wszystkie foldery w moim środowisku lokalnym ~/Projects/my_app/project/my_app/static/i tworzy folder w katalogu głównym zasobnika (tj .: S3bucket / img / jak w powyższym przykładzie). Więc otrzymujesz oddzielenie plików. Na przykład możesz mieć mediafolder w staticfolderze i uzyskać do niego dostęp za pomocą szablonu z tym:

{% static "media/" %}

Mam nadzieję, że to pomoże. Przyszedłem tutaj, szukając odpowiedzi, i naciskałem nieco bardziej na znalezienie prostszego rozwiązania niż rozszerzenie systemu pamięci masowej. Zamiast tego przeczytałem dokumentację dotyczącą zamierzonego użycia Boto i odkryłem, że wiele z tego, czego potrzebowałem, było domyślnie wbudowanych. Twoje zdrowie!

e.thompsy
źródło