จะตั้งค่าโครงการ Django ด้วย django-storages และ Amazon S3 ได้อย่างไร แต่มีโฟลเดอร์ต่างกันสำหรับไฟล์คงที่และไฟล์มีเดีย


92

ฉันกำลังกำหนดค่าโครงการ Django ที่ใช้ระบบไฟล์เซิร์ฟเวอร์เพื่อจัดเก็บไฟล์คงที่ของแอป ( STATIC_ROOT) และไฟล์ที่ผู้ใช้อัปโหลด ( MEDIA_ROOT)

ตอนนี้ฉันต้องการโฮสต์เนื้อหาทั้งหมดใน 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.

วิธีนี้สามารถทำได้?

ขอบคุณ!


8
เนื่องจากมีการตั้งค่าเพียงอย่างเดียวในการระบุชื่อที่เก็บข้อมูล ( AWS_STORAGE_BUCKET_NAME) และนั่นคือการตั้งค่าที่ใช้เมื่ออินสแตนซ์ของคลาสที่ระบุในSTATICFILES_STORAGEอินสแตนซ์
Armando PérezMarqués

คำตอบ:


126

ฉันคิดว่าสิ่งต่อไปนี้น่าจะใช้ได้และง่ายกว่าวิธีของ 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_ไฟล์ที่นี่


1
ง่ายกว่าและดีกว่าเวอร์ชันของฉันแน่นอน แม้ว่าฉันจะไม่ได้ทดสอบ แต่ฉันก็คิดว่ามันจะได้ผลเช่นกัน ขอบคุณ! ฉันกำลังตรวจสอบrepo django-s3storageของคุณดูเหมือนจะเป็นโซลูชันที่มีน้ำหนักเบามากหากโครงการใช้ S3 โดยเฉพาะ
Armando PérezMarqués

1
และถ้าคุณมีมากขึ้นในบรรจุภัณฑ์ตรวจสอบDjango-S3-โฟลเดอร์จัดเก็บข้อมูล ฉันเพิ่งค้นพบไม่สามารถบอกได้ว่านี่เป็นโซลูชันเดียวกัน แต่บรรจุไว้ล่วงหน้า
Armando PérezMarqués

4
สิ่งนี้ใช้ไม่ได้กับฉันไฟล์สื่อจะถูกอัปโหลดไปยัง / ของที่เก็บข้อมูล s3 ดูเหมือนว่าการตั้งค่าตำแหน่งจะไม่ได้รับการเคารพ django-storages == 1.1.6, django-extensions == 1.1.1, django = 1.4
Nathan Keller

3
สำหรับฉันแล้วมันสมเหตุสมผลกว่าที่จะมีที่เก็บข้อมูลแยกต่างหากและฉันไม่ชอบให้มีการกำหนดค่านอกโมดูลการตั้งค่าของฉันดังนั้นโซลูชันของฉันจึงดูเหมือนgist.github.com/antonagestam/6075199
antonagestam

1
วิธีแก้ปัญหานี้ใช้ไม่ได้ผลจากสิ่งที่ฉันบอกได้ นี่ควรเป็นแนวทาง: gist.github.com/defrex/82680e858281d3d3e6e4
defrex

8

ฉันกำลังใช้รหัสนี้ใน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ข้อยกเว้นสำหรับเส้นทางกฎหมาย

ฉันกำลังโพสต์สิ่งนี้เพื่อพิจารณาหากใครสามารถให้คำตอบที่ดีกว่านี้หรือปรับปรุงได้ก็จะยินดีมาก


7

ไฟล์: 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


หากคุณตั้งชื่อไฟล์นี้storages.pyแทนคุณcustom_storages.pyจะต้องใช้from __future__ import absolute_import
Aaron McMillin

2

ฉันคิดว่าคำตอบนั้นค่อนข้างง่ายและทำได้โดยค่าเริ่มต้น สิ่งนี้ใช้ได้ผลสำหรับฉันใน AWS Elastic Beanstalk กับ Django 1.6.5 และ 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']

คีย์ AWS ถูกส่งเข้ามาจากไฟล์คอนฟิกคอนเทนเนอร์และฉันไม่มีSTATIC_ROOTหรือSTATIC_URLตั้งค่าเลย นอกจากนี้ไม่จำเป็นต้องใช้s3utils.pyไฟล์ รายละเอียดเหล่านี้จัดการโดยระบบจัดเก็บข้อมูลโดยอัตโนมัติ เคล็ดลับคือฉันต้องอ้างอิงเส้นทางที่ไม่รู้จักนี้ในเทมเพลตของฉันอย่างถูกต้องและแบบไดนามิก ตัวอย่างเช่น:

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

นั่นคือวิธีที่ฉันจัดการไอคอน Fav ของฉันซึ่งอาศัยอยู่ในเครื่อง (การปรับใช้ล่วงหน้า) ใน ~/Projects/my_app/project/my_app/static/img/favicon.icoใน

แน่นอนฉันมีแยกต่างหาก local_settings.pyไฟล์สำหรับเข้าถึงสิ่งนี้ในเครื่องในสภาพแวดล้อม dev และมีการตั้งค่า STATIC และ MEDIA ฉันต้องทำการทดลองและอ่านข้อมูลมากมายเพื่อหาวิธีแก้ปัญหานี้และทำงานได้อย่างสม่ำเสมอโดยไม่มีข้อผิดพลาด

ฉันเข้าใจว่าคุณต้องการการแยกแบบคงที่และรูทและเมื่อพิจารณาว่าคุณสามารถจัดหาที่เก็บข้อมูลได้เพียงชุดเดียวฉันจะชี้ให้เห็นว่าวิธีนี้ใช้โฟลเดอร์ทั้งหมดในสภาพแวดล้อมภายในเครื่องของฉัน~/Projects/my_app/project/my_app/static/และสร้างโฟลเดอร์ในรูทที่เก็บข้อมูล (เช่น S3bucket / img / ดังตัวอย่างด้านบน) คุณจึงแยกไฟล์ได้ ตัวอย่างเช่นคุณสามารถมีmediaโฟลเดอร์ในstaticโฟลเดอร์และเข้าถึงผ่านเทมเพลตด้วยสิ่งนี้:

{% static "media/" %}

ฉันหวังว่านี่จะช่วยได้. ฉันมาที่นี่เพื่อหาคำตอบและพยายามหาวิธีแก้ปัญหาที่ง่ายกว่าการขยายระบบจัดเก็บข้อมูล แต่ฉันอ่านเอกสารเกี่ยวกับการใช้งาน Boto ที่ตั้งใจไว้และพบว่าสิ่งที่ฉันต้องการมากมายนั้นมีอยู่แล้วภายในโดยค่าเริ่มต้น ไชโย!


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.