Commit d5ca0576 by Edwin Lunando Committed by Timothée Peignier

improve post processing

Use generators and add a gzip support.

Signed-off-by: Timothée Peignier <timothee.peignier@tryphon.org>
parent 6f55a2f7
...@@ -52,6 +52,25 @@ tool like Bower. :: ...@@ -52,6 +52,25 @@ tool like Bower. ::
'pipeline.finders.CachedFileFinder', 'pipeline.finders.CachedFileFinder',
) )
GZIP compression
================
Pipeline can also creates a gzipped version of your collected static files,
so that you can avoid compressing them on the fly. ::
STATICFILES_STORAGE = 'your.app.GZIPCachedStorage'
The storage need to inherit from ``GzipMixin``: ::
from staticfiles.storage import CachedStaticFilesStorage
from pipeline.storage import GZIPMixin
class GZIPCachedStorage(GZIPMixin, CachedStaticFilesStorage):
pass
Using with other storages Using with other storages
========================= =========================
...@@ -69,7 +88,7 @@ Your storage only need to inherit from ``PipelineMixin`` and/or ``CachedFilesMix ...@@ -69,7 +88,7 @@ Your storage only need to inherit from ``PipelineMixin`` and/or ``CachedFilesMix
class S3PipelineStorage(PipelineMixin, CachedFilesMixin, S3BotoStorage): class S3PipelineStorage(PipelineMixin, CachedFilesMixin, S3BotoStorage):
pass pass
Using Pipeline with Bower Using Pipeline with Bower
========================= =========================
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.conf import settings as _settings from django.conf import settings as _settings
DEFAULTS = { DEFAULTS = {
......
from __future__ import unicode_literals from __future__ import unicode_literals
import os import gzip
from io import BytesIO
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.contrib.staticfiles.storage import CachedFilesMixin, StaticFilesStorage from django.contrib.staticfiles.storage import CachedStaticFilesStorage, StaticFilesStorage
from django.contrib.staticfiles.utils import matches_patterns
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import File
from django.core.files.storage import get_storage_class from django.core.files.storage import get_storage_class
from django.utils.functional import LazyObject from django.utils.functional import LazyObject
...@@ -17,7 +21,7 @@ class PipelineMixin(object): ...@@ -17,7 +21,7 @@ class PipelineMixin(object):
def post_process(self, paths, dry_run=False, **options): def post_process(self, paths, dry_run=False, **options):
if dry_run: if dry_run:
return [] return
from pipeline.packager import Packager from pipeline.packager import Packager
packager = Packager(storage=self) packager = Packager(storage=self)
...@@ -27,21 +31,19 @@ class PipelineMixin(object): ...@@ -27,21 +31,19 @@ class PipelineMixin(object):
if self.packing: if self.packing:
packager.pack_stylesheets(package) packager.pack_stylesheets(package)
paths[output_file] = (self, output_file) paths[output_file] = (self, output_file)
yield output_file, output_file, True
for package_name in packager.packages['js']: for package_name in packager.packages['js']:
package = packager.package_for('js', package_name) package = packager.package_for('js', package_name)
output_file = package.output_filename output_file = package.output_filename
if self.packing: if self.packing:
packager.pack_javascripts(package) packager.pack_javascripts(package)
paths[output_file] = (self, output_file) paths[output_file] = (self, output_file)
yield output_file, output_file, True
super_class = super(PipelineMixin, self) super_class = super(PipelineMixin, self)
if hasattr(super_class, 'post_process'): if hasattr(super_class, 'post_process'):
return super_class.post_process(paths, dry_run, **options) for name, hashed_name, processed in super_class.post_process(paths.copy(), dry_run, **options):
yield name, hashed_name, processed
return [
(path, path, True)
for path in paths
]
def get_available_name(self, name): def get_available_name(self, name):
if self.exists(name): if self.exists(name):
...@@ -49,6 +51,40 @@ class PipelineMixin(object): ...@@ -49,6 +51,40 @@ class PipelineMixin(object):
return name return name
class GZIPMixin(object):
gzip_patterns = ("*.css", "*.js")
def _compress(self, original_file):
content = BytesIO()
gzip_file = gzip.GzipFile(mode='wb', fileobj=content)
gzip_file.write(original_file.read())
gzip_file.close()
content.seek(0)
return File(content)
def post_process(self, paths, dry_run=False, **options):
super_class = super(GZIPMixin, self)
if hasattr(super_class, 'post_process'):
for name, hashed_name, processed in super_class.post_process(paths.copy(), dry_run, **options):
if hashed_name != name:
paths[hashed_name] = (self, hashed_name)
yield name, hashed_name, processed
if dry_run:
return
for path in paths:
if not matches_patterns(path, self.gzip_patterns):
continue
original_file = self.open(path)
gzipped_path = "{0}.gz".format(path)
if self.exists(gzipped_path):
self.delete(gzipped_path)
gzipped_file = self._compress(original_file)
gzipped_path = self.save(gzipped_path, gzipped_file)
yield gzipped_path, gzipped_path, True
class NonPackagingMixin(object): class NonPackagingMixin(object):
packing = False packing = False
...@@ -61,7 +97,7 @@ class NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage): ...@@ -61,7 +97,7 @@ class NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage):
pass pass
class PipelineCachedStorage(PipelineMixin, CachedFilesMixin, StaticFilesStorage): class PipelineCachedStorage(PipelineMixin, CachedStaticFilesStorage):
pass pass
......
function() { function test() {
alert('this is a test'); alert('this is a test');
} }
...@@ -64,6 +64,6 @@ PIPELINE_JS = { ...@@ -64,6 +64,6 @@ PIPELINE_JS = {
'pipeline/js/application.js', 'pipeline/js/application.js',
'pipeline/templates/**/*.jst' 'pipeline/templates/**/*.jst'
), ),
'output_filename': 'scripts.css' 'output_filename': 'scripts.js'
} }
} }
from __future__ import unicode_literals from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.utils.datastructures import SortedDict
from pipeline.storage import PipelineStorage, PipelineFinderStorage from pipeline.storage import PipelineStorage, PipelineFinderStorage
...@@ -11,18 +10,15 @@ from tests.utils import pipeline_settings ...@@ -11,18 +10,15 @@ from tests.utils import pipeline_settings
class StorageTest(TestCase): class StorageTest(TestCase):
def test_post_process_dry_run(self): def test_post_process_dry_run(self):
with pipeline_settings(PIPELINE_JS_COMPRESSOR=None, PIPELINE_CSS_COMPRESSOR=None): with pipeline_settings(PIPELINE_JS_COMPRESSOR=None, PIPELINE_CSS_COMPRESSOR=None):
processed_files = PipelineStorage().post_process([], True) processed_files = PipelineStorage().post_process({}, True)
self.assertEqual(processed_files, []) self.assertEqual(list(processed_files), [])
def test_post_process(self): def test_post_process(self):
storage = PipelineStorage() storage = PipelineStorage()
with pipeline_settings(PIPELINE_JS_COMPRESSOR=None, PIPELINE_CSS_COMPRESSOR=None): with pipeline_settings(PIPELINE_JS_COMPRESSOR=None, PIPELINE_CSS_COMPRESSOR=None):
processed_files = storage.post_process(SortedDict({ processed_files = storage.post_process({})
'css/first.css': (storage, 'css/first.css'), self.assertTrue(('screen.css', 'screen.css', True) in processed_files)
'images/arrow.png': (storage, 'images/arrow.png') self.assertTrue(('scripts.js', 'scripts.js', True) in processed_files)
}))
self.assertTrue(('css/first.css', 'css/first.css', True) in processed_files)
self.assertTrue(('images/arrow.png', 'images/arrow.png', True) in processed_files)
def test_find_storage(self): def test_find_storage(self):
try: try:
......
...@@ -35,7 +35,7 @@ class JinjaTest(TestCase): ...@@ -35,7 +35,7 @@ class JinjaTest(TestCase):
def test_package_js(self): def test_package_js(self):
template = self.env.from_string(u"""{% compressed_js "scripts" %}""") template = self.env.from_string(u"""{% compressed_js "scripts" %}""")
self.assertEqual(u'<script type="text/css" src="/static/scripts.css" charset="utf-8"></script>', template.render()) self.assertEqual(u'<script type="text/javascript" src="/static/scripts.js" charset="utf-8"></script>', template.render())
class DjangoTest(TestCase): class DjangoTest(TestCase):
...@@ -52,4 +52,4 @@ class DjangoTest(TestCase): ...@@ -52,4 +52,4 @@ class DjangoTest(TestCase):
def test_compressed_js(self): def test_compressed_js(self):
rendered = self.render_template(u"""{% load compressed %}{% compressed_js "scripts" %}""") rendered = self.render_template(u"""{% load compressed %}{% compressed_js "scripts" %}""")
self.assertEqual(u'<script type="text/css" src="/static/scripts.css" charset="utf-8"></script>', rendered) self.assertEqual(u'<script type="text/javascript" src="/static/scripts.js" charset="utf-8"></script>', rendered)
...@@ -8,18 +8,15 @@ def _(path): ...@@ -8,18 +8,15 @@ def _(path):
# Make sure the path contains only the correct separator # Make sure the path contains only the correct separator
return path.replace('/', os.sep).replace('\\', os.sep) return path.replace('/', os.sep).replace('\\', os.sep)
@contextlib.contextmanager @contextlib.contextmanager
def pipeline_settings(**kwargs): def pipeline_settings(**kwargs):
try: try:
saved = {} saved = {}
for name, value in kwargs.items(): for name, value in kwargs.items():
saved[name] = getattr(settings, name) saved[name] = getattr(settings, name)
setattr(settings, name, value) setattr(settings, name, value)
yield yield
finally: finally:
for name, value in saved.items(): for name, value in saved.items():
setattr(settings, name, value) setattr(settings, name, value)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment