Django-compressor:如何写入S3,从CloudFront读取?

18 投票
6 回答
8679 浏览
提问于 2025-04-17 09:19

我想通过CloudFront来提供我压缩后的CSS和JS文件(这些文件存放在S3上),但是我不知道怎么在settings.py里的压缩设置中做到这一点。我现在的设置是这样的:

    COMPRESS_OFFLINE = True 
    COMPRESS_URL = 'http://static.example.com/' #same as STATIC_URL, so unnecessary, just here for simplicity
    COMPRESS_STORAGE = 'my_example_dir.storage.CachedS3BotoStorage' #subclass suggested in [docs][1]
    COMPRESS_OUTPUT_DIR = 'compressed_static'
    COMPRESS_ROOT = '/home/dotcloud/current/static/' #location of static files on server

尽管我设置了COMPRESS_URL,但我的文件还是从我的S3桶里读取的:
<link rel="stylesheet" href="https://example.s3.amazonaws.com/compressed_static/css/e0684a1d5c25.css?Signature=blahblahblah;Expires=farfuture;AWSAccessKeyId=blahblahblah" type="text/css" />

我想问题在于,我希望把文件写入S3,但从CloudFront读取。这可能吗?

6 个回答

4

看起来这个问题其实在Django的更新中已经修复了,具体可以查看这个链接:https://github.com/django/django/commit/5c954136eaef3d98d532368deec4c19cf892f664

对于旧版本的Django,可以尝试在本地修补一下有问题的_get_size方法,以便绕过这个问题。

补充:可以看看这个链接 https://github.com/jezdez/django_compressor/issues/100,里面有实际的解决办法。

11

我对settings.py做了一些不同的修改。

AWS_S3_CUSTOM_DOMAIN = 'XXXXXXX.cloudfront.net' #important: no "http://"
AWS_S3_SECURE_URLS = True #default, but must set to false if using an alias on cloudfront

COMPRESS_STORAGE = 'example_app.storage.CachedS3BotoStorage' #from the docs (linked below)
STATICFILES_STORAGE = 'example_app.storage.CachedS3BotoStorage'

压缩器文档

上面的解决方案让我可以把文件保存在本地,同时也上传到s3。这让我可以在离线状态下压缩文件。如果你没有使用gzip,上面的做法应该可以用来从CloudFront提供压缩文件。

不过,添加gzip会带来一些麻烦:

settings.py

AWS_IS_GZIPPED = True

但是每当有可压缩的文件(根据存储的说法是css和js)在执行collectstatic时被推送到s3时,就会出现错误:

AttributeError: 'cStringIO.StringO'对象没有'name'这个属性

这个错误是由于css/js文件的压缩出现了一些奇怪的问题,我也搞不太懂。这些文件我需要在本地保留未压缩的版本,而不需要放在s3上,所以如果我稍微调整一下上面提到的存储子类(在压缩器的文档中提供),就可以完全避免这个问题。

新的storage.py

from os.path import splitext 
from django.core.files.storage import get_storage_class  
from storages.backends.s3boto import S3BotoStorage  


class StaticToS3Storage(S3BotoStorage): 

    def __init__(self, *args, **kwargs): 
        super(StaticToS3Storage, self).__init__(*args, **kwargs) 
        self.local_storage = get_storage_class('compressor.storage.CompressorFileStorage')() 

    def save(self, name, content): 
        ext = splitext(name)[1] 
        parent_dir = name.split('/')[0] 
        if ext in ['.css', '.js'] and not parent_dir == 'admin': 
            self.local_storage._save(name, content) 
        else:     
            filename = super(StaticToS3Storage, self).save(name, content) 
            return filename 

这样一来,我就可以在推送其他文件到s3的同时,保存所有的.css和.js文件(不包括管理员文件,这些我从CloudFront提供未压缩的版本),而且不需要在本地保存它们(不过我可以很容易地添加self.local_storage._save这一行)。

但是当我运行压缩时,我希望我的压缩后的.js和.css文件能被推送到s3,所以我为压缩器创建了另一个子类来使用:

class CachedS3BotoStorage(S3BotoStorage): 
        """ 
        django-compressor uses this class to gzip the compressed files and send them to s3 
        these files are then saved locally, which ensures that they only create fresh copies 
        when they need to 
        """ 
        def __init__(self, *args, **kwargs): 
            super(CachedS3BotoStorage, self).__init__(*args, **kwargs) 
            self.local_storage = get_storage_class('compressor.storage.CompressorFileStorage')() 


        def save(self, filename, content): 
            filename = super(CachedS3BotoStorage, self).save(filename, content) 
            self.local_storage._save(filename, content) 
            return filename 

最后,考虑到这些新的子类,我需要更新一些设置:

COMPRESS_STORAGE = 'example_app.storage.CachedS3BotoStorage' #from the docs (linked below)
STATICFILES_STORAGE = 'example_app.storage.StaticToS3Storage'

这就是我想说的全部内容。

33

我写了一个包装存储后端,围绕着boto提供的那个。

我的文件是myapp/storage_backends.py:

import urlparse
from django.conf import settings
from storages.backends.s3boto import S3BotoStorage

def domain(url):
    return urlparse.urlparse(url).hostname    

class MediaFilesStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        kwargs['bucket'] = settings.MEDIA_FILES_BUCKET
        kwargs['custom_domain'] = domain(settings.MEDIA_URL)
        super(MediaFilesStorage, self).__init__(*args, **kwargs)

class StaticFilesStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        kwargs['bucket'] = settings.STATIC_FILES_BUCKET
        kwargs['custom_domain'] = domain(settings.STATIC_URL)
        super(StaticFilesStorage, self).__init__(*args, **kwargs)

而我的settings.py文件里有...

STATIC_FILES_BUCKET = "myappstatic"
MEDIA_FILES_BUCKET = "myappmedia"
STATIC_URL = "http://XXXXXXXX.cloudfront.net/"
MEDIA_URL = "http://XXXXXXXX.cloudfront.net/"

DEFAULT_FILE_STORAGE = 'myapp.storage_backends.MediaFilesStorage'
COMPRESS_STORAGE = STATICFILES_STORAGE = 'myapp.storage_backends.StaticFilesStorage'

撰写回答