如何使用django-storages和Amazon S3设置Django项目,但为静态文件和媒体文件设置不同的文件夹?


92

我正在配置一个Django项目,该项目使用服务器文件系统来存储应用程序的静态文件(STATIC_ROOT)和用户上传的文件(MEDIA_ROOT)。

我现在需要将所有内容托管在Amazon S3上,因此我为此创建了一个存储桶。使用django-storagesboto存储后端,我设法收集静态上传到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_ROOTSTATIC_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实例化指定的类的实例时使用的设置。
阿曼多·佩雷斯·马克斯

Answers:


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
绝对比我的版本简单和更好。尽管我尚未对此进行测试,但我也认为这会起作用。谢谢!我还在检查您的django-s3storage存储库,如果该项目仅使用S3,这似乎是一个非常轻巧的解决方案。
阿曼多·佩雷斯·马奎斯

1
而且,如果您更喜欢包装,请查看django-s3-folder-storage。我刚刚找到它,无法确定它是否是相同的解决方案,但已预先打包。
ArmandoPérezMarqués2012年

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 2014年

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

2

我认为答案很简单,默认情况下会完成。这对使用Django 1.6.5和Boto 2.28.0的AWS Elastic Beanstalk来说对我有用:

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" %}">

这就是我处理我的图标的方式,该图标位于本地(部署前)~/Projects/my_app/project/my_app/static/img/favicon.ico

当然,我有一个单独的local_settings.py文件,用于在开发环境中本地访问此内容,并且它确实具有STATIC和MEDIA设置。我必须进行大量的实验和阅读才能找到此解决方案,并且该解决方案始终如一且没有错误。

我了解您需要静态和根目录分离,并且考虑到您只能提供一个存储桶,我会指出,该方法将本地环境下的所有文件~/Projects/my_app/project/my_app/static/夹都放在存储桶的根目录下(即:S3bucket / img /如上例所示)。这样您就可以分离文件。例如,您可以在media文件夹中有一个文件static夹,并通过使用以下模板进行访问:

{% static "media/" %}

我希望这有帮助。我来到这里寻找答案,并且比扩展存储系统更加费力地寻找一种更简单的解决方案。相反,我阅读了有关Boto预期用途的文档,发现默认情况下内置了许多我需要的东西。干杯!


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.