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!


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 .


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.


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


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!


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.