Commit 7f87d3f2 by Chris Reeves Committed by Timothée Peignier

add jinja2 support

Signed-off-by: Timothée Peignier <timothee.peignier@tryphon.org>
parent 04cba3b9
Pipeline is a fork of django-compress which was originally created by Pipeline is a fork of django-compress which was originally created by
Andreas Pelme <andreas@pelme.se> in 2008. Andreas Pelme <andreas@pelme.se> in 2008.
These people have provided bug fixes, new features, improved the documentation These people have provided bug fixes, new features, improved the documentation
or just made Pipeline more awesome. or just made Pipeline more awesome.
* Adam Charnock <adam@omniwiki.co.uk> * Adam Charnock <adam@omniwiki.co.uk>
...@@ -15,6 +15,7 @@ or just made Pipeline more awesome. ...@@ -15,6 +15,7 @@ or just made Pipeline more awesome.
* Brant Young <brant.young@gmail.com> * Brant Young <brant.young@gmail.com>
* Bryan Chow <bryan@fullfactor.com> * Bryan Chow <bryan@fullfactor.com>
* Casey Greene <csgreene@princeton.edu> * Casey Greene <csgreene@princeton.edu>
* Chris Reeves <hello@chris.reeves.io>
* Christian Hammond <chipx86@chipx86.com> * Christian Hammond <chipx86@chipx86.com>
* David Charbonnier <d.charbonnier@oxys.net> * David Charbonnier <d.charbonnier@oxys.net>
* David Cramer <dcramer@gmail.com> * David Cramer <dcramer@gmail.com>
......
...@@ -33,14 +33,13 @@ with the name “scripts”, you would use the following code to output them all ...@@ -33,14 +33,13 @@ with the name “scripts”, you would use the following code to output them all
{% compressed_css 'colors' %} {% compressed_css 'colors' %}
{% compressed_js 'stats' %} {% compressed_js 'stats' %}
Collect static Collect static
============== ==============
Pipeline integrates with staticfiles, you just need to setup ``STATICFILES_STORAGE`` to :: Pipeline integrates with staticfiles, you just need to setup ``STATICFILES_STORAGE`` to ::
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage' STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
Then when you run ``collectstatic`` command, your CSS and your javascripts will be compressed in the same time :: Then when you run ``collectstatic`` command, your CSS and your javascripts will be compressed in the same time ::
$ python oslo/manage.py collectstatic $ python oslo/manage.py collectstatic
...@@ -49,7 +48,7 @@ Then when you run ``collectstatic`` command, your CSS and your javascripts will ...@@ -49,7 +48,7 @@ Then when you run ``collectstatic`` command, your CSS and your javascripts will
Middleware Middleware
========== ==========
To enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``, To enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``,
to your ``MIDDLEWARE_CLASSES`` settings. to your ``MIDDLEWARE_CLASSES`` settings.
Ensure that it comes after any middleware which modifies your HTML, like ``GZipMiddleware`` :: Ensure that it comes after any middleware which modifies your HTML, like ``GZipMiddleware`` ::
...@@ -66,3 +65,42 @@ Pipeline provide a way to add your javascripts and stylesheets files to a ...@@ -66,3 +65,42 @@ Pipeline provide a way to add your javascripts and stylesheets files to a
cache-manifest via `Manifesto <http://manifesto.readthedocs.org/>`_. cache-manifest via `Manifesto <http://manifesto.readthedocs.org/>`_.
To do so, you just need to add manifesto app to your ``INSTALLED_APPS``. To do so, you just need to add manifesto app to your ``INSTALLED_APPS``.
Jinja2
======
Pipeline also includes Jinja2 support and is used almost identically to the Django
Template tags implementation.
.. note::
You have to expose the Jinja2 functions provided by pipeline to the Jinja2
environment yourself, Pipeline will not do this for you. There are several implementations
of Jinja2 for Django, like ``django-ninja`` or ``coffin``.
See the vendor documentation for examples on how to expose functions to the Jinja2 environment
and pick a solution that best suites your use case.
For more information on Jinja2 see the documentation at http://jinja.pocoo.org/docs/.
Functions
---------
The functions to expose to the Jinja2 environment are: ::
pipeline.jinja2.ext.compressed_css
pipeline.jinja2.ext.compressed_js
Example
-------
To use in the templates: ::
{{ compressed_css('group_name') }}
{{ compressed_js('group_name') }}
Templates
---------
Unlike the Django template tag implementation the Jinja2 implementation uses different templates, so if you
wish to override them please override ``pipeline/css.jinja`` and ``pipeline/js.jinja``.
...@@ -155,7 +155,6 @@ class Compressor(object): ...@@ -155,7 +155,6 @@ class Compressor(object):
"""Is the asset embeddable ?""" """Is the asset embeddable ?"""
name, ext = os.path.splitext(path) name, ext = os.path.splitext(path)
font = ext in FONT_EXTS font = ext in FONT_EXTS
if not variant: if not variant:
return False return False
if not (re.search(settings.PIPELINE_EMBED_PATH, path) and self.storage.exists(path)): if not (re.search(settings.PIPELINE_EMBED_PATH, path) and self.storage.exists(path)):
......
import inspect
try:
from staticfiles.storage import staticfiles_storage
except ImportError:
from django.contrib.staticfiles.storage import staticfiles_storage # noqa
from django.conf import settings as django_settings
from jinja2 import Environment, FileSystemLoader
from pipeline.conf import settings as pipeline_settings
from pipeline.packager import Packager, PackageNotFound
from pipeline.utils import guess_type
class Jinja2Compressed(object):
def __init__(self, package_type):
from django.template.loaders import app_directories
if package_type not in ['css', 'js']:
raise PackageNotFound("Package type must be css or js, supplied %s" % package_type)
self.package_type = package_type
self.loader = FileSystemLoader((app_directories.app_template_dirs +
django_settings.TEMPLATE_DIRS))
self.get_pipeline_settings()
def get_pipeline_settings(self):
"""
Because extra Jinja2 functions have to be declared
at creation time the new functions have to be declared before
django settings evaluation so when pipeline tries to import django
settings it will get the default globals rather than user defined
settings. This function attempts to fudge back in user defined
settings into pipeline settings as django.conf.settings is lazy
loaded and pipeline settings are not.
No harm intended :)
I guess a better more robust solution would be to make pipeline
settings lazy loaded also.
"""
members = inspect.getmembers(pipeline_settings)
for setting, val in members:
if setting.startswith('PIPELINE'):
if hasattr(django_settings, setting):
val = getattr(django_settings, setting)
else:
if type(getattr(pipeline_settings, setting)) == str:
val = "'%s'" % val
val = val if val else "''"
expr = "pipeline_settings.%s = %s" % (
setting, val)
exec expr
pipeline_settings.PIPELINE = getattr(django_settings,
'PIPELINE', not django_settings.DEBUG)
self.settings = pipeline_settings
def get_package(self, name):
"""Get the js or css package."""
package = {
'js': self.settings.PIPELINE_JS.get(name, {}),
'css': self.settings.PIPELINE_CSS.get(name, {}),
}[self.package_type]
if package:
package = {name: package}
self.packager = {
'js': Packager(css_packages={}, js_packages=package),
'css': Packager(css_packages=package, js_packages={}),
}[self.package_type]
try:
self.package = self.packager.package_for(self.package_type, name)
except PackageNotFound:
self.package = None
def render(self, path):
"""Render the HTML tag."""
if not self.package.template_name:
template_name = {
'js': 'pipeline/js.jinja',
'css': 'pipeline/css.jinja',
}[self.package_type]
else:
template_name = self.package.template_name
mimetype = {
'js': 'text/javascript',
'css': 'text/css',
}[self.package_type]
context = self.package.extra_context
context.update({
'type': guess_type(path, mimetype),
'url': staticfiles_storage.url(path)
})
env = Environment(loader=self.loader)
tpl = env.get_template(template_name)
return tpl.render(**context)
def html(self, name):
"""Render the HTML Snippet"""
self.get_package(name)
if self.package:
if self.settings.PIPELINE:
return self.render(self.package.output_filename)
else:
paths = self.packager.compile(self.package.paths)
templates = self.packager.pack_templates(self.package)
return {
'css': self.render_individual_css(paths),
'js': self.render_individual_js(paths, templates)
}[self.package_type]
else:
return '' # don't return anything if no package found
def render_individual_css(self, paths):
"""Render individual CSS files"""
tags = [self.render(path) for path in paths]
return '\n'.join(tags)
def render_individual_js(self, paths, templates=None):
"""Render individual JS files"""
tags = [self.render(path) for path in paths]
if templates:
tags.append(self.render_inline_js(self.package, templates))
return '\n'.join(tags)
def render_inline_js(self, package, js):
template_name = (self.package.template_name or
"pipeline/inline_js.jinja")
context = self.package.extra_context
context.update({
'source': js
})
env = Environment(loader=self.loader)
tpl = env.get_template(template_name)
return tpl.render(**context)
def compressed_css(package_name):
compress = Jinja2Compressed('css')
return compress.html(package_name)
def compressed_js(package_name):
compress = Jinja2Compressed('js')
return compress.html(package_name)
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="text/javascript" charset="utf-8">
{{ source|safe }}
</script>
<script {% if async %}async{% endif %} {% if defer %}defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
{{ compressed_css('screen') }}
{{ compressed_js('scripts') }}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from compiler import * from compiler import *
from compressor import * from compressor import *
from glob import * from glob import *
from jinja2 import *
from packager import * from packager import *
from storage import * from storage import *
from utils import * from utils import *
\ No newline at end of file
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from django.conf import settings
from django.test import TestCase
from jinja2 import Environment, FileSystemLoader
from pipeline.packager import PackageNotFound
from pipeline.jinja2.ext import compressed_css, compressed_js, Jinja2Compressed
EXPECTED_INDIVIDUAL_CSS = u'<link href="/static/css/first.css" rel="'\
'stylesheet" type="text/css" />\n<link href="/static/css/second.css" '\
'rel="stylesheet" type="text/css" />\n<link href="/static/css'\
'/urls.css" rel="stylesheet" type="text/css" />'
EXPECTED_COMPRESSED_CSS = u'<link href="/static/screen.css" rel="stylesheet" '\
'type="text/css" />'
EXPECTED_INDIVIDUAL_JS = u'<script type="text/javascript" src="/static/js/'\
'first.js" charset="utf-8"></script>\n<script type="text/'\
'javascript" src="/static/js/second.js" charset="utf-8">'\
'</script>\n<script type="text/javascript" src="/static/js/'\
'application.js" charset="utf-8"></script>\n<script '\
'type="text/javascript" charset="utf-8">\n window.JST = '\
'window.JST || {};\nvar template = function(str){var fn = '\
'new Function(\'obj\', \'var __p=[],print=function(){__p.'\
'push.apply(__p,arguments);};with(obj||{}){__p.push(\\\'\'+'\
'str.replace(/\\\\/g, \'\\\\\\\\\').replace(/\'/g, "\\\\\'").'\
'replace(/<%=([\\s\\S]+?)%>/g,function(match,code){'\
'return "\',"+code.replace(/\\\\\'/g, "\'")+",\'";}).'\
'replace(/<%([\\s\\S]+?)%>/g,function(match,code){return '\
'"\');"+code.replace(/\\\\\'/g, "\'").replace(/[\\r\\n\\t]/g'\
',\' \')+"__p.push(\'";}).replace(/\\r/g,\'\\\\r\').replace'\
'(/\\n/g,\'\\\\n\').replace(/\\t/g,\'\\\\t\')+"\');}'\
'return __p.join(\'\');");return fn;};\nwindow.JST[\''\
'photo_detail\'] = template(\'<div class="photo">\\n '\
'<img src="<%= src %>" />\\n <div class="caption">\\n '\
'<%= caption %> by <%= author %>\\n </div>\\n</div>\');\n'\
'window.JST[\'photo_list\'] = template(\'<div class="photo'\
'">\\n <img src="<%= src %>" />\\n <div class="caption">\\n '\
'<%= caption %>\\n </div>\\n</div>\');\nwindow.JST[\''\
'video_detail\'] = template(\'<div class="video">\\n <video '\
'src="<%= src %>" />\\n <div class="caption">\\n <%= '\
'description %>\\n </div>\\n</div>\');\n\n</script>'
EXPECTED_COMPRESSED_JS = u'<script type="text/css" src="/static/scripts.'\
'css" charset="utf-8"></script>'
class Jinja2Test(TestCase):
def setUp(self):
from django.template.loaders import app_directories # has to be here
self.loader = FileSystemLoader((app_directories.app_template_dirs +
settings.TEMPLATE_DIRS))
self.environment = Environment(loader=self.loader)
self.environment.globals['compressed_css'] = compressed_css
self.environment.globals['compressed_js'] = compressed_js
self.maxDiff = None
def test_exception_raised_with_unknown_ftype(self):
try:
Jinja2Compressed('png')
self.fail()
except PackageNotFound:
pass
def test_render_css_debug_is_not_compressed(self):
settings.PIPELINE = False
compress = Jinja2Compressed('css')
output = compress.html('screen')
expected = EXPECTED_INDIVIDUAL_CSS
self.assertEqual(output, expected)
def test_render_css_not_debug_is_compressed(self):
settings.PIPELINE = True
compress = Jinja2Compressed('css')
output = compress.html('screen')
expected = EXPECTED_COMPRESSED_CSS
self.assertEqual(output, expected)
def test_render_js_debug_is_not_compressed(self):
settings.PIPELINE = False
compress = Jinja2Compressed('js')
output = compress.html('scripts')
expected = EXPECTED_INDIVIDUAL_JS
self.assertEqual(output, expected)
def test_render_js_not_debug_is_compressed(self):
settings.PIPELINE = True
compress = Jinja2Compressed('js')
output = compress.html('scripts')
expected = EXPECTED_COMPRESSED_JS
self.assertEqual(output, expected)
def test_template_css_function_individual(self):
settings.PIPELINE = False
tpl = self.environment.get_template('css.jinja')
output = tpl.render()
expected = EXPECTED_INDIVIDUAL_CSS
self.assertEquals(output, expected)
def test_template_css_function_compressed(self):
settings.PIPELINE = True
tpl = self.environment.get_template('css.jinja')
output = tpl.render()
expected = EXPECTED_COMPRESSED_CSS
self.assertEquals(output, expected)
def test_template_js_function_individual(self):
settings.PIPELINE = False
tpl = self.environment.get_template('js.jinja')
output = tpl.render()
expected = EXPECTED_INDIVIDUAL_JS
self.assertEquals(output, expected)
def test_template_js_function_compressed(self):
settings.PIPELINE = True
tpl = self.environment.get_template('js.jinja')
output = tpl.render()
expected = EXPECTED_COMPRESSED_JS
self.assertEquals(output, expected)
...@@ -19,6 +19,7 @@ deps = ...@@ -19,6 +19,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py26-1.2.X] [testenv:py26-1.2.X]
basepython = python2.6 basepython = python2.6
...@@ -27,6 +28,7 @@ deps = ...@@ -27,6 +28,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py27-1.2.X] [testenv:py27-1.2.X]
basepython = python2.7 basepython = python2.7
...@@ -35,6 +37,7 @@ deps = ...@@ -35,6 +37,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py25-1.3.X] [testenv:py25-1.3.X]
basepython = python2.5 basepython = python2.5
...@@ -43,6 +46,7 @@ deps = ...@@ -43,6 +46,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py26-1.3.X] [testenv:py26-1.3.X]
basepython = python2.6 basepython = python2.6
...@@ -51,6 +55,7 @@ deps = ...@@ -51,6 +55,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py27-1.3.X] [testenv:py27-1.3.X]
basepython = python2.7 basepython = python2.7
...@@ -59,6 +64,7 @@ deps = ...@@ -59,6 +64,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py25] [testenv:py25]
basepython = python2.5 basepython = python2.5
...@@ -67,6 +73,7 @@ deps = ...@@ -67,6 +73,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py26] [testenv:py26]
basepython = python2.6 basepython = python2.6
...@@ -75,6 +82,7 @@ deps = ...@@ -75,6 +82,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:py27] [testenv:py27]
basepython = python2.7 basepython = python2.7
...@@ -83,6 +91,7 @@ deps = ...@@ -83,6 +91,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:pypy] [testenv:pypy]
basepython = pypy basepython = pypy
...@@ -91,6 +100,7 @@ deps = ...@@ -91,6 +100,7 @@ deps =
mock mock
django-staticfiles==1.2.1 django-staticfiles==1.2.1
unittest2 unittest2
jinja2
[testenv:docs] [testenv:docs]
basepython = python2.7 basepython = python2.7
......
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