Bagaimana cara menyiapkan proyek Django dengan penyimpanan-django dan Amazon S3, tetapi dengan folder berbeda untuk berkas statis dan berkas media?

92

Saya mengonfigurasi proyek Django yang menggunakan sistem berkas server untuk menyimpan berkas statis aplikasi ( STATIC_ROOT) dan berkas unggahan pengguna ( MEDIA_ROOT).

Sekarang saya perlu menghosting semua konten itu di Amazon S3, jadi saya telah membuat keranjang untuk ini. Menggunakan django-storagesdengan botobackend penyimpanan, saya berhasil mengunggah statika yang dikumpulkan ke bucket 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'

Kemudian, saya mendapat masalah: MEDIA_ROOTdan STATIC_ROOTtidak digunakan dalam bucket, jadi root bucket berisi file statis dan jalur yang diupload pengguna.

Jadi saya bisa mengatur:

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

Dan gunakan pengaturan tersebut di template, tetapi tidak ada perbedaan antara file statis / media saat menyimpan di S3 dengan django-storages.

Bagaimana ini bisa dilakukan?

Terima kasih!

Armando Pérez Marqués
sumber
8
Karena hanya ada satu setelan untuk menentukan nama keranjang ( AWS_STORAGE_BUCKET_NAME), dan itulah yang digunakan saat instance kelas yang ditentukan di STATICFILES_STORAGEdibuatkan.
Armando Pérez Marqués

Jawaban:

126

Saya pikir yang berikut harus berfungsi, dan lebih sederhana daripada metode Mandx, meskipun sangat mirip:

Buat s3utils.pyfile:

from storages.backends.s3boto import S3BotoStorage

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

Kemudian di settings.py:

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

Contoh yang berbeda namun terkait (yang sebenarnya telah saya uji) dapat dilihat di dua example_file di sini .

bradenm.dll
sumber
1
Jelas lebih sederhana dan lebih baik dari versi saya. Meskipun saya belum menguji ini, saya juga berpikir ini akan berhasil. Terima kasih! Saya juga memeriksa repo django-s3storage Anda , tampaknya solusi yang sangat ringan jika proyek tersebut menggunakan S3 secara eksklusif.
Armando Pérez Marqués
1
Dan, jika Anda lebih menyukai pengemasan, periksa django-s3-folder-storage . Saya baru saja menemukannya, tidak tahu apakah ini solusi yang sama tetapi sudah dikemas sebelumnya.
Armando Pérez Marqués
4
Ini tidak berfungsi dari saya, file media diunggah ke / dari keranjang s3. Sepertinya pengaturan lokasi tidak dipatuhi. django-storages == 1.1.6, django-extensions == 1.1.1, django = 1.4
Nathan Keller
3
Bagi saya, lebih masuk akal untuk memiliki ember terpisah dan saya tidak suka memiliki konfigurasi di luar modul pengaturan saya sehingga solusi saya akhirnya terlihat seperti ini gist.github.com/antonagestam/6075199
antonagestam
1
Solusi ini tidak berhasil, dari apa yang saya tahu. Ini harus menjadi pendekatan: gist.github.com/defrex/82680e858281d3d3e6e4
defrex
8

Saat ini saya menggunakan kode ini dalam s3utilsmodul terpisah :

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)

Kemudian, di modul pengaturan saya:

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

Saya harus mendefinisikan ulang _normalize_name()metode privat untuk menggunakan versi "tetap" dari safe_join()fungsi tersebut, karena kode asli memberi saya SuspiciousOperationpengecualian untuk jalur hukum.

Saya memposting ini untuk pertimbangan, jika ada yang bisa memberikan jawaban yang lebih baik atau meningkatkan yang ini, itu akan sangat disambut.

Armando Pérez Marqués
sumber
7

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

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

Dan lari: python manage.py collectstatic

Oscar Enrique Lotero S.
sumber
Jika Anda kebetulan memberi nama file ini storages.pyalih-alih custom_storages.pyAnda ingin menggunakanfrom __future__ import absolute_import
Aaron McMillin
2

Saya pikir jawabannya cukup sederhana dan dilakukan secara default. Ini berfungsi untuk saya di AWS Elastic Beanstalk dengan Django 1.6.5 dan 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']

Kunci AWS diteruskan dari file konfigurasi penampung dan saya tidak memilikinya STATIC_ROOTatau STATIC_URLdisetel sama sekali. Juga, tidak perlu s3utils.pyfile tersebut. Detail ini ditangani oleh sistem penyimpanan secara otomatis. Triknya di sini adalah saya perlu mereferensikan jalur yang tidak diketahui ini di templat saya dengan benar dan dinamis. Sebagai contoh:

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

Begitulah cara saya menangani favicon saya yang ada secara lokal (pra-penerapan) di ~/Projects/my_app/project/my_app/static/img/favicon.ico.

Tentu saja saya memiliki local_settings.pyfile terpisah untuk mengakses barang ini secara lokal di lingkungan dev dan itu memang memiliki pengaturan STATIC dan MEDIA. Saya harus melakukan banyak percobaan dan membaca untuk menemukan solusi ini dan bekerja secara konsisten tanpa kesalahan.

Saya memahami bahwa Anda memerlukan pemisahan statis dan root dan mengingat bahwa Anda hanya dapat menyediakan satu bucket, saya akan menunjukkan bahwa metode ini mengambil semua folder di lingkungan lokal saya di bawah ~/Projects/my_app/project/my_app/static/dan membuat folder di root bucket (yaitu: S3bucket / img / seperti pada contoh di atas). Jadi Anda mendapatkan pemisahan file. Misalnya Anda bisa memiliki mediafolder di staticfolder dan mengaksesnya melalui templat dengan ini:

{% static "media/" %}

Saya harap ini membantu. Saya datang ke sini mencari jawabannya dan mendorong sedikit lebih keras untuk menemukan solusi yang lebih sederhana daripada memperluas sistem penyimpanan. Sebaliknya, saya membaca dokumentasi tentang tujuan penggunaan Boto dan saya menemukan bahwa banyak hal yang saya butuhkan sudah ada di dalamnya secara default. Bersulang!

e.thompsy
sumber