How to set-up a Django project with django-storages and Amazon S3, but with different folders for static files and media files?

后端 未结 5 1440
情歌与酒
情歌与酒 2020-11-28 00:47

I\'m configuring a Django project that were using the server filesystem for storing the apps static files (STATIC_ROOT) and user uploaded files (MEDIA_ROO

相关标签:
5条回答
  • 2020-11-28 01:08

    I think the following should work, and be simpler than Mandx's method, although it's very similar:

    Create a s3utils.py file:

    from storages.backends.s3boto import S3BotoStorage
    
    StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static')
    MediaRootS3BotoStorage  = lambda: S3BotoStorage(location='media')
    

    Then in your settings.py:

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

    A different but related example (that I've actually tested) can be seen in the two example_ files here.

    0 讨论(0)
  • 2020-11-28 01:24

    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
    

    And run: python manage.py collectstatic

    0 讨论(0)
  • 2020-11-28 01:27

    I think the answer is pretty simple and done by default. This is working for me on AWS Elastic Beanstalk with Django 1.6.5 and 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']
    

    The AWS keys are passed in from the container config file and I have no STATIC_ROOT or STATIC_URL set at all. Also, no need for the s3utils.py file. These details are handled by the storage system automatically. The trick here is that I needed to reference this unknown path in my templates correctly and dynamically. For example:

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

    That is how I address my favicon which lives locally (pre-deployment) in ~/Projects/my_app/project/my_app/static/img/favicon.ico.

    Of course I have a separate local_settings.py file for accessing this stuff locally in dev environment and it does have STATIC and MEDIA settings. I had to do a lot of experimenting and reading to find this solution and it works consistently with no errors.

    I understand that you need the static and root separation and considering that you can only provide one bucket I would point out that this method takes all the folders in my local environment under ~/Projects/my_app/project/my_app/static/and creates a folder in the bucket root (ie: S3bucket/img/ as in the example above). So you do get separation of files. For example you could have a media folder in the static folder and access it via templating with this:

    {% static "media/" %}
    

    I hope this helps. I came here looking for the answer and pushed a bit harder to find a simpler solution than to extend the storage system. Instead, I read the documentation about the intended use of Boto and I found that a lot of what I needed was built-in by default. Cheers!

    0 讨论(0)
  • 2020-11-28 01:27

    If you want to have subfolders even before media or static seperations, you can use AWS_LOCATION on top of bradenm answer. Reference: https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#usage

    AWS_STORAGE_BUCKET_NAME = 'bucket_name'
    AWS_LOCATION = 'path1/path2/'
    
    0 讨论(0)
  • 2020-11-28 01:34

    I'm currently using this code in a separated s3utils module:

    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)
    

    Then, in my settings module:

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

    I got to redefine the _normalize_name() private method to use a "fixed" version of the safe_join() function, since the original code is giving me SuspiciousOperation exceptions for legal paths.

    I'm posting this for consideration, if anyone can give a better answer or improve this one, it will be very welcome.

    0 讨论(0)
提交回复
热议问题