Wie richte ich ein Django-Projekt mit Django-Speichern und Amazon S3 ein, jedoch mit unterschiedlichen Ordnern für statische Dateien und Mediendateien?

92

Ich konfiguriere ein Django-Projekt, das das Server-Dateisystem zum Speichern der statischen Dateien der Apps ( STATIC_ROOT) und der vom Benutzer hochgeladenen Dateien ( MEDIA_ROOT) verwendet.

Ich muss jetzt all diese Inhalte auf Amazon S3 hosten, also habe ich einen Bucket dafür erstellt. Mit django-storagesdem botoSpeicher-Backend konnte ich gesammelte Statiken in den S3-Bucket hochladen:

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'

Dann habe ich ein Problem: Die MEDIA_ROOTund STATIC_ROOTwerden nicht im Bucket verwendet, sodass der Bucket-Stamm sowohl die statischen Dateien als auch die vom Benutzer hochgeladenen Pfade enthält.

Also könnte ich einstellen:

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

Und verwenden Sie diese Einstellungen in den Vorlagen, aber es gibt keine Unterscheidung zwischen statischen / Mediendateien beim Speichern in S3 mit django-storages.

Wie kann das gemacht werden?

Vielen Dank!

Armando Pérez Marqués
quelle
8
Da es nur eine Einstellung gibt, mit der der Name des Buckets ( AWS_STORAGE_BUCKET_NAME) angegeben wird, wird diese verwendet, wenn eine Instanz der in angegebenen Klasse STATICFILES_STORAGEinstanziiert wird.
Armando Pérez Marqués

Antworten:

126

Ich denke, das Folgende sollte funktionieren und einfacher sein als Mandx 'Methode, obwohl sie sehr ähnlich ist:

Erstellen Sie eine s3utils.pyDatei:

from storages.backends.s3boto import S3BotoStorage

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

Dann in Ihrem settings.py:

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

Ein anderes, aber verwandtes Beispiel (das ich tatsächlich getestet habe) ist in den beiden example_Dateien hier zu sehen .

Bradenm
quelle
1
Auf jeden Fall einfacher und besser als meine Version. Obwohl ich dies nicht getestet habe, denke ich auch, dass dies funktionieren wird. Vielen Dank! Ich überprüfe auch Ihr django-s3storage- Repo. Es scheint eine sehr leichte Lösung zu sein, wenn das Projekt ausschließlich S3 verwendet.
Armando Pérez Marqués
1
Wenn Sie sich mehr für Verpackungen interessieren , schauen Sie sich django-s3-folder-storage an . Ich habe es gerade gefunden, kann nicht sagen, ob es die gleiche Lösung ist, aber vorverpackt.
Armando Pérez Marqués
4
Das funktioniert bei mir nicht, Mediendateien werden in den / des s3-Buckets hochgeladen. Anscheinend wird die Standorteinstellung nicht eingehalten. Django-Speicher == 1.1.6, Django-Erweiterungen == 1.1.1, Django = 1.4
Nathan Keller
3
Für mich war es sinnvoller, separate Buckets zu haben, und ich mag es nicht, eine Konfiguration außerhalb meines Einstellungsmoduls zu haben, sodass meine Lösung so aussah: gist.github.com/antonagestam/6075199
antonagestam
1
Diese Lösung funktioniert nicht, soweit ich das beurteilen kann. Dies sollte der Ansatz sein: gist.github.com/defrex/82680e858281d3d3e6e4
defrex
8

Ich verwende diesen Code derzeit in einem separaten s3utilsModul:

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)

Dann in meinem Einstellungsmodul:

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

Ich musste die _normalize_name()private Methode neu definieren , um eine "feste" Version der safe_join()Funktion zu verwenden, da der ursprüngliche Code mir SuspiciousOperationAusnahmen für legale Pfade gibt.

Ich poste dies zur Prüfung. Wenn jemand eine bessere Antwort geben oder diese verbessern kann, ist dies sehr willkommen.

Armando Pérez Marqués
quelle
7

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

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

Und Renn: python manage.py collectstatic

Oscar Enrique Lotero S.
quelle
Wenn Sie diese Datei zufällig benennen, storages.pyanstatt custom_storages.pySie verwenden möchtenfrom __future__ import absolute_import
Aaron McMillin
2

Ich denke, die Antwort ist ziemlich einfach und erfolgt standardmäßig. Dies funktioniert für mich an AWS Elastic Beanstalk mit Django 1.6.5 und 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']

Die AWS-Schlüssel werden aus der Containerkonfigurationsdatei übergeben und ich habe keine STATIC_ROOToder überhaupt keine STATIC_URL. Auch keine Notwendigkeit für die s3utils.pyDatei. Diese Details werden vom Speichersystem automatisch verarbeitet. Der Trick dabei ist, dass ich diesen unbekannten Pfad in meinen Vorlagen korrekt und dynamisch referenzieren musste. Beispielsweise:

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

Auf diese Weise adressiere ich mein Favicon, das lokal (vor der Bereitstellung) in lebt ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Natürlich habe ich eine separate local_settings.pyDatei für den lokalen Zugriff auf dieses Material in der Entwicklungsumgebung und sie hat STATIC- und MEDIA-Einstellungen. Ich musste viel experimentieren und lesen, um diese Lösung zu finden, und sie funktioniert konsistent ohne Fehler.

Ich verstehe, dass Sie die statische Trennung und die Root-Trennung benötigen, und da Sie nur einen Bucket bereitstellen können, möchte ich darauf hinweisen, dass diese Methode alle Ordner in meiner lokalen Umgebung unter ~/Projects/my_app/project/my_app/static/und einen Ordner im Bucket-Root erstellt (dh: S3bucket / img / wie im obigen Beispiel). Sie erhalten also eine Trennung von Dateien. Zum Beispiel könnten Sie einen mediaOrdner im staticOrdner haben und über folgende Vorlagen darauf zugreifen:

{% static "media/" %}

Ich hoffe das hilft. Ich bin hierher gekommen, um nach der Antwort zu suchen, und habe mich etwas mehr Mühe gegeben, eine einfachere Lösung zu finden, als das Speichersystem zu erweitern. Stattdessen las ich die Dokumentation über die beabsichtigte Verwendung von Boto und stellte fest, dass vieles, was ich brauchte, standardmäßig integriert war. Prost!

e.thompsy
quelle