Added support for pluggable storage backends to agnostically handle the actual build location.

This commit is contained in:
Jannis Leidel
2010-02-10 18:42:28 +01:00
parent 7f314c5134
commit 4701bd3a55
4 changed files with 89 additions and 35 deletions

View File

@@ -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)

View File

@@ -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
View 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)

View File

@@ -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))