Added support for pluggable storage backends to agnostically handle the actual build location.
This commit is contained in:
@@ -5,6 +5,9 @@ from django import template
|
||||
from django.conf import settings as django_settings
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import get_storage_class
|
||||
|
||||
from compressor.conf import settings
|
||||
from compressor import filters
|
||||
|
||||
@@ -42,16 +45,12 @@ class Compressor(object):
|
||||
raise NotImplementedError('split_contents must be defined in a subclass')
|
||||
|
||||
def get_filename(self, url):
|
||||
if not url.startswith(settings.MEDIA_URL):
|
||||
raise UncompressableFileError('"%s" is not in COMPRESS_URL ("%s") and can not be compressed' % (url, settings.MEDIA_URL))
|
||||
# .lstrip used to remove leading slashes because os.path.join
|
||||
# counterintuitively takes "/foo/bar" and "/baaz" to produce "/baaz",
|
||||
# not the "/foo/bar/baaz" which you might expect:
|
||||
basename = url.replace(settings.MEDIA_URL, "", 1).lstrip("/")
|
||||
filename = os.path.join(settings.MEDIA_ROOT, basename)
|
||||
if not os.path.exists(filename):
|
||||
raise UncompressableFileError('"%s" does not exist' % (filename,))
|
||||
return filename
|
||||
if not url.startswith(self.storage.base_url):
|
||||
raise UncompressableFileError('"%s" is not in COMPRESS_URL ("%s") and can not be compressed' % (url, self.storage.base_url))
|
||||
basename = url.replace(self.storage.base_url, "", 1)
|
||||
if not self.storage.exists(basename):
|
||||
raise UncompressableFileError('"%s" does not exist' % self.storage.path(basename))
|
||||
return self.storage.path(basename)
|
||||
|
||||
@property
|
||||
def mtimes(self):
|
||||
@@ -64,6 +63,10 @@ class Compressor(object):
|
||||
cachestr = "".join(cachebits)
|
||||
return "django_compressor.%s" % get_hexdigest(cachestr)[:12]
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
return get_storage_class(settings.STORAGE)()
|
||||
|
||||
@property
|
||||
def hunks(self):
|
||||
if getattr(self, '_hunks', ''):
|
||||
@@ -119,28 +122,20 @@ class Compressor(object):
|
||||
@property
|
||||
def new_filepath(self):
|
||||
filename = "".join([self.hash, self.extension])
|
||||
filepath = "%s/%s/%s" % (settings.OUTPUT_DIR.strip('/'), self.ouput_prefix, filename)
|
||||
return filepath
|
||||
return "/".join((settings.OUTPUT_DIR.strip('/'), self.ouput_prefix, filename))
|
||||
|
||||
def save_file(self):
|
||||
filename = "%s/%s" % (settings.MEDIA_ROOT.rstrip('/'), self.new_filepath)
|
||||
if os.path.exists(filename):
|
||||
if self.storage.exists(self.new_filepath):
|
||||
return False
|
||||
dirname = os.path.dirname(filename)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
fd = open(filename, 'wb+')
|
||||
fd.write(self.combined)
|
||||
fd.close()
|
||||
self.storage.save(self.new_filepath, ContentFile(self.combined))
|
||||
return True
|
||||
|
||||
def output(self):
|
||||
if not settings.COMPRESS:
|
||||
return self.content
|
||||
url = "%s/%s" % (settings.MEDIA_URL.rstrip('/'), self.new_filepath)
|
||||
self.save_file()
|
||||
context = getattr(self, 'extra_context', {})
|
||||
context['url'] = url
|
||||
context['url'] = self.storage.url(self.new_filepath)
|
||||
return render_to_string(self.template_name, context)
|
||||
|
||||
|
||||
@@ -149,7 +144,10 @@ class CssCompressor(Compressor):
|
||||
def __init__(self, content, ouput_prefix="css"):
|
||||
self.extension = ".css"
|
||||
self.template_name = "compressor/css.html"
|
||||
self.filters = ['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.css_default.CssMediaFilter']
|
||||
self.filters = [
|
||||
'compressor.filters.css_default.CssAbsoluteFilter',
|
||||
'compressor.filters.css_default.CssMediaFilter',
|
||||
]
|
||||
self.filters.extend(settings.COMPRESS_CSS_FILTERS)
|
||||
self.type = 'css'
|
||||
super(CssCompressor, self).__init__(content, ouput_prefix)
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.conf import settings
|
||||
MEDIA_URL = getattr(settings, 'COMPRESS_URL', settings.MEDIA_URL)
|
||||
MEDIA_ROOT = getattr(settings, 'COMPRESS_ROOT', settings.MEDIA_ROOT)
|
||||
OUTPUT_DIR = getattr(settings, 'COMPRESS_OUTPUT_DIR', 'CACHE')
|
||||
STORAGE = getattr(settings, 'COMPRESS_STORAGE', 'compressor.storage.CompressorFileStorage')
|
||||
|
||||
COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG)
|
||||
COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', [])
|
||||
|
||||
20
compressor/storage.py
Normal file
20
compressor/storage.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.core.files.storage import get_storage_class
|
||||
|
||||
from compressor.conf import settings
|
||||
|
||||
class CompressorFileStorage(FileSystemStorage):
|
||||
"""
|
||||
Standard file system storage for files handled by django-compressor.
|
||||
|
||||
The defaults for ``location`` and ``base_url`` are ``COMPRESS_ROOT`` and
|
||||
``COMPRESS_URL``.
|
||||
|
||||
"""
|
||||
def __init__(self, location=None, base_url=None, *args, **kwargs):
|
||||
if location is None:
|
||||
location = settings.MEDIA_ROOT
|
||||
if base_url is None:
|
||||
base_url = settings.MEDIA_URL
|
||||
super(CompressorFileStorage, self).__init__(location, base_url,
|
||||
*args, **kwargs)
|
||||
@@ -1,9 +1,12 @@
|
||||
import os, re
|
||||
import gzip
|
||||
|
||||
from django.template import Template, Context
|
||||
from django.test import TestCase
|
||||
from compressor import CssCompressor, JsCompressor
|
||||
from compressor.conf import settings
|
||||
from compressor.storage import CompressorFileStorage
|
||||
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
@@ -148,19 +151,19 @@ class CssMediaTestCase(TestCase):
|
||||
out = u'@media screen {body { background:#990; }}\n@media print {p { border:5px solid green;}}\n@media all {body { color:#fff; }}'
|
||||
self.assertEqual(out, self.cssNode.combined)
|
||||
|
||||
def render(template_string, context_dict=None):
|
||||
"""A shortcut for testing template output."""
|
||||
if context_dict is None:
|
||||
context_dict = {}
|
||||
|
||||
c = Context(context_dict)
|
||||
t = Template(template_string)
|
||||
return t.render(c).strip()
|
||||
|
||||
class TemplatetagTestCase(TestCase):
|
||||
def setUp(self):
|
||||
settings.COMPRESS = True
|
||||
|
||||
def render(self, template_string, context_dict=None):
|
||||
"""A shortcut for testing template output."""
|
||||
if context_dict is None:
|
||||
context_dict = {}
|
||||
|
||||
c = Context(context_dict)
|
||||
t = Template(template_string)
|
||||
return t.render(c).strip()
|
||||
|
||||
def test_css_tag(self):
|
||||
template = u"""{% load compress %}{% compress css %}
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/one.css" type="text/css" charset="utf-8">
|
||||
@@ -170,7 +173,7 @@ class TemplatetagTestCase(TestCase):
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/f7c661b7a124.css" type="text/css" media="all" charset="utf-8">'
|
||||
self.assertEqual(out, self.render(template, context))
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
def test_nonascii_css_tag(self):
|
||||
template = u"""{% load compress %}{% compress css %}
|
||||
@@ -180,7 +183,7 @@ class TemplatetagTestCase(TestCase):
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = '<link rel="stylesheet" href="/media/CACHE/css/68da639dbb24.css" type="text/css" media="all" charset="utf-8">'
|
||||
self.assertEqual(out, self.render(template, context))
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
def test_js_tag(self):
|
||||
template = u"""{% load compress %}{% compress js %}
|
||||
@@ -190,5 +193,37 @@ class TemplatetagTestCase(TestCase):
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<script type="text/javascript" src="/media/CACHE/js/3f33b9146e12.js" charset="utf-8"></script>'
|
||||
self.assertEqual(out, self.render(template, context))
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
class TestStorage(CompressorFileStorage):
|
||||
"""
|
||||
Test compressor storage that gzips storage files
|
||||
"""
|
||||
def url(self, name):
|
||||
return u'%s.gz' % super(TestStorage, self).url(name)
|
||||
|
||||
def save(self, filename, content):
|
||||
filename = super(TestStorage, self).save(filename, content)
|
||||
out = gzip.open(u'%s.gz' % self.path(filename), 'wb')
|
||||
out.writelines(open(self.path(filename), 'rb'))
|
||||
out.close()
|
||||
|
||||
class StorageTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self._storage = settings.STORAGE
|
||||
settings.STORAGE = 'core.tests.TestStorage'
|
||||
settings.COMPRESS = True
|
||||
|
||||
def tearDown(self):
|
||||
settings.STORAGE = self._storage
|
||||
|
||||
def test_css_tag_with_storage(self):
|
||||
template = u"""{% load compress %}{% compress css %}
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/one.css" type="text/css" charset="utf-8">
|
||||
<style type="text/css">p { border:5px solid white;}</style>
|
||||
<link rel="stylesheet" href="{{ MEDIA_URL }}css/two.css" type="text/css" charset="utf-8">
|
||||
{% endcompress %}
|
||||
"""
|
||||
context = { 'MEDIA_URL': settings.MEDIA_URL }
|
||||
out = u'<link rel="stylesheet" href="/media/CACHE/css/5b231a62e9a6.css.gz" type="text/css" media="all" charset="utf-8">'
|
||||
self.assertEqual(out, render(template, context))
|
||||
|
||||
Reference in New Issue
Block a user