Django-compressor:如何写入S3,从CloudFront读取?
我想通过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 个回答
看起来这个问题其实在Django的更新中已经修复了,具体可以查看这个链接:https://github.com/django/django/commit/5c954136eaef3d98d532368deec4c19cf892f664
对于旧版本的Django,可以尝试在本地修补一下有问题的_get_size方法,以便绕过这个问题。
补充:可以看看这个链接 https://github.com/jezdez/django_compressor/issues/100,里面有实际的解决办法。
我对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'
这就是我想说的全部内容。
我写了一个包装存储后端,围绕着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'