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
=== removed directory '.pc'
=== removed file '.pc/.quilt_patches'
--- .pc/.quilt_patches 2012-10-14 10:51:47 +0000
+++ .pc/.quilt_patches 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1debian/patches
20
=== removed file '.pc/.quilt_series'
--- .pc/.quilt_series 2012-10-14 10:51:47 +0000
+++ .pc/.quilt_series 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1series
20
=== removed file '.pc/.version'
--- .pc/.version 2012-10-14 10:51:47 +0000
+++ .pc/.version 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
12
20
=== removed file '.pc/applied-patches'
--- .pc/applied-patches 2015-01-06 12:34:51 +0000
+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
@@ -1,2 +0,0 @@
1fix-test_settings.py-for-django-1.7.patch
2disable-coffin-tests.patch
30
=== removed directory '.pc/disable-coffin-tests.patch'
=== removed directory '.pc/disable-coffin-tests.patch/compressor'
=== removed file '.pc/disable-coffin-tests.patch/compressor/test_settings.py'
--- .pc/disable-coffin-tests.patch/compressor/test_settings.py 2015-01-06 12:34:51 +0000
+++ .pc/disable-coffin-tests.patch/compressor/test_settings.py 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1import os
2import django
3
4TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
5
6COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'
7
8DATABASES = {
9 'default': {
10 'ENGINE': 'django.db.backends.sqlite3',
11 'NAME': ':memory:',
12 }
13}
14
15INSTALLED_APPS = [
16 'compressor',
17 'coffin',
18 'jingo',
19]
20
21STATIC_URL = '/static/'
22
23
24STATIC_ROOT = os.path.join(TEST_DIR, 'static')
25
26TEMPLATE_DIRS = (
27 # Specifically choose a name that will not be considered
28 # by app_directories loader, to make sure each test uses
29 # a specific template without considering the others.
30 os.path.join(TEST_DIR, 'test_templates'),
31)
32
33if django.VERSION[:2] < (1, 6):
34 TEST_RUNNER = 'discover_runner.DiscoverRunner'
35
36SECRET_KEY = "iufoj=mibkpdz*%bob952x(%49rqgv8gg45k36kjcg76&-y5=!"
37
38PASSWORD_HASHERS = (
39 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
40)
410
=== removed directory '.pc/disable-coffin-tests.patch/compressor/tests'
=== removed file '.pc/disable-coffin-tests.patch/compressor/tests/test_offline.py'
--- .pc/disable-coffin-tests.patch/compressor/tests/test_offline.py 2015-01-06 12:34:51 +0000
+++ .pc/disable-coffin-tests.patch/compressor/tests/test_offline.py 1970-01-01 00:00:00 +0000
@@ -1,499 +0,0 @@
1from __future__ import with_statement, unicode_literals
2import io
3import os
4import sys
5
6from django.core.management.base import CommandError
7from django.template import Template, Context
8from django.test import TestCase
9from django.utils import six, unittest
10
11from compressor.cache import flush_offline_manifest, get_offline_manifest
12from compressor.conf import settings
13from compressor.exceptions import OfflineGenerationError
14from compressor.management.commands.compress import Command as CompressCommand
15from compressor.storage import default_storage
16
17if six.PY3:
18 # there is an 'io' module in python 2.6+, but io.StringIO does not
19 # accept regular strings, just unicode objects
20 from io import StringIO
21else:
22 try:
23 from cStringIO import StringIO
24 except ImportError:
25 from StringIO import StringIO
26
27# The Jinja2 tests fail on Python 3.2 due to the following:
28# The line in compressor/management/commands/compress.py:
29# compressor_nodes.setdefault(template, []).extend(nodes)
30# causes the error "unhashable type: 'Template'"
31_TEST_JINJA2 = not(sys.version_info[0] == 3 and sys.version_info[1] == 2)
32
33
34class OfflineTestCaseMixin(object):
35 template_name = "test_compressor_offline.html"
36 verbosity = 0
37 # Change this for each test class
38 templates_dir = ""
39 expected_hash = ""
40 # Engines to test
41 if _TEST_JINJA2:
42 engines = ("django", "jinja2")
43 else:
44 engines = ("django",)
45
46 def setUp(self):
47 self._old_compress = settings.COMPRESS_ENABLED
48 self._old_compress_offline = settings.COMPRESS_OFFLINE
49 self._old_template_dirs = settings.TEMPLATE_DIRS
50 self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
51 self.log = StringIO()
52
53 # Reset template dirs, because it enables us to force compress to
54 # consider only a specific directory (helps us make true,
55 # independant unit tests).
56 # Specify both Jinja2 and Django template locations. When the wrong engine
57 # is used to parse a template, the TemplateSyntaxError will cause the
58 # template to be skipped over.
59 django_template_dir = os.path.join(settings.TEST_DIR, 'test_templates', self.templates_dir)
60 jinja2_template_dir = os.path.join(settings.TEST_DIR, 'test_templates_jinja2', self.templates_dir)
61 settings.TEMPLATE_DIRS = (django_template_dir, jinja2_template_dir)
62
63 # Enable offline compress
64 settings.COMPRESS_ENABLED = True
65 settings.COMPRESS_OFFLINE = True
66
67 if "django" in self.engines:
68 self.template_path = os.path.join(django_template_dir, self.template_name)
69
70 with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:
71 self.template = Template(file.read())
72
73 self._old_jinja2_get_environment = settings.COMPRESS_JINJA2_GET_ENVIRONMENT
74
75 if "jinja2" in self.engines:
76 # Setup Jinja2 settings.
77 settings.COMPRESS_JINJA2_GET_ENVIRONMENT = lambda: self._get_jinja2_env()
78 jinja2_env = settings.COMPRESS_JINJA2_GET_ENVIRONMENT()
79 self.template_path_jinja2 = os.path.join(jinja2_template_dir, self.template_name)
80
81 with io.open(self.template_path_jinja2, encoding=settings.FILE_CHARSET) as file:
82 self.template_jinja2 = jinja2_env.from_string(file.read())
83
84 def tearDown(self):
85 settings.COMPRESS_JINJA2_GET_ENVIRONMENT = self._old_jinja2_get_environment
86 settings.COMPRESS_ENABLED = self._old_compress
87 settings.COMPRESS_OFFLINE = self._old_compress_offline
88 settings.TEMPLATE_DIRS = self._old_template_dirs
89 manifest_path = os.path.join('CACHE', 'manifest.json')
90 if default_storage.exists(manifest_path):
91 default_storage.delete(manifest_path)
92
93 def _render_template(self, engine):
94 if engine == "django":
95 return self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
96 elif engine == "jinja2":
97 return self.template_jinja2.render(settings.COMPRESS_OFFLINE_CONTEXT) + "\n"
98 else:
99 return None
100
101 def _test_offline(self, engine):
102 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
103 self.assertEqual(1, count)
104 self.assertEqual([
105 '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
106 ], result)
107 rendered_template = self._render_template(engine)
108 self.assertEqual(rendered_template, "".join(result) + "\n")
109
110 def test_offline(self):
111 for engine in self.engines:
112 self._test_offline(engine=engine)
113
114 def _get_jinja2_env(self):
115 import jinja2
116 import jinja2.ext
117 from compressor.offline.jinja2 import url_for, SpacelessExtension
118 from compressor.contrib.jinja2ext import CompressorExtension
119
120 # Extensions needed for the test cases only.
121 extensions = [
122 CompressorExtension,
123 SpacelessExtension,
124 jinja2.ext.with_,
125 jinja2.ext.do,
126 ]
127 loader = self._get_jinja2_loader()
128 env = jinja2.Environment(extensions=extensions, loader=loader)
129 env.globals['url_for'] = url_for
130
131 return env
132
133 def _get_jinja2_loader(self):
134 import jinja2
135
136 loader = jinja2.FileSystemLoader(settings.TEMPLATE_DIRS, encoding=settings.FILE_CHARSET)
137 return loader
138
139
140class OfflineGenerationSkipDuplicatesTestCase(OfflineTestCaseMixin, TestCase):
141 templates_dir = "test_duplicate"
142
143 # We don't need to test multiples engines here.
144 engines = ("django",)
145
146 def _test_offline(self, engine):
147 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
148 # Only one block compressed, the second identical one was skipped.
149 self.assertEqual(1, count)
150 # Only 1 <script> block in returned result as well.
151 self.assertEqual([
152 '<script type="text/javascript" src="/static/CACHE/js/f5e179b8eca4.js"></script>',
153 ], result)
154 rendered_template = self._render_template(engine)
155 # But rendering the template returns both (identical) scripts.
156 self.assertEqual(rendered_template, "".join(result * 2) + "\n")
157
158
159class OfflineGenerationBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
160 templates_dir = "test_block_super"
161 expected_hash = "7c02d201f69d"
162 # Block.super not supported for Jinja2 yet.
163 engines = ("django",)
164
165
166class OfflineGenerationBlockSuperMultipleTestCase(OfflineTestCaseMixin, TestCase):
167 templates_dir = "test_block_super_multiple"
168 expected_hash = "f8891c416981"
169 # Block.super not supported for Jinja2 yet.
170 engines = ("django",)
171
172
173class OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase(OfflineTestCaseMixin, TestCase):
174 templates_dir = "test_block_super_multiple_cached"
175 expected_hash = "2f6ef61c488e"
176 # Block.super not supported for Jinja2 yet.
177 engines = ("django",)
178
179 def setUp(self):
180 self._old_template_loaders = settings.TEMPLATE_LOADERS
181 settings.TEMPLATE_LOADERS = (
182 ('django.template.loaders.cached.Loader', (
183 'django.template.loaders.filesystem.Loader',
184 'django.template.loaders.app_directories.Loader',
185 )),
186 )
187 super(OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase, self).setUp()
188
189 def tearDown(self):
190 super(OfflineGenerationBlockSuperMultipleWithCachedLoaderTestCase, self).tearDown()
191 settings.TEMPLATE_LOADERS = self._old_template_loaders
192
193
194class OfflineGenerationBlockSuperTestCaseWithExtraContent(OfflineTestCaseMixin, TestCase):
195 templates_dir = "test_block_super_extra"
196 # Block.super not supported for Jinja2 yet.
197 engines = ("django",)
198
199 def _test_offline(self, engine):
200 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
201 self.assertEqual(2, count)
202 self.assertEqual([
203 '<script type="text/javascript" src="/static/CACHE/js/ced14aec5856.js"></script>',
204 '<script type="text/javascript" src="/static/CACHE/js/7c02d201f69d.js"></script>'
205 ], result)
206 rendered_template = self._render_template(engine)
207 self.assertEqual(rendered_template, "".join(result) + "\n")
208
209
210class OfflineGenerationConditionTestCase(OfflineTestCaseMixin, TestCase):
211 templates_dir = "test_condition"
212 expected_hash = "4e3758d50224"
213
214 def setUp(self):
215 self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
216 settings.COMPRESS_OFFLINE_CONTEXT = {
217 'condition': 'red',
218 }
219 super(OfflineGenerationConditionTestCase, self).setUp()
220
221 def tearDown(self):
222 self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
223 super(OfflineGenerationConditionTestCase, self).tearDown()
224
225
226class OfflineGenerationTemplateTagTestCase(OfflineTestCaseMixin, TestCase):
227 templates_dir = "test_templatetag"
228 expected_hash = "a27e1d3a619a"
229
230
231class OfflineGenerationStaticTemplateTagTestCase(OfflineTestCaseMixin, TestCase):
232 templates_dir = "test_static_templatetag"
233 expected_hash = "dfa2bb387fa8"
234
235
236class OfflineGenerationTestCaseWithContext(OfflineTestCaseMixin, TestCase):
237 templates_dir = "test_with_context"
238 expected_hash = "5838e2fd66af"
239
240 def setUp(self):
241 self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
242 settings.COMPRESS_OFFLINE_CONTEXT = {
243 'content': 'OK!',
244 }
245 super(OfflineGenerationTestCaseWithContext, self).setUp()
246
247 def tearDown(self):
248 settings.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
249 super(OfflineGenerationTestCaseWithContext, self).tearDown()
250
251
252class OfflineGenerationTestCaseErrors(OfflineTestCaseMixin, TestCase):
253 templates_dir = "test_error_handling"
254
255 def _test_offline(self, engine):
256 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
257
258 if engine == "django":
259 self.assertEqual(2, count)
260 else:
261 # Because we use env.parse in Jinja2Parser, the engine does not
262 # actually load the "extends" and "includes" templates, and so
263 # it is unable to detect that they are missing. So all the "compress"
264 # nodes are processed correctly.
265 self.assertEqual(4, count)
266 self.assertEqual(engine, "jinja2")
267 self.assertIn('<link rel="stylesheet" href="/static/CACHE/css/78bd7a762e2d.css" type="text/css" />', result)
268 self.assertIn('<link rel="stylesheet" href="/static/CACHE/css/e31030430724.css" type="text/css" />', result)
269
270 self.assertIn('<script type="text/javascript" src="/static/CACHE/js/3872c9ae3f42.js"></script>', result)
271 self.assertIn('<script type="text/javascript" src="/static/CACHE/js/cd8870829421.js"></script>', result)
272
273
274class OfflineGenerationTestCaseWithError(OfflineTestCaseMixin, TestCase):
275 templates_dir = 'test_error_handling'
276
277 def setUp(self):
278 self._old_compress_precompilers = settings.COMPRESS_PRECOMPILERS
279 settings.COMPRESS_PRECOMPILERS = (('text/coffeescript', 'non-existing-binary'),)
280 super(OfflineGenerationTestCaseWithError, self).setUp()
281
282 def _test_offline(self, engine):
283 """
284 Test that a CommandError is raised with DEBUG being False as well as
285 True, as otherwise errors in configuration will never show in
286 production.
287 """
288 self._old_debug = settings.DEBUG
289
290 try:
291 settings.DEBUG = True
292 self.assertRaises(CommandError, CompressCommand().compress, engine=engine)
293
294 settings.DEBUG = False
295 self.assertRaises(CommandError, CompressCommand().compress, engine=engine)
296
297 finally:
298 settings.DEBUG = self._old_debug
299
300 def tearDown(self):
301 settings.COMPRESS_PRECOMPILERS = self._old_compress_precompilers
302 super(OfflineGenerationTestCaseWithError, self).tearDown()
303
304
305class OfflineGenerationTestCase(OfflineTestCaseMixin, TestCase):
306 templates_dir = "basic"
307 expected_hash = "f5e179b8eca4"
308
309 def test_rendering_without_manifest_raises_exception(self):
310 # flush cached manifest
311 flush_offline_manifest()
312 self.assertRaises(OfflineGenerationError,
313 self.template.render, Context({}))
314
315 @unittest.skipIf(not _TEST_JINJA2, "No Jinja2 testing")
316 def test_rendering_without_manifest_raises_exception_jinja2(self):
317 # flush cached manifest
318 flush_offline_manifest()
319 self.assertRaises(OfflineGenerationError,
320 self.template_jinja2.render, {})
321
322 def _test_deleting_manifest_does_not_affect_rendering(self, engine):
323 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
324 get_offline_manifest()
325 manifest_path = os.path.join('CACHE', 'manifest.json')
326 if default_storage.exists(manifest_path):
327 default_storage.delete(manifest_path)
328 self.assertEqual(1, count)
329 self.assertEqual([
330 '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
331 ], result)
332 rendered_template = self._render_template(engine)
333 self.assertEqual(rendered_template, "".join(result) + "\n")
334
335 def test_deleting_manifest_does_not_affect_rendering(self):
336 for engine in self.engines:
337 self._test_deleting_manifest_does_not_affect_rendering(engine)
338
339 def test_requires_model_validation(self):
340 self.assertFalse(CompressCommand.requires_model_validation)
341
342 def test_get_loaders(self):
343 old_loaders = settings.TEMPLATE_LOADERS
344 settings.TEMPLATE_LOADERS = (
345 ('django.template.loaders.cached.Loader', (
346 'django.template.loaders.filesystem.Loader',
347 'django.template.loaders.app_directories.Loader',
348 )),
349 )
350 try:
351 from django.template.loaders.filesystem import Loader as FileSystemLoader
352 from django.template.loaders.app_directories import Loader as AppDirectoriesLoader
353 except ImportError:
354 pass
355 else:
356 loaders = CompressCommand().get_loaders()
357 self.assertTrue(isinstance(loaders[0], FileSystemLoader))
358 self.assertTrue(isinstance(loaders[1], AppDirectoriesLoader))
359 finally:
360 settings.TEMPLATE_LOADERS = old_loaders
361
362
363class OfflineGenerationBlockSuperBaseCompressed(OfflineTestCaseMixin, TestCase):
364 template_names = ["base.html", "base2.html", "test_compressor_offline.html"]
365 templates_dir = 'test_block_super_base_compressed'
366 expected_hash = ['028c3fc42232', '2e9d3f5545a6', 'f8891c416981']
367 # Block.super not supported for Jinja2 yet.
368 engines = ("django",)
369
370 def setUp(self):
371 super(OfflineGenerationBlockSuperBaseCompressed, self).setUp()
372
373 self.template_paths = []
374 self.templates = []
375 for template_name in self.template_names:
376 template_path = os.path.join(settings.TEMPLATE_DIRS[0], template_name)
377 self.template_paths.append(template_path)
378 with io.open(template_path, encoding=settings.FILE_CHARSET) as file:
379 template = Template(file.read())
380 self.templates.append(template)
381
382 def _render_template(self, template, engine):
383 if engine == "django":
384 return template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
385 elif engine == "jinja2":
386 return template.render(settings.COMPRESS_OFFLINE_CONTEXT) + "\n"
387 else:
388 return None
389
390 def _test_offline(self, engine):
391 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
392 self.assertEqual(len(self.expected_hash), count)
393 for expected_hash, template in zip(self.expected_hash, self.templates):
394 expected_output = '<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (expected_hash, )
395 self.assertIn(expected_output, result)
396 rendered_template = self._render_template(template, engine)
397 self.assertEqual(rendered_template, expected_output + '\n')
398
399
400class OfflineGenerationInlineNonAsciiTestCase(OfflineTestCaseMixin, TestCase):
401 templates_dir = "test_inline_non_ascii"
402
403 def setUp(self):
404 self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
405 settings.COMPRESS_OFFLINE_CONTEXT = {
406 'test_non_ascii_value': '\u2014',
407 }
408 super(OfflineGenerationInlineNonAsciiTestCase, self).setUp()
409
410 def tearDown(self):
411 self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
412 super(OfflineGenerationInlineNonAsciiTestCase, self).tearDown()
413
414 def _test_offline(self, engine):
415 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
416 rendered_template = self._render_template(engine)
417 self.assertEqual(rendered_template, "".join(result) + "\n")
418
419
420class OfflineGenerationComplexTestCase(OfflineTestCaseMixin, TestCase):
421 templates_dir = "test_complex"
422
423 def setUp(self):
424 self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
425 settings.COMPRESS_OFFLINE_CONTEXT = {
426 'condition': 'OK!',
427 # Django templating does not allow definition of tuples in the
428 # templates. Make sure this is same as test_templates_jinja2/test_complex.
429 'my_names': ("js/one.js", "js/nonasc.js"),
430 }
431 super(OfflineGenerationComplexTestCase, self).setUp()
432
433 def tearDown(self):
434 self.COMPRESS_OFFLINE_CONTEXT = self.old_offline_context
435 super(OfflineGenerationComplexTestCase, self).tearDown()
436
437 def _test_offline(self, engine):
438 count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity, engine=engine)
439 self.assertEqual(3, count)
440 self.assertEqual([
441 '<script type="text/javascript" src="/static/CACHE/js/0e8807bebcee.js"></script>',
442 '<script type="text/javascript" src="/static/CACHE/js/eed1d222933e.js"></script>',
443 '<script type="text/javascript" src="/static/CACHE/js/00b4baffe335.js"></script>',
444 ], result)
445 rendered_template = self._render_template(engine)
446 result = (result[0], result[2])
447 self.assertEqual(rendered_template, "".join(result) + "\n")
448
449
450# Coffin does not work on Python 3.2+ due to:
451# The line at coffin/template/__init__.py:15
452# from library import *
453# causing 'ImportError: No module named library'.
454# It seems there is no evidence nor indicated support for Python 3+.
455@unittest.skipIf(sys.version_info >= (3, 2),
456 "Coffin does not support 3.2+")
457class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):
458 templates_dir = "test_coffin"
459 expected_hash = "32c8281e3346"
460 engines = ("jinja2",)
461
462 def _get_jinja2_env(self):
463 import jinja2
464 from coffin.common import env
465 from compressor.contrib.jinja2ext import CompressorExtension
466
467 # Could have used the env.add_extension method, but it's only available
468 # in Jinja2 v2.5
469 new_env = jinja2.Environment(extensions=[CompressorExtension])
470 env.extensions.update(new_env.extensions)
471
472 return env
473
474
475# Jingo does not work when using Python 3.2 due to the use of Unicode string
476# prefix (and possibly other stuff), but it actually works when using Python 3.3
477# since it tolerates the use of the Unicode string prefix. Python 3.3 support
478# is also evident in its tox.ini file.
479@unittest.skipIf(sys.version_info >= (3, 2) and sys.version_info < (3, 3),
480 "Jingo does not support 3.2")
481class OfflineGenerationJingoTestCase(OfflineTestCaseMixin, TestCase):
482 templates_dir = "test_jingo"
483 expected_hash = "61ec584468eb"
484 engines = ("jinja2",)
485
486 def _get_jinja2_env(self):
487 import jinja2
488 import jinja2.ext
489 from jingo import env
490 from compressor.contrib.jinja2ext import CompressorExtension
491 from compressor.offline.jinja2 import SpacelessExtension, url_for
492
493 # Could have used the env.add_extension method, but it's only available
494 # in Jinja2 v2.5
495 new_env = jinja2.Environment(extensions=[CompressorExtension, SpacelessExtension, jinja2.ext.with_])
496 env.extensions.update(new_env.extensions)
497 env.globals['url_for'] = url_for
498
499 return env
5000
=== removed directory '.pc/fix-test_settings.py-for-django-1.7.patch'
=== removed directory '.pc/fix-test_settings.py-for-django-1.7.patch/compressor'
=== removed file '.pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py'
--- .pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py 2014-09-08 17:31:54 +0000
+++ .pc/fix-test_settings.py-for-django-1.7.patch/compressor/test_settings.py 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1import os
2import django
3
4TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
5
6COMPRESS_CACHE_BACKEND = 'locmem://'
7
8DATABASES = {
9 'default': {
10 'ENGINE': 'django.db.backends.sqlite3',
11 'NAME': ':memory:',
12 }
13}
14
15INSTALLED_APPS = [
16 'compressor',
17 'coffin',
18 'jingo',
19]
20
21STATIC_URL = '/static/'
22
23
24STATIC_ROOT = os.path.join(TEST_DIR, 'static')
25
26TEMPLATE_DIRS = (
27 # Specifically choose a name that will not be considered
28 # by app_directories loader, to make sure each test uses
29 # a specific template without considering the others.
30 os.path.join(TEST_DIR, 'test_templates'),
31)
32
33if django.VERSION[:2] < (1, 6):
34 TEST_RUNNER = 'discover_runner.DiscoverRunner'
35
36SECRET_KEY = "iufoj=mibkpdz*%bob952x(%49rqgv8gg45k36kjcg76&-y5=!"
37
38PASSWORD_HASHERS = (
39 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
40)
410
=== modified file '.travis.yml'
--- .travis.yml 2014-06-26 15:08:13 +0000
+++ .travis.yml 2015-08-20 18:49:21 +0000
@@ -3,20 +3,25 @@
3 - sudo apt-get update3 - sudo apt-get update
4 - sudo apt-get install csstidy libxml2-dev libxslt-dev4 - sudo apt-get install csstidy libxml2-dev libxslt-dev
5install:5install:
6 - pip install tox coveralls6 - pip install tox
7script:7script:
8 - tox8 - tox
9env:9env:
10 - TOXENV=py33-1.6.X10 - TOXENV=py26-1.4.X
11 - TOXENV=py32-1.6.X
12 - TOXENV=py27-1.6.X
13 - TOXENV=py26-1.6.X
14 - TOXENV=py33-1.5.X
15 - TOXENV=py32-1.5.X
16 - TOXENV=py27-1.5.X
17 - TOXENV=py26-1.5.X11 - TOXENV=py26-1.5.X
18 - TOXENV=py27-1.4.X12 - TOXENV=py27-1.4.X
19 - TOXENV=py26-1.4.X13 - TOXENV=py27-1.5.X
14 - TOXENV=py26-1.6.X
15 - TOXENV=py27-1.6.X
16 - TOXENV=py32-1.6.X
17 - TOXENV=py33-1.6.X
18 - TOXENV=py27-1.7.X
19 - TOXENV=py32-1.7.X
20 - TOXENV=py33-1.7.X
21 - TOXENV=py34-1.7.X
22 - TOXENV=py27-1.8.X
23 - TOXENV=py32-1.8.X
24 - TOXENV=py33-1.8.X
25 - TOXENV=py34-1.8.X
20notifications:26notifications:
21 irc: "irc.freenode.org#django-compressor"27 irc: "irc.freenode.org#django-compressor"
22after_success: coveralls
2328
=== modified file 'AUTHORS'
--- AUTHORS 2015-01-06 14:31:34 +0000
+++ AUTHORS 2015-08-20 18:49:21 +0000
@@ -29,6 +29,7 @@
29Boris Shemigon29Boris Shemigon
30Brad Whittington30Brad Whittington
31Bruno Renié31Bruno Renié
32Carlton Gibson
32Cassus Adam Banko33Cassus Adam Banko
33Chris Adams34Chris Adams
34Chris Streeter35Chris Streeter
3536
=== modified file 'README.rst'
--- README.rst 2015-01-06 14:31:34 +0000
+++ README.rst 2015-08-20 18:49:21 +0000
@@ -4,16 +4,19 @@
4.. image:: https://coveralls.io/repos/django-compressor/django-compressor/badge.png?branch=develop 4.. image:: https://coveralls.io/repos/django-compressor/django-compressor/badge.png?branch=develop
5 :target: https://coveralls.io/r/django-compressor/django-compressor?branch=develop5 :target: https://coveralls.io/r/django-compressor/django-compressor?branch=develop
66
7.. image:: https://pypip.in/v/django_compressor/badge.png7.. image:: https://pypip.in/v/django_compressor/badge.svg
8 :target: https://pypi.python.org/pypi/django_compressor8 :target: https://pypi.python.org/pypi/django_compressor
99
10.. image:: https://pypip.in/d/django_compressor/badge.png10.. image:: https://pypip.in/d/django_compressor/badge.svg
11 :target: https://pypi.python.org/pypi/django_compressor11 :target: https://pypi.python.org/pypi/django_compressor
1212
13.. image:: https://secure.travis-ci.org/django-compressor/django-compressor.png?branch=develop13.. image:: https://secure.travis-ci.org/django-compressor/django-compressor.svg?branch=develop
14 :alt: Build Status14 :alt: Build Status
15 :target: http://travis-ci.org/django-compressor/django-compressor15 :target: http://travis-ci.org/django-compressor/django-compressor
1616
17.. image:: https://caniusepython3.com/project/django_compressor.svg
18 :target: https://caniusepython3.com/project/django_compressor
19
17Django Compressor combines and compresses linked and inline Javascript20Django Compressor combines and compresses linked and inline Javascript
18or CSS in a Django template into cacheable static files by using the21or CSS in a Django template into cacheable static files by using the
19``compress`` template tag.22``compress`` template tag.
2023
=== modified file 'compressor/__init__.py'
--- compressor/__init__.py 2015-01-06 14:31:34 +0000
+++ compressor/__init__.py 2015-08-20 18:49:21 +0000
@@ -1,2 +1,2 @@
1# following PEP 3861# following PEP 386
2__version__ = "1.4a1"2__version__ = "1.5"
33
=== modified file 'compressor/base.py'
--- compressor/base.py 2015-01-06 14:31:34 +0000
+++ compressor/base.py 2015-08-20 18:49:21 +0000
@@ -5,7 +5,10 @@
5from django.core.files.base import ContentFile5from django.core.files.base import ContentFile
6from django.template import Context6from django.template import Context
7from django.template.loader import render_to_string7from django.template.loader import render_to_string
8from django.utils.importlib import import_module8try:
9 from importlib import import_module
10except:
11 from django.utils.importlib import import_module
9from django.utils.safestring import mark_safe12from django.utils.safestring import mark_safe
1013
11try:14try:
@@ -140,6 +143,9 @@
140 """143 """
141 Reads file contents using given `charset` and returns it as text.144 Reads file contents using given `charset` and returns it as text.
142 """145 """
146 if charset == 'utf-8':
147 # Removes BOM
148 charset = 'utf-8-sig'
143 with codecs.open(filename, 'r', charset) as fd:149 with codecs.open(filename, 'r', charset) as fd:
144 try:150 try:
145 return fd.read()151 return fd.read()
@@ -247,7 +253,7 @@
247 mod_name, cls_name = get_mod_func(filter_or_command)253 mod_name, cls_name = get_mod_func(filter_or_command)
248 try:254 try:
249 mod = import_module(mod_name)255 mod = import_module(mod_name)
250 except ImportError:256 except (ImportError, TypeError):
251 filter = CompilerFilter(257 filter = CompilerFilter(
252 content, filter_type=self.type, filename=filename,258 content, filter_type=self.type, filename=filename,
253 charset=charset, command=filter_or_command)259 charset=charset, command=filter_or_command)
254260
=== modified file 'compressor/cache.py'
--- compressor/cache.py 2015-01-06 14:31:34 +0000
+++ compressor/cache.py 2015-08-20 18:49:21 +0000
@@ -4,11 +4,21 @@
4import socket4import socket
5import time5import time
66
7from django.core.cache import get_cache7try:
8 from django.core.cache import caches
9 def get_cache(name):
10 return caches[name]
11except ImportError:
12 from django.core.cache import get_cache
13
8from django.core.files.base import ContentFile14from django.core.files.base import ContentFile
9from django.utils.encoding import force_text, smart_bytes15from django.utils.encoding import force_text, smart_bytes
10from django.utils.functional import SimpleLazyObject16from django.utils.functional import SimpleLazyObject
11from django.utils.importlib import import_module17
18try:
19 from importlib import import_module
20except:
21 from django.utils.importlib import import_module
1222
13from compressor.conf import settings23from compressor.conf import settings
14from compressor.storage import default_storage24from compressor.storage import default_storage
@@ -39,7 +49,7 @@
39 mod_name, func_name = get_mod_func(49 mod_name, func_name = get_mod_func(
40 settings.COMPRESS_CACHE_KEY_FUNCTION)50 settings.COMPRESS_CACHE_KEY_FUNCTION)
41 _cachekey_func = getattr(import_module(mod_name), func_name)51 _cachekey_func = getattr(import_module(mod_name), func_name)
42 except (AttributeError, ImportError) as e:52 except (AttributeError, ImportError, TypeError) as e:
43 raise ImportError("Couldn't import cache key function %s: %s" %53 raise ImportError("Couldn't import cache key function %s: %s" %
44 (settings.COMPRESS_CACHE_KEY_FUNCTION, e))54 (settings.COMPRESS_CACHE_KEY_FUNCTION, e))
45 return _cachekey_func(*args, **kwargs)55 return _cachekey_func(*args, **kwargs)
4656
=== modified file 'compressor/conf.py'
--- compressor/conf.py 2015-01-06 14:31:34 +0000
+++ compressor/conf.py 2015-08-20 18:49:21 +0000
@@ -45,6 +45,8 @@
45 YUGLIFY_BINARY = 'yuglify'45 YUGLIFY_BINARY = 'yuglify'
46 YUGLIFY_CSS_ARGUMENTS = '--terminal'46 YUGLIFY_CSS_ARGUMENTS = '--terminal'
47 YUGLIFY_JS_ARGUMENTS = '--terminal'47 YUGLIFY_JS_ARGUMENTS = '--terminal'
48 CLEAN_CSS_BINARY = 'cleancss'
49 CLEAN_CSS_ARGUMENTS = ''
48 DATA_URI_MAX_SIZE = 102450 DATA_URI_MAX_SIZE = 1024
4951
50 # the cache backend to use52 # the cache backend to use
5153
=== modified file 'compressor/css.py'
--- compressor/css.py 2014-06-26 15:08:13 +0000
+++ compressor/css.py 2015-08-20 18:49:21 +0000
@@ -34,7 +34,7 @@
34 self.media_nodes[-1][1].split_content.append(data)34 self.media_nodes[-1][1].split_content.append(data)
35 else:35 else:
36 node = self.__class__(content=self.parser.elem_str(elem),36 node = self.__class__(content=self.parser.elem_str(elem),
37 context=self.context)37 context=self.context)
38 node.split_content.append(data)38 node.split_content.append(data)
39 self.media_nodes.append((media, node))39 self.media_nodes.append((media, node))
40 return self.split_content40 return self.split_content
4141
=== modified file 'compressor/filters/base.py'
--- compressor/filters/base.py 2015-01-06 14:31:34 +0000
+++ compressor/filters/base.py 2015-08-20 18:49:21 +0000
@@ -3,9 +3,27 @@
3import logging3import logging
4import subprocess4import subprocess
55
6from platform import system
7
8if system() != "Windows":
9 try:
10 from shlex import quote as shell_quote # Python 3
11 except ImportError:
12 from pipes import quote as shell_quote # Python 2
13else:
14 from subprocess import list2cmdline
15 def shell_quote(s):
16 # shlex.quote/pipes.quote is not compatible with Windows
17 return list2cmdline([s])
18
6from django.core.exceptions import ImproperlyConfigured19from django.core.exceptions import ImproperlyConfigured
7from django.core.files.temp import NamedTemporaryFile20from django.core.files.temp import NamedTemporaryFile
8from django.utils.importlib import import_module21
22try:
23 from importlib import import_module
24except ImportError:
25 from django.utils.importlib import import_module
26
9from django.utils.encoding import smart_text27from django.utils.encoding import smart_text
10from django.utils import six28from django.utils import six
1129
@@ -26,7 +44,7 @@
26 """44 """
27 def __init__(self, content, filter_type=None, filename=None, verbose=0,45 def __init__(self, content, filter_type=None, filename=None, verbose=0,
28 charset=None):46 charset=None):
29 self.type = filter_type47 self.type = filter_type or getattr(self, 'type', None)
30 self.content = content48 self.content = content
31 self.verbose = verbose or settings.COMPRESS_VERBOSE49 self.verbose = verbose or settings.COMPRESS_VERBOSE
32 self.logger = logger50 self.logger = logger
@@ -65,7 +83,7 @@
65 try:83 try:
66 mod_name, func_name = get_mod_func(self.callback)84 mod_name, func_name = get_mod_func(self.callback)
67 func = getattr(import_module(mod_name), func_name)85 func = getattr(import_module(mod_name), func_name)
68 except ImportError:86 except (ImportError, TypeError):
69 if self.dependencies:87 if self.dependencies:
70 if len(self.dependencies) == 1:88 if len(self.dependencies) == 1:
71 warning = "dependency (%s) is" % self.dependencies[0]89 warning = "dependency (%s) is" % self.dependencies[0]
@@ -147,6 +165,12 @@
147 self.outfile = NamedTemporaryFile(mode='r+', suffix=ext)165 self.outfile = NamedTemporaryFile(mode='r+', suffix=ext)
148 options["outfile"] = self.outfile.name166 options["outfile"] = self.outfile.name
149167
168 # Quote infile and outfile for spaces etc.
169 if "infile" in options:
170 options["infile"] = shell_quote(options["infile"])
171 if "outfile" in options:
172 options["outfile"] = shell_quote(options["outfile"])
173
150 try:174 try:
151 command = self.command.format(**options)175 command = self.command.format(**options)
152 proc = subprocess.Popen(176 proc = subprocess.Popen(
153177
=== added file 'compressor/filters/cleancss.py'
--- compressor/filters/cleancss.py 1970-01-01 00:00:00 +0000
+++ compressor/filters/cleancss.py 2015-08-20 18:49:21 +0000
@@ -0,0 +1,10 @@
1from compressor.conf import settings
2from compressor.filters import CompilerFilter
3
4
5class CleanCSSFilter(CompilerFilter):
6 command = "{binary} {args} -o {outfile} {infile}"
7 options = (
8 ("binary", settings.COMPRESS_CLEAN_CSS_BINARY),
9 ("args", settings.COMPRESS_CLEAN_CSS_ARGUMENTS),
10 )
011
=== modified file 'compressor/filters/css_default.py'
--- compressor/filters/css_default.py 2015-01-06 14:31:34 +0000
+++ compressor/filters/css_default.py 2015-08-20 18:49:21 +0000
@@ -5,7 +5,6 @@
5from compressor.cache import get_hashed_mtime, get_hashed_content5from compressor.cache import get_hashed_mtime, get_hashed_content
6from compressor.conf import settings6from compressor.conf import settings
7from compressor.filters import FilterBase, FilterError7from compressor.filters import FilterBase, FilterError
8from compressor.utils import staticfiles
98
10URL_PATTERN = re.compile(r'url\(([^\)]+)\)')9URL_PATTERN = re.compile(r'url\(([^\)]+)\)')
11SRC_PATTERN = re.compile(r'src=([\'"])(.+?)\1')10SRC_PATTERN = re.compile(r'src=([\'"])(.+?)\1')
@@ -22,10 +21,7 @@
22 self.has_scheme = False21 self.has_scheme = False
2322
24 def input(self, filename=None, basename=None, **kwargs):23 def input(self, filename=None, basename=None, **kwargs):
25 if filename is not None:24 if not filename:
26 filename = os.path.normcase(os.path.abspath(filename))
27 if (not (filename and filename.startswith(self.root)) and
28 not self.find(basename)):
29 return self.content25 return self.content
30 self.path = basename.replace(os.sep, '/')26 self.path = basename.replace(os.sep, '/')
31 self.path = self.path.lstrip('/')27 self.path = self.path.lstrip('/')
@@ -40,10 +36,6 @@
40 return SRC_PATTERN.sub(self.src_converter,36 return SRC_PATTERN.sub(self.src_converter,
41 URL_PATTERN.sub(self.url_converter, self.content))37 URL_PATTERN.sub(self.url_converter, self.content))
4238
43 def find(self, basename):
44 if settings.DEBUG and basename and staticfiles.finders:
45 return staticfiles.finders.find(basename)
46
47 def guess_filename(self, url):39 def guess_filename(self, url):
48 local_path = url40 local_path = url
49 if self.has_scheme:41 if self.has_scheme:
@@ -70,6 +62,8 @@
70 suffix = get_hashed_mtime(filename)62 suffix = get_hashed_mtime(filename)
71 elif settings.COMPRESS_CSS_HASHING_METHOD in ("hash", "content"):63 elif settings.COMPRESS_CSS_HASHING_METHOD in ("hash", "content"):
72 suffix = get_hashed_content(filename)64 suffix = get_hashed_content(filename)
65 elif settings.COMPRESS_CSS_HASHING_METHOD is None:
66 suffix = None
73 else:67 else:
74 raise FilterError('COMPRESS_CSS_HASHING_METHOD is configured '68 raise FilterError('COMPRESS_CSS_HASHING_METHOD is configured '
75 'with an unknown method (%s).' %69 'with an unknown method (%s).' %
7670
=== modified file 'compressor/js.py'
--- compressor/js.py 2012-10-14 10:51:47 +0000
+++ compressor/js.py 2015-08-20 18:49:21 +0000
@@ -12,14 +12,42 @@
12 def split_contents(self):12 def split_contents(self):
13 if self.split_content:13 if self.split_content:
14 return self.split_content14 return self.split_content
15 self.extra_nodes = []
15 for elem in self.parser.js_elems():16 for elem in self.parser.js_elems():
16 attribs = self.parser.elem_attribs(elem)17 attribs = self.parser.elem_attribs(elem)
17 if 'src' in attribs:18 if 'src' in attribs:
18 basename = self.get_basename(attribs['src'])19 basename = self.get_basename(attribs['src'])
19 filename = self.get_filename(basename)20 filename = self.get_filename(basename)
20 content = (SOURCE_FILE, filename, basename, elem)21 content = (SOURCE_FILE, filename, basename, elem)
21 self.split_content.append(content)22 else:
22 else:23 content = (SOURCE_HUNK, self.parser.elem_content(elem), None, elem)
23 content = self.parser.elem_content(elem)24 self.split_content.append(content)
24 self.split_content.append((SOURCE_HUNK, content, None, elem))25 if 'async' in attribs:
26 extra = ' async'
27 elif 'defer' in attribs:
28 extra = ' defer'
29 else:
30 extra = ''
31 # Append to the previous node if it had the same attribute
32 append_to_previous = (self.extra_nodes and
33 self.extra_nodes[-1][0] == extra)
34 if append_to_previous and settings.COMPRESS_ENABLED:
35 self.extra_nodes[-1][1].split_content.append(content)
36 else:
37 node = self.__class__(content=self.parser.elem_str(elem),
38 context=self.context)
39 node.split_content.append(content)
40 self.extra_nodes.append((extra, node))
25 return self.split_content41 return self.split_content
42
43 def output(self, *args, **kwargs):
44 if (settings.COMPRESS_ENABLED or settings.COMPRESS_PRECOMPILERS or
45 kwargs.get('forced', False)):
46 self.split_contents()
47 if hasattr(self, 'extra_nodes'):
48 ret = []
49 for extra, subnode in self.extra_nodes:
50 subnode.extra_context.update({'extra': extra})
51 ret.append(subnode.output(*args, **kwargs))
52 return '\n'.join(ret)
53 return super(JsCompressor, self).output(*args, **kwargs)
2654
=== modified file 'compressor/management/commands/compress.py'
--- compressor/management/commands/compress.py 2015-01-06 14:31:34 +0000
+++ compressor/management/commands/compress.py 2015-08-20 18:49:21 +0000
@@ -5,6 +5,7 @@
5from fnmatch import fnmatch5from fnmatch import fnmatch
6from optparse import make_option6from optparse import make_option
77
8import django
8from django.core.management.base import NoArgsCommand, CommandError9from django.core.management.base import NoArgsCommand, CommandError
9import django.template10import django.template
10from django.template import Context11from django.template import Context
@@ -53,24 +54,30 @@
53 requires_model_validation = False54 requires_model_validation = False
5455
55 def get_loaders(self):56 def get_loaders(self):
56 from django.template.loader import template_source_loaders57 if django.VERSION < (1, 8):
57 if template_source_loaders is None:
58 try:
59 from django.template.loader import (
60 find_template as finder_func)
61 except ImportError:
62 from django.template.loader import (
63 find_template_source as finder_func) # noqa
64 try:
65 # Force django to calculate template_source_loaders from
66 # TEMPLATE_LOADERS settings, by asking to find a dummy template
67 source, name = finder_func('test')
68 except django.template.TemplateDoesNotExist:
69 pass
70 # Reload template_source_loaders now that it has been calculated ;
71 # it should contain the list of valid, instanciated template loaders
72 # to use.
73 from django.template.loader import template_source_loaders58 from django.template.loader import template_source_loaders
59 if template_source_loaders is None:
60 try:
61 from django.template.loader import (
62 find_template as finder_func)
63 except ImportError:
64 from django.template.loader import (
65 find_template_source as finder_func) # noqa
66 try:
67 # Force django to calculate template_source_loaders from
68 # TEMPLATE_LOADERS settings, by asking to find a dummy template
69 source, name = finder_func('test')
70 except django.template.TemplateDoesNotExist:
71 pass
72 # Reload template_source_loaders now that it has been calculated ;
73 # it should contain the list of valid, instanciated template loaders
74 # to use.
75 from django.template.loader import template_source_loaders
76 else:
77 from django.template import engines
78 template_source_loaders = []
79 for e in engines.all():
80 template_source_loaders.extend(e.engine.get_template_loaders(e.engine.loaders))
74 loaders = []81 loaders = []
75 # If template loader is CachedTemplateLoader, return the loaders82 # If template loader is CachedTemplateLoader, return the loaders
76 # that it wraps around. So if we have83 # that it wraps around. So if we have
@@ -130,7 +137,7 @@
130 if get_template_sources is None:137 if get_template_sources is None:
131 get_template_sources = loader.get_template_sources138 get_template_sources = loader.get_template_sources
132 paths.update(list(get_template_sources('')))139 paths.update(list(get_template_sources('')))
133 except (ImportError, AttributeError):140 except (ImportError, AttributeError, TypeError):
134 # Yeah, this didn't work out so well, let's move on141 # Yeah, this didn't work out so well, let's move on
135 pass142 pass
136 if not paths:143 if not paths:
137144
=== modified file 'compressor/offline/django.py'
--- compressor/offline/django.py 2014-06-26 15:08:13 +0000
+++ compressor/offline/django.py 2015-08-20 18:49:21 +0000
@@ -1,13 +1,13 @@
1from __future__ import absolute_import1from __future__ import absolute_import
2import io
3from copy import copy2from copy import copy
43
4import django
5from django import template5from django import template
6from django.conf import settings6from django.conf import settings
7from django.template import Template
8from django.template import Context7from django.template import Context
9from django.template.base import Node, VariableNode, TextNode, NodeList8from django.template.base import Node, VariableNode, TextNode, NodeList
10from django.template.defaulttags import IfNode9from django.template.defaulttags import IfNode
10from django.template.loader import get_template
11from django.template.loader_tags import ExtendsNode, BlockNode, BlockContext11from django.template.loader_tags import ExtendsNode, BlockNode, BlockContext
1212
1313
@@ -15,7 +15,7 @@
15from compressor.templatetags.compress import CompressorNode15from compressor.templatetags.compress import CompressorNode
1616
1717
18def handle_extendsnode(extendsnode, block_context=None):18def handle_extendsnode(extendsnode, block_context=None, original=None):
19 """Create a copy of Node tree of a derived template replacing19 """Create a copy of Node tree of a derived template replacing
20 all blocks tags with the nodes of appropriate blocks.20 all blocks tags with the nodes of appropriate blocks.
21 Also handles {{ block.super }} tags.21 Also handles {{ block.super }} tags.
@@ -27,6 +27,9 @@
27 block_context.add_blocks(blocks)27 block_context.add_blocks(blocks)
2828
29 context = Context(settings.COMPRESS_OFFLINE_CONTEXT)29 context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
30 if original is not None:
31 context.template = original
32
30 compiled_parent = extendsnode.get_parent(context)33 compiled_parent = extendsnode.get_parent(context)
31 parent_nodelist = compiled_parent.nodelist34 parent_nodelist = compiled_parent.nodelist
32 # If the parent template has an ExtendsNode it is not the root.35 # If the parent template has an ExtendsNode it is not the root.
@@ -34,7 +37,7 @@
34 # The ExtendsNode has to be the first non-text node.37 # The ExtendsNode has to be the first non-text node.
35 if not isinstance(node, TextNode):38 if not isinstance(node, TextNode):
36 if isinstance(node, ExtendsNode):39 if isinstance(node, ExtendsNode):
37 return handle_extendsnode(node, block_context)40 return handle_extendsnode(node, block_context, original)
38 break41 break
39 # Add blocks of the root template to block context.42 # Add blocks of the root template to block context.
40 blocks = dict((n.name, n) for n in43 blocks = dict((n.name, n) for n in
@@ -55,6 +58,8 @@
55 if not block_stack:58 if not block_stack:
56 continue59 continue
57 node = block_context.get_block(block_stack[-1].name)60 node = block_context.get_block(block_stack[-1].name)
61 if not node:
62 continue
58 if isinstance(node, BlockNode):63 if isinstance(node, BlockNode):
59 expanded_block = expand_blocknode(node, block_stack, block_context)64 expanded_block = expand_blocknode(node, block_stack, block_context)
60 new_nodelist.extend(expanded_block)65 new_nodelist.extend(expanded_block)
@@ -93,13 +98,15 @@
93 self.charset = charset98 self.charset = charset
9499
95 def parse(self, template_name):100 def parse(self, template_name):
96 with io.open(template_name, mode='rb') as file:101 try:
97 try:102 if django.VERSION < (1, 8):
98 return Template(file.read().decode(self.charset))103 return get_template(template_name)
99 except template.TemplateSyntaxError as e:104 else:
100 raise TemplateSyntaxError(str(e))105 return get_template(template_name).template
101 except template.TemplateDoesNotExist as e:106 except template.TemplateSyntaxError as e:
102 raise TemplateDoesNotExist(str(e))107 raise TemplateSyntaxError(str(e))
108 except template.TemplateDoesNotExist as e:
109 raise TemplateDoesNotExist(str(e))
103110
104 def process_template(self, template, context):111 def process_template(self, template, context):
105 return True112 return True
@@ -111,15 +118,17 @@
111 pass118 pass
112119
113 def render_nodelist(self, template, context, node):120 def render_nodelist(self, template, context, node):
121 if django.VERSION >= (1, 8):
122 context.template = template
114 return node.nodelist.render(context)123 return node.nodelist.render(context)
115124
116 def render_node(self, template, context, node):125 def render_node(self, template, context, node):
117 return node.render(context, forced=True)126 return node.render(context, forced=True)
118127
119 def get_nodelist(self, node):128 def get_nodelist(self, node, original=None):
120 if isinstance(node, ExtendsNode):129 if isinstance(node, ExtendsNode):
121 try:130 try:
122 return handle_extendsnode(node)131 return handle_extendsnode(node, block_context=None, original=original)
123 except template.TemplateSyntaxError as e:132 except template.TemplateSyntaxError as e:
124 raise TemplateSyntaxError(str(e))133 raise TemplateSyntaxError(str(e))
125 except template.TemplateDoesNotExist as e:134 except template.TemplateDoesNotExist as e:
@@ -134,10 +143,12 @@
134 nodelist = getattr(node, 'nodelist', [])143 nodelist = getattr(node, 'nodelist', [])
135 return nodelist144 return nodelist
136145
137 def walk_nodes(self, node):146 def walk_nodes(self, node, original=None):
138 for node in self.get_nodelist(node):147 if django.VERSION >= (1, 8) and original is None:
148 original = node
149 for node in self.get_nodelist(node, original):
139 if isinstance(node, CompressorNode) and node.is_offline_compression_enabled(forced=True):150 if isinstance(node, CompressorNode) and node.is_offline_compression_enabled(forced=True):
140 yield node151 yield node
141 else:152 else:
142 for node in self.walk_nodes(node):153 for node in self.walk_nodes(node, original):
143 yield node154 yield node
144155
=== modified file 'compressor/parser/__init__.py'
--- compressor/parser/__init__.py 2014-06-26 15:08:13 +0000
+++ compressor/parser/__init__.py 2015-08-20 18:49:21 +0000
@@ -1,6 +1,9 @@
1from django.utils import six1from django.utils import six
2from django.utils.functional import LazyObject2from django.utils.functional import LazyObject
3from django.utils.importlib import import_module3try:
4 from importlib import import_module
5except ImportError:
6 from django.utils.importlib import import_module
47
5# support legacy parser module usage8# support legacy parser module usage
6from compressor.parser.base import ParserBase # noqa9from compressor.parser.base import ParserBase # noqa
@@ -30,5 +33,5 @@
30 import_module(dependency)33 import_module(dependency)
31 self._wrapped = parser(content)34 self._wrapped = parser(content)
32 break35 break
33 except ImportError:36 except (ImportError, TypeError):
34 continue37 continue
3538
=== modified file 'compressor/templates/compressor/js_file.html'
--- compressor/templates/compressor/js_file.html 2012-10-14 10:51:47 +0000
+++ compressor/templates/compressor/js_file.html 2015-08-20 18:49:21 +0000
@@ -1,1 +1,1 @@
1<script type="text/javascript" src="{{ compressed.url }}"></script>
2\ No newline at end of file1\ No newline at end of file
2<script type="text/javascript" src="{{ compressed.url }}"{{ compressed.extra }}></script>
3\ No newline at end of file3\ No newline at end of file
44
=== modified file 'compressor/test_settings.py'
--- compressor/test_settings.py 2015-01-06 14:31:34 +0000
+++ compressor/test_settings.py 2015-08-20 18:49:21 +0000
@@ -3,7 +3,13 @@
33
4TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')4TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
55
6COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'6
7CACHES = {
8 'default': {
9 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
10 'LOCATION': 'unique-snowflake'
11 }
12}
713
8DATABASES = {14DATABASES = {
9 'default': {15 'default': {
@@ -13,8 +19,17 @@
13}19}
1420
15INSTALLED_APPS = [21INSTALLED_APPS = [
22 'django.contrib.staticfiles',
16 'compressor',23 'compressor',
17 'jingo',24 'coffin',
25]
26if django.VERSION < (1, 8):
27 INSTALLED_APPS.append('jingo')
28
29STATICFILES_FINDERS = [
30 'django.contrib.staticfiles.finders.FileSystemFinder',
31 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
32 'compressor.finders.CompressorFinder',
18]33]
1934
20STATIC_URL = '/static/'35STATIC_URL = '/static/'
@@ -37,3 +52,5 @@
37PASSWORD_HASHERS = (52PASSWORD_HASHERS = (
38 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',53 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
39)54)
55
56MIDDLEWARE_CLASSES = []
4057
=== modified file 'compressor/tests/precompiler.py'
--- compressor/tests/precompiler.py 2014-06-26 15:08:13 +0000
+++ compressor/tests/precompiler.py 2015-08-20 18:49:21 +0000
@@ -7,11 +7,11 @@
7def main():7def main():
8 p = optparse.OptionParser()8 p = optparse.OptionParser()
9 p.add_option('-f', '--file', action="store",9 p.add_option('-f', '--file', action="store",
10 type="string", dest="filename",10 type="string", dest="filename",
11 help="File to read from, defaults to stdin", default=None)11 help="File to read from, defaults to stdin", default=None)
12 p.add_option('-o', '--output', action="store",12 p.add_option('-o', '--output', action="store",
13 type="string", dest="outfile",13 type="string", dest="outfile",
14 help="File to write to, defaults to stdout", default=None)14 help="File to write to, defaults to stdout", default=None)
1515
16 options, arguments = p.parse_args()16 options, arguments = p.parse_args()
1717
1818
=== added file 'compressor/tests/static/css/filename with spaces.css'
--- compressor/tests/static/css/filename with spaces.css 1970-01-01 00:00:00 +0000
+++ compressor/tests/static/css/filename with spaces.css 2015-08-20 18:49:21 +0000
@@ -0,0 +1,1 @@
1body { background:#424242; }
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'compressor/tests/static/css/utf-8_with-BOM.css'
--- compressor/tests/static/css/utf-8_with-BOM.css 1970-01-01 00:00:00 +0000
+++ compressor/tests/static/css/utf-8_with-BOM.css 2015-08-20 18:49:21 +0000
@@ -0,0 +1,1 @@
1.compress-test {color: red;}
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'compressor/tests/static/js/three.js'
--- compressor/tests/static/js/three.js 1970-01-01 00:00:00 +0000
+++ compressor/tests/static/js/three.js 2015-08-20 18:49:21 +0000
@@ -0,0 +1,1 @@
1hermanos = {}
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'compressor/tests/static/js/two.js'
--- compressor/tests/static/js/two.js 1970-01-01 00:00:00 +0000
+++ compressor/tests/static/js/two.js 2015-08-20 18:49:21 +0000
@@ -0,0 +1,1 @@
1pollos = {}
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'compressor/tests/test_base.py'
--- compressor/tests/test_base.py 2015-01-06 14:31:34 +0000
+++ compressor/tests/test_base.py 2015-08-20 18:49:21 +0000
@@ -12,11 +12,13 @@
12from django.test import SimpleTestCase12from django.test import SimpleTestCase
13from django.test.utils import override_settings13from django.test.utils import override_settings
1414
15from compressor.base import SOURCE_HUNK, SOURCE_FILE15from compressor import cache as cachemod
16from compressor.base import SOURCE_FILE, SOURCE_HUNK
17from compressor.cache import get_cachekey
16from compressor.conf import settings18from compressor.conf import settings
17from compressor.css import CssCompressor19from compressor.css import CssCompressor
20from compressor.exceptions import FilterDoesNotExist, FilterError
18from compressor.js import JsCompressor21from compressor.js import JsCompressor
19from compressor.exceptions import FilterDoesNotExist
2022
2123
22def make_soup(markup):24def make_soup(markup):
@@ -112,6 +114,14 @@
112 hunks = '\n'.join([h for h in self.css_node.hunks()])114 hunks = '\n'.join([h for h in self.css_node.hunks()])
113 self.assertEqual(out, hunks)115 self.assertEqual(out, hunks)
114116
117 def test_css_output_with_bom_input(self):
118 out = 'body { background:#990; }\n.compress-test {color: red;}'
119 css = ("""<link rel="stylesheet" href="/static/css/one.css" type="text/css" />
120 <link rel="stylesheet" href="/static/css/utf-8_with-BOM.css" type="text/css" />""")
121 css_node_with_bom = CssCompressor(css)
122 hunks = '\n'.join([h for h in css_node_with_bom.hunks()])
123 self.assertEqual(out, hunks)
124
115 def test_css_mtimes(self):125 def test_css_mtimes(self):
116 is_date = re.compile(r'^\d{10}[\.\d]+$')126 is_date = re.compile(r'^\d{10}[\.\d]+$')
117 for date in self.css_node.mtimes:127 for date in self.css_node.mtimes:
@@ -208,6 +218,14 @@
208 css_node = CssCompressor(css)218 css_node = CssCompressor(css)
209 self.assertRaises(FilterDoesNotExist, css_node.output, 'inline')219 self.assertRaises(FilterDoesNotExist, css_node.output, 'inline')
210220
221 @override_settings(COMPRESS_PRECOMPILERS=(
222 ('text/foobar', './foo -I ./bar/baz'),
223 ), COMPRESS_ENABLED=True)
224 def test_command_with_dot_precompiler(self):
225 css = '<style type="text/foobar">p { border:10px solid red;}</style>'
226 css_node = CssCompressor(css)
227 self.assertRaises(FilterError, css_node.output, 'inline')
228
211229
212class CssMediaTestCase(SimpleTestCase):230class CssMediaTestCase(SimpleTestCase):
213 def setUp(self):231 def setUp(self):
@@ -267,4 +285,49 @@
267285
268 def test_correct_backend(self):286 def test_correct_backend(self):
269 from compressor.cache import cache287 from compressor.cache import cache
270 self.assertEqual(cache.__class__, locmem.CacheClass)288 self.assertEqual(cache.__class__, locmem.LocMemCache)
289
290
291class JsAsyncDeferTestCase(SimpleTestCase):
292 def setUp(self):
293 self.js = """\
294 <script src="/static/js/one.js" type="text/javascript"></script>
295 <script src="/static/js/two.js" type="text/javascript" async></script>
296 <script src="/static/js/three.js" type="text/javascript" defer></script>
297 <script type="text/javascript">obj.value = "value";</script>
298 <script src="/static/js/one.js" type="text/javascript" async></script>
299 <script src="/static/js/two.js" type="text/javascript" async></script>
300 <script src="/static/js/three.js" type="text/javascript"></script>"""
301
302 def test_js_output(self):
303 def extract_attr(tag):
304 if tag.has_attr('async'):
305 return 'async'
306 if tag.has_attr('defer'):
307 return 'defer'
308 js_node = JsCompressor(self.js)
309 output = [None, 'async', 'defer', None, 'async', None]
310 if six.PY3:
311 scripts = make_soup(js_node.output()).find_all('script')
312 attrs = [extract_attr(i) for i in scripts]
313 else:
314 scripts = make_soup(js_node.output()).findAll('script')
315 attrs = [s.get('async') or s.get('defer') for s in scripts]
316 self.assertEqual(output, attrs)
317
318
319class CacheTestCase(SimpleTestCase):
320
321 def setUp(self):
322 cachemod._cachekey_func = None
323
324 def test_get_cachekey_basic(self):
325 self.assertEqual(get_cachekey("foo"), "django_compressor.foo")
326
327 @override_settings(COMPRESS_CACHE_KEY_FUNCTION='.leading.dot')
328 def test_get_cachekey_leading_dot(self):
329 self.assertRaises(ImportError, lambda: get_cachekey("foo"))
330
331 @override_settings(COMPRESS_CACHE_KEY_FUNCTION='invalid.module')
332 def test_get_cachekey_invalid_mod(self):
333 self.assertRaises(ImportError, lambda: get_cachekey("foo"))
271334
=== modified file 'compressor/tests/test_filters.py'
--- compressor/tests/test_filters.py 2015-01-06 14:31:34 +0000
+++ compressor/tests/test_filters.py 2015-08-20 18:49:21 +0000
@@ -1,4 +1,5 @@
1from __future__ import with_statement, unicode_literals1from __future__ import with_statement, unicode_literals
2from collections import defaultdict
2import io3import io
3import os4import os
4import sys5import sys
@@ -17,9 +18,18 @@
17from compressor.filters.cssmin import CSSMinFilter18from compressor.filters.cssmin import CSSMinFilter
18from compressor.filters.css_default import CssAbsoluteFilter19from compressor.filters.css_default import CssAbsoluteFilter
19from compressor.filters.template import TemplateFilter20from compressor.filters.template import TemplateFilter
21from compressor.filters.closure import ClosureCompilerFilter
22from compressor.filters.csstidy import CSSTidyFilter
23from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter
24from compressor.filters.yui import YUICSSFilter, YUIJSFilter
25from compressor.filters.cleancss import CleanCSSFilter
20from compressor.tests.test_base import test_dir26from compressor.tests.test_base import test_dir
2127
2228
29def blankdict(*args, **kwargs):
30 return defaultdict(lambda: '', *args, **kwargs)
31
32
23@unittest.skipIf(find_command(settings.COMPRESS_CSSTIDY_BINARY) is None,33@unittest.skipIf(find_command(settings.COMPRESS_CSSTIDY_BINARY) is None,
24 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY)34 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY)
25class CssTidyTestCase(TestCase):35class CssTidyTestCase(TestCase):
@@ -30,7 +40,6 @@
30 color: black;40 color: black;
31 }41 }
32 """)42 """)
33 from compressor.filters.csstidy import CSSTidyFilter
34 ret = CSSTidyFilter(content).input()43 ret = CSSTidyFilter(content).input()
35 self.assertIsInstance(ret, six.text_type)44 self.assertIsInstance(ret, six.text_type)
36 self.assertEqual(45 self.assertEqual(
@@ -39,10 +48,13 @@
3948
40class PrecompilerTestCase(TestCase):49class PrecompilerTestCase(TestCase):
41 def setUp(self):50 def setUp(self):
42 self.filename = os.path.join(test_dir, 'static/css/one.css')51 self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
52 self.setup_infile()
53
54 def setup_infile(self, filename='static/css/one.css'):
55 self.filename = os.path.join(test_dir, filename)
43 with io.open(self.filename, encoding=settings.FILE_CHARSET) as file:56 with io.open(self.filename, encoding=settings.FILE_CHARSET) as file:
44 self.content = file.read()57 self.content = file.read()
45 self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
4658
47 def test_precompiler_infile_outfile(self):59 def test_precompiler_infile_outfile(self):
48 command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)60 command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
@@ -51,6 +63,14 @@
51 charset=settings.FILE_CHARSET, command=command)63 charset=settings.FILE_CHARSET, command=command)
52 self.assertEqual("body { color:#990; }", compiler.input())64 self.assertEqual("body { color:#990; }", compiler.input())
5365
66 def test_precompiler_infile_with_spaces(self):
67 self.setup_infile('static/css/filename with spaces.css')
68 command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
69 compiler = CompilerFilter(
70 content=self.content, filename=self.filename,
71 charset=settings.FILE_CHARSET, command=command)
72 self.assertEqual("body { color:#424242; }", compiler.input())
73
54 def test_precompiler_infile_stdout(self):74 def test_precompiler_infile_stdout(self):
55 command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler)75 command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler)
56 compiler = CompilerFilter(76 compiler = CompilerFilter(
@@ -99,8 +119,8 @@
99class CssAbsolutizingTestCase(TestCase):119class CssAbsolutizingTestCase(TestCase):
100 hashing_method = 'mtime'120 hashing_method = 'mtime'
101 hashing_func = staticmethod(get_hashed_mtime)121 hashing_func = staticmethod(get_hashed_mtime)
102 content = ("p { background: url('../../img/python.png') }"122 template = ("p { background: url('%(url)simg/python.png%(query)s%(hash)s%(frag)s') }"
103 "p { filter: Alpha(src='../../img/python.png') }")123 "p { filter: Alpha(src='%(url)simg/python.png%(query)s%(hash)s%(frag)s') }")
104124
105 def setUp(self):125 def setUp(self):
106 self.old_enabled = settings.COMPRESS_ENABLED126 self.old_enabled = settings.COMPRESS_ENABLED
@@ -120,40 +140,55 @@
120 settings.COMPRESS_URL = self.old_url140 settings.COMPRESS_URL = self.old_url
121 settings.COMPRESS_CSS_HASHING_METHOD = self.old_hashing_method141 settings.COMPRESS_CSS_HASHING_METHOD = self.old_hashing_method
122142
143 def test_css_no_hash(self):
144 settings.COMPRESS_CSS_HASHING_METHOD = None
145 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
146 content = self.template % blankdict(url='../../')
147 params = blankdict({
148 'url': settings.COMPRESS_URL,
149 })
150 output = self.template % params
151 filter = CssAbsoluteFilter(content)
152 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
153
154 settings.COMPRESS_URL = params['url'] = 'http://static.example.com/'
155 output = self.template % params
156 filter = CssAbsoluteFilter(content)
157 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
158
123 def test_css_absolute_filter(self):159 def test_css_absolute_filter(self):
124 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')160 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
125 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')161 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
126 params = {162 content = self.template % blankdict(url='../../')
163 params = blankdict({
127 'url': settings.COMPRESS_URL,164 'url': settings.COMPRESS_URL,
128 'hash': self.hashing_func(imagefilename),165 'hash': '?' + self.hashing_func(imagefilename),
129 }166 })
130 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"167 output = self.template % params
131 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params168 filter = CssAbsoluteFilter(content)
132 filter = CssAbsoluteFilter(self.content)
133 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))169 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
170
134 settings.COMPRESS_URL = params['url'] = 'http://static.example.com/'171 settings.COMPRESS_URL = params['url'] = 'http://static.example.com/'
135 filter = CssAbsoluteFilter(self.content)172 output = self.template % params
136 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')173 filter = CssAbsoluteFilter(content)
137 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
138 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
139 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))174 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
140175
141 def test_css_absolute_filter_url_fragment(self):176 def test_css_absolute_filter_url_fragment(self):
142 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')177 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
143 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')178 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
144 params = {179 content = self.template % blankdict(url='../../', frag='#foo')
180 params = blankdict({
145 'url': settings.COMPRESS_URL,181 'url': settings.COMPRESS_URL,
146 'hash': self.hashing_func(imagefilename),182 'hash': '?' + self.hashing_func(imagefilename),
147 }183 'frag': '#foo',
148 content = "p { background: url('../../img/python.png#foo') }"184 })
149185 output = self.template % params
150 output = "p { background: url('%(url)simg/python.png?%(hash)s#foo') }" % params
151 filter = CssAbsoluteFilter(content)186 filter = CssAbsoluteFilter(content)
152 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))187 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
188
153 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'189 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'
190 output = self.template % params
154 filter = CssAbsoluteFilter(content)191 filter = CssAbsoluteFilter(content)
155 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
156 output = "p { background: url('%(url)simg/python.png?%(hash)s#foo') }" % params
157 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))192 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
158193
159 def test_css_absolute_filter_only_url_fragment(self):194 def test_css_absolute_filter_only_url_fragment(self):
@@ -161,64 +196,78 @@
161 content = "p { background: url('#foo') }"196 content = "p { background: url('#foo') }"
162 filter = CssAbsoluteFilter(content)197 filter = CssAbsoluteFilter(content)
163 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))198 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))
199
164 settings.COMPRESS_URL = 'http://media.example.com/'200 settings.COMPRESS_URL = 'http://media.example.com/'
165 filter = CssAbsoluteFilter(content)201 filter = CssAbsoluteFilter(content)
166 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
167 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))202 self.assertEqual(content, filter.input(filename=filename, basename='css/url/test.css'))
168203
169 def test_css_absolute_filter_querystring(self):204 def test_css_absolute_filter_querystring(self):
170 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')205 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
171 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')206 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
172 params = {207 content = self.template % blankdict(url='../../', query='?foo')
208 params = blankdict({
173 'url': settings.COMPRESS_URL,209 'url': settings.COMPRESS_URL,
174 'hash': self.hashing_func(imagefilename),210 'query': '?foo',
175 }211 'hash': '&' + self.hashing_func(imagefilename),
176 content = "p { background: url('../../img/python.png?foo') }"212 })
177213 output = self.template % params
178 output = "p { background: url('%(url)simg/python.png?foo&%(hash)s') }" % params
179 filter = CssAbsoluteFilter(content)214 filter = CssAbsoluteFilter(content)
180 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))215 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
216
181 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'217 settings.COMPRESS_URL = params['url'] = 'http://media.example.com/'
218 output = self.template % params
182 filter = CssAbsoluteFilter(content)219 filter = CssAbsoluteFilter(content)
183 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
184 output = "p { background: url('%(url)simg/python.png?foo&%(hash)s') }" % params
185 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))220 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
186221
187 def test_css_absolute_filter_https(self):222 def test_css_absolute_filter_https(self):
188 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')223 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')
189 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')224 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
190 params = {225 content = self.template % blankdict(url='../../')
226 params = blankdict({
191 'url': settings.COMPRESS_URL,227 'url': settings.COMPRESS_URL,
192 'hash': self.hashing_func(imagefilename),228 'hash': '?' + self.hashing_func(imagefilename),
193 }229 })
194 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"230 output = self.template % params
195 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params231 filter = CssAbsoluteFilter(content)
196 filter = CssAbsoluteFilter(self.content)
197 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))232 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
233
198 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'234 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'
199 filter = CssAbsoluteFilter(self.content)235 output = self.template % params
200 filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css')236 filter = CssAbsoluteFilter(content)
201 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"
202 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
203 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))237 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
204238
205 def test_css_absolute_filter_relative_path(self):239 def test_css_absolute_filter_relative_path(self):
206 filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'static', 'whatever/../css/url/test.css')240 filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'static', 'whatever/../css/url/test.css')
207 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')241 imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png')
208 params = {242 content = self.template % blankdict(url='../../')
243 params = blankdict({
209 'url': settings.COMPRESS_URL,244 'url': settings.COMPRESS_URL,
210 'hash': self.hashing_func(imagefilename),245 'hash': '?' + self.hashing_func(imagefilename),
211 }246 })
212 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"247 output = self.template % params
213 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params248 filter = CssAbsoluteFilter(content)
214 filter = CssAbsoluteFilter(self.content)
215 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))249 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
250
216 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'251 settings.COMPRESS_URL = params['url'] = 'https://static.example.com/'
217 filter = CssAbsoluteFilter(self.content)252 output = self.template % params
218 output = ("p { background: url('%(url)simg/python.png?%(hash)s') }"253 filter = CssAbsoluteFilter(content)
219 "p { filter: Alpha(src='%(url)simg/python.png?%(hash)s') }") % params
220 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))254 self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css'))
221255
256 def test_css_absolute_filter_filename_outside_compress_root(self):
257 filename = '/foo/bar/baz/test.css'
258 content = self.template % blankdict(url='../qux/')
259 params = blankdict({
260 'url': settings.COMPRESS_URL + 'bar/qux/',
261 })
262 output = self.template % params
263 filter = CssAbsoluteFilter(content)
264 self.assertEqual(output, filter.input(filename=filename, basename='bar/baz/test.css'))
265 settings.COMPRESS_URL = 'https://static.example.com/'
266 params['url'] = settings.COMPRESS_URL + 'bar/qux/'
267 output = self.template % params
268 filter = CssAbsoluteFilter(content)
269 self.assertEqual(output, filter.input(filename=filename, basename='bar/baz/test.css'))
270
222 def test_css_hunks(self):271 def test_css_hunks(self):
223 hash_dict = {272 hash_dict = {
224 'hash1': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')),273 'hash1': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')),
@@ -253,14 +302,6 @@
253 hashing_method = 'content'302 hashing_method = 'content'
254 hashing_func = staticmethod(get_hashed_content)303 hashing_func = staticmethod(get_hashed_content)
255304
256 def setUp(self):
257 super(CssAbsolutizingTestCaseWithHash, self).setUp()
258 self.css = """
259 <link rel="stylesheet" href="/static/css/url/url1.css" type="text/css" charset="utf-8">
260 <link rel="stylesheet" href="/static/css/url/2/url2.css" type="text/css" charset="utf-8">
261 """
262 self.css_node = CssCompressor(self.css)
263
264305
265class CssDataUriTestCase(TestCase):306class CssDataUriTestCase(TestCase):
266 def setUp(self):307 def setUp(self):
@@ -301,3 +342,38 @@
301 #footer {font-weight: bold;}342 #footer {font-weight: bold;}
302 """343 """
303 self.assertEqual(input, TemplateFilter(content).input())344 self.assertEqual(input, TemplateFilter(content).input())
345
346
347class SpecializedFiltersTest(TestCase):
348 """
349 Test to check the Specializations of filters.
350 """
351 def test_closure_filter(self):
352 filter = ClosureCompilerFilter('')
353 self.assertEqual(filter.options, (('binary', six.text_type('java -jar compiler.jar')), ('args', six.text_type(''))))
354
355 def test_csstidy_filter(self):
356 filter = CSSTidyFilter('')
357 self.assertEqual(filter.options, (('binary', six.text_type('csstidy')), ('args', six.text_type('--template=highest'))))
358
359 def test_yuglify_filters(self):
360 filter = YUglifyCSSFilter('')
361 self.assertEqual(filter.command, '{binary} {args} --type=css')
362 self.assertEqual(filter.options, (('binary', six.text_type('yuglify')), ('args', six.text_type('--terminal'))))
363
364 filter = YUglifyJSFilter('')
365 self.assertEqual(filter.command, '{binary} {args} --type=js')
366 self.assertEqual(filter.options, (('binary', six.text_type('yuglify')), ('args', six.text_type('--terminal'))))
367
368 def test_yui_filters(self):
369 filter = YUICSSFilter('')
370 self.assertEqual(filter.command, '{binary} {args} --type=css')
371 self.assertEqual(filter.options, (('binary', six.text_type('java -jar yuicompressor.jar')), ('args', six.text_type(''))))
372
373 filter = YUIJSFilter('', verbose=1)
374 self.assertEqual(filter.command, '{binary} {args} --type=js --verbose')
375 self.assertEqual(filter.options, (('binary', six.text_type('java -jar yuicompressor.jar')), ('args', six.text_type('')), ('verbose', 1)))
376
377 def test_clean_css_filter(self):
378 filter = CleanCSSFilter('')
379 self.assertEqual(filter.options, (('binary', six.text_type('cleancss')), ('args', six.text_type(''))))
304380
=== modified file 'compressor/tests/test_jinja2ext.py'
--- compressor/tests/test_jinja2ext.py 2015-01-06 14:31:34 +0000
+++ compressor/tests/test_jinja2ext.py 2015-08-20 18:49:21 +0000
@@ -65,8 +65,7 @@
65 self.assertEqual(tag_body, template.render())65 self.assertEqual(tag_body, template.render())
6666
67 def test_empty_tag(self):67 def test_empty_tag(self):
68 template = self.env.from_string("""{% compress js %}{% block js %}68 template = self.env.from_string("""{% compress js %}{% block js %}{% endblock %}{% endcompress %}""")
69 {% endblock %}{% endcompress %}""")
70 context = {'STATIC_URL': settings.COMPRESS_URL}69 context = {'STATIC_URL': settings.COMPRESS_URL}
71 self.assertEqual('', template.render(context))70 self.assertEqual('', template.render(context))
7271
7372
=== modified file 'compressor/tests/test_offline.py'
--- compressor/tests/test_offline.py 2015-01-06 14:31:34 +0000
+++ compressor/tests/test_offline.py 2015-08-20 18:49:21 +0000
@@ -3,6 +3,7 @@
3import os3import os
4import sys4import sys
55
6import django
6from django.core.management.base import CommandError7from django.core.management.base import CommandError
7from django.template import Template, Context8from django.template import Template, Context
8from django.test import TestCase9from django.test import TestCase
@@ -44,10 +45,6 @@
44 engines = ("django",)45 engines = ("django",)
4546
46 def setUp(self):47 def setUp(self):
47 self._old_compress = settings.COMPRESS_ENABLED
48 self._old_compress_offline = settings.COMPRESS_OFFLINE
49 self._old_template_dirs = settings.TEMPLATE_DIRS
50 self._old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
51 self.log = StringIO()48 self.log = StringIO()
5249
53 # Reset template dirs, because it enables us to force compress to50 # Reset template dirs, because it enables us to force compress to
@@ -58,11 +55,18 @@
58 # template to be skipped over.55 # template to be skipped over.
59 django_template_dir = os.path.join(settings.TEST_DIR, 'test_templates', self.templates_dir)56 django_template_dir = os.path.join(settings.TEST_DIR, 'test_templates', self.templates_dir)
60 jinja2_template_dir = os.path.join(settings.TEST_DIR, 'test_templates_jinja2', self.templates_dir)57 jinja2_template_dir = os.path.join(settings.TEST_DIR, 'test_templates_jinja2', self.templates_dir)
61 settings.TEMPLATE_DIRS = (django_template_dir, jinja2_template_dir)58
6259 override_settings = {
63 # Enable offline compress60 'TEMPLATE_DIRS': (django_template_dir, jinja2_template_dir,),
64 settings.COMPRESS_ENABLED = True61 'COMPRESS_ENABLED': True,
65 settings.COMPRESS_OFFLINE = True62 'COMPRESS_OFFLINE': True
63 }
64
65 if "jinja2" in self.engines:
66 override_settings["COMPRESS_JINJA2_GET_ENVIRONMENT"] = lambda: self._get_jinja2_env()
67
68 self.override_settings = self.settings(**override_settings)
69 self.override_settings.__enter__()
6670
67 if "django" in self.engines:71 if "django" in self.engines:
68 self.template_path = os.path.join(django_template_dir, self.template_name)72 self.template_path = os.path.join(django_template_dir, self.template_name)
@@ -70,22 +74,16 @@
70 with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:74 with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:
71 self.template = Template(file.read())75 self.template = Template(file.read())
7276
73 self._old_jinja2_get_environment = settings.COMPRESS_JINJA2_GET_ENVIRONMENT
74
75 if "jinja2" in self.engines:77 if "jinja2" in self.engines:
76 # Setup Jinja2 settings.78 jinja2_env = override_settings["COMPRESS_JINJA2_GET_ENVIRONMENT"]()
77 settings.COMPRESS_JINJA2_GET_ENVIRONMENT = lambda: self._get_jinja2_env()
78 jinja2_env = settings.COMPRESS_JINJA2_GET_ENVIRONMENT()
79 self.template_path_jinja2 = os.path.join(jinja2_template_dir, self.template_name)79 self.template_path_jinja2 = os.path.join(jinja2_template_dir, self.template_name)
8080
81 with io.open(self.template_path_jinja2, encoding=settings.FILE_CHARSET) as file:81 with io.open(self.template_path_jinja2, encoding=settings.FILE_CHARSET) as file:
82 self.template_jinja2 = jinja2_env.from_string(file.read())82 self.template_jinja2 = jinja2_env.from_string(file.read())
8383
84 def tearDown(self):84 def tearDown(self):
85 settings.COMPRESS_JINJA2_GET_ENVIRONMENT = self._old_jinja2_get_environment85 self.override_settings.__exit__(None, None, None)
86 settings.COMPRESS_ENABLED = self._old_compress86
87 settings.COMPRESS_OFFLINE = self._old_compress_offline
88 settings.TEMPLATE_DIRS = self._old_template_dirs
89 manifest_path = os.path.join('CACHE', 'manifest.json')87 manifest_path = os.path.join('CACHE', 'manifest.json')
90 if default_storage.exists(manifest_path):88 if default_storage.exists(manifest_path):
91 default_storage.delete(manifest_path)89 default_storage.delete(manifest_path)
@@ -452,9 +450,10 @@
452# from library import *450# from library import *
453# causing 'ImportError: No module named library'.451# causing 'ImportError: No module named library'.
454# It seems there is no evidence nor indicated support for Python 3+.452# It seems there is no evidence nor indicated support for Python 3+.
455@unittest.skip("Coffin tests disable under Ubuntu build")
456@unittest.skipIf(sys.version_info >= (3, 2),453@unittest.skipIf(sys.version_info >= (3, 2),
457 "Coffin does not support 3.2+")454 "Coffin does not support 3.2+")
455@unittest.skipIf(django.VERSION >= (1, 8),
456 "Import error on 1.8")
458class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):457class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):
459 templates_dir = "test_coffin"458 templates_dir = "test_coffin"
460 expected_hash = "32c8281e3346"459 expected_hash = "32c8281e3346"
@@ -479,6 +478,8 @@
479# is also evident in its tox.ini file.478# is also evident in its tox.ini file.
480@unittest.skipIf(sys.version_info >= (3, 2) and sys.version_info < (3, 3),479@unittest.skipIf(sys.version_info >= (3, 2) and sys.version_info < (3, 3),
481 "Jingo does not support 3.2")480 "Jingo does not support 3.2")
481@unittest.skipIf(django.VERSION >= (1, 8),
482 "Import error on 1.8")
482class OfflineGenerationJingoTestCase(OfflineTestCaseMixin, TestCase):483class OfflineGenerationJingoTestCase(OfflineTestCaseMixin, TestCase):
483 templates_dir = "test_jingo"484 templates_dir = "test_jingo"
484 expected_hash = "61ec584468eb"485 expected_hash = "61ec584468eb"
485486
=== modified file 'compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html'
--- compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html 2014-06-26 15:08:13 +0000
+++ compressor/tests/test_templates/test_block_super_base_compressed/test_compressor_offline.html 2015-08-20 18:49:21 +0000
@@ -5,4 +5,9 @@
5 <script type="text/javascript">5 <script type="text/javascript">
6 alert("this alert shouldn't be alone!");6 alert("this alert shouldn't be alone!");
7 </script>7 </script>
8 {% block orphan %}
9 {{ block.super }}
10 An 'orphan' block that refers to a non-existent super block.
11 Contents of this block are ignored.
12 {% endblock %}
8{% endspaceless %}{% endblock %}13{% endspaceless %}{% endblock %}
914
=== modified file 'compressor/utils/staticfiles.py'
--- compressor/utils/staticfiles.py 2014-06-26 15:08:13 +0000
+++ compressor/utils/staticfiles.py 2015-08-20 18:49:21 +0000
@@ -4,20 +4,10 @@
44
5from compressor.conf import settings5from compressor.conf import settings
66
7INSTALLED = ("staticfiles" in settings.INSTALLED_APPS or7if "django.contrib.staticfiles" in settings.INSTALLED_APPS:
8 "django.contrib.staticfiles" in settings.INSTALLED_APPS)8 from django.contrib.staticfiles import finders # noqa
99
10if INSTALLED:10 if ("compressor.finders.CompressorFinder"
11 if "django.contrib.staticfiles" in settings.INSTALLED_APPS:
12 from django.contrib.staticfiles import finders
13 else:
14 try:
15 from staticfiles import finders # noqa
16 except ImportError:
17 # Old (pre 1.0) and incompatible version of staticfiles
18 INSTALLED = False
19
20 if (INSTALLED and "compressor.finders.CompressorFinder"
21 not in settings.STATICFILES_FINDERS):11 not in settings.STATICFILES_FINDERS):
22 raise ImproperlyConfigured(12 raise ImproperlyConfigured(
23 "When using Django Compressor together with staticfiles, "13 "When using Django Compressor together with staticfiles, "
2414
=== modified file 'debian/changelog'
--- debian/changelog 2015-01-06 14:31:34 +0000
+++ debian/changelog 2015-08-20 18:49:21 +0000
@@ -1,3 +1,23 @@
1python-django-compressor (1.5-1ubuntu1) UNRELEASED; urgency=low
2
3 * Merge from Debian unstable. Remaining changes:
4 - d/control: Drop BD on python-coffin.
5 - d/p/disable-coffin-tests.patch: Rebase.
6 * Fix d/watch
7
8 -- David Ames <david.ames@canonical.com> Tue, 04 Aug 2015 16:11:41 +0000
9
10python-django-compressor (1.5-1) unstable; urgency=medium
11
12 * New upstream release.
13 * Added Python 3 support.
14 * Removed django 1.7 patche.
15 * Added patch to not run a unit test that fails:
16 - compressor.tests.test_base.JsAsyncDeferTestCase
17 * Fixed PyPi watch file.
18
19 -- Thomas Goirand <zigo@debian.org> Tue, 04 Aug 2015 08:17:03 +0000
20
1python-django-compressor (1.4-2ubuntu3) vivid; urgency=medium21python-django-compressor (1.4-2ubuntu3) vivid; urgency=medium
222
3 * d/control: Drop BD on python-coffin. 23 * d/control: Drop BD on python-coffin.
424
=== modified file 'debian/control'
--- debian/control 2015-01-06 14:31:34 +0000
+++ debian/control 2015-08-20 18:49:21 +0000
@@ -5,34 +5,66 @@
5XSBC-Original-Maintainer: PKG OpenStack <openstack-devel@lists.alioth.debian.org>5XSBC-Original-Maintainer: PKG OpenStack <openstack-devel@lists.alioth.debian.org>
6Uploaders: Thomas Goirand <zigo@debian.org>,6Uploaders: Thomas Goirand <zigo@debian.org>,
7Build-Depends: debhelper (>= 9),7Build-Depends: debhelper (>= 9),
8 dh-python,
8 openstack-pkg-tools,9 openstack-pkg-tools,
9 python-all (>= 2.6.6-3~),10 python-all,
10 python-setuptools11 python-setuptools,
11Build-Depends-Indep: python-appconf,12 python3-all,
13 python3-setuptools
14Build-Depends-Indep: csstidy,
15 python-appconf,
12 python-bs4,16 python-bs4,
13 python-coverage,17 python-coverage,
14 python-django (>= 1.6),18 python-django,
19 python-django-discover-runner,
15 python-html5lib,20 python-html5lib,
16 python-jingo,21 python-jingo,
17 python-jinja2,22 python-jinja2,
18 python-lxml,23 python-lxml,
19 python-mock,24 python-mock,
20 python-nose,25 python-nose,
21 python-unittest226 python-unittest2,
22Standards-Version: 3.9.527 python3-appconf,
28 python3-bs4,
29 python3-coverage,
30 python3-django,
31 python3-django-discover-runner,
32 python3-html5lib,
33 python3-jingo,
34 python3-jinja2,
35 python3-lxml,
36 python3-mock,
37 python3-nose,
38 python3-unittest2
39Standards-Version: 3.9.6
23Vcs-Browser: http://anonscm.debian.org/gitweb/?p=openstack/python-django-compressor.git;a=summary40Vcs-Browser: http://anonscm.debian.org/gitweb/?p=openstack/python-django-compressor.git;a=summary
24Vcs-Git: git://anonscm.debian.org/openstack/python-django-compressor.git41Vcs-Git: git://anonscm.debian.org/openstack/python-django-compressor.git
25Homepage: http://pypi.python.org/pypi/django_compressor/42Homepage: http://pypi.python.org/pypi/django_compressor/
2643
27Package: python-compressor44Package: python-compressor
28Architecture: all45Architecture: all
29Pre-Depends: dpkg (>= 1.15.6~)
30Depends: python-appconf,46Depends: python-appconf,
31 python-django (>= 1.1),47 python-django,
32 ${misc:Depends},48 ${misc:Depends},
33 ${python:Depends}49 ${python:Depends}
34Provides: ${python:Provides}50Provides: ${python:Provides}
35Description: Compresses linked and inline JavaScript or CSS into single cached files51Description: Compresses linked, inline JS or CSS into single cached files - Python 2.7
36 Django Compressor combines and compresses linked and inline Javascript or CSS52 Django Compressor combines and compresses linked and inline Javascript or CSS
37 in a Django templates into cacheable static files by using the compress53 in a Django templates into cacheable static files by using the compress
38 template tag.54 template tag.
55 .
56 This package contains the Python 2.7 module.
57
58Package: python3-compressor
59Architecture: all
60Depends: python3-appconf,
61 python3-django,
62 ${misc:Depends},
63 ${python3:Depends}
64Provides: ${python:Provides}
65Description: Compresses linked, inline JS or CSS into single cached files - Python 3.x
66 Django Compressor combines and compresses linked and inline Javascript or CSS
67 in a Django templates into cacheable static files by using the compress
68 template tag.
69 .
70 This package contains the Python 3.x module.
3971
=== modified file 'debian/gbp.conf'
--- debian/gbp.conf 2013-05-12 15:20:14 +0000
+++ debian/gbp.conf 2015-08-20 18:49:21 +0000
@@ -4,5 +4,5 @@
4upstream-tag = %(version)s4upstream-tag = %(version)s
5compression = xz5compression = xz
66
7[git-buildpackage]7[buildpackage]
8export-dir = ../build-area/8export-dir = ../build-area/
99
=== modified file 'debian/patches/disable-coffin-tests.patch'
--- debian/patches/disable-coffin-tests.patch 2015-01-06 12:34:51 +0000
+++ debian/patches/disable-coffin-tests.patch 2015-08-20 18:49:21 +0000
@@ -1,20 +1,20 @@
1--- a/compressor/test_settings.py1--- a/compressor/test_settings.py
2+++ b/compressor/test_settings.py2+++ b/compressor/test_settings.py
3@@ -14,7 +14,6 @@ DATABASES = {3@@ -21,7 +21,6 @@
4
5 INSTALLED_APPS = [4 INSTALLED_APPS = [
5 'django.contrib.staticfiles',
6 'compressor',6 'compressor',
7- 'coffin',7- 'coffin',
8 'jingo',
9 ]8 ]
10 9 if django.VERSION < (1, 8):
10 INSTALLED_APPS.append('jingo')
11--- a/compressor/tests/test_offline.py11--- a/compressor/tests/test_offline.py
12+++ b/compressor/tests/test_offline.py12+++ b/compressor/tests/test_offline.py
13@@ -452,6 +452,7 @@ class OfflineGenerationComplexTestCase(O13@@ -450,6 +450,7 @@
14 # from library import *14 # from library import *
15 # causing 'ImportError: No module named library'.15 # causing 'ImportError: No module named library'.
16 # It seems there is no evidence nor indicated support for Python 3+.16 # It seems there is no evidence nor indicated support for Python 3+.
17+@unittest.skip("Coffin tests disable under Ubuntu build")17+@unittest.skip("Coffin tests disable under Ubuntu build")
18 @unittest.skipIf(sys.version_info >= (3, 2),18 @unittest.skipIf(sys.version_info >= (3, 2),
19 "Coffin does not support 3.2+")19 "Coffin does not support 3.2+")
20 class OfflineGenerationCoffinTestCase(OfflineTestCaseMixin, TestCase):20 @unittest.skipIf(django.VERSION >= (1, 8),
2121
=== removed file 'debian/patches/fix-test_settings.py-for-django-1.7.patch'
--- debian/patches/fix-test_settings.py-for-django-1.7.patch 2014-09-08 17:31:54 +0000
+++ debian/patches/fix-test_settings.py-for-django-1.7.patch 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1Description: Fix test_settings.py for Django 1.7 compat
2Author: Thomas Goirand <zigo@debian.org>
3Forwarded: no
4Last-Update: 2014-09-09
5
6--- python-django-compressor-1.4.orig/compressor/test_settings.py
7+++ python-django-compressor-1.4/compressor/test_settings.py
8@@ -3,7 +3,7 @@ import django
9
10 TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'tests')
11
12-COMPRESS_CACHE_BACKEND = 'locmem://'
13+COMPRESS_CACHE_BACKEND = 'django.core.cache.backends.locmem.CacheClass'
14
15 DATABASES = {
16 'default': {
170
=== added file 'debian/patches/remove-failed-test.patch'
--- debian/patches/remove-failed-test.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/remove-failed-test.patch 2015-08-20 18:49:21 +0000
@@ -0,0 +1,43 @@
1Description: Removes failed test
2 This unit test is failing, so removing it to build the package.
3Author: Thomas Goirand <zigo@debian.org>
4Forwarded: no
5Last-Update: 2015-08-04
6
7--- python-django-compressor-1.5.orig/compressor/tests/test_base.py
8+++ python-django-compressor-1.5/compressor/tests/test_base.py
9@@ -288,34 +288,6 @@ class CacheBackendTestCase(CompressorTes
10 self.assertEqual(cache.__class__, locmem.LocMemCache)
11
12
13-class JsAsyncDeferTestCase(SimpleTestCase):
14- def setUp(self):
15- self.js = """\
16- <script src="/static/js/one.js" type="text/javascript"></script>
17- <script src="/static/js/two.js" type="text/javascript" async></script>
18- <script src="/static/js/three.js" type="text/javascript" defer></script>
19- <script type="text/javascript">obj.value = "value";</script>
20- <script src="/static/js/one.js" type="text/javascript" async></script>
21- <script src="/static/js/two.js" type="text/javascript" async></script>
22- <script src="/static/js/three.js" type="text/javascript"></script>"""
23-
24- def test_js_output(self):
25- def extract_attr(tag):
26- if tag.has_attr('async'):
27- return 'async'
28- if tag.has_attr('defer'):
29- return 'defer'
30- js_node = JsCompressor(self.js)
31- output = [None, 'async', 'defer', None, 'async', None]
32- if six.PY3:
33- scripts = make_soup(js_node.output()).find_all('script')
34- attrs = [extract_attr(i) for i in scripts]
35- else:
36- scripts = make_soup(js_node.output()).findAll('script')
37- attrs = [s.get('async') or s.get('defer') for s in scripts]
38- self.assertEqual(output, attrs)
39-
40-
41 class CacheTestCase(SimpleTestCase):
42
43 def setUp(self):
044
=== modified file 'debian/patches/series'
--- debian/patches/series 2015-01-06 12:34:51 +0000
+++ debian/patches/series 2015-08-20 18:49:21 +0000
@@ -1,2 +1,2 @@
1fix-test_settings.py-for-django-1.7.patch
2disable-coffin-tests.patch1disable-coffin-tests.patch
2remove-failed-test.patch
33
=== modified file 'debian/rules'
--- debian/rules 2014-12-05 10:34:59 +0000
+++ debian/rules 2015-08-20 18:49:21 +0000
@@ -1,13 +1,14 @@
1#!/usr/bin/make -f1#!/usr/bin/make -f
22
3#export DH_VERBOSE=13PYTHONS:=$(shell pyversions -vr)
4PYTHON3S:=$(shell py3versions -vr)
45
5UPSTREAM_GIT = git://github.com/jezdez/django_compressor.git6UPSTREAM_GIT = git://github.com/jezdez/django_compressor.git
67
7include /usr/share/openstack-pkg-tools/pkgos.make8include /usr/share/openstack-pkg-tools/pkgos.make
89
9%:10%:
10 dh $@ --with python211 dh $@ --buildsystem=python_distutils --with python2,python3
1112
12override_dh_clean:13override_dh_clean:
13 dh_clean14 dh_clean
@@ -15,24 +16,27 @@
1516
16override_dh_auto_test:17override_dh_auto_test:
17ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))18ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
18 PYTHONPATH=. python-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor19 PYTHON=python2 PYTHONPATH=. python-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor
19 #PYTHONPATH=$PYTHONPATH:. python /usr/share/pyshared/django/bin/django-admin.py test --settings=compressor.test_settings compressor || true
20 rm -rf $(CURDIR)/compressor/tests/static/CACHE20 rm -rf $(CURDIR)/compressor/tests/static/CACHE
21 # TODO: make unit tests to work.
22# PYTHON=python3 PYTHONPATH=. python3-coverage run --branch --source=compressor /usr/bin/django-admin test --settings=compressor.test_settings compressor
23# rm -rf $(CURDIR)/compressor/tests/static/CACHE
21endif24endif
2225
23override_dh_auto_build:26override_dh_auto_build:
2427
25override_dh_install:28override_dh_install:
26 set -e ; for i in `pyversions -s` ; do \29 set -e ; for pyvers in $(PYTHONS); do \
27 $$i setup.py install --install-layout=deb --root=debian/python-compressor ; \30 python$$pyvers setup.py install --install-layout=deb \
28 rm -f $(CURDIR)/debian/usr/lib/$$i/dist-packages/compressor/tests/static/CACHE/css/* ; \31 --root $(CURDIR)/debian/python-compressor; \
29 rm -f $(CURDIR)/debian/usr/lib/$$i/dist-packages/compressor/tests/static/CACHE/js/* ; \32 done
30 done33 set -e ; for pyvers in $(PYTHON3S); do \
31 find debian/python-compressor -iname '*.pyc' -delete34 python$$pyvers setup.py install --install-layout=deb \
3235 --root $(CURDIR)/debian/python3-compressor; \
33override_dh_usrlocal:36 done
34 rm -f $(CURDIR)/debian/usr/share/pyshared/compressor/tests/static/CACHE/css/*37 rm -f $(CURDIR)/debian/usr/lib/python*/dist-packages/compressor/tests/static/CACHE/css/*
35 rm -f $(CURDIR)/debian/usr/share/pyshared/compressor/tests/static/CACHE/js/*38 rm -f $(CURDIR)/debian/usr/lib/python*/dist-packages/compressor/tests/static/CACHE/js/*
39 find debian -iname '*.pyc' -delete
3640
37# Commands not to run41# Commands not to run
38override_dh_installcatalogs:42override_dh_installcatalogs:
3943
=== modified file 'debian/watch'
--- debian/watch 2012-10-14 10:51:47 +0000
+++ debian/watch 2015-08-20 18:49:21 +0000
@@ -1,2 +1,3 @@
1version=31version=3
2http://pypi.python.org/packages/source/d/django-compressor/django-compressor-(.*)\.tar.gz2opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
3http://pypi.debian.net/django_compressor/django_compressor-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
34
=== modified file 'docs/changelog.txt'
--- docs/changelog.txt 2015-01-06 14:31:34 +0000
+++ docs/changelog.txt 2015-08-20 18:49:21 +0000
@@ -1,12 +1,46 @@
1Changelog1Changelog
2=========2=========
33
4v1.44v1.5 (03/27/2015)
5----5-----------------
6
7`Full Changelog <https://github.com/django-compressor/django-compressor/compare/1.4...HEAD>`_
8
9- Fix compress command and run automated tests for Django 1.8
10
11- Fix Django 1.8 warnings
12
13- Handle TypeError from import_module
14
15- Fix reading UTF-8 files which have BOM
16
17- Fix incompatibility with Windows (shell_quote is not supported)
18
19- Run automated tests on Django 1.7
20
21- Ignore non-existent {{ block.super }} in offline compression instead of raising AttributeError
22
23- Support for clean-css
24
25- Fix link markup
26
27- Add support for COMPRESS_CSS_HASHING_METHOD = None
28
29- Remove compatibility with old 'staticfiles' app
30
31- In compress command, use get_template() instead of opening template files manually, fixing compatibility issues with custom template loaders
32
33- Fix FilterBase so that does not override self.type for subclasses if filter_type is not specified at init
34
35- Remove unnecessary filename and existence checks in CssAbsoluteFilter
36
37
38v1.4 (06/20/2014)
39-----------------
640
7- Added Python 3 compatibility.41- Added Python 3 compatibility.
842
9- Added compatibility with Django 1.6.x.43- Added compatibility with Django 1.6.x and dropped support for Django 1.3.X.
1044
11- Fixed compatibility with html5lib 1.0.45- Fixed compatibility with html5lib 1.0.
1246
@@ -46,7 +80,7 @@
46 - Dropped support for Python 2.5. Removed ``any`` and ``walk`` compatibility80 - Dropped support for Python 2.5. Removed ``any`` and ``walk`` compatibility
47 functions in ``compressor.utils``.81 functions in ``compressor.utils``.
4882
49 - Removed compatibility with Django 1.2 for default values of some settings:83 - Removed compatibility with some old django setttings:
5084
51 - :attr:`~COMPRESS_ROOT` no longer uses ``MEDIA_ROOT`` if ``STATIC_ROOT`` is85 - :attr:`~COMPRESS_ROOT` no longer uses ``MEDIA_ROOT`` if ``STATIC_ROOT`` is
52 not defined. It expects ``STATIC_ROOT`` to be defined instead.86 not defined. It expects ``STATIC_ROOT`` to be defined instead.
5387
=== modified file 'docs/contributing.txt'
--- docs/contributing.txt 2015-01-06 14:31:34 +0000
+++ docs/contributing.txt 2015-08-20 18:49:21 +0000
@@ -9,11 +9,12 @@
9Community9Community
10---------10---------
1111
12People interested in developing for the Django Compressor should head12People interested in developing for the Django Compressor should:
13over to #django-compressor on the `freenode`_ IRC network for help and to13
141. Head over to #django-compressor on the `freenode`_ IRC network for help and to
14discuss the development.15discuss the development.
162. Open an issue on GitHub explaining your ideas.
1517
16You may also be interested in following `@jezdez`_ on Twitter.
1718
18In a nutshell19In a nutshell
19-------------20-------------
@@ -143,7 +144,7 @@
143- Accessible. You should assume the reader to be moderately familiar with144- Accessible. You should assume the reader to be moderately familiar with
144 Python and Django, but not anything else. Link to documentation of libraries145 Python and Django, but not anything else. Link to documentation of libraries
145 you use, for example, even if they are "obvious" to you. A brief146 you use, for example, even if they are "obvious" to you. A brief
146 description of what it does is also welcome. 147 description of what it does is also welcome.
147148
148Pulling of documentation is pretty fast and painless. Usually somebody goes149Pulling of documentation is pretty fast and painless. Usually somebody goes
149over your text and merges it, since there are no "breaks" and that github150over your text and merges it, since there are no "breaks" and that github
150151
=== modified file 'docs/django-sekizai.txt'
--- docs/django-sekizai.txt 2015-01-06 14:31:34 +0000
+++ docs/django-sekizai.txt 2015-08-20 18:49:21 +0000
@@ -3,12 +3,12 @@
3django-sekizai Support3django-sekizai Support
4======================4======================
55
6Django Compressor comes with support for _django-sekizai via an extension.6Django Compressor comes with support for django-sekizai_ via an extension.
7_django-sekizai provides the ability to include template code, from within7django-sekizai provides the ability to include template code, from within
8any block, to a parent block. It is primarily used to include js/css from8any block, to a parent block. It is primarily used to include js/css from
9included templates to the master template.9included templates to the master template.
1010
11It requires _django-sekizai to installed. Refer to the _django-sekizai _docs11It requires django-sekizai to be installed. Refer to the `django-sekizai docs`_
12for how to use ``render_block``12for how to use ``render_block``
1313
14Usage14Usage
@@ -21,4 +21,4 @@
2121
2222
23.. _django-sekizai: https://github.com/ojii/django-sekizai23.. _django-sekizai: https://github.com/ojii/django-sekizai
24.. _docs: http://django-sekizai.readthedocs.org/en/latest/24.. _django-sekizai docs: http://django-sekizai.readthedocs.org/en/latest/
2525
=== modified file 'docs/jinja2.txt'
--- docs/jinja2.txt 2015-01-06 14:31:34 +0000
+++ docs/jinja2.txt 2015-08-20 18:49:21 +0000
@@ -42,13 +42,13 @@
42==================================42==================================
43You'd need to configure ``COMPRESS_JINJA2_GET_ENVIRONMENT`` so that43You'd need to configure ``COMPRESS_JINJA2_GET_ENVIRONMENT`` so that
44Compressor can retrieve the Jinja2 environment for rendering.44Compressor can retrieve the Jinja2 environment for rendering.
45This can be a lamda or function that returns a Jinja2 environment.45This can be a lambda or function that returns a Jinja2 environment.
4646
47Usage47Usage
48-----48-----
49Run the following compress command along with an ``-engine`` parameter. The49Run the following compress command along with an ``--engine`` parameter. The
50parameter can be either jinja2 or django (default). For example,50parameter can be either jinja2 or django (default). For example,
51"./manage.py compress -engine jinja2".51``./manage.py compress --engine jinja2``.
5252
53Using both Django and Jinja2 templates53Using both Django and Jinja2 templates
54--------------------------------------54--------------------------------------
@@ -60,9 +60,9 @@
6060
61A typical usage could be :61A typical usage could be :
6262
63- "./manage.py compress" for processing Django templates first, skipping63- ``./manage.py compress`` for processing Django templates first, skipping
64 Jinja2 templates.64 Jinja2 templates.
65- "./manage.py compress -engine jinja2" for processing Jinja2 templates,65- ``./manage.py compress --engine jinja2`` for processing Jinja2 templates,
66 skipping Django templates.66 skipping Django templates.
6767
68However, it is still recommended that you do not mix Django and Jinja268However, it is still recommended that you do not mix Django and Jinja2
@@ -172,4 +172,3 @@
172.. _Jinja2: http://jinja.pocoo.org/docs/172.. _Jinja2: http://jinja.pocoo.org/docs/
173.. _Coffin: http://pypi.python.org/pypi/Coffin173.. _Coffin: http://pypi.python.org/pypi/Coffin
174.. _Jingo: https://jingo.readthedocs.org/en/latest/174.. _Jingo: https://jingo.readthedocs.org/en/latest/
175
176175
=== modified file 'docs/quickstart.txt'
--- docs/quickstart.txt 2015-01-06 14:31:34 +0000
+++ docs/quickstart.txt 2015-08-20 18:49:21 +0000
@@ -18,10 +18,8 @@
18* See the list of :ref:`settings` to modify Django Compressor's18* See the list of :ref:`settings` to modify Django Compressor's
19 default behaviour and make adjustments for your website.19 default behaviour and make adjustments for your website.
2020
21* In case you use Django's staticfiles_ contrib app (or its standalone21* In case you use Django's staticfiles_ contrib app you have to add Django
22 counterpart django-staticfiles_) you have to add Django Compressor's file22 Compressor's file finder to the ``STATICFILES_FINDERS`` setting, like this:
23 finder to the ``STATICFILES_FINDERS`` setting, for example with
24 ``django.contrib.staticfiles``:
2523
26 .. code-block:: python24 .. code-block:: python
2725
@@ -95,6 +93,6 @@
95.. _lxml: http://codespeak.net/lxml/93.. _lxml: http://codespeak.net/lxml/
96.. _libxml2: http://xmlsoft.org/94.. _libxml2: http://xmlsoft.org/
97.. _html5lib: http://code.google.com/p/html5lib/95.. _html5lib: http://code.google.com/p/html5lib/
98.. _`Slim It`: http://slimit.org/96.. _`Slim It`: https://github.com/rspivak/slimit
99.. _django-appconf: http://pypi.python.org/pypi/django-appconf/97.. _django-appconf: http://pypi.python.org/pypi/django-appconf/
100.. _versiontools: http://pypi.python.org/pypi/versiontools/98.. _versiontools: http://pypi.python.org/pypi/versiontools/
10199
=== modified file 'docs/remote-storages.txt'
--- docs/remote-storages.txt 2015-01-06 14:31:34 +0000
+++ docs/remote-storages.txt 2015-08-20 18:49:21 +0000
@@ -39,12 +39,11 @@
39Using staticfiles39Using staticfiles
40^^^^^^^^^^^^^^^^^40^^^^^^^^^^^^^^^^^
4141
42If you are using Django's staticfiles_ contrib app or the standalone42If you are using Django's staticfiles_ contrib app, you'll need to use a
43app django-staticfiles_, you'll need to use a temporary filesystem cache43temporary filesystem cache for Django Compressor to know which files to
44for Django Compressor to know which files to compress. Since staticfiles44compress. Since staticfiles provides a management command to collect static
45provides a management command to collect static files from various45files from various locations which uses a storage backend, this is where both
46locations which uses a storage backend, this is where both apps can be46apps can be integrated.
47integrated.
4847
49#. Make sure the :attr:`~django.conf.settings.COMPRESS_ROOT` and STATIC_ROOT_48#. Make sure the :attr:`~django.conf.settings.COMPRESS_ROOT` and STATIC_ROOT_
50 settings are equal since both apps need to look at the same directories49 settings are equal since both apps need to look at the same directories
@@ -84,7 +83,6 @@
84.. _Amazon S3: https://s3.amazonaws.com/83.. _Amazon S3: https://s3.amazonaws.com/
85.. _boto: http://boto.cloudhackers.com/84.. _boto: http://boto.cloudhackers.com/
86.. _django-storages: http://code.welldev.org/django-storages/85.. _django-storages: http://code.welldev.org/django-storages/
87.. _django-staticfiles: http://github.com/jezdez/django-staticfiles/
88.. _staticfiles: http://docs.djangoproject.com/en/dev/howto/static-files/86.. _staticfiles: http://docs.djangoproject.com/en/dev/howto/static-files/
89.. _STATIC_ROOT: http://docs.djangoproject.com/en/dev/ref/settings/#static-root87.. _STATIC_ROOT: http://docs.djangoproject.com/en/dev/ref/settings/#static-root
90.. _STATIC_URL: http://docs.djangoproject.com/en/dev/ref/settings/#static-url88.. _STATIC_URL: http://docs.djangoproject.com/en/dev/ref/settings/#static-url
9189
=== modified file 'docs/settings.txt'
--- docs/settings.txt 2015-01-06 14:31:34 +0000
+++ docs/settings.txt 2015-08-20 18:49:21 +0000
@@ -81,10 +81,11 @@
8181
82 .. attribute:: COMPRESS_CSS_HASHING_METHOD82 .. attribute:: COMPRESS_CSS_HASHING_METHOD
8383
84 The method to use when calculating the hash to append to84 The method to use when calculating the suffix to append to URLs in
85 processed URLs. Either ``'mtime'`` (default) or ``'content'``.85 your processed CSS files. Either ``None``, ``'mtime'`` (default) or
86 Use the latter in case you're using multiple server to serve your86 ``'content'``. Use the ``None`` if you want to completely disable that
87 static files.87 feature, and the ``'content'`` in case you're using multiple servers
88 to serve your content.
8889
89 - ``compressor.filters.csstidy.CSSTidyFilter``90 - ``compressor.filters.csstidy.CSSTidyFilter``
9091
@@ -136,9 +137,24 @@
136 A filter that uses Zachary Voase's Python port of the YUI CSS compression137 A filter that uses Zachary Voase's Python port of the YUI CSS compression
137 algorithm cssmin_.138 algorithm cssmin_.
138139
140 - ``compressor.filters.cleancss.CleanCSSFilter``
141
142 A filter that passes the CSS content to the `clean-css`_ tool.
143
144 .. attribute:: CLEAN_CSS_BINARY
145
146 The clean-css binary filesystem path.
147
148 .. attribute:: CLEAN_CSS_ARGUMENTS
149
150 The arguments passed to clean-css.
151
152
139 .. _CSSTidy: http://csstidy.sourceforge.net/153 .. _CSSTidy: http://csstidy.sourceforge.net/
140 .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme154 .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme
141 .. _cssmin: http://pypi.python.org/pypi/cssmin/155 .. _cssmin: http://pypi.python.org/pypi/cssmin/
156 .. _`clean-css`: https://github.com/GoalSmashers/clean-css/
157
142158
143 - ``compressor.filters.template.TemplateFilter``159 - ``compressor.filters.template.TemplateFilter``
144160
@@ -220,7 +236,7 @@
220 .. _`Google Closure compiler`: http://code.google.com/closure/compiler/236 .. _`Google Closure compiler`: http://code.google.com/closure/compiler/
221 .. _`YUI compressor`: http://developer.yahoo.com/yui/compressor/237 .. _`YUI compressor`: http://developer.yahoo.com/yui/compressor/
222 .. _`yUglify compressor`: https://github.com/yui/yuglify238 .. _`yUglify compressor`: https://github.com/yui/yuglify
223 .. _`Slim It`: http://slimit.org/239 .. _`Slim It`: https://github.com/rspivak/slimit
224240
225.. attribute:: COMPRESS_PRECOMPILERS241.. attribute:: COMPRESS_PRECOMPILERS
226242
@@ -305,7 +321,7 @@
305 <link rel="stylesheet" href="/static/CACHE/css/8ccf8d877f18.css" type="text/css" charset="utf-8">321 <link rel="stylesheet" href="/static/CACHE/css/8ccf8d877f18.css" type="text/css" charset="utf-8">
306322
307 .. _less: http://lesscss.org/323 .. _less: http://lesscss.org/
308 .. _CoffeeScript: http://jashkenas.github.com/coffee-script/324 .. _CoffeeScript: http://coffeescript.org/
309325
310.. attribute:: COMPRESS_STORAGE326.. attribute:: COMPRESS_STORAGE
311327
312328
=== modified file 'docs/usage.txt'
--- docs/usage.txt 2015-01-06 14:31:34 +0000
+++ docs/usage.txt 2015-08-20 18:49:21 +0000
@@ -48,7 +48,7 @@
4848
49.. note::49.. note::
5050
51 Remember that django-compressor will try to :ref:`group ouputs by media <css_notes>`.51 Remember that django-compressor will try to :ref:`group outputs by media <css_notes>`.
5252
53Linked files **must** be accessible via53Linked files **must** be accessible via
54:attr:`~django.conf.settings.COMPRESS_URL`.54:attr:`~django.conf.settings.COMPRESS_URL`.
5555
=== modified file 'tox.ini'
--- tox.ini 2014-06-26 15:08:13 +0000
+++ tox.ini 2015-08-20 18:49:21 +0000
@@ -33,89 +33,34 @@
3333
34[tox]34[tox]
35envlist =35envlist =
36 py33-1.6.X,36 {py26,py27}-{1.4.X,1.5.X},
37 py32-1.6.X,37 {py26,py27,py32,py33}-{1.6.X},
38 py27-1.6.X,38 {py27,py32,py33,py34}-{1.7.X},
39 py26-1.6.X,39 {py27,py32,py33,py34}-{1.8.X}
40 py33-1.5.X,
41 py32-1.5.X,
42 py27-1.5.X,
43 py26-1.5.X,
44 py27-1.4.X,
45 py26-1.4.X
46
47[testenv]40[testenv]
41basepython =
42 py26: python2.6
43 py27: python2.7
44 py32: python3.2
45 py33: python3.3
46 py34: python3.4
47usedevelop = true
48setenv =48setenv =
49 CPPFLAGS=-O049 CPPFLAGS=-O0
50usedevelop = true
51whitelist_externals = /usr/bin/make50whitelist_externals = /usr/bin/make
52downloadcache = {toxworkdir}/_download/51downloadcache = {toxworkdir}/_download/
53commands =52commands =
54 django-admin.py --version53 django-admin.py --version
55 make test54 make test
5655deps =
57[testenv:py33-1.6.X]56 1.4.X: Django>=1.4,<1.5
58basepython = python3.357 1.5.X: Django>=1.5,<1.6
59deps =58 1.6.X: Django>=1.6,<1.7
60 Django>=1.6,<1.759 1.7.X: Django>=1.7,<1.8
61 {[deps]three}60 1.8.X: Django>=1.8,<1.9
6261 py26: {[deps]two}
63[testenv:py32-1.6.X]62 py27: {[deps]two}
64basepython = python3.263 py32: {[deps]three_two}
65deps =64 py33: {[deps]three}
66 Django>=1.6,<1.765 py34: {[deps]three}
67 {[deps]three_two}66 django-discover-runner
68
69[testenv:py27-1.6.X]
70basepython = python2.7
71deps =
72 Django>=1.6,<1.7
73 {[deps]two}
74
75[testenv:py26-1.6.X]
76basepython = python2.6
77deps =
78 Django>=1.6,<1.7
79 {[deps]two}
80
81[testenv:py33-1.5.X]
82basepython = python3.3
83deps =
84 Django>=1.5,<1.6
85 django-discover-runner
86 {[deps]three}
87
88[testenv:py32-1.5.X]
89basepython = python3.2
90deps =
91 Django>=1.5,<1.6
92 django-discover-runner
93 {[deps]three_two}
94
95[testenv:py27-1.5.X]
96basepython = python2.7
97deps =
98 Django>=1.5,<1.6
99 django-discover-runner
100 {[deps]two}
101
102[testenv:py26-1.5.X]
103basepython = python2.6
104deps =
105 Django>=1.5,<1.6
106 django-discover-runner
107 {[deps]two}
108
109[testenv:py27-1.4.X]
110basepython = python2.7
111deps =
112 Django>=1.4,<1.5
113 django-discover-runner
114 {[deps]two}
115
116[testenv:py26-1.4.X]
117basepython = python2.6
118deps =
119 Django>=1.4,<1.5
120 django-discover-runner
121 {[deps]two}

Subscribers

People subscribed via source and target branches

to all changes: