Merge lp:~thedac/ubuntu/wily/python-django-compressor/debian-merge into lp:ubuntu/wily/python-django-compressor

Proposed by David Ames
Status: Merged
Merge reported by: Martin Pitt
Merged at revision: not available
Proposed branch: lp:~thedac/ubuntu/wily/python-django-compressor/debian-merge
Merge into: lp:ubuntu/wily/python-django-compressor
Diff against target: 2565 lines (+680/-930)
53 files modified
.pc/.quilt_patches (+0/-1)
.pc/.quilt_series (+0/-1)
.pc/.version (+0/-1)
.pc/applied-patches (+0/-2)
.pc/disable-coffin-tests.patch/compressor/test_settings.py (+0/-40)
.pc/disable-coffin-tests.patch/compressor/tests/test_offline.py (+0/-499)
.pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py (+0/-40)
.travis.yml (+15/-10)
AUTHORS (+1/-0)
README.rst (+10/-7)
compressor/__init__.py (+1/-1)
compressor/base.py (+8/-2)
compressor/cache.py (+13/-3)
compressor/conf.py (+2/-0)
compressor/css.py (+1/-1)
compressor/filters/base.py (+27/-3)
compressor/filters/cleancss.py (+10/-0)
compressor/filters/css_default.py (+3/-9)
compressor/js.py (+32/-4)
compressor/management/commands/compress.py (+25/-18)
compressor/offline/django.py (+27/-16)
compressor/parser/__init__.py (+5/-2)
compressor/templates/compressor/js_file.html (+1/-1)
compressor/test_settings.py (+19/-2)
compressor/tests/precompiler.py (+4/-4)
compressor/tests/static/css/filename with spaces.css (+1/-0)
compressor/tests/static/css/utf-8_with-BOM.css (+1/-0)
compressor/tests/static/js/three.js (+1/-0)
compressor/tests/static/js/two.js (+1/-0)
compressor/tests/test_base.py (+66/-3)
compressor/tests/test_filters.py (+135/-59)
compressor/tests/test_jinja2ext.py (+1/-2)
compressor/tests/test_offline.py (+20/-19)
compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html (+5/-0)
compressor/utils/staticfiles.py (+4/-14)
debian/changelog (+20/-0)
debian/control (+44/-12)
debian/gbp.conf (+1/-1)
debian/patches/disable-coffin-tests.patch (+6/-6)
debian/patches/fix-test_settings.py-for-django-1.7.patch (+0/-16)
debian/patches/remove-failed-test.patch (+43/-0)
debian/patches/series (+1/-1)
debian/rules (+18/-14)
debian/watch (+2/-1)
docs/changelog.txt (+38/-4)
docs/contributing.txt (+5/-4)
docs/django-sekizai.txt (+4/-4)
docs/jinja2.txt (+5/-6)
docs/quickstart.txt (+3/-5)
docs/remote-storages.txt (+5/-7)
docs/settings.txt (+22/-6)
docs/usage.txt (+1/-1)
tox.ini (+23/-78)
To merge this branch: bzr merge lp:~thedac/ubuntu/wily/python-django-compressor/debian-merge
Reviewer Review Type Date Requested Status
Corey Bryant Approve
Ubuntu branches Pending
Review via email: mp+268621@code.launchpad.net

Description of the change

Debian Merge

To post a comment you must log in.
Revision history for this message
Corey Bryant (corey.bryant) wrote :

Looks good but just a few minor adjustments.

There are a few extra differences in the d/control build-depends. Can we get those in sync or is that for a reason?

Also d/watch wasn't pulling down the orig tar for me, so you may want to update with:

version=3
opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
http://pypi.debian.net/django_compressor/django_compressor-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))

then we can submit that in a patch to debian (just the d/watch part) via the 'submittodebian' command.

8. By David Ames

Sync up d/control

9. By David Ames

Fix d/watch

Revision history for this message
Corey Bryant (corey.bryant) :
review: Approve
Revision history for this message
Martin Pitt (pitti) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed directory '.pc'
2=== removed file '.pc/.quilt_patches'
3--- .pc/.quilt_patches 2012-10-14 10:51:47 +0000
4+++ .pc/.quilt_patches 1970-01-01 00:00:00 +0000
5@@ -1,1 +0,0 @@
6-debian/patches
7
8=== removed file '.pc/.quilt_series'
9--- .pc/.quilt_series 2012-10-14 10:51:47 +0000
10+++ .pc/.quilt_series 1970-01-01 00:00:00 +0000
11@@ -1,1 +0,0 @@
12-series
13
14=== removed file '.pc/.version'
15--- .pc/.version 2012-10-14 10:51:47 +0000
16+++ .pc/.version 1970-01-01 00:00:00 +0000
17@@ -1,1 +0,0 @@
18-2
19
20=== removed file '.pc/applied-patches'
21--- .pc/applied-patches 2015-01-06 12:34:51 +0000
22+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
23@@ -1,2 +0,0 @@
24-fix-test_settings.py-for-django-1.7.patch
25-disable-coffin-tests.patch
26
27=== removed directory '.pc/disable-coffin-tests.patch'
28=== removed directory '.pc/disable-coffin-tests.patch/compressor'
29=== removed file '.pc/disable-coffin-tests.patch/compressor/test_settings.py'
30--- .pc/disable-coffin-tests.patch/compressor/test_settings.py 2015-01-06 12:34:51 +0000
31+++ .pc/disable-coffin-tests.patch/compressor/test_settings.py 1970-01-01 00:00:00 +0000
32@@ -1,40 +0,0 @@
33-import os
34-import django
35-
36-TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
37-
38-COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'
39-
40-DATABASES = {
41- 'default': {
42- 'ENGINE': 'django.db.backends.sqlite3',
43- 'NAME': ':memory:',
44- }
45-}
46-
47-INSTALLED_APPS = [
48- 'compressor',
49- 'coffin',
50- 'jingo',
51-]
52-
53-STATIC_URL = '/static/'
54-
55-
56-STATIC_ROOT = os.path.join(TEST_DIR, 'static')
57-
58-TEMPLATE_DIRS = (
59- # Specifically choose a name that will not be considered
60- # by app_directories loader, to make sure each test uses
61- # a specific template without considering the others.
62- os.path.join(TEST_DIR, 'test_templates'),
63-)
64-
65-if django.VERSION[:2] < (1, 6):
66- TEST_RUNNER = 'discover_runner.DiscoverRunner'
67-
68-SECRET_KEY = "iufoj=mibkpdz*%bob952x(%49rqgv8gg45k36kjcg76&-y5=!"
69-
70-PASSWORD_HASHERS = (
71- 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
72-)
73
74=== removed directory '.pc/disable-coffin-tests.patch/compressor/tests'
75=== removed file '.pc/disable-coffin-tests.patch/compressor/tests/test_offline.py'
76--- .pc/disable-coffin-tests.patch/compressor/tests/test_offline.py 2015-01-06 12:34:51 +0000
77+++ .pc/disable-coffin-tests.patch/compressor/tests/test_offline.py 1970-01-01 00:00:00 +0000
78@@ -1,499 +0,0 @@
79-from __future__ import with_statement, unicode_literals
80-import io
81-import os
82-import sys
83-
84-from django.core.management.base import CommandError
85-from django.template import Template, Context
86-from django.test import TestCase
87-from django.utils import six, unittest
88-
89-from compressor.cache import flush_offline_manifest, get_offline_manifest
90-from compressor.conf import settings
91-from compressor.exceptions import OfflineGenerationError
92-from compressor.management.commands.compress import Command as CompressCommand
93-from compressor.storage import default_storage
94-
95-if six.PY3:
96- # there is an 'io' module in python 2.6+, but io.StringIO does not
97- # accept regular strings, just unicode objects
98- from io import StringIO
99-else:
100- try:
101- from cStringIO import StringIO
102- except ImportError:
103- from StringIO import StringIO
104-
105-# The Jinja2 tests fail on Python 3.2 due to the following:
106-# The line in compressor/management/commands/compress.py:
107-# compressor_nodes.setdefault(template, []).extend(nodes)
108-# causes the error "unhashable type: 'Template'"
109-_TEST_JINJA2 = not(sys.version_info[0] == 3 and sys.version_info[1] == 2)
110-
111-
112-class OfflineTestCaseMixin(object):
113- template_name = "test_compressor_offline.html"
114- verbosity = 0
115- # Change this for each test class
116- templates_dir = ""
117- expected_hash = ""
118- # Engines to test
119- if _TEST_JINJA2:
120- engines = ("django", "jinja2")
121- else:
122- engines = ("django",)
123-
124- def setUp(self):
125- self._old_compress = settings.COMPRESS_ENABLED
126- self._old_compress_offline = settings.COMPRESS_OFFLINE
127- self._old_template_dirs = settings.TEMPLATE_DIRS
128- self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
129- self.log = StringIO()
130-
131- # Reset template dirs, because it enables us to force compress to
132- # consider only a specific directory (helps us make true,
133- # independant unit tests).
134- # Specify both Jinja2 and Django template locations. When the wrong engine
135- # is used to parse a template, the TemplateSyntaxError will cause the
136- # template to be skipped over.
137- django_template_dir = os.path.join(settings.TEST_DIR, 'test_templates', self.templates_dir)
138- jinja2_template_dir = os.path.join(settings.TEST_DIR, 'test_templates_jinja2', self.templates_dir)
139- settings.TEMPLATE_DIRS = (django_template_dir, jinja2_template_dir)
140-
141- # Enable offline compress
142- settings.COMPRESS_ENABLED = True
143- settings.COMPRESS_OFFLINE = True
144-
145- if "django" in self.engines:
146- self.template_path = os.path.join(django_template_dir, self.template_name)
147-
148- with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:
149- self.template = Template(file.read())
150-
151- self._old_jinja2_get_environment = settings.COMPRESS_JINJA2_GET_ENVIRONMENT
152-
153- if "jinja2" in self.engines:
154- # Setup Jinja2 settings.
155- settings.COMPRESS_JINJA2_GET_ENVIRONMENT = lambda: self._get_jinja2_env()
156- jinja2_env = settings.COMPRESS_JINJA2_GET_ENVIRONMENT()
157- self.template_path_jinja2 = os.path.join(jinja2_template_dir, self.template_name)
158-
159- with io.open(self.template_path_jinja2, encoding=settings.FILE_CHARSET) as file:
160- self.template_jinja2 = jinja2_env.from_string(file.read())
161-
162- def tearDown(self):
163- settings.COMPRESS_JINJA2_GET_ENVIRONMENT = self._old_jinja2_get_environment
164- settings.COMPRESS_ENABLED = self._old_compress
165- settings.COMPRESS_OFFLINE = self._old_compress_offline
166- settings.TEMPLATE_DIRS = self._old_template_dirs
167- manifest_path = os.path.join('CACHE', 'manifest.json')
168- if default_storage.exists(manifest_path):
169- default_storage.delete(manifest_path)
170-
171- def _render_template(self, engine):
172- if engine == "django":
173- return self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
174- elif engine == "jinja2":
175- return self.template_jinja2.render(settings.COMPRESS_OFFLINE_CONTEXT) + "\n"
176- else:
177- return None
178-
179- def _test_offline(self, engine):
180- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
181- self.assertEqual(1, count)
182- self.assertEqual([
183- '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
184- ], result)
185- rendered_template = self._render_template(engine)
186- self.assertEqual(rendered_template, "".join(result) + "\n")
187-
188- def test_offline(self):
189- for engine in self.engines:
190- self._test_offline(engine=engine)
191-
192- def _get_jinja2_env(self):
193- import jinja2
194- import jinja2.ext
195- from compressor.offline.jinja2 import url_for, SpacelessExtension
196- from compressor.contrib.jinja2ext import CompressorExtension
197-
198- # Extensions needed for the test cases only.
199- extensions = [
200- CompressorExtension,
201- SpacelessExtension,
202- jinja2.ext.with_,
203- jinja2.ext.do,
204- ]
205- loader = self._get_jinja2_loader()
206- env = jinja2.Environment(extensions=extensions, loader=loader)
207- env.globals['url_for'] = url_for
208-
209- return env
210-
211- def _get_jinja2_loader(self):
212- import jinja2
213-
214- loader = jinja2.FileSystemLoader(settings.TEMPLATE_DIRS, encoding=settings.FILE_CHARSET)
215- return loader
216-
217-
218-class OfflineGenerationSkipDuplicatesTestCase(OfflineTestCaseMixin, TestCase):
219- templates_dir = "test_duplicate"
220-
221- # We don't need to test multiples engines here.
222- engines = ("django",)
223-
224- def _test_offline(self, engine):
225- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
226- # Only one block compressed, the second identical one was skipped.
227- self.assertEqual(1, count)
228- # Only 1 <script> block in returned result as well.
229- self.assertEqual([
230- '<script type="text/javascript" src="/static/CACHE/js/f5e179b8eca4.js"></script>',
231- ], result)
232- rendered_template = self._render_template(engine)
233- # But rendering the template returns both (identical) scripts.
234- self.assertEqual(rendered_template, "".join(result * 2) + "\n")
235-
236-
237-class OfflineGenerationBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
238- templates_dir = "test_block_super"
239- expected_hash = "7c02d201f69d"
240- # Block.super not supported for Jinja2 yet.
241- engines = ("django",)
242-
243-
244-class OfflineGenerationBlockSuperMultipleTestCase(OfflineTestCaseMixin, TestCase):
245- templates_dir = "test_block_super_multiple"
246- expected_hash = "f8891c416981"
247- # Block.super not supported for Jinja2 yet.
248- engines = ("django",)
249-
250-
251-class OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase(OfflineTestCaseMixin, TestCase):
252- templates_dir = "test_block_super_multiple_cached"
253- expected_hash = "2f6ef61c488e"
254- # Block.super not supported for Jinja2 yet.
255- engines = ("django",)
256-
257- def setUp(self):
258- self._old_template_loaders = settings.TEMPLATE_LOADERS
259- settings.TEMPLATE_LOADERS = (
260- ('django.template.loaders.cached.Loader', (
261- 'django.template.loaders.filesystem.Loader',
262- 'django.template.loaders.app_directories.Loader',
263- )),
264- )
265- super(OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase, self).setUp()
266-
267- def tearDown(self):
268- super(OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase, self).tearDown()
269- settings.TEMPLATE_LOADERS = self._old_template_loaders
270-
271-
272-class OfflineGenerationBlockSuperTestCaseWithExtraContent(OfflineTestCaseMixin, TestCase):
273- templates_dir = "test_block_super_extra"
274- # Block.super not supported for Jinja2 yet.
275- engines = ("django",)
276-
277- def _test_offline(self, engine):
278- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
279- self.assertEqual(2, count)
280- self.assertEqual([
281- '<script type="text/javascript" src="/static/CACHE/js/ced14aec5856.js"></script>',
282- '<script type="text/javascript" src="/static/CACHE/js/7c02d201f69d.js"></script>'
283- ], result)
284- rendered_template = self._render_template(engine)
285- self.assertEqual(rendered_template, "".join(result) + "\n")
286-
287-
288-class OfflineGenerationConditionTestCase(OfflineTestCaseMixin, TestCase):
289- templates_dir = "test_condition"
290- expected_hash = "4e3758d50224"
291-
292- def setUp(self):
293- self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
294- settings.COMPRESS_OFFLINE_CONTEXT = {
295- 'condition': 'red',
296- }
297- super(OfflineGenerationConditionTestCase, self).setUp()
298-
299- def tearDown(self):
300- self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
301- super(OfflineGenerationConditionTestCase, self).tearDown()
302-
303-
304-class OfflineGenerationTemplateTagTestCase(OfflineTestCaseMixin, TestCase):
305- templates_dir = "test_templatetag"
306- expected_hash = "a27e1d3a619a"
307-
308-
309-class OfflineGenerationStaticTemplateTagTestCase(OfflineTestCaseMixin, TestCase):
310- templates_dir = "test_static_templatetag"
311- expected_hash = "dfa2bb387fa8"
312-
313-
314-class OfflineGenerationTestCaseWithContext(OfflineTestCaseMixin, TestCase):
315- templates_dir = "test_with_context"
316- expected_hash = "5838e2fd66af"
317-
318- def setUp(self):
319- self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
320- settings.COMPRESS_OFFLINE_CONTEXT = {
321- 'content': 'OK!',
322- }
323- super(OfflineGenerationTestCaseWithContext, self).setUp()
324-
325- def tearDown(self):
326- settings.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
327- super(OfflineGenerationTestCaseWithContext, self).tearDown()
328-
329-
330-class OfflineGenerationTestCaseErrors(OfflineTestCaseMixin, TestCase):
331- templates_dir = "test_error_handling"
332-
333- def _test_offline(self, engine):
334- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
335-
336- if engine == "django":
337- self.assertEqual(2, count)
338- else:
339- # Because we use env.parse in Jinja2Parser, the engine does not
340- # actually load the "extends" and "includes" templates, and so
341- # it is unable to detect that they are missing. So all the "compress"
342- # nodes are processed correctly.
343- self.assertEqual(4, count)
344- self.assertEqual(engine, "jinja2")
345- self.assertIn('<link rel="stylesheet" href="/static/CACHE/css/78bd7a762e2d.css" type="text/css" />', result)
346- self.assertIn('<link rel="stylesheet" href="/static/CACHE/css/e31030430724.css" type="text/css" />', result)
347-
348- self.assertIn('<script type="text/javascript" src="/static/CACHE/js/3872c9ae3f42.js"></script>', result)
349- self.assertIn('<script type="text/javascript" src="/static/CACHE/js/cd8870829421.js"></script>', result)
350-
351-
352-class OfflineGenerationTestCaseWithError(OfflineTestCaseMixin, TestCase):
353- templates_dir = 'test_error_handling'
354-
355- def setUp(self):
356- self._old_compress_precompilers = settings.COMPRESS_PRECOMPILERS
357- settings.COMPRESS_PRECOMPILERS = (('text/coffeescript', 'non-existing-binary'),)
358- super(OfflineGenerationTestCaseWithError, self).setUp()
359-
360- def _test_offline(self, engine):
361- """
362- Test that a CommandError is raised with DEBUG being False as well as
363- True, as otherwise errors in configuration will never show in
364- production.
365- """
366- self._old_debug = settings.DEBUG
367-
368- try:
369- settings.DEBUG = True
370- self.assertRaises(CommandError, CompressCommand().compress, engine=engine)
371-
372- settings.DEBUG = False
373- self.assertRaises(CommandError, CompressCommand().compress, engine=engine)
374-
375- finally:
376- settings.DEBUG = self._old_debug
377-
378- def tearDown(self):
379- settings.COMPRESS_PRECOMPILERS = self._old_compress_precompilers
380- super(OfflineGenerationTestCaseWithError, self).tearDown()
381-
382-
383-class OfflineGenerationTestCase(OfflineTestCaseMixin, TestCase):
384- templates_dir = "basic"
385- expected_hash = "f5e179b8eca4"
386-
387- def test_rendering_without_manifest_raises_exception(self):
388- # flush cached manifest
389- flush_offline_manifest()
390- self.assertRaises(OfflineGenerationError,
391- self.template.render, Context({}))
392-
393- @unittest.skipIf(not _TEST_JINJA2, "No Jinja2 testing")
394- def test_rendering_without_manifest_raises_exception_jinja2(self):
395- # flush cached manifest
396- flush_offline_manifest()
397- self.assertRaises(OfflineGenerationError,
398- self.template_jinja2.render, {})
399-
400- def _test_deleting_manifest_does_not_affect_rendering(self, engine):
401- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
402- get_offline_manifest()
403- manifest_path = os.path.join('CACHE', 'manifest.json')
404- if default_storage.exists(manifest_path):
405- default_storage.delete(manifest_path)
406- self.assertEqual(1, count)
407- self.assertEqual([
408- '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
409- ], result)
410- rendered_template = self._render_template(engine)
411- self.assertEqual(rendered_template, "".join(result) + "\n")
412-
413- def test_deleting_manifest_does_not_affect_rendering(self):
414- for engine in self.engines:
415- self._test_deleting_manifest_does_not_affect_rendering(engine)
416-
417- def test_requires_model_validation(self):
418- self.assertFalse(CompressCommand.requires_model_validation)
419-
420- def test_get_loaders(self):
421- old_loaders = settings.TEMPLATE_LOADERS
422- settings.TEMPLATE_LOADERS = (
423- ('django.template.loaders.cached.Loader', (
424- 'django.template.loaders.filesystem.Loader',
425- 'django.template.loaders.app_directories.Loader',
426- )),
427- )
428- try:
429- from django.template.loaders.filesystem import Loader as FileSystemLoader
430- from django.template.loaders.app_directories import Loader as AppDirectoriesLoader
431- except ImportError:
432- pass
433- else:
434- loaders = CompressCommand().get_loaders()
435- self.assertTrue(isinstance(loaders[0], FileSystemLoader))
436- self.assertTrue(isinstance(loaders[1], AppDirectoriesLoader))
437- finally:
438- settings.TEMPLATE_LOADERS = old_loaders
439-
440-
441-class OfflineGenerationBlockSuperBaseCompressed(OfflineTestCaseMixin, TestCase):
442- template_names = ["base.html", "base2.html", "test_compressor_offline.html"]
443- templates_dir = 'test_block_super_base_compressed'
444- expected_hash = ['028c3fc42232', '2e9d3f5545a6', 'f8891c416981']
445- # Block.super not supported for Jinja2 yet.
446- engines = ("django",)
447-
448- def setUp(self):
449- super(OfflineGenerationBlockSuperBaseCompressed, self).setUp()
450-
451- self.template_paths = []
452- self.templates = []
453- for template_name in self.template_names:
454- template_path = os.path.join(settings.TEMPLATE_DIRS[0], template_name)
455- self.template_paths.append(template_path)
456- with io.open(template_path, encoding=settings.FILE_CHARSET) as file:
457- template = Template(file.read())
458- self.templates.append(template)
459-
460- def _render_template(self, template, engine):
461- if engine == "django":
462- return template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
463- elif engine == "jinja2":
464- return template.render(settings.COMPRESS_OFFLINE_CONTEXT) + "\n"
465- else:
466- return None
467-
468- def _test_offline(self, engine):
469- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
470- self.assertEqual(len(self.expected_hash), count)
471- for expected_hash, template in zip(self.expected_hash, self.templates):
472- expected_output = '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (expected_hash, )
473- self.assertIn(expected_output, result)
474- rendered_template = self._render_template(template, engine)
475- self.assertEqual(rendered_template, expected_output + '\n')
476-
477-
478-class OfflineGenerationInlineNonAsciiTestCase(OfflineTestCaseMixin, TestCase):
479- templates_dir = "test_inline_non_ascii"
480-
481- def setUp(self):
482- self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
483- settings.COMPRESS_OFFLINE_CONTEXT = {
484- 'test_non_ascii_value': '\u2014',
485- }
486- super(OfflineGenerationInlineNonAsciiTestCase, self).setUp()
487-
488- def tearDown(self):
489- self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
490- super(OfflineGenerationInlineNonAsciiTestCase, self).tearDown()
491-
492- def _test_offline(self, engine):
493- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
494- rendered_template = self._render_template(engine)
495- self.assertEqual(rendered_template, "".join(result) + "\n")
496-
497-
498-class OfflineGenerationComplexTestCase(OfflineTestCaseMixin, TestCase):
499- templates_dir = "test_complex"
500-
501- def setUp(self):
502- self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
503- settings.COMPRESS_OFFLINE_CONTEXT = {
504- 'condition': 'OK!',
505- # Django templating does not allow definition of tuples in the
506- # templates. Make sure this is same as test_templates_jinja2/test_complex.
507- 'my_names': ("js/one.js", "js/nonasc.js"),
508- }
509- super(OfflineGenerationComplexTestCase, self).setUp()
510-
511- def tearDown(self):
512- self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
513- super(OfflineGenerationComplexTestCase, self).tearDown()
514-
515- def _test_offline(self, engine):
516- count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
517- self.assertEqual(3, count)
518- self.assertEqual([
519- '<script type="text/javascript" src="/static/CACHE/js/0e8807bebcee.js"></script>',
520- '<script type="text/javascript" src="/static/CACHE/js/eed1d222933e.js"></script>',
521- '<script type="text/javascript" src="/static/CACHE/js/00b4baffe335.js"></script>',
522- ], result)
523- rendered_template = self._render_template(engine)
524- result = (result[0], result[2])
525- self.assertEqual(rendered_template, "".join(result) + "\n")
526-
527-
528-# Coffin does not work on Python 3.2+ due to:
529-# The line at coffin/template/__init__.py:15
530-# from library import *
531-# causing 'ImportError: No module named library'.
532-# It seems there is no evidence nor indicated support for Python 3+.
533-@unittest.skipIf(sys.version_info >= (3, 2),
534- "Coffin does not support 3.2+")
535-class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):
536- templates_dir = "test_coffin"
537- expected_hash = "32c8281e3346"
538- engines = ("jinja2",)
539-
540- def _get_jinja2_env(self):
541- import jinja2
542- from coffin.common import env
543- from compressor.contrib.jinja2ext import CompressorExtension
544-
545- # Could have used the env.add_extension method, but it's only available
546- # in Jinja2 v2.5
547- new_env = jinja2.Environment(extensions=[CompressorExtension])
548- env.extensions.update(new_env.extensions)
549-
550- return env
551-
552-
553-# Jingo does not work when using Python 3.2 due to the use of Unicode string
554-# prefix (and possibly other stuff), but it actually works when using Python 3.3
555-# since it tolerates the use of the Unicode string prefix. Python 3.3 support
556-# is also evident in its tox.ini file.
557-@unittest.skipIf(sys.version_info >= (3, 2) and sys.version_info < (3, 3),
558- "Jingo does not support 3.2")
559-class OfflineGenerationJingoTestCase(OfflineTestCaseMixin, TestCase):
560- templates_dir = "test_jingo"
561- expected_hash = "61ec584468eb"
562- engines = ("jinja2",)
563-
564- def _get_jinja2_env(self):
565- import jinja2
566- import jinja2.ext
567- from jingo import env
568- from compressor.contrib.jinja2ext import CompressorExtension
569- from compressor.offline.jinja2 import SpacelessExtension, url_for
570-
571- # Could have used the env.add_extension method, but it's only available
572- # in Jinja2 v2.5
573- new_env = jinja2.Environment(extensions=[CompressorExtension, SpacelessExtension, jinja2.ext.with_])
574- env.extensions.update(new_env.extensions)
575- env.globals['url_for'] = url_for
576-
577- return env
578
579=== removed directory '.pc/fix-test_settings.py-for-django-1.7.patch'
580=== removed directory '.pc/fix-test_settings.py-for-django-1.7.patch/compressor'
581=== removed file '.pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py'
582--- .pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py 2014-09-08 17:31:54 +0000
583+++ .pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py 1970-01-01 00:00:00 +0000
584@@ -1,40 +0,0 @@
585-import os
586-import django
587-
588-TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
589-
590-COMPRESS_CACHE_BACKEND = 'locmem://'
591-
592-DATABASES = {
593- 'default': {
594- 'ENGINE': 'django.db.backends.sqlite3',
595- 'NAME': ':memory:',
596- }
597-}
598-
599-INSTALLED_APPS = [
600- 'compressor',
601- 'coffin',
602- 'jingo',
603-]
604-
605-STATIC_URL = '/static/'
606-
607-
608-STATIC_ROOT = os.path.join(TEST_DIR, 'static')
609-
610-TEMPLATE_DIRS = (
611- # Specifically choose a name that will not be considered
612- # by app_directories loader, to make sure each test uses
613- # a specific template without considering the others.
614- os.path.join(TEST_DIR, 'test_templates'),
615-)
616-
617-if django.VERSION[:2] < (1, 6):
618- TEST_RUNNER = 'discover_runner.DiscoverRunner'
619-
620-SECRET_KEY = "iufoj=mibkpdz*%bob952x(%49rqgv8gg45k36kjcg76&-y5=!"
621-
622-PASSWORD_HASHERS = (
623- 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
624-)
625
626=== modified file '.travis.yml'
627--- .travis.yml 2014-06-26 15:08:13 +0000
628+++ .travis.yml 2015-08-20 18:49:21 +0000
629@@ -3,20 +3,25 @@
630 - sudo apt-get update
631 - sudo apt-get install csstidy libxml2-dev libxslt-dev
632 install:
633- - pip install tox coveralls
634+ - pip install tox
635 script:
636 - tox
637 env:
638- - TOXENV=py33-1.6.X
639- - TOXENV=py32-1.6.X
640- - TOXENV=py27-1.6.X
641- - TOXENV=py26-1.6.X
642- - TOXENV=py33-1.5.X
643- - TOXENV=py32-1.5.X
644- - TOXENV=py27-1.5.X
645+ - TOXENV=py26-1.4.X
646 - TOXENV=py26-1.5.X
647 - TOXENV=py27-1.4.X
648- - TOXENV=py26-1.4.X
649+ - TOXENV=py27-1.5.X
650+ - TOXENV=py26-1.6.X
651+ - TOXENV=py27-1.6.X
652+ - TOXENV=py32-1.6.X
653+ - TOXENV=py33-1.6.X
654+ - TOXENV=py27-1.7.X
655+ - TOXENV=py32-1.7.X
656+ - TOXENV=py33-1.7.X
657+ - TOXENV=py34-1.7.X
658+ - TOXENV=py27-1.8.X
659+ - TOXENV=py32-1.8.X
660+ - TOXENV=py33-1.8.X
661+ - TOXENV=py34-1.8.X
662 notifications:
663 irc: "irc.freenode.org#django-compressor"
664-after_success: coveralls
665
666=== modified file 'AUTHORS'
667--- AUTHORS 2015-01-06 14:31:34 +0000
668+++ AUTHORS 2015-08-20 18:49:21 +0000
669@@ -29,6 +29,7 @@
670 Boris Shemigon
671 Brad Whittington
672 Bruno Renié
673+Carlton Gibson
674 Cassus Adam Banko
675 Chris Adams
676 Chris Streeter
677
678=== modified file 'README.rst'
679--- README.rst 2015-01-06 14:31:34 +0000
680+++ README.rst 2015-08-20 18:49:21 +0000
681@@ -4,16 +4,19 @@
682 .. image:: https://coveralls.io/repos/django-compressor/django-compressor/badge.png?branch=develop
683 :target: https://coveralls.io/r/django-compressor/django-compressor?branch=develop
684
685-.. image:: https://pypip.in/v/django_compressor/badge.png
686- :target: https://pypi.python.org/pypi/django_compressor
687-
688-.. image:: https://pypip.in/d/django_compressor/badge.png
689- :target: https://pypi.python.org/pypi/django_compressor
690-
691-.. image:: https://secure.travis-ci.org/django-compressor/django-compressor.png?branch=develop
692+.. image:: https://pypip.in/v/django_compressor/badge.svg
693+ :target: https://pypi.python.org/pypi/django_compressor
694+
695+.. image:: https://pypip.in/d/django_compressor/badge.svg
696+ :target: https://pypi.python.org/pypi/django_compressor
697+
698+.. image:: https://secure.travis-ci.org/django-compressor/django-compressor.svg?branch=develop
699 :alt: Build Status
700 :target: http://travis-ci.org/django-compressor/django-compressor
701
702+.. image:: https://caniusepython3.com/project/django_compressor.svg
703+ :target: https://caniusepython3.com/project/django_compressor
704+
705 Django Compressor combines and compresses linked and inline Javascript
706 or CSS in a Django template into cacheable static files by using the
707 ``compress`` template tag.
708
709=== modified file 'compressor/__init__.py'
710--- compressor/__init__.py 2015-01-06 14:31:34 +0000
711+++ compressor/__init__.py 2015-08-20 18:49:21 +0000
712@@ -1,2 +1,2 @@
713 # following PEP 386
714-__version__ = "1.4a1"
715+__version__ = "1.5"
716
717=== modified file 'compressor/base.py'
718--- compressor/base.py 2015-01-06 14:31:34 +0000
719+++ compressor/base.py 2015-08-20 18:49:21 +0000
720@@ -5,7 +5,10 @@
721 from django.core.files.base import ContentFile
722 from django.template import Context
723 from django.template.loader import render_to_string
724-from django.utils.importlib import import_module
725+try:
726+ from importlib import import_module
727+except:
728+ from django.utils.importlib import import_module
729 from django.utils.safestring import mark_safe
730
731 try:
732@@ -140,6 +143,9 @@
733 """
734 Reads file contents using given `charset` and returns it as text.
735 """
736+ if charset == 'utf-8':
737+ # Removes BOM
738+ charset = 'utf-8-sig'
739 with codecs.open(filename, 'r', charset) as fd:
740 try:
741 return fd.read()
742@@ -247,7 +253,7 @@
743 mod_name, cls_name = get_mod_func(filter_or_command)
744 try:
745 mod = import_module(mod_name)
746- except ImportError:
747+ except (ImportError, TypeError):
748 filter = CompilerFilter(
749 content, filter_type=self.type, filename=filename,
750 charset=charset, command=filter_or_command)
751
752=== modified file 'compressor/cache.py'
753--- compressor/cache.py 2015-01-06 14:31:34 +0000
754+++ compressor/cache.py 2015-08-20 18:49:21 +0000
755@@ -4,11 +4,21 @@
756 import socket
757 import time
758
759-from django.core.cache import get_cache
760+try:
761+ from django.core.cache import caches
762+ def get_cache(name):
763+ return caches[name]
764+except ImportError:
765+ from django.core.cache import get_cache
766+
767 from django.core.files.base import ContentFile
768 from django.utils.encoding import force_text, smart_bytes
769 from django.utils.functional import SimpleLazyObject
770-from django.utils.importlib import import_module
771+
772+try:
773+ from importlib import import_module
774+except:
775+ from django.utils.importlib import import_module
776
777 from compressor.conf import settings
778 from compressor.storage import default_storage
779@@ -39,7 +49,7 @@
780 mod_name, func_name = get_mod_func(
781 settings.COMPRESS_CACHE_KEY_FUNCTION)
782 _cachekey_func = getattr(import_module(mod_name), func_name)
783- except (AttributeError, ImportError) as e:
784+ except (AttributeError, ImportError, TypeError) as e:
785 raise ImportError("Couldn't import cache key function %s: %s" %
786 (settings.COMPRESS_CACHE_KEY_FUNCTION, e))
787 return _cachekey_func(*args, **kwargs)
788
789=== modified file 'compressor/conf.py'
790--- compressor/conf.py 2015-01-06 14:31:34 +0000
791+++ compressor/conf.py 2015-08-20 18:49:21 +0000
792@@ -45,6 +45,8 @@
793 YUGLIFY_BINARY = 'yuglify'
794 YUGLIFY_CSS_ARGUMENTS = '--terminal'
795 YUGLIFY_JS_ARGUMENTS = '--terminal'
796+ CLEAN_CSS_BINARY = 'cleancss'
797+ CLEAN_CSS_ARGUMENTS = ''
798 DATA_URI_MAX_SIZE = 1024
799
800 # the cache backend to use
801
802=== modified file 'compressor/css.py'
803--- compressor/css.py 2014-06-26 15:08:13 +0000
804+++ compressor/css.py 2015-08-20 18:49:21 +0000
805@@ -34,7 +34,7 @@
806 self.media_nodes[-1][1].split_content.append(data)
807 else:
808 node = self.__class__(content=self.parser.elem_str(elem),
809- context=self.context)
810+ context=self.context)
811 node.split_content.append(data)
812 self.media_nodes.append((media, node))
813 return self.split_content
814
815=== modified file 'compressor/filters/base.py'
816--- compressor/filters/base.py 2015-01-06 14:31:34 +0000
817+++ compressor/filters/base.py 2015-08-20 18:49:21 +0000
818@@ -3,9 +3,27 @@
819 import logging
820 import subprocess
821
822+from platform import system
823+
824+if system() != "Windows":
825+ try:
826+ from shlex import quote as shell_quote # Python 3
827+ except ImportError:
828+ from pipes import quote as shell_quote # Python 2
829+else:
830+ from subprocess import list2cmdline
831+ def shell_quote(s):
832+ # shlex.quote/pipes.quote is not compatible with Windows
833+ return list2cmdline([s])
834+
835 from django.core.exceptions import ImproperlyConfigured
836 from django.core.files.temp import NamedTemporaryFile
837-from django.utils.importlib import import_module
838+
839+try:
840+ from importlib import import_module
841+except ImportError:
842+ from django.utils.importlib import import_module
843+
844 from django.utils.encoding import smart_text
845 from django.utils import six
846
847@@ -26,7 +44,7 @@
848 """
849 def __init__(self, content, filter_type=None, filename=None, verbose=0,
850 charset=None):
851- self.type = filter_type
852+ self.type = filter_type or getattr(self, 'type', None)
853 self.content = content
854 self.verbose = verbose or settings.COMPRESS_VERBOSE
855 self.logger = logger
856@@ -65,7 +83,7 @@
857 try:
858 mod_name, func_name = get_mod_func(self.callback)
859 func = getattr(import_module(mod_name), func_name)
860- except ImportError:
861+ except (ImportError, TypeError):
862 if self.dependencies:
863 if len(self.dependencies) == 1:
864 warning = "dependency (%s) is" % self.dependencies[0]
865@@ -147,6 +165,12 @@
866 self.outfile = NamedTemporaryFile(mode='r+', suffix=ext)
867 options["outfile"] = self.outfile.name
868
869+ # Quote infile and outfile for spaces etc.
870+ if "infile" in options:
871+ options["infile"] = shell_quote(options["infile"])
872+ if "outfile" in options:
873+ options["outfile"] = shell_quote(options["outfile"])
874+
875 try:
876 command = self.command.format(**options)
877 proc = subprocess.Popen(
878
879=== added file 'compressor/filters/cleancss.py'
880--- compressor/filters/cleancss.py 1970-01-01 00:00:00 +0000
881+++ compressor/filters/cleancss.py 2015-08-20 18:49:21 +0000
882@@ -0,0 +1,10 @@
883+from compressor.conf import settings
884+from compressor.filters import CompilerFilter
885+
886+
887+class CleanCSSFilter(CompilerFilter):
888+ command = "{binary} {args} -o {outfile} {infile}"
889+ options = (
890+ ("binary", settings.COMPRESS_CLEAN_CSS_BINARY),
891+ ("args", settings.COMPRESS_CLEAN_CSS_ARGUMENTS),
892+ )
893
894=== modified file 'compressor/filters/css_default.py'
895--- compressor/filters/css_default.py 2015-01-06 14:31:34 +0000
896+++ compressor/filters/css_default.py 2015-08-20 18:49:21 +0000
897@@ -5,7 +5,6 @@
898 from compressor.cache import get_hashed_mtime, get_hashed_content
899 from compressor.conf import settings
900 from compressor.filters import FilterBase, FilterError
901-from compressor.utils import staticfiles
902
903 URL_PATTERN = re.compile(r'url\(([^\)]+)\)')
904 SRC_PATTERN = re.compile(r'src=([\'"])(.+?)\1')
905@@ -22,10 +21,7 @@
906 self.has_scheme = False
907
908 def input(self, filename=None, basename=None, **kwargs):
909- if filename is not None:
910- filename = os.path.normcase(os.path.abspath(filename))
911- if (not (filename and filename.startswith(self.root)) and
912- not self.find(basename)):
913+ if not filename:
914 return self.content
915 self.path = basename.replace(os.sep, '/')
916 self.path = self.path.lstrip('/')
917@@ -40,10 +36,6 @@
918 return SRC_PATTERN.sub(self.src_converter,
919 URL_PATTERN.sub(self.url_converter, self.content))
920
921- def find(self, basename):
922- if settings.DEBUG and basename and staticfiles.finders:
923- return staticfiles.finders.find(basename)
924-
925 def guess_filename(self, url):
926 local_path = url
927 if self.has_scheme:
928@@ -70,6 +62,8 @@
929 suffix = get_hashed_mtime(filename)
930 elif settings.COMPRESS_CSS_HASHING_METHOD in ("hash", "content"):
931 suffix = get_hashed_content(filename)
932+ elif settings.COMPRESS_CSS_HASHING_METHOD is None:
933+ suffix = None
934 else:
935 raise FilterError('COMPRESS_CSS_HASHING_METHOD is configured '
936 'with an unknown method (%s).' %
937
938=== modified file 'compressor/js.py'
939--- compressor/js.py 2012-10-14 10:51:47 +0000
940+++ compressor/js.py 2015-08-20 18:49:21 +0000
941@@ -12,14 +12,42 @@
942 def split_contents(self):
943 if self.split_content:
944 return self.split_content
945+ self.extra_nodes = []
946 for elem in self.parser.js_elems():
947 attribs = self.parser.elem_attribs(elem)
948 if 'src' in attribs:
949 basename = self.get_basename(attribs['src'])
950 filename = self.get_filename(basename)
951 content = (SOURCE_FILE, filename, basename, elem)
952- self.split_content.append(content)
953- else:
954- content = self.parser.elem_content(elem)
955- self.split_content.append((SOURCE_HUNK, content, None, elem))
956+ else:
957+ content = (SOURCE_HUNK, self.parser.elem_content(elem), None, elem)
958+ self.split_content.append(content)
959+ if 'async' in attribs:
960+ extra = ' async'
961+ elif 'defer' in attribs:
962+ extra = ' defer'
963+ else:
964+ extra = ''
965+ # Append to the previous node if it had the same attribute
966+ append_to_previous = (self.extra_nodes and
967+ self.extra_nodes[-1][0] == extra)
968+ if append_to_previous and settings.COMPRESS_ENABLED:
969+ self.extra_nodes[-1][1].split_content.append(content)
970+ else:
971+ node = self.__class__(content=self.parser.elem_str(elem),
972+ context=self.context)
973+ node.split_content.append(content)
974+ self.extra_nodes.append((extra, node))
975 return self.split_content
976+
977+ def output(self, *args, **kwargs):
978+ if (settings.COMPRESS_ENABLED or settings.COMPRESS_PRECOMPILERS or
979+ kwargs.get('forced', False)):
980+ self.split_contents()
981+ if hasattr(self, 'extra_nodes'):
982+ ret = []
983+ for extra, subnode in self.extra_nodes:
984+ subnode.extra_context.update({'extra': extra})
985+ ret.append(subnode.output(*args, **kwargs))
986+ return '\n'.join(ret)
987+ return super(JsCompressor, self).output(*args, **kwargs)
988
989=== modified file 'compressor/management/commands/compress.py'
990--- compressor/management/commands/compress.py 2015-01-06 14:31:34 +0000
991+++ compressor/management/commands/compress.py 2015-08-20 18:49:21 +0000
992@@ -5,6 +5,7 @@
993 from fnmatch import fnmatch
994 from optparse import make_option
995
996+import django
997 from django.core.management.base import NoArgsCommand, CommandError
998 import django.template
999 from django.template import Context
1000@@ -53,24 +54,30 @@
1001 requires_model_validation = False
1002
1003 def get_loaders(self):
1004- from django.template.loader import template_source_loaders
1005- if template_source_loaders is None:
1006- try:
1007- from django.template.loader import (
1008- find_template as finder_func)
1009- except ImportError:
1010- from django.template.loader import (
1011- find_template_source as finder_func) # noqa
1012- try:
1013- # Force django to calculate template_source_loaders from
1014- # TEMPLATE_LOADERS settings, by asking to find a dummy template
1015- source, name = finder_func('test')
1016- except django.template.TemplateDoesNotExist:
1017- pass
1018- # Reload template_source_loaders now that it has been calculated ;
1019- # it should contain the list of valid, instanciated template loaders
1020- # to use.
1021+ if django.VERSION < (1, 8):
1022 from django.template.loader import template_source_loaders
1023+ if template_source_loaders is None:
1024+ try:
1025+ from django.template.loader import (
1026+ find_template as finder_func)
1027+ except ImportError:
1028+ from django.template.loader import (
1029+ find_template_source as finder_func) # noqa
1030+ try:
1031+ # Force django to calculate template_source_loaders from
1032+ # TEMPLATE_LOADERS settings, by asking to find a dummy template
1033+ source, name = finder_func('test')
1034+ except django.template.TemplateDoesNotExist:
1035+ pass
1036+ # Reload template_source_loaders now that it has been calculated ;
1037+ # it should contain the list of valid, instanciated template loaders
1038+ # to use.
1039+ from django.template.loader import template_source_loaders
1040+ else:
1041+ from django.template import engines
1042+ template_source_loaders = []
1043+ for e in engines.all():
1044+ template_source_loaders.extend(e.engine.get_template_loaders(e.engine.loaders))
1045 loaders = []
1046 # If template loader is CachedTemplateLoader, return the loaders
1047 # that it wraps around. So if we have
1048@@ -130,7 +137,7 @@
1049 if get_template_sources is None:
1050 get_template_sources = loader.get_template_sources
1051 paths.update(list(get_template_sources('')))
1052- except (ImportError, AttributeError):
1053+ except (ImportError, AttributeError, TypeError):
1054 # Yeah, this didn't work out so well, let's move on
1055 pass
1056 if not paths:
1057
1058=== modified file 'compressor/offline/django.py'
1059--- compressor/offline/django.py 2014-06-26 15:08:13 +0000
1060+++ compressor/offline/django.py 2015-08-20 18:49:21 +0000
1061@@ -1,13 +1,13 @@
1062 from __future__ import absolute_import
1063-import io
1064 from copy import copy
1065
1066+import django
1067 from django import template
1068 from django.conf import settings
1069-from django.template import Template
1070 from django.template import Context
1071 from django.template.base import Node, VariableNode, TextNode, NodeList
1072 from django.template.defaulttags import IfNode
1073+from django.template.loader import get_template
1074 from django.template.loader_tags import ExtendsNode, BlockNode, BlockContext
1075
1076
1077@@ -15,7 +15,7 @@
1078 from compressor.templatetags.compress import CompressorNode
1079
1080
1081-def handle_extendsnode(extendsnode, block_context=None):
1082+def handle_extendsnode(extendsnode, block_context=None, original=None):
1083 """Create a copy of Node tree of a derived template replacing
1084 all blocks tags with the nodes of appropriate blocks.
1085 Also handles {{ block.super }} tags.
1086@@ -27,6 +27,9 @@
1087 block_context.add_blocks(blocks)
1088
1089 context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
1090+ if original is not None:
1091+ context.template = original
1092+
1093 compiled_parent = extendsnode.get_parent(context)
1094 parent_nodelist = compiled_parent.nodelist
1095 # If the parent template has an ExtendsNode it is not the root.
1096@@ -34,7 +37,7 @@
1097 # The ExtendsNode has to be the first non-text node.
1098 if not isinstance(node, TextNode):
1099 if isinstance(node, ExtendsNode):
1100- return handle_extendsnode(node, block_context)
1101+ return handle_extendsnode(node, block_context, original)
1102 break
1103 # Add blocks of the root template to block context.
1104 blocks = dict((n.name, n) for n in
1105@@ -55,6 +58,8 @@
1106 if not block_stack:
1107 continue
1108 node = block_context.get_block(block_stack[-1].name)
1109+ if not node:
1110+ continue
1111 if isinstance(node, BlockNode):
1112 expanded_block = expand_blocknode(node, block_stack, block_context)
1113 new_nodelist.extend(expanded_block)
1114@@ -93,13 +98,15 @@
1115 self.charset = charset
1116
1117 def parse(self, template_name):
1118- with io.open(template_name, mode='rb') as file:
1119- try:
1120- return Template(file.read().decode(self.charset))
1121- except template.TemplateSyntaxError as e:
1122- raise TemplateSyntaxError(str(e))
1123- except template.TemplateDoesNotExist as e:
1124- raise TemplateDoesNotExist(str(e))
1125+ try:
1126+ if django.VERSION < (1, 8):
1127+ return get_template(template_name)
1128+ else:
1129+ return get_template(template_name).template
1130+ except template.TemplateSyntaxError as e:
1131+ raise TemplateSyntaxError(str(e))
1132+ except template.TemplateDoesNotExist as e:
1133+ raise TemplateDoesNotExist(str(e))
1134
1135 def process_template(self, template, context):
1136 return True
1137@@ -111,15 +118,17 @@
1138 pass
1139
1140 def render_nodelist(self, template, context, node):
1141+ if django.VERSION >= (1, 8):
1142+ context.template = template
1143 return node.nodelist.render(context)
1144
1145 def render_node(self, template, context, node):
1146 return node.render(context, forced=True)
1147
1148- def get_nodelist(self, node):
1149+ def get_nodelist(self, node, original=None):
1150 if isinstance(node, ExtendsNode):
1151 try:
1152- return handle_extendsnode(node)
1153+ return handle_extendsnode(node, block_context=None, original=original)
1154 except template.TemplateSyntaxError as e:
1155 raise TemplateSyntaxError(str(e))
1156 except template.TemplateDoesNotExist as e:
1157@@ -134,10 +143,12 @@
1158 nodelist = getattr(node, 'nodelist', [])
1159 return nodelist
1160
1161- def walk_nodes(self, node):
1162- for node in self.get_nodelist(node):
1163+ def walk_nodes(self, node, original=None):
1164+ if django.VERSION >= (1, 8) and original is None:
1165+ original = node
1166+ for node in self.get_nodelist(node, original):
1167 if isinstance(node, CompressorNode) and node.is_offline_compression_enabled(forced=True):
1168 yield node
1169 else:
1170- for node in self.walk_nodes(node):
1171+ for node in self.walk_nodes(node, original):
1172 yield node
1173
1174=== modified file 'compressor/parser/__init__.py'
1175--- compressor/parser/__init__.py 2014-06-26 15:08:13 +0000
1176+++ compressor/parser/__init__.py 2015-08-20 18:49:21 +0000
1177@@ -1,6 +1,9 @@
1178 from django.utils import six
1179 from django.utils.functional import LazyObject
1180-from django.utils.importlib import import_module
1181+try:
1182+ from importlib import import_module
1183+except ImportError:
1184+ from django.utils.importlib import import_module
1185
1186 # support legacy parser module usage
1187 from compressor.parser.base import ParserBase # noqa
1188@@ -30,5 +33,5 @@
1189 import_module(dependency)
1190 self._wrapped = parser(content)
1191 break
1192- except ImportError:
1193+ except (ImportError, TypeError):
1194 continue
1195
1196=== modified file 'compressor/templates/compressor/js_file.html'
1197--- compressor/templates/compressor/js_file.html 2012-10-14 10:51:47 +0000
1198+++ compressor/templates/compressor/js_file.html 2015-08-20 18:49:21 +0000
1199@@ -1,1 +1,1 @@
1200-<script type="text/javascript" src="{{ compressed.url }}"></script>
1201\ No newline at end of file
1202+<script type="text/javascript" src="{{ compressed.url }}"{{ compressed.extra }}></script>
1203\ No newline at end of file
1204
1205=== modified file 'compressor/test_settings.py'
1206--- compressor/test_settings.py 2015-01-06 14:31:34 +0000
1207+++ compressor/test_settings.py 2015-08-20 18:49:21 +0000
1208@@ -3,7 +3,13 @@
1209
1210 TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
1211
1212-COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'
1213+
1214+CACHES = {
1215+ 'default': {
1216+ 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
1217+ 'LOCATION': 'unique-snowflake'
1218+ }
1219+}
1220
1221 DATABASES = {
1222 'default': {
1223@@ -13,8 +19,17 @@
1224 }
1225
1226 INSTALLED_APPS = [
1227+ 'django.contrib.staticfiles',
1228 'compressor',
1229- 'jingo',
1230+ 'coffin',
1231+]
1232+if django.VERSION < (1, 8):
1233+ INSTALLED_APPS.append('jingo')
1234+
1235+STATICFILES_FINDERS = [
1236+ 'django.contrib.staticfiles.finders.FileSystemFinder',
1237+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
1238+ 'compressor.finders.CompressorFinder',
1239 ]
1240
1241 STATIC_URL = '/static/'
1242@@ -37,3 +52,5 @@
1243 PASSWORD_HASHERS = (
1244 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
1245 )
1246+
1247+MIDDLEWARE_CLASSES = []
1248
1249=== modified file 'compressor/tests/precompiler.py'
1250--- compressor/tests/precompiler.py 2014-06-26 15:08:13 +0000
1251+++ compressor/tests/precompiler.py 2015-08-20 18:49:21 +0000
1252@@ -7,11 +7,11 @@
1253 def main():
1254 p = optparse.OptionParser()
1255 p.add_option('-f', '--file', action="store",
1256- type="string", dest="filename",
1257- help="File to read from, defaults to stdin", default=None)
1258+ type="string", dest="filename",
1259+ help="File to read from, defaults to stdin", default=None)
1260 p.add_option('-o', '--output', action="store",
1261- type="string", dest="outfile",
1262- help="File to write to, defaults to stdout", default=None)
1263+ type="string", dest="outfile",
1264+ help="File to write to, defaults to stdout", default=None)
1265
1266 options, arguments = p.parse_args()
1267
1268
1269=== added file 'compressor/tests/static/css/filename with spaces.css'
1270--- compressor/tests/static/css/filename with spaces.css 1970-01-01 00:00:00 +0000
1271+++ compressor/tests/static/css/filename with spaces.css 2015-08-20 18:49:21 +0000
1272@@ -0,0 +1,1 @@
1273+body { background:#424242; }
1274\ No newline at end of file
1275
1276=== added file 'compressor/tests/static/css/utf-8_with-BOM.css'
1277--- compressor/tests/static/css/utf-8_with-BOM.css 1970-01-01 00:00:00 +0000
1278+++ compressor/tests/static/css/utf-8_with-BOM.css 2015-08-20 18:49:21 +0000
1279@@ -0,0 +1,1 @@
1280+.compress-test {color: red;}
1281\ No newline at end of file
1282
1283=== added file 'compressor/tests/static/js/three.js'
1284--- compressor/tests/static/js/three.js 1970-01-01 00:00:00 +0000
1285+++ compressor/tests/static/js/three.js 2015-08-20 18:49:21 +0000
1286@@ -0,0 +1,1 @@
1287+hermanos = {}
1288\ No newline at end of file
1289
1290=== added file 'compressor/tests/static/js/two.js'
1291--- compressor/tests/static/js/two.js 1970-01-01 00:00:00 +0000
1292+++ compressor/tests/static/js/two.js 2015-08-20 18:49:21 +0000
1293@@ -0,0 +1,1 @@
1294+pollos = {}
1295\ No newline at end of file
1296
1297=== modified file 'compressor/tests/test_base.py'
1298--- compressor/tests/test_base.py 2015-01-06 14:31:34 +0000
1299+++ compressor/tests/test_base.py 2015-08-20 18:49:21 +0000
1300@@ -12,11 +12,13 @@
1301 from django.test import SimpleTestCase
1302 from django.test.utils import override_settings
1303
1304-from compressor.base import SOURCE_HUNK, SOURCE_FILE
1305+from compressor import cache as cachemod
1306+from compressor.base import SOURCE_FILE, SOURCE_HUNK
1307+from compressor.cache import get_cachekey
1308 from compressor.conf import settings
1309 from compressor.css import CssCompressor
1310+from compressor.exceptions import FilterDoesNotExist, FilterError
1311 from compressor.js import JsCompressor
1312-from compressor.exceptions import FilterDoesNotExist
1313
1314
1315 def make_soup(markup):
1316@@ -112,6 +114,14 @@
1317 hunks = '\n'.join([h for h in self.css_node.hunks()])
1318 self.assertEqual(out, hunks)
1319
1320+ def test_css_output_with_bom_input(self):
1321+ out = 'body { background:#990; }\n.compress-test {color: red;}'
1322+ css = ("""<link rel="stylesheet" href="/static/css/one.css" type="text/css" />
1323+ <link rel="stylesheet" href="/static/css/utf-8_with-BOM.css" type="text/css" />""")
1324+ css_node_with_bom = CssCompressor(css)
1325+ hunks = '\n'.join([h for h in css_node_with_bom.hunks()])
1326+ self.assertEqual(out, hunks)
1327+
1328 def test_css_mtimes(self):
1329 is_date = re.compile(r'^\d{10}[\.\d]+$')
1330 for date in self.css_node.mtimes:
1331@@ -208,6 +218,14 @@
1332 css_node = CssCompressor(css)
1333 self.assertRaises(FilterDoesNotExist, css_node.output, 'inline')
1334
1335+ @override_settings(COMPRESS_PRECOMPILERS=(
1336+ ('text/foobar', './foo -I ./bar/baz'),
1337+ ), COMPRESS_ENABLED=True)
1338+ def test_command_with_dot_precompiler(self):
1339+ css = '<style type="text/foobar">p { border:10px solid red;}</style>'
1340+ css_node = CssCompressor(css)
1341+ self.assertRaises(FilterError, css_node.output, 'inline')
1342+
1343
1344 class CssMediaTestCase(SimpleTestCase):
1345 def setUp(self):
1346@@ -267,4 +285,49 @@
1347
1348 def test_correct_backend(self):
1349 from compressor.cache import cache
1350- self.assertEqual(cache.__class__, locmem.CacheClass)
1351+ self.assertEqual(cache.__class__, locmem.LocMemCache)
1352+
1353+
1354+class JsAsyncDeferTestCase(SimpleTestCase):
1355+ def setUp(self):
1356+ self.js = """\
1357+ <script src="/static/js/one.js" type="text/javascript"></script>
1358+ <script src="/static/js/two.js" type="text/javascript" async></script>
1359+ <script src="/static/js/three.js" type="text/javascript" defer></script>
1360+ <script type="text/javascript">obj.value = "value";</script>
1361+ <script src="/static/js/one.js" type="text/javascript" async></script>
1362+ <script src="/static/js/two.js" type="text/javascript" async></script>
1363+ <script src="/static/js/three.js" type="text/javascript"></script>"""
1364+
1365+ def test_js_output(self):
1366+ def extract_attr(tag):
1367+ if tag.has_attr('async'):
1368+ return 'async'
1369+ if tag.has_attr('defer'):
1370+ return 'defer'
1371+ js_node = JsCompressor(self.js)
1372+ output = [None, 'async', 'defer', None, 'async', None]
1373+ if six.PY3:
1374+ scripts = make_soup(js_node.output()).find_all('script')
1375+ attrs = [extract_attr(i) for i in scripts]
1376+ else:
1377+ scripts = make_soup(js_node.output()).findAll('script')
1378+ attrs = [s.get('async') or s.get('defer') for s in scripts]
1379+ self.assertEqual(output, attrs)
1380+
1381+
1382+class CacheTestCase(SimpleTestCase):
1383+
1384+ def setUp(self):
1385+ cachemod._cachekey_func = None
1386+
1387+ def test_get_cachekey_basic(self):
1388+ self.assertEqual(get_cachekey("foo"), "django_compressor.foo")
1389+
1390+ @override_settings(COMPRESS_CACHE_KEY_FUNCTION='.leading.dot')
1391+ def test_get_cachekey_leading_dot(self):
1392+ self.assertRaises(ImportError, lambda: get_cachekey("foo"))
1393+
1394+ @override_settings(COMPRESS_CACHE_KEY_FUNCTION='invalid.module')
1395+ def test_get_cachekey_invalid_mod(self):
1396+ self.assertRaises(ImportError, lambda: get_cachekey("foo"))
1397
1398=== modified file 'compressor/tests/test_filters.py'
1399--- compressor/tests/test_filters.py 2015-01-06 14:31:34 +0000
1400+++ compressor/tests/test_filters.py 2015-08-20 18:49:21 +0000
1401@@ -1,4 +1,5 @@
1402 from __future__ import with_statement, unicode_literals
1403+from collections import defaultdict
1404 import io
1405 import os
1406 import sys
1407@@ -17,9 +18,18 @@
1408 from compressor.filters.cssmin import CSSMinFilter
1409 from compressor.filters.css_default import CssAbsoluteFilter
1410 from compressor.filters.template import TemplateFilter
1411+from compressor.filters.closure import ClosureCompilerFilter
1412+from compressor.filters.csstidy import CSSTidyFilter
1413+from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter
1414+from compressor.filters.yui import YUICSSFilter, YUIJSFilter
1415+from compressor.filters.cleancss import CleanCSSFilter
1416 from compressor.tests.test_base import test_dir
1417
1418
1419+def blankdict(*args, **kwargs):
1420+ return defaultdict(lambda: '', *args, **kwargs)
1421+
1422+
1423 @unittest.skipIf(find_command(settings.COMPRESS_CSSTIDY_BINARY) is None,
1424 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY)
1425 class CssTidyTestCase(TestCase):
1426@@ -30,7 +40,6 @@
1427 color: black;
1428 }
1429 """)
1430- from compressor.filters.csstidy import CSSTidyFilter
1431 ret = CSSTidyFilter(content).input()
1432 self.assertIsInstance(ret, six.text_type)
1433 self.assertEqual(
1434@@ -39,10 +48,13 @@
1435
1436 class PrecompilerTestCase(TestCase):
1437 def setUp(self):
1438- self.filename = os.path.join(test_dir, 'static/css/one.css')
1439+ self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
1440+ self.setup_infile()
1441+
1442+ def setup_infile(self, filename='static/css/one.css'):
1443+ self.filename = os.path.join(test_dir, filename)
1444 with io.open(self.filename, encoding=settings.FILE_CHARSET) as file:
1445 self.content = file.read()
1446- self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
1447
1448 def test_precompiler_infile_outfile(self):
1449 command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
1450@@ -51,6 +63,14 @@
1451 charset=settings.FILE_CHARSET, command=command)
1452 self.assertEqual("body { color:#990; }", compiler.input())
1453
1454+ def test_precompiler_infile_with_spaces(self):
1455+ self.setup_infile('static/css/filename with spaces.css')
1456+ command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
1457+ compiler = CompilerFilter(
1458+ content=self.content, filename=self.filename,
1459+ charset=settings.FILE_CHARSET, command=command)
1460+ self.assertEqual("body { color:#424242; }", compiler.input())
1461+
1462 def test_precompiler_infile_stdout(self):
1463 command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler)
1464 compiler = CompilerFilter(
1465@@ -99,8 +119,8 @@
1466 class CssAbsolutizingTestCase(TestCase):
1467 hashing_method = 'mtime'
1468 hashing_func = staticmethod(get_hashed_mtime)
1469- content = ("p { background: url('../../img/python.png') }"
1470- "p { filter: Alpha(src='../../img/python.png') }")
1471+ template = ("p { background: url('%(url)simg/python.png%(query)s%(hash)s%(frag)s') }"
1472+ "p { filter: Alpha(src='%(url)simg/python.png%(query)s%(hash)s%(frag)s') }")
1473
1474 def setUp(self):
1475 self.old_enabled = settings.COMPRESS_ENABLED
1476@@ -120,40 +140,55 @@
1477 settings.COMPRESS_URL = self.old_url
1478 settings.COMPRESS_CSS_HASHING_METHOD = self.old_hashing_method
1479
1480+ def test_css_no_hash(self):
1481+ settings.COMPRESS_CSS_HASHING_METHOD = None
1482+ filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1483+ content = self.template % blankdict(url='../../')
1484+ params = blankdict({
1485+ 'url': settings.COMPRESS_URL,
1486+ })
1487+ output = self.template % params
1488+ filter = CssAbsoluteFilter(content)
1489+ self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1490+
1491+ settings.COMPRESS_URL = params['url'] = 'http://static.example.com/'
1492+ output = self.template % params
1493+ filter = CssAbsoluteFilter(content)
1494+ self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1495+
1496 def test_css_absolute_filter(self):
1497 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1498 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
1499- params = {
1500+ content = self.template % blankdict(url='../../')
1501+ params = blankdict({
1502 'url': settings.COMPRESS_URL,
1503- 'hash': self.hashing_func(imagefilename),
1504- }
1505- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1506- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1507- filter = CssAbsoluteFilter(self.content)
1508+ 'hash': '?' + self.hashing_func(imagefilename),
1509+ })
1510+ output = self.template % params
1511+ filter = CssAbsoluteFilter(content)
1512 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1513+
1514 settings.COMPRESS_URL = params['url'] = 'http://static.example.com/'
1515- filter = CssAbsoluteFilter(self.content)
1516- filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1517- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1518- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1519+ output = self.template % params
1520+ filter = CssAbsoluteFilter(content)
1521 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1522
1523 def test_css_absolute_filter_url_fragment(self):
1524 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1525 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
1526- params = {
1527+ content = self.template % blankdict(url='../../', frag='#foo')
1528+ params = blankdict({
1529 'url': settings.COMPRESS_URL,
1530- 'hash': self.hashing_func(imagefilename),
1531- }
1532- content = "p { background: url('../../img/python.png#foo') }"
1533-
1534- output = "p { background: url('%(url)simg/python.png?%(hash)s#foo') }" % params
1535+ 'hash': '?' + self.hashing_func(imagefilename),
1536+ 'frag': '#foo',
1537+ })
1538+ output = self.template % params
1539 filter = CssAbsoluteFilter(content)
1540 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1541+
1542 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'
1543+ output = self.template % params
1544 filter = CssAbsoluteFilter(content)
1545- filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1546- output = "p { background: url('%(url)simg/python.png?%(hash)s#foo') }" % params
1547 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1548
1549 def test_css_absolute_filter_only_url_fragment(self):
1550@@ -161,64 +196,78 @@
1551 content = "p { background: url('#foo') }"
1552 filter = CssAbsoluteFilter(content)
1553 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))
1554+
1555 settings.COMPRESS_URL = 'http://media.example.com/'
1556 filter = CssAbsoluteFilter(content)
1557- filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1558 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))
1559
1560 def test_css_absolute_filter_querystring(self):
1561 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1562 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
1563- params = {
1564+ content = self.template % blankdict(url='../../', query='?foo')
1565+ params = blankdict({
1566 'url': settings.COMPRESS_URL,
1567- 'hash': self.hashing_func(imagefilename),
1568- }
1569- content = "p { background: url('../../img/python.png?foo') }"
1570-
1571- output = "p { background: url('%(url)simg/python.png?foo&%(hash)s') }" % params
1572+ 'query': '?foo',
1573+ 'hash': '&' + self.hashing_func(imagefilename),
1574+ })
1575+ output = self.template % params
1576 filter = CssAbsoluteFilter(content)
1577 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1578+
1579 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'
1580+ output = self.template % params
1581 filter = CssAbsoluteFilter(content)
1582- filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1583- output = "p { background: url('%(url)simg/python.png?foo&%(hash)s') }" % params
1584 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1585
1586 def test_css_absolute_filter_https(self):
1587 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1588 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
1589- params = {
1590+ content = self.template % blankdict(url='../../')
1591+ params = blankdict({
1592 'url': settings.COMPRESS_URL,
1593- 'hash': self.hashing_func(imagefilename),
1594- }
1595- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1596- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1597- filter = CssAbsoluteFilter(self.content)
1598+ 'hash': '?' + self.hashing_func(imagefilename),
1599+ })
1600+ output = self.template % params
1601+ filter = CssAbsoluteFilter(content)
1602 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1603+
1604 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'
1605- filter = CssAbsoluteFilter(self.content)
1606- filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
1607- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1608- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1609+ output = self.template % params
1610+ filter = CssAbsoluteFilter(content)
1611 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1612
1613 def test_css_absolute_filter_relative_path(self):
1614 filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'static', 'whatever/../css/url/test.css')
1615 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
1616- params = {
1617+ content = self.template % blankdict(url='../../')
1618+ params = blankdict({
1619 'url': settings.COMPRESS_URL,
1620- 'hash': self.hashing_func(imagefilename),
1621- }
1622- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1623- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1624- filter = CssAbsoluteFilter(self.content)
1625+ 'hash': '?' + self.hashing_func(imagefilename),
1626+ })
1627+ output = self.template % params
1628+ filter = CssAbsoluteFilter(content)
1629 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1630+
1631 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'
1632- filter = CssAbsoluteFilter(self.content)
1633- output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
1634- "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
1635+ output = self.template % params
1636+ filter = CssAbsoluteFilter(content)
1637 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
1638
1639+ def test_css_absolute_filter_filename_outside_compress_root(self):
1640+ filename = '/foo/bar/baz/test.css'
1641+ content = self.template % blankdict(url='../qux/')
1642+ params = blankdict({
1643+ 'url': settings.COMPRESS_URL + 'bar/qux/',
1644+ })
1645+ output = self.template % params
1646+ filter = CssAbsoluteFilter(content)
1647+ self.assertEqual(output, filter.input(filename=filename, basename='bar/baz/test.css'))
1648+ settings.COMPRESS_URL = 'https://static.example.com/'
1649+ params['url'] = settings.COMPRESS_URL + 'bar/qux/'
1650+ output = self.template % params
1651+ filter = CssAbsoluteFilter(content)
1652+ self.assertEqual(output, filter.input(filename=filename, basename='bar/baz/test.css'))
1653+
1654 def test_css_hunks(self):
1655 hash_dict = {
1656 'hash1': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')),
1657@@ -253,14 +302,6 @@
1658 hashing_method = 'content'
1659 hashing_func = staticmethod(get_hashed_content)
1660
1661- def setUp(self):
1662- super(CssAbsolutizingTestCaseWithHash, self).setUp()
1663- self.css = """
1664- <link rel="stylesheet" href="/static/css/url/url1.css" type="text/css" charset="utf-8">
1665- <link rel="stylesheet" href="/static/css/url/2/url2.css" type="text/css" charset="utf-8">
1666- """
1667- self.css_node = CssCompressor(self.css)
1668-
1669
1670 class CssDataUriTestCase(TestCase):
1671 def setUp(self):
1672@@ -301,3 +342,38 @@
1673 #footer {font-weight: bold;}
1674 """
1675 self.assertEqual(input, TemplateFilter(content).input())
1676+
1677+
1678+class SpecializedFiltersTest(TestCase):
1679+ """
1680+ Test to check the Specializations of filters.
1681+ """
1682+ def test_closure_filter(self):
1683+ filter = ClosureCompilerFilter('')
1684+ self.assertEqual(filter.options, (('binary', six.text_type('java -jar compiler.jar')), ('args', six.text_type(''))))
1685+
1686+ def test_csstidy_filter(self):
1687+ filter = CSSTidyFilter('')
1688+ self.assertEqual(filter.options, (('binary', six.text_type('csstidy')), ('args', six.text_type('--template=highest'))))
1689+
1690+ def test_yuglify_filters(self):
1691+ filter = YUglifyCSSFilter('')
1692+ self.assertEqual(filter.command, '{binary} {args} --type=css')
1693+ self.assertEqual(filter.options, (('binary', six.text_type('yuglify')), ('args', six.text_type('--terminal'))))
1694+
1695+ filter = YUglifyJSFilter('')
1696+ self.assertEqual(filter.command, '{binary} {args} --type=js')
1697+ self.assertEqual(filter.options, (('binary', six.text_type('yuglify')), ('args', six.text_type('--terminal'))))
1698+
1699+ def test_yui_filters(self):
1700+ filter = YUICSSFilter('')
1701+ self.assertEqual(filter.command, '{binary} {args} --type=css')
1702+ self.assertEqual(filter.options, (('binary', six.text_type('java -jar yuicompressor.jar')), ('args', six.text_type(''))))
1703+
1704+ filter = YUIJSFilter('', verbose=1)
1705+ self.assertEqual(filter.command, '{binary} {args} --type=js --verbose')
1706+ self.assertEqual(filter.options, (('binary', six.text_type('java -jar yuicompressor.jar')), ('args', six.text_type('')), ('verbose', 1)))
1707+
1708+ def test_clean_css_filter(self):
1709+ filter = CleanCSSFilter('')
1710+ self.assertEqual(filter.options, (('binary', six.text_type('cleancss')), ('args', six.text_type(''))))
1711
1712=== modified file 'compressor/tests/test_jinja2ext.py'
1713--- compressor/tests/test_jinja2ext.py 2015-01-06 14:31:34 +0000
1714+++ compressor/tests/test_jinja2ext.py 2015-08-20 18:49:21 +0000
1715@@ -65,8 +65,7 @@
1716 self.assertEqual(tag_body, template.render())
1717
1718 def test_empty_tag(self):
1719- template = self.env.from_string("""{% compress js %}{% block js %}
1720- {% endblock %}{% endcompress %}""")
1721+ template = self.env.from_string("""{% compress js %}{% block js %}{% endblock %}{% endcompress %}""")
1722 context = {'STATIC_URL': settings.COMPRESS_URL}
1723 self.assertEqual('', template.render(context))
1724
1725
1726=== modified file 'compressor/tests/test_offline.py'
1727--- compressor/tests/test_offline.py 2015-01-06 14:31:34 +0000
1728+++ compressor/tests/test_offline.py 2015-08-20 18:49:21 +0000
1729@@ -3,6 +3,7 @@
1730 import os
1731 import sys
1732
1733+import django
1734 from django.core.management.base import CommandError
1735 from django.template import Template, Context
1736 from django.test import TestCase
1737@@ -44,10 +45,6 @@
1738 engines = ("django",)
1739
1740 def setUp(self):
1741- self._old_compress = settings.COMPRESS_ENABLED
1742- self._old_compress_offline = settings.COMPRESS_OFFLINE
1743- self._old_template_dirs = settings.TEMPLATE_DIRS
1744- self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
1745 self.log = StringIO()
1746
1747 # Reset template dirs, because it enables us to force compress to
1748@@ -58,11 +55,18 @@
1749 # template to be skipped over.
1750 django_template_dir = os.path.join(settings.TEST_DIR, 'test_templates', self.templates_dir)
1751 jinja2_template_dir = os.path.join(settings.TEST_DIR, 'test_templates_jinja2', self.templates_dir)
1752- settings.TEMPLATE_DIRS = (django_template_dir, jinja2_template_dir)
1753-
1754- # Enable offline compress
1755- settings.COMPRESS_ENABLED = True
1756- settings.COMPRESS_OFFLINE = True
1757+
1758+ override_settings = {
1759+ 'TEMPLATE_DIRS': (django_template_dir, jinja2_template_dir,),
1760+ 'COMPRESS_ENABLED': True,
1761+ 'COMPRESS_OFFLINE': True
1762+ }
1763+
1764+ if "jinja2" in self.engines:
1765+ override_settings["COMPRESS_JINJA2_GET_ENVIRONMENT"] = lambda: self._get_jinja2_env()
1766+
1767+ self.override_settings = self.settings(**override_settings)
1768+ self.override_settings.__enter__()
1769
1770 if "django" in self.engines:
1771 self.template_path = os.path.join(django_template_dir, self.template_name)
1772@@ -70,22 +74,16 @@
1773 with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:
1774 self.template = Template(file.read())
1775
1776- self._old_jinja2_get_environment = settings.COMPRESS_JINJA2_GET_ENVIRONMENT
1777-
1778 if "jinja2" in self.engines:
1779- # Setup Jinja2 settings.
1780- settings.COMPRESS_JINJA2_GET_ENVIRONMENT = lambda: self._get_jinja2_env()
1781- jinja2_env = settings.COMPRESS_JINJA2_GET_ENVIRONMENT()
1782+ jinja2_env = override_settings["COMPRESS_JINJA2_GET_ENVIRONMENT"]()
1783 self.template_path_jinja2 = os.path.join(jinja2_template_dir, self.template_name)
1784
1785 with io.open(self.template_path_jinja2, encoding=settings.FILE_CHARSET) as file:
1786 self.template_jinja2 = jinja2_env.from_string(file.read())
1787
1788 def tearDown(self):
1789- settings.COMPRESS_JINJA2_GET_ENVIRONMENT = self._old_jinja2_get_environment
1790- settings.COMPRESS_ENABLED = self._old_compress
1791- settings.COMPRESS_OFFLINE = self._old_compress_offline
1792- settings.TEMPLATE_DIRS = self._old_template_dirs
1793+ self.override_settings.__exit__(None, None, None)
1794+
1795 manifest_path = os.path.join('CACHE', 'manifest.json')
1796 if default_storage.exists(manifest_path):
1797 default_storage.delete(manifest_path)
1798@@ -452,9 +450,10 @@
1799 # from library import *
1800 # causing 'ImportError: No module named library'.
1801 # It seems there is no evidence nor indicated support for Python 3+.
1802-@unittest.skip("Coffin tests disable under Ubuntu build")
1803 @unittest.skipIf(sys.version_info >= (3, 2),
1804 "Coffin does not support 3.2+")
1805+@unittest.skipIf(django.VERSION >= (1, 8),
1806+ "Import error on 1.8")
1807 class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):
1808 templates_dir = "test_coffin"
1809 expected_hash = "32c8281e3346"
1810@@ -479,6 +478,8 @@
1811 # is also evident in its tox.ini file.
1812 @unittest.skipIf(sys.version_info >= (3, 2) and sys.version_info < (3, 3),
1813 "Jingo does not support 3.2")
1814+@unittest.skipIf(django.VERSION >= (1, 8),
1815+ "Import error on 1.8")
1816 class OfflineGenerationJingoTestCase(OfflineTestCaseMixin, TestCase):
1817 templates_dir = "test_jingo"
1818 expected_hash = "61ec584468eb"
1819
1820=== modified file 'compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html'
1821--- compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html 2014-06-26 15:08:13 +0000
1822+++ compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html 2015-08-20 18:49:21 +0000
1823@@ -5,4 +5,9 @@
1824 <script type="text/javascript">
1825 alert("this alert shouldn't be alone!");
1826 </script>
1827+ {% block orphan %}
1828+ {{ block.super }}
1829+ An 'orphan' block that refers to a non-existent super block.
1830+ Contents of this block are ignored.
1831+ {% endblock %}
1832 {% endspaceless %}{% endblock %}
1833
1834=== modified file 'compressor/utils/staticfiles.py'
1835--- compressor/utils/staticfiles.py 2014-06-26 15:08:13 +0000
1836+++ compressor/utils/staticfiles.py 2015-08-20 18:49:21 +0000
1837@@ -4,20 +4,10 @@
1838
1839 from compressor.conf import settings
1840
1841-INSTALLED = ("staticfiles" in settings.INSTALLED_APPS or
1842- "django.contrib.staticfiles" in settings.INSTALLED_APPS)
1843-
1844-if INSTALLED:
1845- if "django.contrib.staticfiles" in settings.INSTALLED_APPS:
1846- from django.contrib.staticfiles import finders
1847- else:
1848- try:
1849- from staticfiles import finders # noqa
1850- except ImportError:
1851- # Old (pre 1.0) and incompatible version of staticfiles
1852- INSTALLED = False
1853-
1854- if (INSTALLED and "compressor.finders.CompressorFinder"
1855+if "django.contrib.staticfiles" in settings.INSTALLED_APPS:
1856+ from django.contrib.staticfiles import finders # noqa
1857+
1858+ if ("compressor.finders.CompressorFinder"
1859 not in settings.STATICFILES_FINDERS):
1860 raise ImproperlyConfigured(
1861 "When using Django Compressor together with staticfiles, "
1862
1863=== modified file 'debian/changelog'
1864--- debian/changelog 2015-01-06 14:31:34 +0000
1865+++ debian/changelog 2015-08-20 18:49:21 +0000
1866@@ -1,3 +1,23 @@
1867+python-django-compressor (1.5-1ubuntu1) UNRELEASED; urgency=low
1868+
1869+ * Merge from Debian unstable. Remaining changes:
1870+ - d/control: Drop BD on python-coffin.
1871+ - d/p/disable-coffin-tests.patch: Rebase.
1872+ * Fix d/watch
1873+
1874+ -- David Ames <david.ames@canonical.com> Tue, 04 Aug 2015 16:11:41 +0000
1875+
1876+python-django-compressor (1.5-1) unstable; urgency=medium
1877+
1878+ * New upstream release.
1879+ * Added Python 3 support.
1880+ * Removed django 1.7 patche.
1881+ * Added patch to not run a unit test that fails:
1882+ - compressor.tests.test_base.JsAsyncDeferTestCase
1883+ * Fixed PyPi watch file.
1884+
1885+ -- Thomas Goirand <zigo@debian.org> Tue, 04 Aug 2015 08:17:03 +0000
1886+
1887 python-django-compressor (1.4-2ubuntu3) vivid; urgency=medium
1888
1889 * d/control: Drop BD on python-coffin.
1890
1891=== modified file 'debian/control'
1892--- debian/control 2015-01-06 14:31:34 +0000
1893+++ debian/control 2015-08-20 18:49:21 +0000
1894@@ -5,34 +5,66 @@
1895 XSBC-Original-Maintainer: PKG OpenStack <openstack-devel@lists.alioth.debian.org>
1896 Uploaders: Thomas Goirand <zigo@debian.org>,
1897 Build-Depends: debhelper (>= 9),
1898+ dh-python,
1899 openstack-pkg-tools,
1900- python-all (>= 2.6.6-3~),
1901- python-setuptools
1902-Build-Depends-Indep: python-appconf,
1903+ python-all,
1904+ python-setuptools,
1905+ python3-all,
1906+ python3-setuptools
1907+Build-Depends-Indep: csstidy,
1908+ python-appconf,
1909 python-bs4,
1910 python-coverage,
1911- python-django (>= 1.6),
1912+ python-django,
1913+ python-django-discover-runner,
1914 python-html5lib,
1915 python-jingo,
1916 python-jinja2,
1917 python-lxml,
1918 python-mock,
1919 python-nose,
1920- python-unittest2
1921-Standards-Version: 3.9.5
1922+ python-unittest2,
1923+ python3-appconf,
1924+ python3-bs4,
1925+ python3-coverage,
1926+ python3-django,
1927+ python3-django-discover-runner,
1928+ python3-html5lib,
1929+ python3-jingo,
1930+ python3-jinja2,
1931+ python3-lxml,
1932+ python3-mock,
1933+ python3-nose,
1934+ python3-unittest2
1935+Standards-Version: 3.9.6
1936 Vcs-Browser: http://anonscm.debian.org/gitweb/?p=openstack/python-django-compressor.git;a=summary
1937 Vcs-Git: git://anonscm.debian.org/openstack/python-django-compressor.git
1938 Homepage: http://pypi.python.org/pypi/django_compressor/
1939
1940 Package: python-compressor
1941 Architecture: all
1942-Pre-Depends: dpkg (>= 1.15.6~)
1943 Depends: python-appconf,
1944- python-django (>= 1.1),
1945+ python-django,
1946 ${misc:Depends},
1947 ${python:Depends}
1948 Provides: ${python:Provides}
1949-Description: Compresses linked and inline JavaScript or CSS into single cached files
1950- Django Compressor combines and compresses linked and inline Javascript or CSS
1951- in a Django templates into cacheable static files by using the compress
1952- template tag.
1953+Description: Compresses linked, inline JS or CSS into single cached files - Python 2.7
1954+ Django Compressor combines and compresses linked and inline Javascript or CSS
1955+ in a Django templates into cacheable static files by using the compress
1956+ template tag.
1957+ .
1958+ This package contains the Python 2.7 module.
1959+
1960+Package: python3-compressor
1961+Architecture: all
1962+Depends: python3-appconf,
1963+ python3-django,
1964+ ${misc:Depends},
1965+ ${python3:Depends}
1966+Provides: ${python:Provides}
1967+Description: Compresses linked, inline JS or CSS into single cached files - Python 3.x
1968+ Django Compressor combines and compresses linked and inline Javascript or CSS
1969+ in a Django templates into cacheable static files by using the compress
1970+ template tag.
1971+ .
1972+ This package contains the Python 3.x module.
1973
1974=== modified file 'debian/gbp.conf'
1975--- debian/gbp.conf 2013-05-12 15:20:14 +0000
1976+++ debian/gbp.conf 2015-08-20 18:49:21 +0000
1977@@ -4,5 +4,5 @@
1978 upstream-tag = %(version)s
1979 compression = xz
1980
1981-[git-buildpackage]
1982+[buildpackage]
1983 export-dir = ../build-area/
1984
1985=== modified file 'debian/patches/disable-coffin-tests.patch'
1986--- debian/patches/disable-coffin-tests.patch 2015-01-06 12:34:51 +0000
1987+++ debian/patches/disable-coffin-tests.patch 2015-08-20 18:49:21 +0000
1988@@ -1,20 +1,20 @@
1989 --- a/compressor/test_settings.py
1990 +++ b/compressor/test_settings.py
1991-@@ -14,7 +14,6 @@ DATABASES = {
1992-
1993+@@ -21,7 +21,6 @@
1994 INSTALLED_APPS = [
1995+ 'django.contrib.staticfiles',
1996 'compressor',
1997 - 'coffin',
1998- 'jingo',
1999 ]
2000-
2001+ if django.VERSION < (1, 8):
2002+ INSTALLED_APPS.append('jingo')
2003 --- a/compressor/tests/test_offline.py
2004 +++ b/compressor/tests/test_offline.py
2005-@@ -452,6 +452,7 @@ class OfflineGenerationComplexTestCase(O
2006+@@ -450,6 +450,7 @@
2007 # from library import *
2008 # causing 'ImportError: No module named library'.
2009 # It seems there is no evidence nor indicated support for Python 3+.
2010 +@unittest.skip("Coffin tests disable under Ubuntu build")
2011 @unittest.skipIf(sys.version_info >= (3, 2),
2012 "Coffin does not support 3.2+")
2013- class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):
2014+ @unittest.skipIf(django.VERSION >= (1, 8),
2015
2016=== removed file 'debian/patches/fix-test_settings.py-for-django-1.7.patch'
2017--- debian/patches/fix-test_settings.py-for-django-1.7.patch 2014-09-08 17:31:54 +0000
2018+++ debian/patches/fix-test_settings.py-for-django-1.7.patch 1970-01-01 00:00:00 +0000
2019@@ -1,16 +0,0 @@
2020-Description: Fix test_settings.py for Django 1.7 compat
2021-Author: Thomas Goirand <zigo@debian.org>
2022-Forwarded: no
2023-Last-Update: 2014-09-09
2024-
2025---- python-django-compressor-1.4.orig/compressor/test_settings.py
2026-+++ python-django-compressor-1.4/compressor/test_settings.py
2027-@@ -3,7 +3,7 @@ import django
2028-
2029- TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
2030-
2031--COMPRESS_CACHE_BACKEND = 'locmem://'
2032-+COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'
2033-
2034- DATABASES = {
2035- 'default': {
2036
2037=== added file 'debian/patches/remove-failed-test.patch'
2038--- debian/patches/remove-failed-test.patch 1970-01-01 00:00:00 +0000
2039+++ debian/patches/remove-failed-test.patch 2015-08-20 18:49:21 +0000
2040@@ -0,0 +1,43 @@
2041+Description: Removes failed test
2042+ This unit test is failing, so removing it to build the package.
2043+Author: Thomas Goirand <zigo@debian.org>
2044+Forwarded: no
2045+Last-Update: 2015-08-04
2046+
2047+--- python-django-compressor-1.5.orig/compressor/tests/test_base.py
2048++++ python-django-compressor-1.5/compressor/tests/test_base.py
2049+@@ -288,34 +288,6 @@ class CacheBackendTestCase(CompressorTes
2050+ self.assertEqual(cache.__class__, locmem.LocMemCache)
2051+
2052+
2053+-class JsAsyncDeferTestCase(SimpleTestCase):
2054+- def setUp(self):
2055+- self.js = """\
2056+- <script src="/static/js/one.js" type="text/javascript"></script>
2057+- <script src="/static/js/two.js" type="text/javascript" async></script>
2058+- <script src="/static/js/three.js" type="text/javascript" defer></script>
2059+- <script type="text/javascript">obj.value = "value";</script>
2060+- <script src="/static/js/one.js" type="text/javascript" async></script>
2061+- <script src="/static/js/two.js" type="text/javascript" async></script>
2062+- <script src="/static/js/three.js" type="text/javascript"></script>"""
2063+-
2064+- def test_js_output(self):
2065+- def extract_attr(tag):
2066+- if tag.has_attr('async'):
2067+- return 'async'
2068+- if tag.has_attr('defer'):
2069+- return 'defer'
2070+- js_node = JsCompressor(self.js)
2071+- output = [None, 'async', 'defer', None, 'async', None]
2072+- if six.PY3:
2073+- scripts = make_soup(js_node.output()).find_all('script')
2074+- attrs = [extract_attr(i) for i in scripts]
2075+- else:
2076+- scripts = make_soup(js_node.output()).findAll('script')
2077+- attrs = [s.get('async') or s.get('defer') for s in scripts]
2078+- self.assertEqual(output, attrs)
2079+-
2080+-
2081+ class CacheTestCase(SimpleTestCase):
2082+
2083+ def setUp(self):
2084
2085=== modified file 'debian/patches/series'
2086--- debian/patches/series 2015-01-06 12:34:51 +0000
2087+++ debian/patches/series 2015-08-20 18:49:21 +0000
2088@@ -1,2 +1,2 @@
2089-fix-test_settings.py-for-django-1.7.patch
2090 disable-coffin-tests.patch
2091+remove-failed-test.patch
2092
2093=== modified file 'debian/rules'
2094--- debian/rules 2014-12-05 10:34:59 +0000
2095+++ debian/rules 2015-08-20 18:49:21 +0000
2096@@ -1,13 +1,14 @@
2097 #!/usr/bin/make -f
2098
2099-#export DH_VERBOSE=1
2100+PYTHONS:=$(shell pyversions -vr)
2101+PYTHON3S:=$(shell py3versions -vr)
2102
2103 UPSTREAM_GIT = git://github.com/jezdez/django_compressor.git
2104
2105 include /usr/share/openstack-pkg-tools/pkgos.make
2106
2107 %:
2108- dh $@ --with python2
2109+ dh $@ --buildsystem=python_distutils --with python2,python3
2110
2111 override_dh_clean:
2112 dh_clean
2113@@ -15,24 +16,27 @@
2114
2115 override_dh_auto_test:
2116 ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
2117- PYTHONPATH=. python-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor
2118- #PYTHONPATH=$PYTHONPATH:. python /usr/share/pyshared/django/bin/django-admin.py test --settings=compressor.test_settings compressor || true
2119+ PYTHON=python2 PYTHONPATH=. python-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor
2120 rm -rf $(CURDIR)/compressor/tests/static/CACHE
2121+ # TODO: make unit tests to work.
2122+# PYTHON=python3 PYTHONPATH=. python3-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor
2123+# rm -rf $(CURDIR)/compressor/tests/static/CACHE
2124 endif
2125
2126 override_dh_auto_build:
2127
2128 override_dh_install:
2129- set -e ; for i in `pyversions -s` ; do \
2130- $$i setup.py install --install-layout=deb --root=debian/python-compressor ; \
2131- rm -f $(CURDIR)/debian/usr/lib/$$i/dist-packages/compressor/tests/static/CACHE/css/* ; \
2132- rm -f $(CURDIR)/debian/usr/lib/$$i/dist-packages/compressor/tests/static/CACHE/js/* ; \
2133- done
2134- find debian/python-compressor -iname '*.pyc' -delete
2135-
2136-override_dh_usrlocal:
2137- rm -f $(CURDIR)/debian/usr/share/pyshared/compressor/tests/static/CACHE/css/*
2138- rm -f $(CURDIR)/debian/usr/share/pyshared/compressor/tests/static/CACHE/js/*
2139+ set -e ; for pyvers in $(PYTHONS); do \
2140+ python$$pyvers setup.py install --install-layout=deb \
2141+ --root $(CURDIR)/debian/python-compressor; \
2142+ done
2143+ set -e ; for pyvers in $(PYTHON3S); do \
2144+ python$$pyvers setup.py install --install-layout=deb \
2145+ --root $(CURDIR)/debian/python3-compressor; \
2146+ done
2147+ rm -f $(CURDIR)/debian/usr/lib/python*/dist-packages/compressor/tests/static/CACHE/css/*
2148+ rm -f $(CURDIR)/debian/usr/lib/python*/dist-packages/compressor/tests/static/CACHE/js/*
2149+ find debian -iname '*.pyc' -delete
2150
2151 # Commands not to run
2152 override_dh_installcatalogs:
2153
2154=== modified file 'debian/watch'
2155--- debian/watch 2012-10-14 10:51:47 +0000
2156+++ debian/watch 2015-08-20 18:49:21 +0000
2157@@ -1,2 +1,3 @@
2158 version=3
2159-http://pypi.python.org/packages/source/d/django-compressor/django-compressor-(.*)\.tar.gz
2160+opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
2161+http://pypi.debian.net/django_compressor/django_compressor-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
2162
2163=== modified file 'docs/changelog.txt'
2164--- docs/changelog.txt 2015-01-06 14:31:34 +0000
2165+++ docs/changelog.txt 2015-08-20 18:49:21 +0000
2166@@ -1,12 +1,46 @@
2167 Changelog
2168 =========
2169
2170-v1.4
2171-----
2172+v1.5 (03/27/2015)
2173+-----------------
2174+
2175+`Full Changelog <https://github.com/django-compressor/django-compressor/compare/1.4...HEAD>`_
2176+
2177+- Fix compress command and run automated tests for Django 1.8
2178+
2179+- Fix Django 1.8 warnings
2180+
2181+- Handle TypeError from import_module
2182+
2183+- Fix reading UTF-8 files which have BOM
2184+
2185+- Fix incompatibility with Windows (shell_quote is not supported)
2186+
2187+- Run automated tests on Django 1.7
2188+
2189+- Ignore non-existent {{ block.super }} in offline compression instead of raising AttributeError
2190+
2191+- Support for clean-css
2192+
2193+- Fix link markup
2194+
2195+- Add support for COMPRESS_CSS_HASHING_METHOD = None
2196+
2197+- Remove compatibility with old 'staticfiles' app
2198+
2199+- In compress command, use get_template() instead of opening template files manually, fixing compatibility issues with custom template loaders
2200+
2201+- Fix FilterBase so that does not override self.type for subclasses if filter_type is not specified at init
2202+
2203+- Remove unnecessary filename and existence checks in CssAbsoluteFilter
2204+
2205+
2206+v1.4 (06/20/2014)
2207+-----------------
2208
2209 - Added Python 3 compatibility.
2210
2211-- Added compatibility with Django 1.6.x.
2212+- Added compatibility with Django 1.6.x and dropped support for Django 1.3.X.
2213
2214 - Fixed compatibility with html5lib 1.0.
2215
2216@@ -46,7 +80,7 @@
2217 - Dropped support for Python 2.5. Removed ``any`` and ``walk`` compatibility
2218 functions in ``compressor.utils``.
2219
2220- - Removed compatibility with Django 1.2 for default values of some settings:
2221+ - Removed compatibility with some old django setttings:
2222
2223 - :attr:`~COMPRESS_ROOT` no longer uses ``MEDIA_ROOT`` if ``STATIC_ROOT`` is
2224 not defined. It expects ``STATIC_ROOT`` to be defined instead.
2225
2226=== modified file 'docs/contributing.txt'
2227--- docs/contributing.txt 2015-01-06 14:31:34 +0000
2228+++ docs/contributing.txt 2015-08-20 18:49:21 +0000
2229@@ -9,11 +9,12 @@
2230 Community
2231 ---------
2232
2233-People interested in developing for the Django Compressor should head
2234-over to #django-compressor on the `freenode`_ IRC network for help and to
2235+People interested in developing for the Django Compressor should:
2236+
2237+1. Head over to #django-compressor on the `freenode`_ IRC network for help and to
2238 discuss the development.
2239+2. Open an issue on GitHub explaining your ideas.
2240
2241-You may also be interested in following `@jezdez`_ on Twitter.
2242
2243 In a nutshell
2244 -------------
2245@@ -143,7 +144,7 @@
2246 - Accessible. You should assume the reader to be moderately familiar with
2247 Python and Django, but not anything else. Link to documentation of libraries
2248 you use, for example, even if they are "obvious" to you. A brief
2249- description of what it does is also welcome.
2250+ description of what it does is also welcome.
2251
2252 Pulling of documentation is pretty fast and painless. Usually somebody goes
2253 over your text and merges it, since there are no "breaks" and that github
2254
2255=== modified file 'docs/django-sekizai.txt'
2256--- docs/django-sekizai.txt 2015-01-06 14:31:34 +0000
2257+++ docs/django-sekizai.txt 2015-08-20 18:49:21 +0000
2258@@ -3,12 +3,12 @@
2259 django-sekizai Support
2260 ======================
2261
2262-Django Compressor comes with support for _django-sekizai via an extension.
2263-_django-sekizai provides the ability to include template code, from within
2264+Django Compressor comes with support for django-sekizai_ via an extension.
2265+django-sekizai provides the ability to include template code, from within
2266 any block, to a parent block. It is primarily used to include js/css from
2267 included templates to the master template.
2268
2269-It requires _django-sekizai to installed. Refer to the _django-sekizai _docs
2270+It requires django-sekizai to be installed. Refer to the `django-sekizai docs`_
2271 for how to use ``render_block``
2272
2273 Usage
2274@@ -21,4 +21,4 @@
2275
2276
2277 .. _django-sekizai: https://github.com/ojii/django-sekizai
2278-.. _docs: http://django-sekizai.readthedocs.org/en/latest/
2279+.. _django-sekizai docs: http://django-sekizai.readthedocs.org/en/latest/
2280
2281=== modified file 'docs/jinja2.txt'
2282--- docs/jinja2.txt 2015-01-06 14:31:34 +0000
2283+++ docs/jinja2.txt 2015-08-20 18:49:21 +0000
2284@@ -42,13 +42,13 @@
2285 ==================================
2286 You'd need to configure ``COMPRESS_JINJA2_GET_ENVIRONMENT`` so that
2287 Compressor can retrieve the Jinja2 environment for rendering.
2288-This can be a lamda or function that returns a Jinja2 environment.
2289+This can be a lambda or function that returns a Jinja2 environment.
2290
2291 Usage
2292 -----
2293-Run the following compress command along with an ``-engine`` parameter. The
2294+Run the following compress command along with an ``--engine`` parameter. The
2295 parameter can be either jinja2 or django (default). For example,
2296-"./manage.py compress -engine jinja2".
2297+``./manage.py compress --engine jinja2``.
2298
2299 Using both Django and Jinja2 templates
2300 --------------------------------------
2301@@ -60,9 +60,9 @@
2302
2303 A typical usage could be :
2304
2305-- "./manage.py compress" for processing Django templates first, skipping
2306+- ``./manage.py compress`` for processing Django templates first, skipping
2307 Jinja2 templates.
2308-- "./manage.py compress -engine jinja2" for processing Jinja2 templates,
2309+- ``./manage.py compress --engine jinja2`` for processing Jinja2 templates,
2310 skipping Django templates.
2311
2312 However, it is still recommended that you do not mix Django and Jinja2
2313@@ -172,4 +172,3 @@
2314 .. _Jinja2: http://jinja.pocoo.org/docs/
2315 .. _Coffin: http://pypi.python.org/pypi/Coffin
2316 .. _Jingo: https://jingo.readthedocs.org/en/latest/
2317-
2318
2319=== modified file 'docs/quickstart.txt'
2320--- docs/quickstart.txt 2015-01-06 14:31:34 +0000
2321+++ docs/quickstart.txt 2015-08-20 18:49:21 +0000
2322@@ -18,10 +18,8 @@
2323 * See the list of :ref:`settings` to modify Django Compressor's
2324 default behaviour and make adjustments for your website.
2325
2326-* In case you use Django's staticfiles_ contrib app (or its standalone
2327- counterpart django-staticfiles_) you have to add Django Compressor's file
2328- finder to the ``STATICFILES_FINDERS`` setting, for example with
2329- ``django.contrib.staticfiles``:
2330+* In case you use Django's staticfiles_ contrib app you have to add Django
2331+ Compressor's file finder to the ``STATICFILES_FINDERS`` setting, like this:
2332
2333 .. code-block:: python
2334
2335@@ -95,6 +93,6 @@
2336 .. _lxml: http://codespeak.net/lxml/
2337 .. _libxml2: http://xmlsoft.org/
2338 .. _html5lib: http://code.google.com/p/html5lib/
2339-.. _`Slim It`: http://slimit.org/
2340+.. _`Slim It`: https://github.com/rspivak/slimit
2341 .. _django-appconf: http://pypi.python.org/pypi/django-appconf/
2342 .. _versiontools: http://pypi.python.org/pypi/versiontools/
2343
2344=== modified file 'docs/remote-storages.txt'
2345--- docs/remote-storages.txt 2015-01-06 14:31:34 +0000
2346+++ docs/remote-storages.txt 2015-08-20 18:49:21 +0000
2347@@ -39,12 +39,11 @@
2348 Using staticfiles
2349 ^^^^^^^^^^^^^^^^^
2350
2351-If you are using Django's staticfiles_ contrib app or the standalone
2352-app django-staticfiles_, you'll need to use a temporary filesystem cache
2353-for Django Compressor to know which files to compress. Since staticfiles
2354-provides a management command to collect static files from various
2355-locations which uses a storage backend, this is where both apps can be
2356-integrated.
2357+If you are using Django's staticfiles_ contrib app, you'll need to use a
2358+temporary filesystem cache for Django Compressor to know which files to
2359+compress. Since staticfiles provides a management command to collect static
2360+files from various locations which uses a storage backend, this is where both
2361+apps can be integrated.
2362
2363 #. Make sure the :attr:`~django.conf.settings.COMPRESS_ROOT` and STATIC_ROOT_
2364 settings are equal since both apps need to look at the same directories
2365@@ -84,7 +83,6 @@
2366 .. _Amazon S3: https://s3.amazonaws.com/
2367 .. _boto: http://boto.cloudhackers.com/
2368 .. _django-storages: http://code.welldev.org/django-storages/
2369-.. _django-staticfiles: http://github.com/jezdez/django-staticfiles/
2370 .. _staticfiles: http://docs.djangoproject.com/en/dev/howto/static-files/
2371 .. _STATIC_ROOT: http://docs.djangoproject.com/en/dev/ref/settings/#static-root
2372 .. _STATIC_URL: http://docs.djangoproject.com/en/dev/ref/settings/#static-url
2373
2374=== modified file 'docs/settings.txt'
2375--- docs/settings.txt 2015-01-06 14:31:34 +0000
2376+++ docs/settings.txt 2015-08-20 18:49:21 +0000
2377@@ -81,10 +81,11 @@
2378
2379 .. attribute:: COMPRESS_CSS_HASHING_METHOD
2380
2381- The method to use when calculating the hash to append to
2382- processed URLs. Either ``'mtime'`` (default) or ``'content'``.
2383- Use the latter in case you're using multiple server to serve your
2384- static files.
2385+ The method to use when calculating the suffix to append to URLs in
2386+ your processed CSS files. Either ``None``, ``'mtime'`` (default) or
2387+ ``'content'``. Use the ``None`` if you want to completely disable that
2388+ feature, and the ``'content'`` in case you're using multiple servers
2389+ to serve your content.
2390
2391 - ``compressor.filters.csstidy.CSSTidyFilter``
2392
2393@@ -136,9 +137,24 @@
2394 A filter that uses Zachary Voase's Python port of the YUI CSS compression
2395 algorithm cssmin_.
2396
2397+ - ``compressor.filters.cleancss.CleanCSSFilter``
2398+
2399+ A filter that passes the CSS content to the `clean-css`_ tool.
2400+
2401+ .. attribute:: CLEAN_CSS_BINARY
2402+
2403+ The clean-css binary filesystem path.
2404+
2405+ .. attribute:: CLEAN_CSS_ARGUMENTS
2406+
2407+ The arguments passed to clean-css.
2408+
2409+
2410 .. _CSSTidy: http://csstidy.sourceforge.net/
2411 .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme
2412 .. _cssmin: http://pypi.python.org/pypi/cssmin/
2413+ .. _`clean-css`: https://github.com/GoalSmashers/clean-css/
2414+
2415
2416 - ``compressor.filters.template.TemplateFilter``
2417
2418@@ -220,7 +236,7 @@
2419 .. _`Google Closure compiler`: http://code.google.com/closure/compiler/
2420 .. _`YUI compressor`: http://developer.yahoo.com/yui/compressor/
2421 .. _`yUglify compressor`: https://github.com/yui/yuglify
2422- .. _`Slim It`: http://slimit.org/
2423+ .. _`Slim It`: https://github.com/rspivak/slimit
2424
2425 .. attribute:: COMPRESS_PRECOMPILERS
2426
2427@@ -305,7 +321,7 @@
2428 <link rel="stylesheet" href="/static/CACHE/css/8ccf8d877f18.css" type="text/css" charset="utf-8">
2429
2430 .. _less: http://lesscss.org/
2431- .. _CoffeeScript: http://jashkenas.github.com/coffee-script/
2432+ .. _CoffeeScript: http://coffeescript.org/
2433
2434 .. attribute:: COMPRESS_STORAGE
2435
2436
2437=== modified file 'docs/usage.txt'
2438--- docs/usage.txt 2015-01-06 14:31:34 +0000
2439+++ docs/usage.txt 2015-08-20 18:49:21 +0000
2440@@ -48,7 +48,7 @@
2441
2442 .. note::
2443
2444- Remember that django-compressor will try to :ref:`group ouputs by media <css_notes>`.
2445+ Remember that django-compressor will try to :ref:`group outputs by media <css_notes>`.
2446
2447 Linked files **must** be accessible via
2448 :attr:`~django.conf.settings.COMPRESS_URL`.
2449
2450=== modified file 'tox.ini'
2451--- tox.ini 2014-06-26 15:08:13 +0000
2452+++ tox.ini 2015-08-20 18:49:21 +0000
2453@@ -33,89 +33,34 @@
2454
2455 [tox]
2456 envlist =
2457- py33-1.6.X,
2458- py32-1.6.X,
2459- py27-1.6.X,
2460- py26-1.6.X,
2461- py33-1.5.X,
2462- py32-1.5.X,
2463- py27-1.5.X,
2464- py26-1.5.X,
2465- py27-1.4.X,
2466- py26-1.4.X
2467-
2468+ {py26,py27}-{1.4.X,1.5.X},
2469+ {py26,py27,py32,py33}-{1.6.X},
2470+ {py27,py32,py33,py34}-{1.7.X},
2471+ {py27,py32,py33,py34}-{1.8.X}
2472 [testenv]
2473+basepython =
2474+ py26: python2.6
2475+ py27: python2.7
2476+ py32: python3.2
2477+ py33: python3.3
2478+ py34: python3.4
2479+usedevelop = true
2480 setenv =
2481 CPPFLAGS=-O0
2482-usedevelop = true
2483 whitelist_externals = /usr/bin/make
2484 downloadcache = {toxworkdir}/_download/
2485 commands =
2486 django-admin.py --version
2487 make test
2488-
2489-[testenv:py33-1.6.X]
2490-basepython = python3.3
2491-deps =
2492- Django>=1.6,<1.7
2493- {[deps]three}
2494-
2495-[testenv:py32-1.6.X]
2496-basepython = python3.2
2497-deps =
2498- Django>=1.6,<1.7
2499- {[deps]three_two}
2500-
2501-[testenv:py27-1.6.X]
2502-basepython = python2.7
2503-deps =
2504- Django>=1.6,<1.7
2505- {[deps]two}
2506-
2507-[testenv:py26-1.6.X]
2508-basepython = python2.6
2509-deps =
2510- Django>=1.6,<1.7
2511- {[deps]two}
2512-
2513-[testenv:py33-1.5.X]
2514-basepython = python3.3
2515-deps =
2516- Django>=1.5,<1.6
2517- django-discover-runner
2518- {[deps]three}
2519-
2520-[testenv:py32-1.5.X]
2521-basepython = python3.2
2522-deps =
2523- Django>=1.5,<1.6
2524- django-discover-runner
2525- {[deps]three_two}
2526-
2527-[testenv:py27-1.5.X]
2528-basepython = python2.7
2529-deps =
2530- Django>=1.5,<1.6
2531- django-discover-runner
2532- {[deps]two}
2533-
2534-[testenv:py26-1.5.X]
2535-basepython = python2.6
2536-deps =
2537- Django>=1.5,<1.6
2538- django-discover-runner
2539- {[deps]two}
2540-
2541-[testenv:py27-1.4.X]
2542-basepython = python2.7
2543-deps =
2544- Django>=1.4,<1.5
2545- django-discover-runner
2546- {[deps]two}
2547-
2548-[testenv:py26-1.4.X]
2549-basepython = python2.6
2550-deps =
2551- Django>=1.4,<1.5
2552- django-discover-runner
2553- {[deps]two}
2554+deps =
2555+ 1.4.X: Django>=1.4,<1.5
2556+ 1.5.X: Django>=1.5,<1.6
2557+ 1.6.X: Django>=1.6,<1.7
2558+ 1.7.X: Django>=1.7,<1.8
2559+ 1.8.X: Django>=1.8,<1.9
2560+ py26: {[deps]two}
2561+ py27: {[deps]two}
2562+ py32: {[deps]three_two}
2563+ py33: {[deps]three}
2564+ py34: {[deps]three}
2565+ django-discover-runner

Subscribers

People subscribed via source and target branches

to all changes: