pax_global_header00006660000000000000000000000064144466570460014532gustar00rootroot0000000000000052 comment=95971ee8c5e5d0327eb7ec1024a851e9818bb09e django-compressor-4.4/000077500000000000000000000000001444665704600150355ustar00rootroot00000000000000django-compressor-4.4/.github/000077500000000000000000000000001444665704600163755ustar00rootroot00000000000000django-compressor-4.4/.github/dependabot.yml000066400000000000000000000002111444665704600212170ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/requirements" schedule: interval: daily open-pull-requests-limit: 99 django-compressor-4.4/.github/workflows/000077500000000000000000000000001444665704600204325ustar00rootroot00000000000000django-compressor-4.4/.github/workflows/ci.yml000066400000000000000000000025021444665704600215470ustar00rootroot00000000000000name: CI on: push: branches: - develop - master pull_request: jobs: test: strategy: fail-fast: false matrix: versions: - python: 3.8 toxenv: py38-3.2.X - python: 3.9 toxenv: py39-3.2.X - python: "3.10" toxenv: py310-3.2.X - python: 3.8 toxenv: py38-4.0.X - python: 3.9 toxenv: py39-4.0.X - python: "3.10" toxenv: py310-4.0.X - python: 3.8 toxenv: py38-4.1.X - python: 3.9 toxenv: py39-4.1.X - python: "3.10" toxenv: py310-4.1.X - python: "3.11" toxenv: py311-4.1.X - python: 3.8 toxenv: py38-4.2.X - python: 3.9 toxenv: py39-4.2.X - python: "3.10" toxenv: py310-4.2.X - python: "3.11" toxenv: py311-4.2.X runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.versions.python }} - run: | pip install tox python setup.py install_egg_info - run: tox -e ${{ matrix.versions.toxenv }} - run: | pip install codecov codecov django-compressor-4.4/.gitignore000066400000000000000000000005451444665704600170310ustar00rootroot00000000000000build compressor/tests/static/CACHE compressor/tests/static/custom compressor/tests/static/js/066cd253eada.js compressor/tests/static/js/d728fc7f9301.js compressor/tests/static/js/74e158ccb432.js compressor/tests/static/js/8a0fed36c317.js compressor/tests/static/test.txt* dist MANIFEST *.pyc *.egg-info *.egg docs/_build/ .sass-cache .coverage .tox .eggs django-compressor-4.4/AUTHORS000066400000000000000000000027411444665704600161110ustar00rootroot00000000000000Christian Metts Carl Meyer Jannis Leidel Mathieu Pillard Django Compressor's filters started life as the filters from Andreas Pelme's django-compress. Contributors: Aaron Godfrey Adam "Cezar" Jenkins Adrian Holovaty Alen Mujezinovic Alex Kessinger Alexander van Ratingen Andreas Pelme Antti Hirvonen Apostolos Bessas Ashley Camba Garrido Atamert Ölçgen Aymeric Augustin Bartek Ciszkowski Ben Firshman Benjamin Gilbert Benjamin Wohlwend Ben Spaulding Bojan Mihelac Boris Shemigon Brad Whittington Bruno Renié Carlton Gibson Cassus Adam Banko Chris Adams Chris Streeter Clay McClure David Medina David Ziegler Eugene Mirotin Fenn Bailey Francisco Souza Gert Van Gool Greg McGuire Harro van der Klauw Isaac Bythewood Iván Raskovsky Jaap Roes James Roe Jason Davies Jens Diemer Jeremy Dunck Jervis Whitley John-Scott Atlakson Jonas von Poser Jonathan Lukens Julian Scheid Julien Phalip Justin Lilly Keith Hackbarth Lucas Tan Luis Nell Lukas Lehner Łukasz Balcerzak Łukasz Langa Maciek Szczesniak Maor Ben-Dayan Mark Lavin Marsel Mavletkulov Matthew Tretter Matt Schick Mehmet S. Catalbas Michael van de Waeter Mike Yumatov Nick Pope Nicolas Charlot Niran Babalola Paul McMillan Petar Radosevic Peter Bengtsson Peter Lundberg Philipp Bosch Philipp Wollermann Rich Leland Rick van Hattem (wolph) Sam Dornan Saul Shanabrook Sébastien Piquemal Selwin Ong Shabda Raaj Stefano Brentegani Thomas Schreiber Thom Linton Tino de Bruijn tufedtm Ulrich Petri Ulysses V Vladislav Poluhin wesleyb Wilson Júnior django-compressor-4.4/LICENSE000066400000000000000000000044511444665704600160460ustar00rootroot00000000000000django_compressor ----------------- Copyright (c) 2009-2022 Django Compressor authors (see AUTHORS file) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django_compressor contains code from Andreas Pelme's django-compress -------------------------------------------------------------------- Copyright (c) 2008 Andreas Pelme Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django-compressor-4.4/MANIFEST.in000066400000000000000000000005031444665704600165710ustar00rootroot00000000000000include AUTHORS include README.rst include LICENSE include Makefile include tox.ini recursive-include docs * recursive-include requirements * recursive-include compressor/templates/compressor *.html recursive-include compressor/tests/media *.js *.css *.png *.coffee recursive-include compressor/tests/test_templates *.html django-compressor-4.4/Makefile000066400000000000000000000006261444665704600165010ustar00rootroot00000000000000testenv: pip install -e . pip install -r requirements/tests.txt pip install Django flake8: flake8 compressor --ignore=E203,E501,W503 runtests: coverage run --branch --source=compressor `which django-admin` test --settings=compressor.test_settings compressor coveragereport: coverage report --omit=compressor/test* test: flake8 runtests coveragereport .PHONY: test runtests flake8 coveragereport django-compressor-4.4/README.rst000066400000000000000000000073751444665704600165400ustar00rootroot00000000000000Django Compressor ================= .. image:: https://codecov.io/github/django-compressor/django-compressor/coverage.svg?branch=develop :target: https://codecov.io/github/django-compressor/django-compressor?branch=develop .. image:: https://img.shields.io/pypi/v/django_compressor.svg :target: https://pypi.python.org/pypi/django_compressor .. image:: https://img.shields.io/github/actions/workflow/status/django-compressor/django-compressor/ci.yml?branch=develop :alt: Build Status :target: https://github.com/django-compressor/django-compressor/actions?query=workflow%3ACI Django Compressor processes, combines and minifies linked and inline Javascript or CSS in a Django template into cacheable static files. It supports compilers such as coffeescript, LESS and SASS and is extensible by custom processing steps. How it works ------------ In your templates, all HTML code between the tags ``{% compress js/css %}`` and ``{% endcompress %}`` is parsed and searched for CSS or JS. These styles and scripts are subsequently processed with optional, configurable compilers and filters. The default filter for CSS rewrites paths to static files to be absolute. Both Javascript and CSS files are by default concatenated and minified. As the final step the template tag outputs a ``django-compressor-4.4/compressor/templates/compressor/js_inline.html000066400000000000000000000000561444665704600262640ustar00rootroot00000000000000django-compressor-4.4/compressor/templates/compressor/js_preload.html000066400000000000000000000000761444665704600264360ustar00rootroot00000000000000django-compressor-4.4/compressor/templatetags/000077500000000000000000000000001444665704600217235ustar00rootroot00000000000000django-compressor-4.4/compressor/templatetags/__init__.py000066400000000000000000000000001444665704600240220ustar00rootroot00000000000000django-compressor-4.4/compressor/templatetags/compress.py000066400000000000000000000152771444665704600241440ustar00rootroot00000000000000from django import template from django.core.exceptions import ImproperlyConfigured from compressor.cache import ( cache_get, cache_set, get_offline_hexdigest, get_offline_manifest, get_templatetag_cachekey, ) from compressor.conf import settings from compressor.exceptions import OfflineGenerationError from compressor.utils import get_class register = template.Library() OUTPUT_FILE = "file" OUTPUT_INLINE = "inline" OUTPUT_PRELOAD = "preload" OUTPUT_MODES = (OUTPUT_FILE, OUTPUT_INLINE, OUTPUT_PRELOAD) class CompressorMixin: def get_original_content(self, context): raise NotImplementedError @property def compressors(self): return settings.COMPRESSORS def compressor_cls(self, kind): if kind not in self.compressors.keys(): raise template.TemplateSyntaxError( "The compress tag's argument must be one of: %s." % ", ".join(map(repr, self.compressors.keys())) ) return get_class(self.compressors.get(kind), exception=ImproperlyConfigured) def get_compressor(self, context, kind, log, verbosity): cls = self.compressor_cls(kind) return cls( kind, content=self.get_original_content(context), context=context, log=log, verbosity=verbosity, ) def debug_mode(self, context): if settings.COMPRESS_DEBUG_TOGGLE: # Only check for the debug parameter if a RequestContext was used request = context.get("request", None) if request is not None: return settings.COMPRESS_DEBUG_TOGGLE in request.GET def is_offline_compression_enabled(self, forced): """ Check if offline compression is enabled or forced Defaults to just checking the settings and forced argument, but can be overridden to completely disable compression for a subclass, for instance. """ return (settings.COMPRESS_ENABLED and settings.COMPRESS_OFFLINE) or forced def render_offline(self, context): """ If enabled and in offline mode, and not forced check the offline cache and return the result if given """ original_content = self.get_original_content(context) key = get_offline_hexdigest(original_content) offline_manifest = get_offline_manifest() if key in offline_manifest: return offline_manifest[key].replace( settings.COMPRESS_URL_PLACEHOLDER, # Cast ``settings.COMPRESS_URL`` to a string to allow it to be # a string-alike object to e.g. add ``SCRIPT_NAME`` WSGI param # as a *path prefix* to the output URL. # See https://code.djangoproject.com/ticket/25598. str(settings.COMPRESS_URL), ) else: raise OfflineGenerationError( "You have offline compression " 'enabled but key "%s" is missing from offline manifest. ' 'You may need to run "python manage.py compress". Here ' "is the original content:\n\n%s" % (key, original_content) ) def render_cached(self, compressor, kind, mode): """ If enabled checks the cache for the given compressor's cache key and return a tuple of cache key and output """ cache_key = get_templatetag_cachekey(compressor, mode, kind) cache_content = cache_get(cache_key) return cache_key, cache_content def render_compressed( self, context, kind, mode, name=None, forced=False, log=None, verbosity=0 ): # See if it has been rendered offline if self.is_offline_compression_enabled(forced) and not forced: return self.render_offline(context) # Take a shortcut if we really don't have anything to do if ( not settings.COMPRESS_ENABLED and not settings.COMPRESS_PRECOMPILERS and not forced ): return self.get_original_content(context) name = name or getattr(self, "name", None) context["compressed"] = {"name": name} compressor = self.get_compressor(context, kind, log, verbosity) # Check cache cache_key = None if settings.COMPRESS_ENABLED and not forced: cache_key, cache_content = self.render_cached(compressor, kind, mode) if cache_content is not None: return cache_content file_basename = name or getattr(self, "basename", None) if file_basename is None: file_basename = "output" rendered_output = compressor.output(mode, forced=forced, basename=file_basename) assert isinstance(rendered_output, str) if cache_key: cache_set(cache_key, rendered_output) return rendered_output class CompressorNode(CompressorMixin, template.Node): def __init__(self, nodelist, kind=None, mode=OUTPUT_FILE, name=None): self.nodelist = nodelist self.kind = kind self.mode = mode self.name = name def get_original_content(self, context): return self.nodelist.render(context) def render(self, context, forced=False): # Check if in debug mode if self.debug_mode(context): return self.get_original_content(context) # pass logger to compressor object try: log, verbosity = context.template._log, context.template._log_verbosity except AttributeError: log, verbosity = None, 0 return self.render_compressed( context, self.kind, self.mode, forced=forced, log=log, verbosity=verbosity ) @register.tag def compress(parser, token): """ Compresses linked and inline javascript or CSS into a single cached file. Syntax:: {% compress [ [block_name]] %} {% endcompress %} Examples:: See docs/usage.rst """ nodelist = parser.parse(("endcompress",)) parser.delete_first_token() args = token.split_contents() if not len(args) in (2, 3, 4): raise template.TemplateSyntaxError( "%r tag requires either one, two or three arguments." % args[0] ) kind = args[1] if len(args) >= 3: mode = args[2] if mode not in OUTPUT_MODES: raise template.TemplateSyntaxError( "%r's second argument must be '%s' or '%s'." % (args[0], OUTPUT_FILE, OUTPUT_INLINE) ) else: mode = OUTPUT_FILE if len(args) == 4: name = args[3] else: name = None return CompressorNode(nodelist, kind, mode, name) django-compressor-4.4/compressor/test_settings.py000066400000000000000000000032231444665704600225020ustar00rootroot00000000000000import os TEST_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "tests") CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "unique-snowflake", } } DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", } } INSTALLED_APPS = [ "django.contrib.staticfiles", "compressor", "sekizai", ] STATICFILES_FINDERS = [ "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", "compressor.finders.CompressorFinder", ] STATIC_URL = "/static/" STATIC_ROOT = os.path.join(TEST_DIR, "static") TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "DIRS": [ # Specifically choose a name that will not be considered # by app_directories loader, to make sure each test uses # a specific template without considering the others. os.path.join(TEST_DIR, "test_templates"), ], }, { "BACKEND": "django.template.backends.jinja2.Jinja2", "APP_DIRS": True, "DIRS": [ # Specifically choose a name that will not be considered # by app_directories loader, to make sure each test uses # a specific template without considering the others. os.path.join(TEST_DIR, "test_templates_jinja2"), ], }, ] SECRET_KEY = "iufoj=mibkpdz*%bob952x(%49rqgv8gg45k36kjcg76&-y5=!" PASSWORD_HASHERS = ("django.contrib.auth.hashers.UnsaltedMD5PasswordHasher",) MIDDLEWARE_CLASSES = [] django-compressor-4.4/compressor/tests/000077500000000000000000000000001444665704600203735ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/__init__.py000066400000000000000000000000001444665704600224720ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/precompiler.py000066400000000000000000000016241444665704600232710ustar00rootroot00000000000000#!/usr/bin/env python import optparse import sys def main(): p = optparse.OptionParser() p.add_option( "-f", "--file", action="store", type="string", dest="filename", help="File to read from, defaults to stdin", default=None, ) p.add_option( "-o", "--output", action="store", type="string", dest="outfile", help="File to write to, defaults to stdout", default=None, ) options, arguments = p.parse_args() if options.filename: with open(options.filename) as f: content = f.read() else: content = sys.stdin.read() content = content.replace("background:", "color:") if options.outfile: with open(options.outfile, "w") as f: f.write(content) else: print(content) if __name__ == "__main__": main() django-compressor-4.4/compressor/tests/static/000077500000000000000000000000001444665704600216625ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/css/000077500000000000000000000000001444665704600224525ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/css/datauri.css000066400000000000000000000010651444665704600246170ustar00rootroot00000000000000.add { background-image: url("../img/add.png"); } .add-with-hash { background-image: url("../img/add.png#add"); } .python { background-image: url("../img/python.png"); } .datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); } django-compressor-4.4/compressor/tests/static/css/filename with spaces.css000066400000000000000000000000341444665704600271340ustar00rootroot00000000000000body { background:#424242; }django-compressor-4.4/compressor/tests/static/css/nonasc.css000066400000000000000000000000451444665704600244440ustar00rootroot00000000000000.byline:before { content: " — "; }django-compressor-4.4/compressor/tests/static/css/one.css000066400000000000000000000000311444665704600237370ustar00rootroot00000000000000body { background:#990; }django-compressor-4.4/compressor/tests/static/css/relative_url.css000066400000000000000000000000531444665704600256570ustar00rootroot00000000000000p { background: url('../img/python.png'); }django-compressor-4.4/compressor/tests/static/css/two.css000066400000000000000000000000241444665704600237710ustar00rootroot00000000000000body { color:#fff; }django-compressor-4.4/compressor/tests/static/css/url/000077500000000000000000000000001444665704600232545ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/css/url/2/000077500000000000000000000000001444665704600234155ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/css/url/2/url2.css000066400000000000000000000004321444665704600250120ustar00rootroot00000000000000p { background: url('../../../img/add.png'); } p { background: url(../../../img/add.png); } p { background: url( ../../../img/add.png ); } p { background: url( '../../../img/add.png' ); } p { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../../img/add.png'); } django-compressor-4.4/compressor/tests/static/css/url/nonasc.css000066400000000000000000000001271444665704600252470ustar00rootroot00000000000000p { background: url( '../../images/test.png' ); } .byline:before { content: " — "; }django-compressor-4.4/compressor/tests/static/css/url/test.css000066400000000000000000000000611444665704600247420ustar00rootroot00000000000000p { background: url('/static/images/image.gif') }django-compressor-4.4/compressor/tests/static/css/url/url1.css000066400000000000000000000004321444665704600246500ustar00rootroot00000000000000p { background: url('../../img/python.png'); } p { background: url(../../img/python.png); } p { background: url( ../../img/python.png ); } p { background: url( '../../img/python.png' ); } p { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../../img/python.png'); } django-compressor-4.4/compressor/tests/static/css/utf-8_with-BOM.css000066400000000000000000000000371444665704600255750ustar00rootroot00000000000000.compress-test {color: red;}django-compressor-4.4/compressor/tests/static/img/000077500000000000000000000000001444665704600224365ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/img/add with spaces.png000066400000000000000000000013351444665704600260710ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<oIDAT8˥Ka[/Y()%X(olNۖskn.-h;8fEP"jïMGˈ}yພ羹$I.tulu AX:𼂒ZHh1DnZJOJB{Z?`2`S=N$ő=;a &jw qJG#<"N2h8޵`6xցn_+ ~Zto}`x%XЛ͈ hXѿƻ/}BJ_G&|Qr-6Aރ EL⬡\U3:WUh[C6+ 6.f *K͸ܝFq ou4܄?d|XҥMvD` *_[ #A20liR|xq`4w=\uQ m+G|%$5Թ5RO*YGMUO Gqj4ְ(X& s1c˭(LVf RdjQ '-1ATA>U j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`django-compressor-4.4/compressor/tests/static/img/add.png000066400000000000000000000013351444665704600236760ustar00rootroot00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<oIDAT8˥Ka[/Y()%X(olNۖskn.-h;8fEP"jïMGˈ}yພ羹$I.tulu AX:𼂒ZHh1DnZJOJB{Z?`2`S=N$ő=;a &jw qJG#<"N2h8޵`6xցn_+ ~Zto}`x%XЛ͈ hXѿƻ/}BJ_G&|Qr-6Aރ EL⬡\U3:WUh[C6+ 6.f *K͸ܝFq ou4܄?d|XҥMvD` *_[ #A20liR|xq`4w=\uQ m+G|%$5Թ5RO*YGMUO Gqj4ְ(X& s1c˭(LVf RdjQ '-1ATA>U j4,pV"4L$e@.ArBY a~myY])Q8tNLܞt2"I o=CSd)__AF(IENDB`django-compressor-4.4/compressor/tests/static/img/python.png000066400000000000000000000256231444665704600244750ustar00rootroot00000000000000PNG  IHDRY]Ɇ&sBIT|d pHYs B4tEXtCreation Time06/05/04Mq%tEXtSoftwareMacromedia Fireworks MX 2004v IDATxׁ2G?,kkԳq\Ґr)ETٽURAWW@6rE󢹢X]Z;WmRnU4uq~4"sqʿ"^ѯHY5;p~!g ߼y]-ޛ7 0@DDDD #,"""0dE!( YDDDD`""""CQ"EDDD,"""0dE!( YDDDD`""""CQ"EDDD,"""0dE!( YDDDD`""""CQ"EDDD,"""0dE!( YDDC^CUU̠V9DҦ~7¡:* 4MCZm?p@[E~1d%TUeрa""JRM "rN9wKX<4] 0,G`tG wZ7-ݙžؖ%wDDD4Йs+8y~<Μ[z[` T| ז.Աtjkoʬֽ-u 5z;xwA?""$`gέW.`BO.%|۵ҸʋE_ sLۃG?;A""aJӯ\׿"Nrޒ X.+cpgW(NVŷ5>a .\š}O}(Q1d%ԟ}Y^S<([%""J$&׿NV_oi XA X'_=ѷKDDHJ <,`,p_'f+LDDDJbFw܆~غy.6s/KCye pB< _O[~!+a&w=wo!lmmQ={5| !ȥ hQ]O<_<;'JzI Ȕe2(A>w-i4Mþ}Nlfl8\ Kk`Y6u,GG8 Y-,A X:M%(xDZEjÆ mu9J%Z< :d%ȹ6 h^׀eߏVqhB{7}nPU7mugllm\.w3Zb>vdڍg!w/[_\jNUSy/cȢPQVQTPVW(38yyepa99B-\G2 `~]DlaaRH,ː$ u* BZ.$Me**//#e8<~CzU(j۾j Y;݊!+q֍=N~ȓ"X2ʻL Xkh- DDԛ\.UUQ[TU0Ia3.'z%|vj 2@g- zJshNx4!69Ͻ??҅VgqʵMZoeض-u:3o><3 Յ //M!%t|- F,"u/h.>z)>|- CVtqg_k>x{c"""Ӂp HYݜp0aDV(k@眪p(e  |>X=Y N[|>EQBhYMZm0(z>dd5<_Y9y܇0Iq&' [a}l<;>lބ4 kEQ011cv- ,)ZXX(=rZ0TUorb80rX0dE+g|cbr`r4H {]cH"M077yO|GEX /RZ] ]Q.C6?ߏRzǏGcFw똞{^0dEʵNV^ 1%^wk} X;Tw̃߾ Uo:PZ B+J4 b/uBUαELNNFJ4M#qjÇG:,w{0dEʵpc9wmrv 69AV=Y\rwv%CAkxcnn.`R0==ɇ}-5{{:똝ER |[[ jQ~ X!(;˯C|GQ.Е$PVpqI",Y[fvW!uLOO:4r9dY({u"* TU }4{GWoBk:Cc0) 0mr-,KQyP6RUCT* M .d2bbbqK %tui,Z5!\gW4,,,`I X\9Nӭ#z zoe@zkޏ$I( ( HRd2d2Ϩf_<B4_XXˣ0 ݇0^Mh poK[RЬ)pOMk/^Ġ\,#I$>|X|PG9E^sN8h%s5¢F/Y͵zwc NV^S-`K1\hЦ'Y?MOtZx^P/4x$IBX "Ţps=~Mӄ%:Br9,dU a O4,þPSϐc"(/&[RS@׶2vtdn_>ZS(BVUYC"رc>%J^ `"&wK$6;;&ǐWOWi=!>ECx p|sǶr#`R2bvA:t(y=v|^)臵Hȗ$ ¼׏D7ׂÈ!GWX]a-=% eBs4[ht,k;ˡNy[j#ڣ8n,:Qܶ;MCڶMܱm_vXn\M+I(35%":\'4M*{@˳i+m$\/Z Y=:tymrBY>w|йdf]p X>{=^Y;_̥%"DzPLho҆ףB&~<%ฅ"zBo@lkUCX5d5<&e02#@`I{_~5uksͫSl_/nG\"w/V Ydb҅4,þPwxNu,ãm>au{ڛ=`ksޫSj5nfp^ @h89L݀$Ҹzp 1>U]6㱏 |{֠r9VwBQո<1d}N֞BLh=̅sVf>лァ `cy\[]8qa !RlMl;{we86Wp V@sҿ t$)<|M8 ?81d¯S=H#6p3.R o_E+`=2!ghaNl}]xcGz{QyX.- ZLJ$2uSyR3x={,`\u A~xxE`vbOV j( YyNSy㷥nŞ{vR}x:dKbвDc1 Y=ܽ +x@s XF𜽾avp#$zA J 4z5u3d@ X=,[g!fكU > dx!khz@kD{E%DM?kqer YdbAsp  k{疵_[ l!{@rlek;ltPyNCAiiB zg V#^n.dzz|=x $~8k5pXD?vqKY{ iAB- Y={;,`95z玵jKHC.^ Tqi}Z#dZRV O dq.yY"c/Y1d(s'!>ny۞)5 jǰRv> Z#!%IRLxi"қzla]uBCk()z;yi:ubK)`=:,$ZqA;:m,ѐ%:|$bZDzԩSRIӜNmzԼ Xx,2Z ^ A{,~A(H ^;ujU}-AP%K*B>-j ;ρbh % CV)ن ֞[^,km~H?VX jHӁcx-h-h4"ZKJ$ i}a A!?l],Y6UO?VX< lj똟*_"WWz!HIBÆr9{e2B+ Y!x6eyLXxѠU }!׵R$4ۿE1N"YtpZ311[Fu?4 e4SNB @+`֪C q 9F3&RϝjdCUU᡺::fggiB{%uiaùпz333<yix0d3}3cn,å_=a~NǁwlETUt(izrG}ΗȇmTZîX,FQ^GT ,9!+DS,z za+k_^A0Ä _>rDu<zb4MѣGzR 177ח^"K]hZ3\dY Z _^"D4LOO8JbiS0Lr܉G?x?N^Ӑukit2^=;c޴ oâh`nnssseL`mWuj5j5x,χv)4Mf( dYv4 C4TՁ@d|瀕ej5|ӟZ,Ţ fV&E JEx$I£!+d_pX|eձO " u+ lJ\,QfZXX|>0ÇO{j5"4M }JJ|l#,3ELOOVbw EXyb B[pGqh;X|mu!̈,[)`e {xs@H*EQb_=4'9r333bT*ILOO 6w,N#5|$T* }!K,9Yz-8~>`ˀCذq@b XC;W'{p||SSSM,>a`K%E&$jC(J܃d28vTUJBt: EQ(J*PT b(3A󡬋o߾$`n3i\o忎'`s@z_g:r/jںjOEV Un RNQHԚ,˲P@z<_^뾕 IDAThgayy1d*I!oI۲,ң7(m1_ig yEQZJT%6 m0lv=YI l6۶KVzJ4FFFJ*Tɲq:/^|!JwanҖt:c}n?t/Wu `CV%9,,}CV0',m8mvY߻oJ5MDacJ8 /^Ov p`JЂW)̠fu˶h1d%NxS]c=],f,"(0d%J?UE~ޤ$A +` Hrxd,"q XA&Ȱ^wz;QzFFFbj ' YhV/ٖGo൯O1ٓEDQ`JA|_s(pϟ4/Cj[ UQ6@[oꎸa+Ϲy^?aT*+SKhaJ~,{}wm{[G7QoOV.5D0d%-Gޓwlkh96,Ή'Z7vc6Q8'+InVC}.uIUUߡ|>t:ShaOV|Ɋ:`P_o[+l'*JoZCDCVlۿ$,z yzxDpb/EÅIKϬnXŠc@2u& ~x+_tnEܮ;ǚK=@4΢VUccc13| "`l; X> :pA`#o.o>\m jnnwQ,8|pĭ""bJ? xRW^??^,ꚦiT*V̱eGA*uDDM YIq ρ \{d-Y.?f*$it]ٳgQհ(p|>2`Ql ZO+jm,,raPj5ʍ$I(\bǐd&\*? n@3`m=]Bh !k.Ms_Xנy9W$ J|>B%;fZ+ds觸BI|W#_RdYZNe r9\@`6 Zw1ppnA`燛Wffh4PUN*T +EQ$AQ(l6^+"8 #M(/W*Jh,oCOAifھ7w2t]o.]qd2sZZL۾t:Ȳd2ȲܧaZ޾ 44 O+[r)"ͣ쩊PV$IghV!(A4Q" ׃Dž7 1h00d%as1ѰbJ 0%⾾jae _DD` Y5*^*Kj<}""$)< *o>"“k}͝MDDo Y$B_jrl*y)ߠe-p} \DD4`KDCV빽>۶W:@}bo \ne8m큲s۶ A\d( Y$L ەnH4\ٷ""u0meH Eaƍ;g-gg=52Y_־ՅP-DDDyXn&['Hyk30Ctn޼7m$ZEDD1cȊ}n>#\nCc%r{+dg"nXN܆:q\ XDD3(m)t9.{ydkn[{3ݼySx|,ׄ\ r:'<'G~5!M~>n/ r5aW.jC% CVLhrԂ^~R'\57lʼn!+N^-^nHYc Y}HpsCV? !l}Ͷ(ϞNm;!"">;0n><dRp Vm"""CqV.c ? ~=FWs,|w aTNk?C߷8Aʸ$ġ@""J$X)+nx""Aǐ5 DÓyaM OhHqaa0Ah1d'f"""" !( YDDDD`""""CQ"EDDD,"""0dE!( YDDDD`""""CQ"EDDD,"""0dE!( YDDDD`""""CQ"EDDD,"""0dE!( YDDDD`""""CQ"ŅwIIENDB`django-compressor-4.4/compressor/tests/static/js/000077500000000000000000000000001444665704600222765ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/static/js/nonasc-latin1.js000066400000000000000000000000371444665704600253030ustar00rootroot00000000000000var test_value = "berstrng"; django-compressor-4.4/compressor/tests/static/js/nonasc.js000066400000000000000000000000301444665704600241060ustar00rootroot00000000000000var test_value = "—"; django-compressor-4.4/compressor/tests/static/js/one.coffee000066400000000000000000000000251444665704600242250ustar00rootroot00000000000000# this is a comment. django-compressor-4.4/compressor/tests/static/js/one.js000066400000000000000000000000111444665704600234050ustar00rootroot00000000000000obj = {};django-compressor-4.4/compressor/tests/static/js/three.js000066400000000000000000000000151444665704600237370ustar00rootroot00000000000000hermanos = {}django-compressor-4.4/compressor/tests/static/js/two.js000066400000000000000000000000131444665704600234370ustar00rootroot00000000000000pollos = {}django-compressor-4.4/compressor/tests/test_base.py000066400000000000000000000505061444665704600227240ustar00rootroot00000000000000import os import re import sys from tempfile import mkdtemp from shutil import rmtree, copytree from bs4 import BeautifulSoup from django.core.cache.backends import locmem from django.test import SimpleTestCase from django.test.utils import override_settings from compressor import cache as cachemod from compressor.base import SOURCE_FILE, SOURCE_HUNK from compressor.cache import get_cachekey, get_precompiler_cachekey from compressor.conf import settings from compressor.css import CssCompressor from compressor.exceptions import FilterDoesNotExist, FilterError from compressor.js import JsCompressor from compressor.storage import DefaultStorage def make_soup(markup): return BeautifulSoup(markup, "html.parser") def css_tag(href, **kwargs): rendered_attrs = "".join([' %s="%s"' % (k, v) for k, v in kwargs.items()]) template = '' return template % (href, rendered_attrs) class TestPrecompiler: """A filter whose output is always the string 'OUTPUT'""" def __init__(self, content, attrs, filter_type=None, filename=None, charset=None): pass def input(self, **kwargs): return "OUTPUT" class PassthroughPrecompiler: """A filter whose outputs the input unmodified""" def __init__(self, content, attrs, filter_type=None, filename=None, charset=None): self.content = content def input(self, **kwargs): return self.content test_dir = os.path.abspath(os.path.join(os.path.dirname(__file__))) class PrecompilerAndAbsoluteFilterTestCase(SimpleTestCase): def setUp(self): self.html_orig = '' self.html_auto_close_removed = '' self.html_link_to_precompiled_css = '' self.html_link_to_absolutized_css = '' self.css_orig = ( "p { background: url('../img/python.png'); }" # content of relative_url.css ) self.css_absolutized = ( "p { background: url('/static/img/python.png?ccb38978f900'); }" ) def helper(self, enabled, use_precompiler, use_absolute_filter, expected_output): precompiler = ( (("text/css", "compressor.tests.test_base.PassthroughPrecompiler"),) if use_precompiler else () ) filters = ( ("compressor.filters.css_default.CssAbsoluteFilter",) if use_absolute_filter else () ) with self.settings( COMPRESS_ENABLED=enabled, COMPRESS_PRECOMPILERS=precompiler, COMPRESS_FILTERS={"css": filters}, ): css_node = CssCompressor("css", self.html_orig) output = list(css_node.hunks())[0] self.assertEqual(output, expected_output) @override_settings(COMPRESS_CSS_HASHING_METHOD="content") def test_precompiler_enables_absolute(self): """ Tests whether specifying a precompiler also runs the CssAbsoluteFilter even if compression is disabled, but only if the CssAbsoluteFilter is actually contained in the filters setting. While at it, ensure that everything runs as expected when compression is enabled. """ self.helper( enabled=False, use_precompiler=False, use_absolute_filter=False, expected_output=self.html_auto_close_removed, ) self.helper( enabled=False, use_precompiler=False, use_absolute_filter=True, expected_output=self.html_auto_close_removed, ) self.helper( enabled=False, use_precompiler=True, use_absolute_filter=False, expected_output=self.html_link_to_precompiled_css, ) self.helper( enabled=False, use_precompiler=True, use_absolute_filter=True, expected_output=self.html_link_to_absolutized_css, ) self.helper( enabled=True, use_precompiler=False, use_absolute_filter=False, expected_output=self.css_orig, ) self.helper( enabled=True, use_precompiler=False, use_absolute_filter=True, expected_output=self.css_absolutized, ) self.helper( enabled=True, use_precompiler=True, use_absolute_filter=False, expected_output=self.css_orig, ) self.helper( enabled=True, use_precompiler=True, use_absolute_filter=True, expected_output=self.css_absolutized, ) @override_settings( COMPRESS_ENABLED=True, COMPRESS_PRECOMPILERS=(), COMPRESS_DEBUG_TOGGLE="nocompress", ) class CompressorTestCase(SimpleTestCase): def setUp(self): self.css = """\ """ self.css_node = CssCompressor("css", self.css) self.js = """\ """ self.js_node = JsCompressor("js", self.js) def assertEqualCollapsed(self, a, b): """ assertEqual with internal newlines collapsed to single, and trailing whitespace removed. """ def collapse(s): return re.sub(r"\n+", "\n", s).rstrip() self.assertEqual(collapse(a), collapse(b)) def assertEqualSplits(self, a, b): """ assertEqual for splits, particularly ignoring the presence of a trailing newline on the content. """ def mangle(split): return [(x[0], x[1], x[2], x[3].rstrip()) for x in split] self.assertEqual(mangle(a), mangle(b)) def test_css_split(self): out = [ ( SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, "css", "one.css"), "css/one.css", '', ), ( SOURCE_HUNK, "p { border:5px solid green;}", None, '', ), ( SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, "css", "two.css"), "css/two.css", '', ), ] split = self.css_node.split_contents() split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] self.assertEqualSplits(split, out) def test_css_hunks(self): out = [ "body { background:#990; }", "p { border:5px solid green;}", "body { color:#fff; }", ] self.assertEqual(out, list(self.css_node.hunks())) def test_css_output(self): out = "body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }" hunks = "\n".join([h for h in self.css_node.hunks()]) self.assertEqual(out, hunks) def test_css_output_with_bom_input(self): out = "body { background:#990; }\n.compress-test {color: red;}" css = """ """ css_node_with_bom = CssCompressor("css", css) hunks = "\n".join([h for h in css_node_with_bom.hunks()]) self.assertEqual(out, hunks) def test_css_mtimes(self): is_date = re.compile(r"^\d{10}[\.\d]+$") for date in self.css_node.mtimes: self.assertTrue( is_date.match(str(float(date))), "mtimes is returning something that doesn't look like a date: %s" % date, ) @override_settings(COMPRESS_ENABLED=False) def test_css_return_if_off(self): self.assertEqualCollapsed(self.css, self.css_node.output()) def test_cachekey(self): is_cachekey = re.compile(r"\w{12}") self.assertTrue( is_cachekey.match(self.css_node.cachekey), r"cachekey is returning something that doesn't look like r'\w{12}'", ) def test_css_return_if_on(self): output = css_tag("/static/CACHE/css/600674ea1d3d.css") self.assertEqual(output, self.css_node.output().strip()) def test_css_preload_output(self): # this needs to have the same hash as in the test above out = '' self.assertEqual(out, self.css_node.output(mode="preload")) def test_js_split(self): out = [ ( SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, "js", "one.js"), "js/one.js", '', ), ( SOURCE_HUNK, 'obj.value = "value";', None, '', ), ] split = self.js_node.split_contents() split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] self.assertEqualSplits(split, out) def test_js_hunks(self): out = ["obj = {};", 'obj.value = "value";'] self.assertEqual(out, list(self.js_node.hunks())) def test_js_output(self): out = '' self.assertEqual(out, self.js_node.output()) def test_js_preload_output(self): # this needs to have the same hash as in the test above out = ( '' ) self.assertEqual(out, self.js_node.output(mode="preload")) def test_js_override_url(self): self.js_node.context.update({"url": "This is not a url, just a text"}) out = '' self.assertEqual(out, self.js_node.output()) def test_css_override_url(self): self.css_node.context.update({"url": "This is not a url, just a text"}) output = css_tag("/static/CACHE/css/600674ea1d3d.css") self.assertEqual(output, self.css_node.output().strip()) @override_settings(COMPRESS_PRECOMPILERS=(), COMPRESS_ENABLED=False) def test_js_return_if_off(self): self.assertEqualCollapsed(self.js, self.js_node.output()) def test_js_return_if_on(self): output = '' self.assertEqual(output, self.js_node.output()) @override_settings(COMPRESS_OUTPUT_DIR="custom") def test_custom_output_dir1(self): output = '' self.assertEqual(output, JsCompressor("js", self.js).output()) @override_settings(COMPRESS_OUTPUT_DIR="") def test_custom_output_dir2(self): output = '' self.assertEqual(output, JsCompressor("js", self.js).output()) @override_settings(COMPRESS_OUTPUT_DIR="/custom/nested/") def test_custom_output_dir3(self): output = '' self.assertEqual(output, JsCompressor("js", self.js).output()) @override_settings( COMPRESS_PRECOMPILERS=( ("text/foobar", "compressor.tests.test_base.TestPrecompiler"), ), COMPRESS_ENABLED=True, ) def test_precompiler_class_used(self): css = '' css_node = CssCompressor("css", css) output = make_soup(css_node.output("inline")) self.assertEqual(output.style.contents[0], "OUTPUT") @override_settings( COMPRESS_PRECOMPILERS=( ("text/foobar", "compressor.tests.test_base.NonexistentFilter"), ), COMPRESS_ENABLED=True, ) def test_nonexistent_precompiler_class_error(self): css = '' css_node = CssCompressor("css", css) self.assertRaises(FilterDoesNotExist, css_node.output, "inline") @override_settings( COMPRESS_PRECOMPILERS=(("text/foobar", "./foo -I ./bar/baz"),), COMPRESS_ENABLED=True, ) def test_command_with_dot_precompiler(self): css = '' css_node = CssCompressor("css", css) self.assertRaises(FilterError, css_node.output, "inline") @override_settings( COMPRESS_PRECOMPILERS=( ("text/django", "compressor.filters.template.TemplateFilter"), ), COMPRESS_ENABLED=True, ) def test_template_precompiler(self): css = '' css_node = CssCompressor("css", css) output = make_soup(css_node.output("inline")) self.assertEqual(output.style.contents[0], "p{border:10px solid green}") class CssMediaTestCase(SimpleTestCase): def setUp(self): self.css = """\ """ def test_css_output(self): css_node = CssCompressor("css", self.css) links = make_soup(css_node.output()).find_all("link") media = ["screen", "print", "all", None] self.assertEqual(len(links), 4) self.assertEqual(media, [link.get("media", None) for link in links]) def test_avoid_reordering_css(self): css = ( self.css + '' ) css_node = CssCompressor("css", css) media = ["screen", "print", "all", None, "print"] links = make_soup(css_node.output()).find_all("link") self.assertEqual(media, [link.get("media", None) for link in links]) @override_settings( COMPRESS_PRECOMPILERS=( ( "text/foobar", "%s %s {infile} {outfile}" % (sys.executable, os.path.join(test_dir, "precompiler.py")), ), ), COMPRESS_ENABLED=False, ) def test_passthough_when_compress_disabled(self): css = """\ """ css_node = CssCompressor("css", css) output = make_soup(css_node.output()).find_all(["link", "style"]) self.assertEqual( ["/static/css/one.css", "/static/css/two.css", None], [link.get("href", None) for link in output], ) self.assertEqual( ["screen", "screen", "screen"], [link.get("media", None) for link in output] ) @override_settings(COMPRESS_VERBOSE=True) class VerboseTestCase(CompressorTestCase): pass class CacheBackendTestCase(CompressorTestCase): def test_correct_backend(self): from compressor.cache import cache self.assertEqual(cache.__class__, locmem.LocMemCache) class JsAsyncDeferTestCase(SimpleTestCase): def setUp(self): self.js = """\ """ def test_js_output(self): def extract_attr(tag): if tag.has_attr("async"): return "async" if tag.has_attr("defer"): return "defer" js_node = JsCompressor("js", self.js) output = [None, "async", "defer", None, "async", None] scripts = make_soup(js_node.output()).find_all("script") attrs = [extract_attr(s) for s in scripts] self.assertEqual(output, attrs) class JSWithParensTestCase(SimpleTestCase): def setUp(self): self.js = """ """ def test_js_content(self): js_node = JsCompressor("js", self.js) content = js_node.filter_input() self.assertEqual(content[0], "obj = {};;") self.assertEqual(content[1], "pollos = {};") class CacheTestCase(SimpleTestCase): def setUp(self): cachemod._cachekey_func = None def test_get_cachekey_basic(self): self.assertEqual(get_cachekey("foo"), "django_compressor.foo") @override_settings(COMPRESS_CACHE_KEY_FUNCTION=".leading.dot") def test_get_cachekey_leading_dot(self): self.assertRaises(ImportError, lambda: get_cachekey("foo")) @override_settings(COMPRESS_CACHE_KEY_FUNCTION="invalid.module") def test_get_cachekey_invalid_mod(self): self.assertRaises(ImportError, lambda: get_cachekey("foo")) def test_get_precompiler_cachekey(self): try: get_precompiler_cachekey("asdf", "asdf") except TypeError: self.fail("get_precompiler_cachekey raised TypeError unexpectedly") class CompressorInDebugModeTestCase(SimpleTestCase): def setUp(self): self.css = ( '' ) self.expected_css_hash = "5c6a60375256" self.tmpdir = mkdtemp() new_static_root = os.path.join(self.tmpdir, "static") copytree(settings.STATIC_ROOT, new_static_root) self.override_settings = self.settings( COMPRESS_ENABLED=True, COMPRESS_PRECOMPILERS=(), COMPRESS_DEBUG_TOGGLE="nocompress", DEBUG=True, STATIC_ROOT=new_static_root, COMPRESS_ROOT=new_static_root, STATICFILES_DIRS=[settings.COMPRESS_ROOT], ) self.override_settings.__enter__() def tearDown(self): rmtree(self.tmpdir) self.override_settings.__exit__(None, None, None) def test_filename_in_debug_mode(self): # In debug mode, compressor should look for files using staticfiles # finders only, and not look into the global static directory, where # files can be outdated. So compressor's output shouldn't change from # the one pre-generated if we modify the file in STATIC_ROOT. def compare(): expected = ( '' % self.expected_css_hash ) compressor = CssCompressor("css", self.css) compressor.storage = DefaultStorage() output = compressor.output() self.assertEqual(expected, output) compare() filename = os.path.join(settings.COMPRESS_ROOT, "css", "one.css") test_css_content = "p { font-family: 'test' }" with open(filename, "a") as css: css.write("\n") css.write(test_css_content) compare() result_filename = os.path.join( settings.COMPRESS_ROOT, "CACHE", "css", "%s.css" % self.expected_css_hash ) with open(result_filename, "r") as f: result = f.read() self.assertTrue(test_css_content not in result) django-compressor-4.4/compressor/tests/test_conf.py000066400000000000000000000024461444665704600227370ustar00rootroot00000000000000from django.test import SimpleTestCase from django.test.utils import override_settings from compressor.conf import settings from compressor.conf import CompressorConf default_css_filters = [ "compressor.filters.css_default.CssAbsoluteFilter", "compressor.filters.cssmin.rCSSMinFilter", ] default_js_filters = ["compressor.filters.jsmin.rJSMinFilter"] def create_conf(**attrs): # Creating a new appconf.AppConf subclass will cause # its configuration to be resolved. # We use this to force the CompressorConf to be re-resolved, # when we've changed the settings. attrs["__module__"] = None return type("TestCompressorConf", (CompressorConf,), attrs) class ConfTestCase(SimpleTestCase): def test_filter_defaults(self): # This used the settings from compressor/test_settings.py # which contains no values for filers and therefore uses the defaults. self.assertEqual(settings.COMPRESS_FILTERS["css"], default_css_filters) self.assertEqual(settings.COMPRESS_FILTERS["js"], default_js_filters) @override_settings(COMPRESS_FILTERS=dict(css=["ham"], js=["spam"])) def test_filters_by_main_setting(self): conf = create_conf() self.assertEqual(conf.FILTERS["css"], ["ham"]) self.assertEqual(conf.FILTERS["js"], ["spam"]) django-compressor-4.4/compressor/tests/test_filters.py000066400000000000000000000612051444665704600234600ustar00rootroot00000000000000import io import os import sys from collections import defaultdict from unittest import mock from django.conf import settings from django.test import override_settings, TestCase from django.utils.encoding import smart_str from compressor.cache import cache, get_hashed_content, get_hashed_mtime from compressor.css import CssCompressor from compressor.filters import CachedCompilerFilter, CompilerFilter from compressor.filters.cleancss import CleanCSSFilter from compressor.filters.closure import ClosureCompilerFilter from compressor.filters.css_default import CssAbsoluteFilter, CssRelativeFilter from compressor.filters.cssmin import CSSCompressorFilter, rCSSMinFilter from compressor.filters.jsmin import CalmjsFilter, rJSMinFilter from compressor.filters.template import TemplateFilter from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter from compressor.filters.yui import YUICSSFilter, YUIJSFilter from compressor.tests.test_base import test_dir def blankdict(*args, **kwargs): return defaultdict(lambda: "", *args, **kwargs) @override_settings(COMPRESS_CACHEABLE_PRECOMPILERS=("text/css",)) class PrecompilerTestCase(TestCase): CHARSET = "utf-8" def setUp(self): self.test_precompiler = os.path.join(test_dir, "precompiler.py") self.setup_infile() self.cached_precompiler_args = dict( content=self.content, charset=self.CHARSET, filename=self.filename, mimetype="text/css", ) def setup_infile(self, filename="static/css/one.css"): self.filename = os.path.join(test_dir, filename) with io.open(self.filename, encoding=self.CHARSET) as file: self.content = file.read() def test_precompiler_dict_options(self): command = "%s %s {option}" % (sys.executable, self.test_precompiler) option = ( "option", "option", ) CompilerFilter.options = dict([option]) compiler = CompilerFilter( content=self.content, filename=self.filename, charset=self.CHARSET, command=command, ) self.assertIn(option, compiler.options) def test_precompiler_infile_outfile(self): command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) compiler = CompilerFilter( content=self.content, filename=self.filename, charset=self.CHARSET, command=command, ) self.assertEqual("body { color:#990; }", compiler.input()) def test_precompiler_infile_with_spaces(self): self.setup_infile("static/css/filename with spaces.css") command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) compiler = CompilerFilter( content=self.content, filename=self.filename, charset=self.CHARSET, command=command, ) self.assertEqual("body { color:#424242; }", compiler.input()) def test_precompiler_infile_stdout(self): command = "%s %s -f {infile}" % (sys.executable, self.test_precompiler) compiler = CompilerFilter( content=self.content, filename=None, charset=None, command=command ) self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input()) def test_precompiler_stdin_outfile(self): command = "%s %s -o {outfile}" % (sys.executable, self.test_precompiler) compiler = CompilerFilter( content=self.content, filename=None, charset=None, command=command ) self.assertEqual("body { color:#990; }", compiler.input()) def test_precompiler_stdin_stdout(self): command = "%s %s" % (sys.executable, self.test_precompiler) compiler = CompilerFilter( content=self.content, filename=None, charset=None, command=command ) self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input()) def test_precompiler_stdin_stdout_filename(self): command = "%s %s" % (sys.executable, self.test_precompiler) compiler = CompilerFilter( content=self.content, filename=self.filename, charset=self.CHARSET, command=command, ) self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input()) def test_precompiler_output_unicode(self): command = "%s %s" % (sys.executable, self.test_precompiler) compiler = CompilerFilter( content=self.content, filename=self.filename, command=command ) self.assertEqual(type(compiler.input()), str) def test_precompiler_cache(self): # The cache may already have data in it depending on the order the tests are # run, so start by clearing it: cache.clear() command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) # We tell whether the precompiler actually ran by inspecting compiler.infile. If not None, the compiler had to # write the input out to the file for the external command. If None, it was in the cache and thus skipped. self.assertIsNotNone(compiler.infile) # Not cached compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) self.assertIsNone(compiler.infile) # Cached self.cached_precompiler_args[ "content" ] += " " # Invalidate cache by slightly changing content compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) self.assertIsNotNone(compiler.infile) # Not cached @mock.patch("django.core.cache.backends.locmem.LocMemCache.get") def test_precompiler_cache_issue750(self, mock_cache): # emulate memcached and return string mock_cache.side_effect = lambda key: str("body { color:#990; }") command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) self.assertEqual( type(compiler.input()), type(smart_str("body { color:#990; }")) ) def test_precompiler_not_cacheable(self): command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) self.cached_precompiler_args["mimetype"] = "text/different" compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) self.assertIsNotNone(compiler.infile) # Not cached compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) self.assertIsNotNone(compiler.infile) # Not cached def test_precompiler_caches_empty_files(self): command = "%s %s -f {infile} -o {outfile}" % ( sys.executable, self.test_precompiler, ) compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("body { color:#990; }", compiler.input()) cache.set(compiler.get_cache_key(), "") compiler = CachedCompilerFilter(command=command, **self.cached_precompiler_args) self.assertEqual("", compiler.input()) class CSSCompressorTestCase(TestCase): def test_csscompressor_filter(self): content = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */ p { background: rgb(51,102,153) url('../../images/image.gif'); } """ output = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */p{background:#369 url('../../images/image.gif')}""" self.assertEqual(output, CSSCompressorFilter(content).output()) class rCssMinTestCase(TestCase): def test_rcssmin_filter(self): content = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */ p { background: rgb(51,102,153) url('../../images/image.gif'); } """ output = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */p{background:rgb(51,102,153) url('../../images/image.gif')}""" self.assertEqual(output, rCSSMinFilter(content).output()) class JsMinTestCase(TestCase): def test_jsmin_filter(self): content = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */ var foo = "bar";""" output = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors */var foo="bar";""" self.assertEqual(output, rJSMinFilter(content).output()) class CalmjsTestCase(TestCase): def test_calmjs_filter(self): content = """ var foo = "bar";""" output = """var foo="bar";""" self.assertEqual(output, CalmjsFilter(content).output()) @override_settings( COMPRESS_ENABLED=True, COMPRESS_URL="/static/", ) class CssAbsolutizingTestCase(TestCase): hashing_method = "mtime" hashing_func = staticmethod(get_hashed_mtime) template = ( "p { background: url('%(url)simg/python.png%(query)s%(hash)s%(frag)s') }" "p { filter: Alpha(src='%(url)simg/python.png%(query)s%(hash)s%(frag)s') }" ) filter_class = CssAbsoluteFilter @property def expected_url_prefix(self): return settings.COMPRESS_URL def setUp(self): self.override_settings = self.settings( COMPRESS_CSS_HASHING_METHOD=self.hashing_method ) self.override_settings.__enter__() def tearDown(self): self.override_settings.__exit__(None, None, None) @override_settings(COMPRESS_CSS_HASHING_METHOD=None) def test_css_no_hash(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") content = self.template % blankdict(url="../../") params = blankdict( { "url": self.expected_url_prefix, } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") imagefilename = os.path.join(settings.COMPRESS_ROOT, "img/python.png") content = self.template % blankdict(url="../../") params = blankdict( { "url": self.expected_url_prefix, "hash": "?" + self.hashing_func(imagefilename), } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_url_fragment(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") imagefilename = os.path.join(settings.COMPRESS_ROOT, "img/python.png") content = self.template % blankdict(url="../../", frag="#foo") params = blankdict( { "url": self.expected_url_prefix, "hash": "?" + self.hashing_func(imagefilename), "frag": "#foo", } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_only_url_fragment(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") content = "p { background: url('#foo') }" filter = self.filter_class(content) self.assertEqual( content, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_only_url_fragment_wrap_double_quotes(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") content = 'p { background: url("#foo") }' filter = self.filter_class(content) self.assertEqual( content, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_querystring(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") imagefilename = os.path.join(settings.COMPRESS_ROOT, "img/python.png") content = self.template % blankdict(url="../../", query="?foo") params = blankdict( { "url": self.expected_url_prefix, "query": "?foo", "hash": "&" + self.hashing_func(imagefilename), } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_https(self): with self.settings(COMPRESS_URL="https://static.example.com/"): self.test_css_absolute_filter() def test_css_absolute_filter_relative_path(self): filename = os.path.join( settings.TEST_DIR, "whatever", "..", "static", "whatever/../css/url/test.css", ) imagefilename = os.path.join(settings.COMPRESS_ROOT, "img/python.png") content = self.template % blankdict(url="../../") params = blankdict( { "url": self.expected_url_prefix, "hash": "?" + self.hashing_func(imagefilename), } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_css_absolute_filter_filename_outside_compress_root(self): filename = "/foo/bar/baz/test.css" content = self.template % blankdict(url="../qux/") params = blankdict( { "url": self.expected_url_prefix + "bar/qux/", } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="bar/baz/test.css") ) def test_css_hunks(self): hash_python_png = self.hashing_func( os.path.join(settings.COMPRESS_ROOT, "img/python.png") ) hash_add_png = self.hashing_func( os.path.join(settings.COMPRESS_ROOT, "img/add.png") ) css1 = """\ p { background: url('%(compress_url)simg/python.png?%(hash)s'); } p { background: url(%(compress_url)simg/python.png?%(hash)s); } p { background: url(%(compress_url)simg/python.png?%(hash)s); } p { background: url('%(compress_url)simg/python.png?%(hash)s'); } p { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%(compress_url)simg/python.png?%(hash)s'); } """ % dict( compress_url=self.expected_url_prefix, hash=hash_python_png ) css2 = """\ p { background: url('%(compress_url)simg/add.png?%(hash)s'); } p { background: url(%(compress_url)simg/add.png?%(hash)s); } p { background: url(%(compress_url)simg/add.png?%(hash)s); } p { background: url('%(compress_url)simg/add.png?%(hash)s'); } p { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='%(compress_url)simg/add.png?%(hash)s'); } """ % dict( compress_url=self.expected_url_prefix, hash=hash_add_png ) css = """ """ css_node = CssCompressor("css", css) self.assertEqual([css1, css2], list(css_node.hunks())) def test_guess_filename(self): url = "%s/img/python.png" % settings.COMPRESS_URL.rstrip("/") path = os.path.join(settings.COMPRESS_ROOT, "img/python.png") content = "p { background: url('%s') }" % url filter = self.filter_class(content) self.assertEqual(path, filter.guess_filename(url)) def test_filenames_with_space(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") imagefilename = os.path.join(settings.COMPRESS_ROOT, "img/add with spaces.png") template = "p { background: url('%(url)simg/add with spaces.png%(query)s%(hash)s%(frag)s') }" content = template % blankdict(url="../../") params = blankdict( { "url": self.expected_url_prefix, "hash": "?" + self.hashing_func(imagefilename), } ) output = template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) def test_does_not_change_nested_urls(self): css = """body { background-image: url("data:image/svg+xml;utf8,");}""" filter = self.filter_class(css, filename="doesntmatter") self.assertEqual( css, filter.input(filename="doesntmatter", basename="doesntmatter") ) def test_does_not_change_quotes_in_src(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") hash_add_png = self.hashing_func( os.path.join(settings.COMPRESS_ROOT, "img/add.png") ) css = """p { filter: Alpha(src="/img/add.png%(hash)s") }""" filter = self.filter_class(css % dict(hash="")) expected = css % dict(hash="?" + hash_add_png) self.assertEqual( expected, filter.input(filename=filename, basename="css/url/test.css") ) @override_settings(COMPRESS_URL="http://static.example.com/") class CssAbsolutizingTestCaseWithDifferentURL(CssAbsolutizingTestCase): pass class CssAbsolutizingTestCaseWithHash(CssAbsolutizingTestCase): hashing_method = "content" hashing_func = staticmethod(get_hashed_content) @override_settings( COMPRESS_ENABLED=True, COMPRESS_URL="/static/", COMPRESS_FILTERS={"css": ["compressor.filters.css_default.CssRelativeFilter"]}, ) class CssRelativizingTestCase(CssAbsolutizingTestCase): filter_class = CssRelativeFilter expected_url_prefix = "../../" @override_settings( COMPRESS_CSS_HASHING_METHOD=None, COMPRESS_OUTPUT_DIR="CACHE/in/depth" ) def test_nested_cache_dir(self): filename = os.path.join(settings.COMPRESS_ROOT, "css/url/test.css") content = self.template % blankdict(url="../../") params = blankdict( { "url": "../../../../", } ) output = self.template % params filter = self.filter_class(content) self.assertEqual( output, filter.input(filename=filename, basename="css/url/test.css") ) @override_settings( COMPRESS_ENABLED=True, COMPRESS_FILTERS={ "css": [ "compressor.filters.css_default.CssAbsoluteFilter", "compressor.filters.datauri.CssDataUriFilter", ] }, COMPRESS_URL="/static/", COMPRESS_CSS_HASHING_METHOD="mtime", ) class CssDataUriTestCase(TestCase): def setUp(self): self.css = """ """ self.css_node = CssCompressor("css", self.css) def test_data_uris(self): datauri_hash = get_hashed_mtime( os.path.join(settings.COMPRESS_ROOT, "img/python.png") ) out = [ """.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); } .add-with-hash { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); } .python { background-image: url("/static/img/python.png?%s"); } .datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); } """ % datauri_hash ] self.assertEqual(out, list(self.css_node.hunks())) class TemplateTestCase(TestCase): @override_settings( COMPRESS_TEMPLATE_FILTER_CONTEXT={"stuff": "thing", "gimmick": "bold"} ) def test_template_filter(self): content = """ #content {background-image: url("{{ STATIC_URL|default:stuff }}/images/bg.png");} #footer {font-weight: {{ gimmick }};} """ input = """ #content {background-image: url("thing/images/bg.png");} #footer {font-weight: bold;} """ self.assertEqual(input, TemplateFilter(content).input()) class SpecializedFiltersTest(TestCase): """ Test to check the Specializations of filters. """ def test_closure_filter(self): filter = ClosureCompilerFilter("") self.assertEqual( filter.options, (("binary", str("java -jar compiler.jar")), ("args", str(""))), ) def test_yuglify_filters(self): filter = YUglifyCSSFilter("") self.assertEqual(filter.command, "{binary} {args} --type=css") self.assertEqual( filter.options, (("binary", str("yuglify")), ("args", str("--terminal"))) ) filter = YUglifyJSFilter("") self.assertEqual(filter.command, "{binary} {args} --type=js") self.assertEqual( filter.options, (("binary", str("yuglify")), ("args", str("--terminal"))) ) def test_yui_filters(self): filter = YUICSSFilter("") self.assertEqual(filter.command, "{binary} {args} --type=css") self.assertEqual( filter.options, (("binary", str("java -jar yuicompressor.jar")), ("args", str(""))), ) filter = YUIJSFilter("", verbose=1) self.assertEqual(filter.command, "{binary} {args} --type=js --verbose") self.assertEqual( filter.options, ( ("binary", str("java -jar yuicompressor.jar")), ("args", str("")), ("verbose", 1), ), ) def test_clean_css_filter(self): filter = CleanCSSFilter("") self.assertEqual( filter.options, (("binary", str("cleancss")), ("args", str(""))) ) django-compressor-4.4/compressor/tests/test_finder.py000066400000000000000000000006731444665704600232610ustar00rootroot00000000000000from django.test import TestCase from compressor.finders import CompressorFinder from compressor.storage import CompressorFileStorage class FinderTestCase(TestCase): def test_has_correct_storage(self): finder = CompressorFinder() self.assertTrue(type(finder.storage) is CompressorFileStorage) def test_list_returns_empty_list(self): finder = CompressorFinder() self.assertEqual(finder.list([]), []) django-compressor-4.4/compressor/tests/test_jinja2ext.py000066400000000000000000000167111444665704600237100ustar00rootroot00000000000000from django.test import TestCase from django.test.utils import override_settings from compressor.conf import settings from compressor.tests.test_base import css_tag class TestJinja2CompressorExtension(TestCase): """ Test case for jinja2 extension. .. note:: At tests we need to make some extra care about whitespace. Please note that we use jinja2 specific controls (*minus* character at block's beginning or end). For more information see jinja2 documentation. """ def assertStrippedEqual(self, result, expected): self.assertEqual( result.strip(), expected.strip(), "%r != %r" % (result.strip(), expected.strip()), ) def setUp(self): import jinja2 self.jinja2 = jinja2 from compressor.contrib.jinja2ext import CompressorExtension self.env = self.jinja2.Environment(extensions=[CompressorExtension]) def test_error_raised_if_no_arguments_given(self): self.assertRaises( self.jinja2.exceptions.TemplateSyntaxError, self.env.from_string, "{% compress %}Foobar{% endcompress %}", ) def test_error_raised_if_wrong_kind_given(self): self.assertRaises( self.jinja2.exceptions.TemplateSyntaxError, self.env.from_string, "{% compress foo %}Foobar{% endcompress %}", ) def test_error_raised_if_wrong_closing_kind_given(self): self.assertRaises( self.jinja2.exceptions.TemplateSyntaxError, self.env.from_string, "{% compress js %}Foobar{% endcompress css %}", ) def test_error_raised_if_wrong_mode_given(self): self.assertRaises( self.jinja2.exceptions.TemplateSyntaxError, self.env.from_string, "{% compress css foo %}Foobar{% endcompress %}", ) @override_settings(COMPRESS_ENABLED=False) def test_compress_is_disabled(self): tag_body = "\n".join( [ '', '', '', ] ) template_string = "{% compress css %}" + tag_body + "{% endcompress %}" template = self.env.from_string(template_string) self.assertEqual(tag_body, template.render()) # Test with explicit kind template_string = "{% compress css %}" + tag_body + "{% endcompress css %}" template = self.env.from_string(template_string) self.assertEqual(tag_body, template.render()) def test_empty_tag(self): template = self.env.from_string( """{% compress js %}{% block js %}{% endblock %}{% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} self.assertEqual("", template.render(context)) def test_empty_tag_with_kind(self): template = self.env.from_string( """{% compress js %}{% block js %} {% endblock %}{% endcompress js %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} self.assertEqual("", template.render(context)) def test_css_tag(self): template = self.env.from_string( """{% compress css -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = css_tag("/static/CACHE/css/output.600674ea1d3d.css") self.assertEqual(out, template.render(context)) def test_nonascii_css_tag(self): template = self.env.from_string( """{% compress css -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = css_tag("/static/CACHE/css/output.d5444a1ab4a3.css") self.assertEqual(out, template.render(context)) def test_js_tag(self): template = self.env.from_string( """{% compress js -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = '' self.assertEqual(out, template.render(context)) def test_nonascii_js_tag(self): template = self.env.from_string( """{% compress js -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = '' self.assertEqual(out, template.render(context)) def test_nonascii_latin1_js_tag(self): template = self.env.from_string( """{% compress js -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = '' self.assertEqual(out, template.render(context)) def test_css_inline(self): template = self.env.from_string( """{% compress css, inline -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = ( '" ) self.assertEqual(out, template.render(context)) def test_js_inline(self): template = self.env.from_string( """{% compress js, inline -%} {% endcompress %}""" ) context = {"STATIC_URL": settings.COMPRESS_URL} out = '' self.assertEqual(out, template.render(context)) def test_nonascii_inline_css(self): with self.settings(COMPRESS_ENABLED=False): template = self.env.from_string( "{% compress css %}" '{% endcompress %}" ) out = '' context = {"STATIC_URL": settings.COMPRESS_URL} self.assertEqual(out, template.render(context)) django-compressor-4.4/compressor/tests/test_mtime_cache.py000066400000000000000000000025461444665704600242510ustar00rootroot00000000000000import io from django.core.management import call_command from django.core.management.base import CommandError from django.test import TestCase class TestMtimeCacheCommand(TestCase): # FIXME: add actual tests, improve the existing ones. exclusion_patterns = [ "*CACHE*", "*custom*", "*066cd253eada.js", "*d728fc7f9301.js", "*8a0fed36c317.js", "test.txt*", ] def default_ignore(self): return ["--ignore=%s" % pattern for pattern in self.exclusion_patterns] def test_handle_no_args(self): with self.assertRaises(CommandError): call_command("mtime_cache") def test_handle_add(self): out = io.StringIO() with self.settings(CACHES={}): call_command("mtime_cache", "--add", *self.default_ignore(), stdout=out) output = out.getvalue() self.assertIn("Deleted mtimes of 20 files from the cache.", output) self.assertIn("Added mtimes of 20 files to cache.", output) def test_handle_clean(self): out = io.StringIO() with self.settings(CACHES={}): call_command("mtime_cache", "--clean", *self.default_ignore(), stdout=out) output = out.getvalue() self.assertIn("Deleted mtimes of 20 files from the cache.", output) self.assertNotIn("Added mtimes of 20 files to cache.", output) django-compressor-4.4/compressor/tests/test_offline.py000066400000000000000000001041471444665704600234350ustar00rootroot00000000000000import copy import io import os from contextlib import contextmanager from importlib import import_module from unittest import SkipTest from unittest.mock import patch from django.conf import settings from django.core.management import call_command, CommandError from django.template import Context, Origin, Template from django.test import override_settings, TestCase from django.urls import get_script_prefix, set_script_prefix from compressor.cache import flush_offline_manifest, get_offline_manifest from compressor.exceptions import OfflineGenerationError from compressor.management.commands.compress import Command as CompressCommand from compressor.storage import default_offline_manifest_storage from compressor.utils import get_mod_func def offline_context_generator(): for i in range(1, 4): yield {"content": "OK %d!" % i} def static_url_context_generator(): yield {"STATIC_URL": settings.STATIC_URL} class LazyScriptNamePrefixedUrl(str): """ Lazy URL with ``SCRIPT_NAME`` WSGI param as path prefix. .. code-block :: python settings.STATIC_URL = LazyScriptNamePrefixedUrl('/static/') # HTTP request to '/some/page/' without SCRIPT_NAME str(settings.STATIC_URL) == '/static/' # HTTP request to '/app/prefix/some/page/` with SCRIPT_NAME = '/app/prefix/' str(settings.STATIC_URL) == '/app/prefix/static/' # HTTP request to '/another/prefix/some/page/` with SCRIPT_NAME = '/another/prefix/' str(settings.STATIC_URL) == '/another/prefix/static/' The implementation is incomplete, all ``str`` methods must be overridden in order to work correctly with the rest of Django core. """ def __str__(self): return get_script_prefix() + self[1:] if self.startswith("/") else self def __unicode__(self): return str(self) def __hash__(self): return str.__hash__(str(self)) def split(self, *args, **kwargs): """ Override ``.split()`` method to make it work with ``{% static %}``. """ return str(self).split(*args, **kwargs) def replace(self, *args, **kwargs): """Override ``.replace()`` to make it work with ``{% static %}``. In ``django.core.files.storage``, ``FileSystemStorage.url()`` passes this object to ``urllib.parse.urljoin``. In ``urrlib.parse``, the function that calls ``replace()`` is ``_remove_unsafe_bytes_from_url()``. """ return str(self).replace(*args, **kwargs) @contextmanager def script_prefix(new_prefix): """ Override ``SCRIPT_NAME`` WSGI param, yield, then restore its original value. :param new_prefix: New ``SCRIPT_NAME`` value. """ old_prefix = get_script_prefix() set_script_prefix(new_prefix) yield set_script_prefix(old_prefix) class OfflineTestCaseMixin: CHARSET = "utf-8" template_name = "test_compressor_offline.html" # Change this for each test class templates_dir = "" expected_basename = "output" expected_hash = "" # Engines to test engines = ("django", "jinja2") additional_test_settings = None def setUp(self): # Reset template dirs, because it enables us to force compress to # consider only a specific directory (helps us make true, # independent unit tests). # Specify both Jinja2 and Django template locations. When the wrong # engine is used to parse a template, the TemplateSyntaxError will # cause the template to be skipped over. # We've hardcoded TEMPLATES[0] to be Django templates backend and # TEMPLATES[1] to be Jinja2 templates backend in test_settings. TEMPLATES = copy.deepcopy(settings.TEMPLATES) django_template_dir = os.path.join(TEMPLATES[0]["DIRS"][0], self.templates_dir) jinja2_template_dir = os.path.join(TEMPLATES[1]["DIRS"][0], self.templates_dir) TEMPLATES[0]["DIRS"] = [django_template_dir] TEMPLATES[1]["DIRS"] = [jinja2_template_dir] override_settings = { "TEMPLATES": TEMPLATES, "COMPRESS_ENABLED": True, "COMPRESS_OFFLINE": True, } if "jinja2" in self.engines: override_settings[ "COMPRESS_JINJA2_GET_ENVIRONMENT" ] = lambda: self._get_jinja2_env() if self.additional_test_settings is not None: override_settings.update(self.additional_test_settings) self.override_settings = self.settings(**override_settings) self.override_settings.__enter__() if "django" in self.engines: self.template_path = os.path.join(django_template_dir, self.template_name) origin = Origin( name=self.template_path, # Absolute path template_name=self.template_name, ) # Loader-relative path with io.open(self.template_path, encoding=self.CHARSET) as file_: self.template = Template(file_.read(), origin=origin) if "jinja2" in self.engines: self.template_path_jinja2 = os.path.join( jinja2_template_dir, self.template_name ) jinja2_env = override_settings["COMPRESS_JINJA2_GET_ENVIRONMENT"]() with io.open(self.template_path_jinja2, encoding=self.CHARSET) as file_: self.template_jinja2 = jinja2_env.from_string(file_.read()) def tearDown(self): self.override_settings.__exit__(None, None, None) manifest_filename = "manifest.json" if default_offline_manifest_storage.exists(manifest_filename): default_offline_manifest_storage.delete(manifest_filename) def _prepare_contexts(self, engine): contexts = settings.COMPRESS_OFFLINE_CONTEXT if not isinstance(contexts, (list, tuple)): contexts = [contexts] if engine == "django": return [Context(c) for c in contexts] if engine == "jinja2": return contexts return None def _render_template(self, engine): contexts = self._prepare_contexts(engine) if engine == "django": return "".join(self.template.render(c) for c in contexts) if engine == "jinja2": return "\n".join(self.template_jinja2.render(c) for c in contexts) + "\n" return None def _render_script(self, hash): return '".format( settings.COMPRESS_URL_PLACEHOLDER, self.expected_basename, hash ) def _render_link(self, hash): return ( ''.format( settings.COMPRESS_URL_PLACEHOLDER, self.expected_basename, hash ) ) def _render_result(self, result, separator="\n"): return (separator.join(result) + "\n").replace( settings.COMPRESS_URL_PLACEHOLDER, str(settings.COMPRESS_URL) ) def _test_offline(self, engine, verbosity=0): hashes = self.expected_hash if not isinstance(hashes, (list, tuple)): hashes = [hashes] count, result = CompressCommand().handle_inner( engines=[engine], verbosity=verbosity ) self.assertEqual(len(hashes), count) self.assertEqual([self._render_script(h) for h in hashes], result) rendered_template = self._render_template(engine) self.assertEqual(rendered_template, self._render_result(result)) def test_offline_django(self): if "django" not in self.engines: raise SkipTest("This test class does not support django engine.") self._test_offline(engine="django") def test_offline_jinja2(self): if "jinja2" not in self.engines: raise SkipTest("This test class does not support jinja2 engine.") self._test_offline(engine="jinja2") def test_offline_django_verbosity_1(self): if "django" not in self.engines: raise SkipTest("This test class does not support django engine.") self._test_offline(engine="django", verbosity=1) def test_offline_jinja2_verbosity_1(self): if "jinja2" not in self.engines: raise SkipTest("This test class does not support jinja2 engine.") self._test_offline(engine="jinja2", verbosity=1) def test_offline_django_verbosity_2(self): if "django" not in self.engines: raise SkipTest("This test class does not support django engine.") self._test_offline(engine="django", verbosity=2) def test_offline_jinja2_verbosity_2(self): if "jinja2" not in self.engines: raise SkipTest("This test class does not support jinja2 engine.") self._test_offline(engine="jinja2", verbosity=2) def _get_jinja2_env(self): import jinja2.ext from compressor.offline.jinja2 import url_for, SpacelessExtension from compressor.contrib.jinja2ext import CompressorExtension # Extensions needed for the test cases only. extensions = [ CompressorExtension, SpacelessExtension, jinja2.ext.do, ] loader = self._get_jinja2_loader() env = jinja2.Environment(extensions=extensions, loader=loader) env.globals["url_for"] = url_for return env def _get_jinja2_loader(self): import jinja2 loader = jinja2.FileSystemLoader( settings.TEMPLATES[1]["DIRS"], encoding=self.CHARSET ) return loader class OfflineCompressBasicTestCase(OfflineTestCaseMixin, TestCase): templates_dir = "basic" expected_hash = "822ac7501287" @patch.object(CompressCommand, "compress") def test_handle_no_args(self, compress_mock): compress_mock.return_value = {}, 1, [] CompressCommand().handle() self.assertEqual(compress_mock.call_count, 1) @patch.object(CompressCommand, "compress") def test_handle_compress_disabled(self, compress_mock): with self.settings(COMPRESS_ENABLED=False): with self.assertRaises(CommandError): CompressCommand().handle() self.assertEqual(compress_mock.call_count, 0) @patch.object(CompressCommand, "compress") def test_handle_compress_offline_disabled(self, compress_mock): with self.settings(COMPRESS_OFFLINE=False): with self.assertRaises(CommandError): CompressCommand().handle() self.assertEqual(compress_mock.call_count, 0) @patch.object(CompressCommand, "compress") def test_handle_compress_offline_disabled_force(self, compress_mock): compress_mock.return_value = {}, 1, [] with self.settings(COMPRESS_OFFLINE=False): CompressCommand().handle(force=True) self.assertEqual(compress_mock.call_count, 1) def test_rendering_without_manifest_raises_exception(self): # flush cached manifest flush_offline_manifest() self.assertRaises(OfflineGenerationError, self.template.render, Context({})) def test_rendering_without_manifest_raises_exception_jinja2(self): # flush cached manifest flush_offline_manifest() self.assertRaises(OfflineGenerationError, self.template_jinja2.render, {}) def _test_deleting_manifest_does_not_affect_rendering(self, engine): count, result = CompressCommand().handle_inner(engines=[engine], verbosity=0) get_offline_manifest() manifest_filename = "manifest.json" if default_offline_manifest_storage.exists(manifest_filename): default_offline_manifest_storage.delete(manifest_filename) self.assertEqual(1, count) self.assertEqual([self._render_script(self.expected_hash)], result) rendered_template = self._render_template(engine) self.assertEqual(rendered_template, self._render_result(result)) def test_deleting_manifest_does_not_affect_rendering(self): for engine in self.engines: self._test_deleting_manifest_does_not_affect_rendering(engine) def test_get_loaders(self): TEMPLATE_LOADERS = ( ( "django.template.loaders.cached.Loader", ( "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ), ), ) with self.settings(TEMPLATE_LOADERS=TEMPLATE_LOADERS): from django.template.loaders.filesystem import Loader as FileSystemLoader from django.template.loaders.app_directories import ( Loader as AppDirectoriesLoader, ) loaders = CompressCommand().get_loaders() self.assertTrue(isinstance(loaders[0], FileSystemLoader)) self.assertTrue(isinstance(loaders[1], AppDirectoriesLoader)) @patch( "compressor.offline.django.DjangoParser.render_node", side_effect=Exception(b"non-ascii character here:\xc3\xa4"), ) def test_non_ascii_exception_messages(self, mock): with self.assertRaises(CommandError): CompressCommand().handle(verbosity=0) class OfflineCompressSkipDuplicatesTestCase(OfflineTestCaseMixin, TestCase): templates_dir = "test_duplicate" def _test_offline(self, engine, verbosity=0): count, result = CompressCommand().handle_inner( engines=[engine], verbosity=verbosity ) # Only one block compressed, the second identical one was skipped. self.assertEqual(1, count) # Only 1 {% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %}""" template = Template(template_string) context = SekizaiContext({"apiKey": "XYZ"}) html = template.render(context).strip() self.assertEqual( html, """ """, ) def test_postprocess_css(self): template_string = """ {% load static compress sekizai_tags %} {% addtoblock "css" %}{% endaddtoblock %} {% addtoblock "css" %}{% endaddtoblock %} {% addtoblock "css" %}{% endaddtoblock %} {% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %}""" template = Template(template_string) context = SekizaiContext() html = template.render(context).strip() self.assertEqual( html, """ """, ) django-compressor-4.4/compressor/tests/test_signals.py000066400000000000000000000046331444665704600234520ustar00rootroot00000000000000from unittest.mock import Mock from django.test import override_settings, TestCase from compressor.css import CssCompressor from compressor.js import JsCompressor from compressor.signals import post_compress @override_settings( COMPRESS_ENABLED=True, COMPRESS_PRECOMPILERS=(), COMPRESS_DEBUG_TOGGLE="nocompress" ) class PostCompressSignalTestCase(TestCase): def setUp(self): self.css = """\ """ self.css_node = CssCompressor("css", self.css) self.js = """\ """ self.js_node = JsCompressor("js", self.js) def tearDown(self): post_compress.disconnect() def test_js_signal_sent(self): def listener(sender, **kwargs): pass callback = Mock(wraps=listener) post_compress.connect(callback) self.js_node.output() args, kwargs = callback.call_args self.assertEqual(JsCompressor, kwargs["sender"]) self.assertEqual("js", kwargs["type"]) self.assertEqual("file", kwargs["mode"]) context = kwargs["context"] assert "url" in context["compressed"] def test_css_signal_sent(self): def listener(sender, **kwargs): pass callback = Mock(wraps=listener) post_compress.connect(callback) self.css_node.output() args, kwargs = callback.call_args self.assertEqual(CssCompressor, kwargs["sender"]) self.assertEqual("css", kwargs["type"]) self.assertEqual("file", kwargs["mode"]) context = kwargs["context"] assert "url" in context["compressed"] def test_css_signal_multiple_media_attributes(self): css = """\ """ css_node = CssCompressor("css", css) def listener(sender, **kwargs): pass callback = Mock(wraps=listener) post_compress.connect(callback) css_node.output() self.assertEqual(3, callback.call_count) django-compressor-4.4/compressor/tests/test_storages.py000066400000000000000000000067331444665704600236440ustar00rootroot00000000000000import os import brotli from django.core.files.base import ContentFile from django.core.files.storage import get_storage_class from django.test import TestCase from django.test.utils import override_settings from django.utils.functional import LazyObject from compressor import storage from compressor.conf import settings from compressor.tests.test_base import css_tag from compressor.tests.test_templatetags import render class GzipStorage(LazyObject): def _setup(self): self._wrapped = get_storage_class( "compressor.storage.GzipCompressorFileStorage" )() class BrotliStorage(LazyObject): def _setup(self): self._wrapped = get_storage_class( "compressor.storage.BrotliCompressorFileStorage" )() @override_settings(COMPRESS_ENABLED=True) class StorageTestCase(TestCase): def setUp(self): self.default_storage = storage.default_storage storage.default_storage = GzipStorage() storage.brotli_storage = BrotliStorage() def tearDown(self): storage.default_storage = self.default_storage def test_gzip_storage(self): storage.default_storage.save("test.txt", ContentFile("yeah yeah")) self.assertTrue( os.path.exists(os.path.join(settings.COMPRESS_ROOT, "test.txt")) ) self.assertTrue( os.path.exists(os.path.join(settings.COMPRESS_ROOT, "test.txt.gz")) ) def test_brotli_storage(self): payload = ",".join([str(i) for i in range(1000)]).encode() chunk_size = 1024 storage.brotli_storage.save("test.txt", ContentFile(payload)) self.assertTrue( os.path.exists(os.path.join(settings.COMPRESS_ROOT, "test.txt")) ) self.assertTrue( os.path.exists(os.path.join(settings.COMPRESS_ROOT, "test.txt.br")) ) decompressed_data = b"" br_decompressor = brotli.Decompressor() with open(os.path.join(settings.COMPRESS_ROOT, "test.txt.br"), "rb") as f: for data in iter(lambda: f.read(chunk_size), b""): decompressed_data += br_decompressor.process(data) self.assertEqual(payload, decompressed_data) def test_css_tag_with_storage(self): template = """{% load compress %}{% compress css %} {% endcompress %} """ context = {"STATIC_URL": settings.COMPRESS_URL} out = css_tag("/static/CACHE/css/output.e701f86c6430.css") self.assertEqual(out, render(template, context)) def test_duplicate_save_overwrites_same_file(self): filename1 = self.default_storage.save("test.txt", ContentFile("yeah yeah")) filename2 = self.default_storage.save("test.txt", ContentFile("yeah yeah")) self.assertEqual(filename1, filename2) self.assertNotIn("_", filename2) def test_offline_manifest_storage(self): storage.default_offline_manifest_storage.save( "test.txt", ContentFile("yeah yeah") ) self.assertTrue( os.path.exists(os.path.join(settings.COMPRESS_ROOT, "CACHE", "test.txt")) ) # Check that the file is stored at the same default location as before the new manifest storage. self.assertTrue(self.default_storage.exists(os.path.join("CACHE", "test.txt"))) django-compressor-4.4/compressor/tests/test_templates/000077500000000000000000000000001444665704600234305ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/basic/000077500000000000000000000000001444665704600245115ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/basic/test_compressor_offline.html000066400000000000000000000002521444665704600323330ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_block_super/000077500000000000000000000000001444665704600267775ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super/base.html000066400000000000000000000004331444665704600305770ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_block_super/test_compressor_offline.html000066400000000000000000000004551444665704600346260ustar00rootroot00000000000000{% extends "base.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_base_compressed/000077500000000000000000000000001444665704600322155ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_base_compressed/base.html000066400000000000000000000003551444665704600340200ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% compress js %} {% block js %} {% endblock %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_base_compressed/base2.html000066400000000000000000000003221444665704600340740ustar00rootroot00000000000000{% extends "base.html" %} {% block js %}{% spaceless %} {{ block.super }} {% endspaceless %}{% endblock %} test_compressor_offline.html000066400000000000000000000006111444665704600377570ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_base_compressed{% extends "base2.html" %} {% block js %}{% spaceless %} {{ block.super }} {% block orphan %} {{ block.super }} An 'orphan' block that refers to a non-existent super block. Contents of this block are ignored. {% endblock %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_extra/000077500000000000000000000000001444665704600302025ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_extra/base.html000066400000000000000000000004331444665704600320020ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000006671444665704600357570ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_extra{% extends "base.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {% endcompress %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple/000077500000000000000000000000001444665704600307125ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple/base.html000066400000000000000000000004641444665704600325160ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple/base2.html000066400000000000000000000003611444665704600325740ustar00rootroot00000000000000{% extends "base.html" %} {% block js %}{% spaceless %} {{ block.super }} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} test_compressor_offline.html000066400000000000000000000004221444665704600364540ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple{% extends "base2.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple_cached/000077500000000000000000000000001444665704600322015ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple_cached/base.html000066400000000000000000000004641444665704600340050ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple_cached/base2.html000066400000000000000000000000711444665704600340610ustar00rootroot00000000000000{% extends "base.html" %} {% block css %}{% endblock %} test_compressor_offline.html000066400000000000000000000004221444665704600377430ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_block_super_multiple_cached{% extends "base2.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_complex/000077500000000000000000000000001444665704600261365ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_complex/test_compressor_offline.html000066400000000000000000000012671444665704600337670ustar00rootroot00000000000000{% load compress static %}{% spaceless %} {% if condition %} {% compress js%} {% with names=my_names %}{% spaceless %} {% for name in names %} {% endfor %} {% endspaceless %}{% endwith %} {% endcompress %} {% endif %}{% if not condition %} {% compress js %} {% endcompress %} {% else %} {% compress js %} {% endcompress %} {% endif %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_compress_command/000077500000000000000000000000001444665704600300205ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000003001444665704600355550ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_compress_command{% load compress %}{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_condition/000077500000000000000000000000001444665704600264555ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_condition/test_compressor_offline.html000066400000000000000000000003331444665704600342770ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% if condition %} {% compress js%} {% endcompress %} {% endif %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_duplicate/000077500000000000000000000000001444665704600264415ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_duplicate/test_compressor_offline.html000066400000000000000000000004351444665704600342660ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% compress js %} {% endcompress %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_error_handling/000077500000000000000000000000001444665704600274645ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_error_handling/buggy_extends.html000066400000000000000000000003001444665704600332120ustar00rootroot00000000000000{% extends "buggy_template.html" %} {% load compress %} {% compress css %} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates/test_error_handling/buggy_template.html000066400000000000000000000002471444665704600333650ustar00rootroot00000000000000{% load compress %} {% compress css %} {% endcompress %} {% fail %} django-compressor-4.4/compressor/tests/test_templates/test_error_handling/missing_extends.html000066400000000000000000000002711444665704600335550ustar00rootroot00000000000000{% extends "missing.html" %} {% load compress %} {% compress css %} {% endcompress %} test_compressor_offline.html000066400000000000000000000003351444665704600352310ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_error_handling{% load compress %}{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_error_handling/with_coffeescript.html000066400000000000000000000002111444665704600340530ustar00rootroot00000000000000{% load compress %} {% compress js %} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates/test_extends_recursion/000077500000000000000000000000001444665704600302325ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_extends_recursion/admin/000077500000000000000000000000001444665704600313225ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_extends_recursion/admin/index.html000066400000000000000000000000411444665704600333120ustar00rootroot00000000000000{% extends "admin/index.html" %} test_compressor_offline.html000066400000000000000000000001631444665704600357760ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_extends_recursion{% load compress %} {% compress js%} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates/test_extends_relative/000077500000000000000000000000001444665704600300345ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_extends_relative/base.html000066400000000000000000000004331444665704600316340ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000004571444665704600356060ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_extends_relative{% extends "./base.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_inline_non_ascii/000077500000000000000000000000001444665704600277675ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000003051444665704600355310ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_inline_non_ascii{% load compress %}{% spaceless %} {% compress js inline %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_static_templatetag/000077500000000000000000000000001444665704600303455ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000003241444665704600361100ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_static_templatetag{% load compress static %}{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_static_url_independence/000077500000000000000000000000001444665704600313415ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002161444665704600371040ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_static_url_independence{% load compress %}{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_templatetag/000077500000000000000000000000001444665704600267765ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_templatetag/test_compressor_offline.html000066400000000000000000000002761444665704600346260ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_templatetag_named/000077500000000000000000000000001444665704600301425ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002721444665704600357070ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_templatetag_named{% load compress %}{% spaceless %} {% compress js file output_name %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_with_context/000077500000000000000000000000001444665704600272065ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context/test_compressor_offline.html000066400000000000000000000002751444665704600350350ustar00rootroot00000000000000{% load compress %}{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates/test_with_context_super/000077500000000000000000000000001444665704600304245ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_super/base.html000066400000000000000000000002441444665704600322240ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000005021444665704600361650ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_super{% extends "base.html" %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance/000077500000000000000000000000001444665704600334245ustar00rootroot00000000000000base.html000066400000000000000000000003511444665704600351440ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance{% load compress %} {% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000000361444665704600411670ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance{% extends parent_template %} django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance_super/000077500000000000000000000000001444665704600346425ustar00rootroot00000000000000base1.html000066400000000000000000000003101444665704600364360ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance_super{% spaceless %} {% block js %} {% endblock %} {% endspaceless %} base2.html000066400000000000000000000003101444665704600364370ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance_super{% spaceless %} {% block js %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000002701444665704600424050ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates/test_with_context_variable_inheritance_super{% extends parent_template %} {% load compress %} {% block js %}{% spaceless %} {% compress js %} {{ block.super }} {% endcompress %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates_jinja2/000077500000000000000000000000001444665704600246655ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/basic/000077500000000000000000000000001444665704600257465ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/basic/test_compressor_offline.html000066400000000000000000000002271444665704600335720ustar00rootroot00000000000000{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super/000077500000000000000000000000001444665704600302345ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super/base.html000066400000000000000000000004331444665704600320340ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000004251444665704600360010ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super{% extends "base.html" %} {% block js %}{% spaceless %} {% compress js %} {{ super() }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_extra/000077500000000000000000000000001444665704600314375ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_extra/base.html000066400000000000000000000004331444665704600332370ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000006371444665704600372110ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_extra{% extends "base.html" %} {% block js %}{% spaceless %} {% compress js %} {% endcompress %} {% compress js %} {{ super() }} {% endcompress %} {% endspaceless %}{% endblock %} {% block css %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple/000077500000000000000000000000001444665704600321475ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple/base.html000066400000000000000000000004641444665704600337530ustar00rootroot00000000000000{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple/base2.html000066400000000000000000000000711444665704600340270ustar00rootroot00000000000000{% extends "base.html" %} {% block css %}{% endblock %} test_compressor_offline.html000066400000000000000000000003721444665704600377150ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple{% extends "base2.html" %} {% block js %}{% spaceless %} {% compress js %} {{ super() }} {% endcompress %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple_cached/000077500000000000000000000000001444665704600334365ustar00rootroot00000000000000base.html000066400000000000000000000004641444665704600351630ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple_cached{% spaceless %} {% block js %} {% endblock %} {% block css %} {% endblock %} {% endspaceless %} base2.html000066400000000000000000000000711444665704600352370ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple_cached{% extends "base.html" %} {% block css %}{% endblock %} test_compressor_offline.html000066400000000000000000000003721444665704600412040ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_block_super_multiple_cached{% extends "base2.html" %} {% block js %}{% spaceless %} {% compress js %} {{ super() }} {% endcompress %} {% endspaceless %}{% endblock %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_coffin/000077500000000000000000000000001444665704600271705ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000006261444665704600347400ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_coffin{%- load compress -%} {% spaceless %} {% compress js%} {% with "js/one.js" as name -%} {%- endwith %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_complex/000077500000000000000000000000001444665704600273735ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000014411444665704600351370ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_complex{% spaceless %} {% if condition %} {% compress js%} {% with names=[] -%} {%- do names.append("js/one.js") -%} {%- do names.append("js/nonasc.js") -%} {% for name in names -%} {%- endfor %} {%- endwith %} {% endcompress %} {% endif %} {% if not condition -%} {% compress js %} {% endcompress %} {%- else -%} {% compress js %} {% endcompress %} {%- endif %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_compress_command/000077500000000000000000000000001444665704600312555ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002551444665704600370230ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_compress_command{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_condition/000077500000000000000000000000001444665704600277125ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000003111444665704600354510ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_condition{% spaceless %} {% if condition %} {% compress js%} {% endcompress %} {% endif %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_duplicate/000077500000000000000000000000001444665704600276765ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000004121444665704600354370ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_duplicate{% spaceless %} {% compress js %} {% endcompress %} {% compress js %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling/000077500000000000000000000000001444665704600307215ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling/buggy_extends.html000066400000000000000000000002541444665704600344570ustar00rootroot00000000000000{% extends "buggy_template.html" %} {% compress css %} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling/buggy_template.html000066400000000000000000000002221444665704600346130ustar00rootroot00000000000000{% compress css %} {% endcompress %} {% fail %} missing_extends.html000066400000000000000000000002451444665704600347340ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling{% extends "missing.html" %} {% compress css %} {% endcompress %} test_compressor_offline.html000066400000000000000000000003121444665704600364610ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling{% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} with_coffeescript.html000066400000000000000000000001641444665704600352400ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_error_handling{% compress js %} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_extends_recursion/000077500000000000000000000000001444665704600314675ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_extends_recursion/admin/000077500000000000000000000000001444665704600325575ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_extends_recursion/admin/index.html000066400000000000000000000000411444665704600345470ustar00rootroot00000000000000{% extends "admin/index.html" %} test_compressor_offline.html000066400000000000000000000001361444665704600372330ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_extends_recursion{% compress js%} {% endcompress %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_inline_non_ascii/000077500000000000000000000000001444665704600312245ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002631444665704600367710ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_inline_non_ascii{% spaceless %} {% compress js, inline %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_jingo/000077500000000000000000000000001444665704600270325ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_jingo/test_compressor_offline.html000066400000000000000000000007051444665704600346570ustar00rootroot00000000000000{% spaceless %} {% compress js%} {% with name="js/one.js" -%} {%- endwith %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_static_templatetag/000077500000000000000000000000001444665704600316025ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000003171444665704600373470ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_static_templatetag{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_static_url_independence/000077500000000000000000000000001444665704600325765ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000001731444665704600403430ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_static_url_independence{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_templatetag/000077500000000000000000000000001444665704600302335ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002621444665704600357770ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_templatetag{% spaceless %} {% compress 'js' file %} {% endcompress %} {% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_templatetag_named/000077500000000000000000000000001444665704600313775ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002471444665704600371460ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_templatetag_named{% spaceless %} {% compress js file output_name %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_with_context/000077500000000000000000000000001444665704600304435ustar00rootroot00000000000000test_compressor_offline.html000066400000000000000000000002531444665704600362070ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_with_context{% spaceless %} {% compress js %} {% endcompress %}{% endspaceless %} django-compressor-4.4/compressor/tests/test_templates_jinja2/test_with_context_variable_inheritance/000077500000000000000000000000001444665704600346615ustar00rootroot00000000000000base.html000066400000000000000000000003261444665704600364030ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_with_context_variable_inheritance {% spaceless %} {% compress js %} {% endcompress %} {% endspaceless %} test_compressor_offline.html000066400000000000000000000000361444665704600424240ustar00rootroot00000000000000django-compressor-4.4/compressor/tests/test_templates_jinja2/test_with_context_variable_inheritance{% extends parent_template %} django-compressor-4.4/compressor/tests/test_templatetags.py000066400000000000000000000325411444665704600245030ustar00rootroot00000000000000import os import sys from unittest.mock import Mock from django.conf import settings from django.template import Context, Template, TemplateSyntaxError from django.test import override_settings, TestCase from sekizai.context import SekizaiContext from compressor.signals import post_compress from compressor.tests.test_base import css_tag, test_dir def render(template_string, context_dict=None, context=None): """ A shortcut for testing template output. """ if context_dict is None: context_dict = {} if context is None: context = Context c = context(context_dict) t = Template(template_string) return t.render(c).strip() @override_settings(COMPRESS_ENABLED=True) class TemplatetagTestCase(TestCase): def setUp(self): self.context = {"STATIC_URL": settings.COMPRESS_URL} def test_empty_tag(self): template = """{% load compress %}{% compress js %}{% block js %} {% endblock %}{% endcompress %}""" self.assertEqual("", render(template, self.context)) def test_css_tag(self): template = """{% load compress %}{% compress css %} {% endcompress %}""" out = css_tag("/static/CACHE/css/output.600674ea1d3d.css") self.assertEqual(out, render(template, self.context)) def test_css_tag_with_block(self): template = """{% load compress %}{% compress css file block_name %} {% endcompress %}""" out = css_tag("/static/CACHE/css/block_name.a1e074d0c4ac.css") self.assertEqual(out, render(template, self.context)) def test_missing_rel_leaves_empty_result(self): template = """{% load compress %}{% compress css %} {% endcompress %}""" out = "" self.assertEqual(out, render(template, self.context)) def test_missing_rel_only_on_one_resource(self): template = """{% load compress %}{% compress css %} {% endcompress %}""" out = css_tag("/static/CACHE/css/output.600674ea1d3d.css") self.assertEqual(out, render(template, self.context)) def test_uppercase_rel(self): template = """{% load compress %}{% compress css %} {% endcompress %}""" out = css_tag("/static/CACHE/css/output.600674ea1d3d.css") self.assertEqual(out, render(template, self.context)) def test_nonascii_css_tag(self): template = """{% load compress %}{% compress css %} {% endcompress %} """ out = css_tag("/static/CACHE/css/output.d5444a1ab4a3.css") self.assertEqual(out, render(template, self.context)) def test_js_tag(self): template = """{% load compress %}{% compress js %} {% endcompress %} """ out = '' self.assertEqual(out, render(template, self.context)) def test_nonascii_js_tag(self): template = """{% load compress %}{% compress js %} {% endcompress %} """ out = '' self.assertEqual(out, render(template, self.context)) def test_nonascii_latin1_js_tag(self): template = """{% load compress %}{% compress js %} {% endcompress %} """ out = '' self.assertEqual(out, render(template, self.context)) def test_compress_tag_with_illegal_arguments(self): template = """{% load compress %}{% compress pony %} {% endcompress %}""" self.assertRaises(TemplateSyntaxError, render, template, {}) @override_settings(COMPRESS_DEBUG_TOGGLE="togglecompress") def test_debug_toggle(self): template = """{% load compress %}{% compress js %} {% endcompress %} """ class MockDebugRequest: GET = {settings.COMPRESS_DEBUG_TOGGLE: "true"} context = dict(self.context, request=MockDebugRequest()) out = """ """ self.assertEqual(out, render(template, context)) def test_inline(self): template = """{% load compress %}{% compress js inline %} {% endcompress %}{% compress css inline %} {% endcompress %}""" out_js = '' out_css = ( '" ) self.assertEqual(out_js + out_css, render(template, self.context)) def test_named_compress_tag(self): template = """{% load compress %}{% compress js inline foo %} {% endcompress %} """ def listener(sender, **kwargs): pass callback = Mock(wraps=listener) post_compress.connect(callback) render(template) args, kwargs = callback.call_args context = kwargs["context"] self.assertEqual("foo", context["compressed"]["name"]) def test_sekizai_only_once(self): template = """{% load sekizai_tags %}{% addtoblock "js" %} {% endaddtoblock %}{% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %} """ out = '' self.assertEqual(out, render(template, self.context, SekizaiContext)) class PrecompilerTemplatetagTestCase(TestCase): def setUp(self): precompiler = os.path.join(test_dir, "precompiler.py") python = sys.executable override_settings = { "COMPRESS_ENABLED": True, "COMPRESS_PRECOMPILERS": ( ("text/coffeescript", "%s %s" % (python, precompiler)), ("text/less", "%s %s" % (python, precompiler)), ), } self.override_settings = self.settings(**override_settings) self.override_settings.__enter__() self.context = {"STATIC_URL": settings.COMPRESS_URL} def tearDown(self): self.override_settings.__exit__(None, None, None) def test_compress_coffeescript_tag(self): template = """{% load compress %}{% compress js %} {% endcompress %}""" out = script(src="/static/CACHE/js/output.ec862f0ff42c.js") self.assertEqual(out, render(template, self.context)) def test_compress_coffeescript_tag_and_javascript_tag(self): template = """{% load compress %}{% compress js %} {% endcompress %}""" out = script(src="/static/CACHE/js/output.fb4a0d84e914.js") self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_coffeescript_and_js_tag_with_compress_enabled_equals_false(self): template = """{% load compress %}{% compress js %} {% endcompress %}""" out = ( script("# this is a comment.\n") + "\n" + script("# this too is a comment.", scripttype="text/javascript") ) self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_compress_coffeescript_tag_compress_enabled_is_false(self): template = """{% load compress %}{% compress js %} {% endcompress %}""" out = script("# this is a comment.\n") self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_compress_coffeescript_file_tag_compress_enabled_is_false(self): template = """ {% load compress %}{% compress js %} {% endcompress %}""" out = script(src="/static/CACHE/js/one.4b3570601b8c.js") self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_multiple_file_order_conserved(self): template = """ {% load compress %}{% compress js %} {% endcompress %}""" out = "\n".join( [ script(src="/static/CACHE/js/one.4b3570601b8c.js"), script(scripttype="", src="/static/js/one.js"), script(src="/static/CACHE/js/one.8ab93aace8fa.js"), ] ) self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_css_multiple_files_disabled_compression(self): assert settings.COMPRESS_PRECOMPILERS template = """ {% load compress %}{% compress css %} {% endcompress %}""" out = "".join( [ '', '', ] ) self.assertEqual(out, render(template, self.context)) @override_settings(COMPRESS_ENABLED=False) def test_css_multiple_files_mixed_precompile_disabled_compression(self): assert settings.COMPRESS_PRECOMPILERS template = """ {% load compress %}{% compress css %} {% endcompress %}""" out = "".join( [ '', '', '', ] ) self.assertEqual(out, render(template, self.context)) def script(content="", src="", scripttype=""): """ returns a unicode text html script element. >>> script('#this is a comment', scripttype="text/applescript") '' """ out_script = "" % content django-compressor-4.4/compressor/tests/test_utils.py000066400000000000000000000041511444665704600231450ustar00rootroot00000000000000from django.test import TestCase from django.test.utils import override_settings from django.conf import settings import django.contrib.staticfiles.finders import django import compressor.utils.staticfiles from compressor.exceptions import FilterError from compressor.utils import get_class from importlib import reload def get_apps_without_staticfiles(apps): return [x for x in apps if x != "django.contrib.staticfiles"] def get_apps_with_staticfiles_using_appconfig(apps): return get_apps_without_staticfiles(apps) + [ "django.contrib.staticfiles.apps.StaticFilesConfig", ] class StaticFilesTestCase(TestCase): def test_has_finders_from_staticfiles(self): self.assertTrue( compressor.utils.staticfiles.finders is django.contrib.staticfiles.finders ) def test_has_finders_from_staticfiles_if_configured_per_appconfig(self): apps = get_apps_with_staticfiles_using_appconfig(settings.INSTALLED_APPS) try: with override_settings(INSTALLED_APPS=apps): reload(compressor.utils.staticfiles) self.assertTrue( compressor.utils.staticfiles.finders is django.contrib.staticfiles.finders ) finally: reload(compressor.utils.staticfiles) def test_finders_is_none_if_staticfiles_is_not_installed(self): apps = get_apps_without_staticfiles(settings.INSTALLED_APPS) try: with override_settings(INSTALLED_APPS=apps): reload(compressor.utils.staticfiles) self.assertTrue(compressor.utils.staticfiles.finders is None) finally: reload(compressor.utils.staticfiles) class TestGetClass(TestCase): def test_get_class_import_exception(self): with self.assertRaises(FilterError) as context: get_class("common.uglify.JsUglifySourcemapCompressor") self.assertTrue( ( "Failed to import common.uglify.JsUglifySourcemapCompressor. " "ImportError is: No module named" in str(context.exception) ) ) django-compressor-4.4/compressor/utils/000077500000000000000000000000001444665704600203715ustar00rootroot00000000000000django-compressor-4.4/compressor/utils/__init__.py000066400000000000000000000026321444665704600225050ustar00rootroot00000000000000import os from compressor.exceptions import FilterError def get_class(class_string, exception=FilterError): """ Convert a string version of a function name to the callable object. """ if not hasattr(class_string, "__bases__"): try: class_string = str(class_string) mod_name, class_name = get_mod_func(class_string) if class_name: return getattr(__import__(mod_name, {}, {}, [str("")]), class_name) except AttributeError as e: raise exception( "Failed to import %s. AttributeError is: %s" % (class_string, e) ) except ImportError as e: raise exception( "Failed to import %s. ImportError is: %s" % (class_string, e) ) raise exception("Invalid class path '%s'" % class_string) def get_mod_func(callback): """ Converts 'django.views.news.stories.story_detail' to ('django.views.news.stories', 'story_detail') """ try: dot = callback.rindex(".") except ValueError: return callback, "" return callback[:dot], callback[dot + 1 :] def get_pathext(default_pathext=None): """ Returns the path extensions from environment or a default """ if default_pathext is None: default_pathext = os.pathsep.join([".COM", ".EXE", ".BAT", ".CMD"]) return os.environ.get("PATHEXT", default_pathext) django-compressor-4.4/compressor/utils/staticfiles.py000066400000000000000000000011071444665704600232540ustar00rootroot00000000000000from django.apps import apps from django.core.exceptions import ImproperlyConfigured from compressor.conf import settings if apps.is_installed("django.contrib.staticfiles"): from django.contrib.staticfiles import finders # noqa if "compressor.finders.CompressorFinder" not in settings.STATICFILES_FINDERS: raise ImproperlyConfigured( "When using Django Compressor together with staticfiles, " "please add 'compressor.finders.CompressorFinder' to the " "STATICFILES_FINDERS setting." ) else: finders = None # noqa django-compressor-4.4/docs/000077500000000000000000000000001444665704600157655ustar00rootroot00000000000000django-compressor-4.4/docs/Makefile000066400000000000000000000110321444665704600174220ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-compressor.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-compressor.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-compressor" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-compressor" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-compressor-4.4/docs/behind-the-scenes.txt000066400000000000000000000050171444665704600220160ustar00rootroot00000000000000.. _behind_the_scenes: Behind the Scenes ================= This document assumes you already have an up and running instance of Django Compressor, and that you understand how to use it in your templates. The goal is to explain what the main template tag, {% compress %}, does behind the scenes, to help you debug performance problems for instance. Offline compression ------------------- If offline compression is activated, the {% compress %} tag will try to retrieve the compressed version for its nodelist from the offline manifest cache. It doesn't parse, doesn't check the modified times of the files, doesn't even know which files are concerned actually, since it doesn't look inside the nodelist of the template block enclosed by the ``compress`` template tag. The offline cache manifest is just a json file, stored on disk inside the directory that holds the compressed files. The format of the manifest is simply a key-value dictionary, with the hash of the nodelist being the key, and the HTML containing the element code for the combined file or piece of code being the value. The ``compress`` management command generates the offline manifest as well as the combined files referenced in the manifest. If offline compression is enabled and the nodelist hash can not be found inside the manifest, {% compress %} will raise an ``OfflineGenerationError``. If offline compression is disabled, the following happens: First step: parsing and file list --------------------------------- A compressor instance is created, which in turns instantiates the HTML parser. The parser is used to determine a file or code hunk list. Each file mtime is checked, first in cache and then on disk/storage, and this is used to determine a unique cache key. Second step: Checking the "main" cache -------------------------------------- Compressor checks if it can get some info about the combined file/hunks corresponding to its instance, using the cache key obtained in the previous step. The cache content here will actually be the HTML containing the final element code, just like in the offline step before. Everything stops here if the cache entry exists. Third step: Generating the combined file if needed -------------------------------------------------- The file is generated if necessary. All precompilers are called and all filters are executed, and a hash is determined from the contents. This in turns helps determine the file name, which is only saved if it didn't exist already. Then the HTML output is returned (and also saved in the cache). And that's it! django-compressor-4.4/docs/changelog.txt000066400000000000000000000512561444665704600204660ustar00rootroot00000000000000Changelog ========= v4.4 (2023-06-28) ------------------- `Full list of changes from v4.3.1 `_ - Officially support Django 4.2 v4.3.1 (2023-01-22) ------------------- `Full list of changes from v4.3 `_ - Documentation fixes only v4.3 (2023-01-06) ----------------- `Full list of changes from v4.2 `_ - Officially support Python 3.11 v4.2 (2023-01-06) ----------------- `Full list of changes from v4.1 `_ - Drop Python 3.6 and 3.7 support - Drop Django 2.2 and 3.1 support - Drop SlimItFilter - Update the `CachedS3Boto3Storage` example storage subclass in "Remote Storages" to work properly after the v4.0 change to how duplicate file names are handled by `CompressorFileStorage` - Update rsmin and jsmin versions v4.1 (2022-08-03) ----------------- `Full list of changes from v4.0 `_ - Add Django 4.1 compatibility - New setting ``COMPRESS_OFFLINE_MANIFEST_STORAGE`` to customize the offline manifest's file storage (#1112) With this change the function ``compressor.cache.get_offline_manifest_filename()`` has been removed. You can now use the new file storage ``compressor.storage.default_offline_manifest_storage`` to access the location of the manifest. v4.0 (2022-03-23) ----------------- `Full list of changes from v3.1 `_ - Fix intermittent No such file or directory errors by changing strategy to deal with duplicate filenames in CompressorFileStorage Note: if your project has a custom storage backend following the example of `CachedS3Boto3Storage` from the "Remote Storages" documentation, it will need to be updated to call `save` instead of `_save` to work properly after this change to `CompressorFileStorage`. - Deprecate SlimItFilter, stop testing it with Python 3.7 or higher v3.1 (2021-12-18) ----------------- `Full list of changes from v3.0 `_ - Fix error with verbose offline compression when COMPRESS_OFFLINE_CONTEXT is a generator v3.0 (2021-12-12) ----------------- `Full list of changes from v2.4.1 `_ - Officially support Python 3.9 and 3.10 as well as Django 3.1, 3.2 and 4.0 - Drop support for Django 1.11, 2.1 and 3.0 - Drop support for Python 2.x and 3.4 - Fix compatibility with Jinja 3.x - Require django-sekizai 2.0 for django-sekizai extension - Make template compression in compress command threaded to improve performance - Correctly handle relative paths in ``extends`` tags (#979) - Enable ``rCSSMinFilter`` by default (#888) - Fix various deprecation warnings - Add ability to pass a logger to various classes & methods - Removed deprecated ``COMPRESS_JS_FILTERS`` and ``COMPRESS_CSS_FILTERS`` settings - Fix offline compression race condition, which could result in incomplete manifest v2.4.1 (2021-04-17) ------------------- `Full list of changes from v2.4 `_ - Raise proper ``DeprecationWarning`` for ``COMPRESS_JS_FILTERS`` and ``COMPRESS_CSS_FILTERS`` v2.4 (2019-12-31) ----------------- `Full list of changes from v2.3 `_ - Add support for Django 3.0 (#950, #967) - Officially support Python 3.8 (#967) - Add better support for JS strict mode and validation (#952) - Add support for rel=preload (#951) - Add support for Calmjs (#957) Note: in 2.3, a new setting ``COMPRESS_FILTERS`` has been introduced that combines the existing ``COMPRESS_CSS_FILTERS`` and ``COMPRESS_JS_FILTERS``. The latter are now deprecated. See `the docs `_ on how to use the new setting, the conversion is straightforward. v2.3 (2019-05-31) ----------------- `Full list of changes from v2.2 `_ - Drop support for Django 1.8, 1.9 and 1.10 - Add support for Django 2.1 and 2.2, as well as Python 3.7 - Update all dependencies. This required minor code changes, you might need to update some optional dependencies if you use any - Allow the mixed use of JS/CSS in Sekizai's templatetags ``{% addtoblock "js" %}`` and ``{% addtoblock "css" %}`` (#891) - Allow the implementation of new types other than css and js. (#900) - Update jinja2 extension to behave similar to the django tag (#899) - Fix crash in offline compression when child nodelist is None, again (#605) - Support STATIC_URL and COMPRESS_URL being string-like objects - Improve compress command memory usage (#870) - Ensure generated file always contains a base name (#775) - Add BrotliCompressorFileStorage (#867) v2.2 (2017-08-16) ----------------- `Full list of changes from v2.1.1 `_ - Switch from MD5 to SHA256 for hashes generation. - Add Django 1.11 compatibility - Various compatibility fixes for Python 3.6 and Django 1.8 - Made OfflineGenerationError easier to debug - Drop support for Python 3.2 - Add new CssRelativeFilter which works like CssAbsoluteFilter but outputs relative URLs. - Fix URL CssAbsoluteFilter URL detection v2.1.1 (2017-02-02) ------------------- `Full list of changes from v2.1 `_ - Fix to file permissions issue with packaging. v2.1 (2016-08-09) ----------------- `Full list of changes from v2.0 `_ - Add Django 1.10 compatibility - Add support for inheritance using a variable in offline compression - Fix recursion error with offline compression when extending templates with the same name - Fix UnicodeDecodeError when using CompilerFilter and caching - Fix CssAbsoluteFilter changing double quotes to single quotes, breaking SVG v2.0 (2016-01-07) ----------------- `Full list of changes from v1.6 `_ - Add Django 1.9 compatibility - Remove official support for Django 1.4 and 1.7 - Add official support for Python 3.5 - Remove official support for Python 2.6 - Remove support for coffin and jingo - Fix Jinja2 compatibility for Django 1.8+ - Stop bundling vendored versions of rcssmin and rjsmin, make them proper dependencies - Remove support for CSSTidy - Remove support for beautifulsoup 3. - Replace cssmin by csscompressor (cssmin is still available for backwards-compatibility but points to rcssmin) v1.6 (2015-11-19) ----------------- `Full list of changes from v1.5 `_ - Upgrade rcssmin and rjsmin - Apply CssAbsoluteFilter to precompiled css even when compression is disabled - Add optional caching to CompilerFilter to avoid re-compiling unchanged files - Fix various deprecation warnings on Django 1.7 / 1.8 - Fix TemplateFilter - Fix double-rendering bug with sekizai extension - Fix debug mode using destination directory instead of staticfiles finders first - Removed some silent exception catching in compress command v1.5 (2015-03-27) ----------------- `Full list of changes from v1.4 `_ - Fix compress command and run automated tests for Django 1.8 - Fix Django 1.8 warnings - Handle TypeError from import_module - Fix reading UTF-8 files which have BOM - Fix incompatibility with Windows (shell_quote is not supported) - Run automated tests on Django 1.7 - Ignore non-existent {{ block.super }} in offline compression instead of raising AttributeError - Support for clean-css - Fix link markup - Add support for COMPRESS_CSS_HASHING_METHOD = None - Remove compatibility with old 'staticfiles' app - In compress command, use get_template() instead of opening template files manually, fixing compatibility issues with custom template loaders - Fix FilterBase so that does not override self.type for subclasses if filter_type is not specified at init - Remove unnecessary filename and existence checks in CssAbsoluteFilter v1.4 (2014-06-20) ----------------- - Added Python 3 compatibility. - Added compatibility with Django 1.6.x and dropped support for Django 1.3.X. - Fixed compatibility with html5lib 1.0. - Added offline compression for Jinja2 with Jingo and Coffin integration. - Improved support for template inheritance in offline compression. - Made offline compression avoid compressing the same block multiple times. - Added a ``testenv`` target in the Makefile to make it easier to set up the test environment. - Allowed data-uri filter to handle external/protocol-relative references. - Made ``CssCompressor`` class easier to extend. - Added support for explicitly stating the block being ended. - Added rcssmin and updated rjsmin. - Removed implicit requirement on BeautifulSoup. - Made GzipCompressorFileStorage set access and modified times to the same time as the corresponding base file. - Defaulted to using django's simplejson, if present. - Fixed CompilerFilter to always output Unicode strings. - Fixed windows line endings in offline compression. v1.3 (2013-03-18) ----------------- - *Backward incompatible changes* - Dropped support for Python 2.5. Removed ``any`` and ``walk`` compatibility functions in ``compressor.utils``. - Removed compatibility with some old django setttings: - :attr:`~COMPRESS_ROOT` no longer uses ``MEDIA_ROOT`` if ``STATIC_ROOT`` is not defined. It expects ``STATIC_ROOT`` to be defined instead. - :attr:`~COMPRESS_URL` no longer uses ``MEDIA_URL`` if ``STATIC_URL`` is not defined. It expects ``STATIC_URL`` to be defined instead. - :attr:`~COMPRESS_CACHE_BACKEND` no longer uses ``CACHE_BACKEND`` and simply defaults to ``default``. - Added precompiler class support. This enables you to write custom precompilers with Python logic in them instead of just relying on executables. - Made CssAbsoluteFilter smarter: it now handles URLs with hash fragments or querystring correctly. In addition, it now leaves alone fragment-only URLs. - Removed a ``fsync()`` call in ``CompilerFilter`` to improve performance. We already called ``self.infile.flush()`` so that call was not necessary. - Added an extension to provide django-sekizai support. See :ref:`django-sekizai Support ` for more information. - Fixed a ``DeprecationWarning`` regarding the use of ``django.utils.hashcompat`` - Updated bundled ``rjsmin.py`` to fix some JavaScript compression errors. v1.2 ---- - Added compatibility with Django 1.4 and dropped support for Django 1.2.X. - Added contributing docs. Be sure to check them out and start contributing! - Moved CI to Travis: http://travis-ci.org/django-compressor/django-compressor - Introduced a new ``compressed`` context dictionary that is passed to the templates that are responsible for rendering the compressed snippets. This is a **backwards-incompatible change** if you've overridden any of the included templates: - ``compressor/css_file.html`` - ``compressor/css_inline.html`` - ``compressor/js_file.html`` - ``compressor/js_inline.html`` The variables passed to those templates have been namespaced in a dictionary, so it's easy to fix your own templates. For example, the old ``compressor/js_file.html``:: The new ``compressor/js_file.html``:: - Removed old templates named ``compressor/css.html`` and ``compressor/js.html`` that were originally left for backwards compatibility. If you've overridden them, just rename them to ``compressor/css_file.html`` or ``compressor/js_file.html`` and make sure you've accounted for the backwards incompatible change of the template context mentioned above. - Reverted an unfortunate change to the YUI filter that prepended ``'java -jar'`` to the binary name, which doesn't alway work, e.g. if the YUI compressor is shipped as a script like ``/usr/bin/yui-compressor``. - Changed the sender parameter of the :func:`~compressor.signals.post_compress` signal to be either :class:`compressor.css.CssCompressor` or :class:`compressor.js.JsCompressor` for easier customization. - Correctly handle offline compressing files that are found in ``{% if %}`` template blocks. - Renamed the second option for the ``COMPRESS_CSS_HASHING_METHOD`` setting from ``'hash'`` to ``'content'`` to better describe what it does. The old name is also supported, as well as the default being ``'mtime'``. - Fixed CssAbsoluteFilter, ``src`` attributes in includes now get transformed. - Added a new hook to allow developers to completely bypass offline compression in CompressorNode subclasses: ``is_offline_compression_enabled``. - Dropped versiontools from required dependencies again. v1.1.2 ------ - Fixed an installation issue related to versiontools. v1.1.1 ------ - Fixed a stupid ImportError bug introduced in 1.1. - Fixed Jinja2 docs of since ``JINJA2_EXTENSIONS`` expects a class, not a module. - Fixed a Windows bug with regard to file resolving with staticfiles finders. - Stopped a potential memory leak when memoizing the rendered output. - Fixed the integration between staticfiles (e.g. in Django <= 1.3.1) and compressor which prevents the collectstatic management command to work. .. warning:: Make sure to **remove** the ``path`` method of your custom :ref:`remote storage ` class! v1.1 ---- - Made offline compression completely independent from cache (by writing a manifest.json file). You can now easily run the ``compress`` management command locally and transfer the :attr:`~django.conf.settings.COMPRESS_ROOT` dir to your server. - Updated installation instructions to properly mention all dependencies, even those internally used. - Fixed a bug introduced in 1.0 which would prevent the proper deactivation of the compression in production. - Added a Jinja2_ :doc:`contrib extension `. - Made sure the rel attribute of link tags can be mixed case. - Avoid overwriting context variables needed for compressor to work. - Stopped the compress management command to require model validation. - Added missing imports and fixed a few :pep:`8` issues. .. _Jinja2: http://jinja.pocoo.org/2/ v1.0.1 ------ - Fixed regression in ``compressor.utils.staticfiles`` compatibility module. v1.0 ---- - **BACKWARDS-INCOMPATIBLE** Stopped swallowing exceptions raised by rendering the template tag in production (``DEBUG = False``). This has the potential to breaking lots of apps but on the other hand will help find bugs. - **BACKWARDS-INCOMPATIBLE** The default function to create the cache key stopped containing the server hostname. Instead the cache key now only has the form ``'django_compressor.'``. To revert to the previous way simply set the ``COMPRESS_CACHE_KEY_FUNCTION`` to ``'compressor.cache.socket_cachekey'``. - **BACKWARDS-INCOMPATIBLE** Renamed ambigously named ``COMPRESS_DATA_URI_MAX_SIZE`` setting to ``COMPRESS_DATA_URI_MAX_SIZE``. It's the maximum size the ``compressor.filters.datauri.DataUriFilter`` filter will embed files as data: URIs. - Added ``COMPRESS_CSS_HASHING_METHOD`` setting with the options ``'mtime'`` (default) and ``'hash'`` for the ``CssAbsoluteFilter`` filter. The latter uses the content of the file to calculate the cache-busting hash. - Added support for ``{{ block.super }}`` to ``compress`` management command. - Dropped Django 1.1.X support. - Fixed compiler filters on Windows. - Handle new-style cached template loaders in the compress management command. - Documented included filters. - Added `Slim It`_ filter. - Added new CallbackOutputFilter to ease the implementation of Python-based callback filters that only need to pass the content to a callable. - Make use of `django-appconf`_ for settings handling and `versiontools`_ for versions. - Uses the current context when rendering the render templates. - Added :func:`post_compress` signal. .. _`Slim It`: http://slimit.org/ .. _`django-appconf`: https://django-appconf.readthedocs.io/ .. _`versiontools`: http://pypi.python.org/pypi/versiontools v0.9.2 ------ - Fixed stdin handling of precompiler filter. v0.9.1 ------ - Fixed encoding related issue. - Minor cleanups. v0.9 ---- - Fixed the precompiler support to also use the full file path instead of a temporarily created file. - Enabled test coverage. - Refactored caching and other utility code. - Switched from SHA1 to MD5 for hash generation to lower the computational impact. v0.8 ---- - Replace naive jsmin.py with rJSmin (http://opensource.perlig.de/rjsmin/) and fixed a few problems with JavaScript comments. - Fixed converting relative URLs in CSS files when running in debug mode. .. note:: If you relied on the ``split_contents`` method of ``Compressor`` classes, please make sure a fourth item is returned in the iterable that denotes the base name of the file that is compressed. v0.7.1 ------ - Fixed import error when using the standalone django-staticfiles app. v0.7 ---- - Created new parser, HtmlParser, based on the stdlib HTMLParser module. - Added a new default AutoSelectParser, which picks the LxmlParser if lxml is available and falls back to HtmlParser. - Use unittest2 for testing goodness. - Fixed YUI JavaScript filter argument handling. - Updated bundled jsmin to use version by Dave St.Germain that was refactored for speed. v0.6.4 ------ - Fixed Closure filter argument handling. v0.6.3 ------ - Fixed options mangling in CompilerFilter initialization. - Fixed tox configuration. - Extended documentation and README. - In the compress command ignore hidden files when looking for templates. - Restructured utilities and added staticfiles compat layer. - Restructered parsers and added a html5lib based parser. v0.6.2 ------ - Minor bugfixes that caused the compression not working reliably in development mode (e.g. updated files didn't trigger a new compression). v0.6.1 ------ - Fixed staticfiles support to also use its finder API to find files during development -- when the static files haven't been collected in ``STATIC_ROOT``. - Fixed regression with the ``COMPRESS`` setting, pre-compilation and staticfiles. v0.6 ---- Major improvements and a lot of bugfixes, some of which are: - New precompilation support, which allows compilation of files and hunks with easily configurable compilers before calling the actual output filters. See the :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS` for more details. - New staticfiles support. With the introduction of the staticfiles app to Django 1.3, compressor officially supports finding the files to compress using the app's finder API. Have a look at the documentation about :ref:`remote storages ` in case you want to use those together with compressor. - New ``compress`` management command which allows pre-running of what the compress template tag does. - Various performance improvements by better caching and mtime cheking. - Deprecated ``COMPRESS_LESSC_BINARY`` setting because it's now superseded by the :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS` setting. Just make sure to use the correct mimetype when linking to less files or adding inline code and add the following to your settings:: COMPRESS_PRECOMPILERS = ( ('text/less', 'lessc {infile} {outfile}'), ) - Added cssmin_ filter (``compressor.filters.CSSMinFilter``) based on Zachary Voase's Python port of the YUI CSS compression algorithm. - Reimplemented the dog-piling prevention. - Make sure the CssAbsoluteFilter works for relative paths. - Added inline render mode. See :ref:`usage ` docs. - Added ``mtime_cache`` management command to add and/or remove all mtimes from the cache. - Moved docs to Read The Docs: https://django-compressor.readthedocs.io/en/latest/ - Added optional ``compressor.storage.GzipCompressorFileStorage`` storage backend that gzips of the saved files automatically for easier deployment. - Reimplemented a few filters on top of the new ``compressor.filters.base.CompilerFilter`` to be a bit more DRY. - Added tox based test configuration, testing on Django 1.1-1.3 and Python 2.5-2.7. .. _cssmin: http://pypi.python.org/pypi/cssmin/ django-compressor-4.4/docs/conf.py000066400000000000000000000163571444665704600173000ustar00rootroot00000000000000# django-compressor documentation build configuration file, created by # sphinx-quickstart on Fri Jan 21 11:47:42 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Django Compressor' copyright = '2023, Django Compressor authors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # try: from compressor import __version__ # The short X.Y version. version = '.'.join(__version__.split('.')[:2]) # The full version, including alpha/beta/rc tags. release = __version__ except ImportError: version = release = 'dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'murphy' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' RTD_NEW_THEME = True # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'django-compressordoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ( 'index', 'django-compressor.tex', 'Django Compressor Documentation', 'Django Compressor authors', 'manual', ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( 'index', 'django-compressor', 'Django Compressor Documentation', ['Django Compressor authors'], 1, ) ] django-compressor-4.4/docs/contributing.txt000066400000000000000000000143741444665704600212460ustar00rootroot00000000000000Contributing ============ Like every open-source project, Django Compressor is always looking for motivated individuals to contribute to its source code. However, to ensure the highest code quality and keep the repository nice and tidy, everybody has to follow a few rules (nothing major, I promise :) ) Community --------- People interested in developing for the Django Compressor should open an issue on GitHub explaining your ideas. In a nutshell ------------- Here's what the contribution process looks like, in a bullet-points fashion, and only for the stuff we host on github: #. Django Compressor is hosted on `github`_, at https://github.com/django-compressor/django-compressor #. The best method to contribute back is to create a github account, then fork the project. You can use this fork as if it was your own project, and should push your changes to it. #. When you feel your code is good enough for inclusion, "send us a `pull request`_", by using the nice github web interface. Contributing Code ----------------- Getting the source code ^^^^^^^^^^^^^^^^^^^^^^^ If you're interested in developing a new feature for Compressor, it is recommended that you first discuss it in an issue in order not to do any work that wouldn't get merged anyway. - Code will be reviewed and tested by at least one core developer, preferably by several. Other community members are welcome to give feedback. - Code *must* be tested. Your pull request should include unit-tests (that cover the piece of code you're submitting, obviously) - Documentation should reflect your changes if relevant. There is nothing worse than invalid documentation. - Usually, if unit tests are written, pass, and your change is relevant, then it'll be merged. Since it's hosted on github, Django Compressor uses `git`_ as a version control system. The `github help`_ is very well written and will get you started on using git and github in a jiffy. It is an invaluable resource for newbies and old timers alike. Syntax and conventions ^^^^^^^^^^^^^^^^^^^^^^ We try to conform to `PEP8`_ as much as possible. A few highlights: - Indentation should be exactly 4 spaces. Not 2, not 6, not 8. **4**. Also, tabs are evil. - We try (loosely) to keep the line length at 79 characters. Generally the rule is "it should look good in a terminal-base editor" (eg vim), but we try not be [Godwin's law] about it. Process ^^^^^^^ This is how you fix a bug or add a feature: #. `Fork`_ us on github. #. Checkout your fork. #. Hack hack hack, test test test, commit commit commit, test again. #. Push to your fork. #. Open a pull request. Tests ^^^^^ Having a wide and comprehensive library of unit-tests and integration tests is of exceeding importance. Contributing tests is widely regarded as a very prestigious contribution (you're making everybody's future work much easier by doing so). Good karma for you. Cookie points. Maybe even a beer if we meet in person :) Generally tests should be: - Unitary (as much as possible). I.E. should test as much as possible only one function/method/class. That's the very definition of unit tests. - Integration tests are interesting too obviously, but require more time to maintain since they have a higher probability of breaking. - Short running. No hard numbers here, but if your one test doubles the time it takes for everybody to run them, it's probably an indication that you're doing it wrong. In a similar way to code, pull requests will be reviewed before pulling (obviously), and we encourage discussion via code review (everybody learns something this way). Running the tests ^^^^^^^^^^^^^^^^^ To run the tests simply fork django_compressor, make the changes and open a pull request. The Github actions will automatically run the tests of your branch/fork and add a status about the test results to the pull requests. Alternatively, create a virtualenv and activate it, then install the requirements **in the virtualenv**:: $ virtualenv compressor_test $ source compressor_test/bin/activate (compressor_test) $ make testenv Then run ``make test`` to run the tests. Please note that this only tests django_compressor in the Python version you've created the virtualenv with not all the versions that are required to be supported. Contributing Documentation -------------------------- Perhaps considered "boring" by hard-core coders, documentation is sometimes even more important than code! This is what brings fresh blood to a project, and serves as a reference for old timers. On top of this, documentation is the one area where less technical people can help most - you just need to write a semi-decent English. People need to understand you. Documentation should be: - We use `Sphinx`_/`restructuredText`_. So obviously this is the format you should use :) File extensions should be ``.txt``. - Written in English. We can discuss how it would bring more people to the project to have a Klingon translation or anything, but that's a problem we will ask ourselves when we already have a good documentation in English. - Accessible. You should assume the reader to be moderately familiar with Python and Django, but not anything else. Link to documentation of libraries you use, for example, even if they are "obvious" to you. A brief description of what it does is also welcome. Pulling of documentation is pretty fast and painless. Usually somebody goes over your text and merges it, since there are no "breaks" and that github parses rst files automagically it's really convenient to work with. Also, contributing to the documentation will earn you great respect from the core developers. You get good karma just like a test contributor, but you get double cookie points. Seriously. You rock. .. note:: This very document is based on the contributing docs of the `django CMS`_ project. Many thanks for allowing us to steal it! .. _Fork: http://github.com/django-compressor/django-compressor .. _Sphinx: http://sphinx.pocoo.org/ .. _PEP8: http://www.python.org/dev/peps/pep-0008/ .. _github : http://www.github.com .. _github help : http://help.github.com .. _pull request : http://help.github.com/send-pull-requests/ .. _git : http://git-scm.com/ .. _restructuredText: http://docutils.sourceforge.net/docs/ref/rst/introduction.html .. _`django CMS`: http://www.django-cms.org/ django-compressor-4.4/docs/django-sekizai.txt000066400000000000000000000051741444665704600214340ustar00rootroot00000000000000.. _django-sekizai_support: django-sekizai Support ====================== Django Compressor comes with support for django-sekizai_ via an extension. django-sekizai provides the ability to include template code, from within any block, to a parent block. It is primarily used to include js/css from included templates to the master template. It requires django-sekizai to be installed. Refer to the `django-sekizai docs`_ for how to use ``render_block`` Please note that the sekizai integration does not work with :ref:`offline compression `. See `this issue`_ for details. Usage ----- In templates which either extend base templates or are included by other templates, use any of these directives to import Stylesheets and JavaScript files: .. code-block:: django {% load static sekizai_tags %} {% addtoblock "css" %}{% endaddtoblock %} {% addtoblock "css" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} {% addtoblock "js" %}{% endaddtoblock %} Note that some files are loaded by the browser directly from a CDN and thus can not be compressed by django-compressor. Therefore the Sekizai compressor checks whether a file is compressable, and only if so, concatenates its payload. .. code-block:: django {% load sekizai_tags %} ... {% render_block "css" postprocessor "compressor.contrib.sekizai.compress" %} ... {% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %} In the above example, we render StyleSheets inside the ```` element and JavaScript files just before closing the ```` tag. Here, we first render some references to CSS and JavaScript files, served from external sources. Afterwards all local files are concatenated and optionally minified. In the last step all references to deferred files are rendered. .. _django-sekizai: https://github.com/ojii/django-sekizai .. _django-sekizai docs: https://django-sekizai.readthedocs.io/en/latest/ .. _this issue: https://github.com/django-compressor/django-compressor/issues/351 django-compressor-4.4/docs/index.txt000066400000000000000000000023171444665704600176400ustar00rootroot00000000000000================= Django Compressor ================= Compresses linked and inline JavaScript or CSS into a single cached file. Why another static file combiner for Django? ============================================ Short version: None of them did exactly what I needed. Long version: **JS/CSS belong in the templates** Every static combiner for Django I've seen makes you configure your static files in your ``settings.py``. While that works, it doesn't make sense. Static files are for display. And it's not even an option if your settings are in completely different repositories and use different deploy processes from the templates that depend on them. **Flexibility** Django Compressor doesn't care if different pages use different combinations of statics. It doesn't care if you use inline scripts or styles. It doesn't get in the way. **Automatic regeneration and cache-foreverable generated output** Statics are never stale and browsers can be told to cache the output forever. **Full test suite** It has one. Contents ======== .. toctree:: :maxdepth: 2 quickstart usage scenarios settings remote-storages behind-the-scenes jinja2 django-sekizai reactjs contributing changelog django-compressor-4.4/docs/jinja2.txt000066400000000000000000000071071444665704600177100ustar00rootroot00000000000000Jinja2 Support ============== Django Compressor comes with support for Jinja2_ via an extension. In-Request Compression ---------------------- In order to use Django Compressor's Jinja2 extension we would need to pass ``compressor.contrib.jinja2ext.CompressorExtension`` into environment:: import jinja2 from compressor.contrib.jinja2ext import CompressorExtension env = jinja2.Environment(extensions=[CompressorExtension]) From now on, you can use same code you'd normally use within Django templates:: from django.conf import settings template = env.from_string('\n'.join([ '{% compress css %}', '', '{% endcompress %}', ])) template.render({'STATIC_URL': settings.STATIC_URL}) Offline Compression ------------------- Usage ^^^^^ First, you will need to configure ``COMPRESS_JINJA2_GET_ENVIRONMENT`` so that Compressor can retrieve the Jinja2 environment for rendering. This can be a lambda or function that returns a Jinja2 environment. Then, run the following compress command along with an ``--engine`` parameter. The parameter can be either jinja2 or django (default). For example, ``./manage.py compress --engine jinja2``. Using both Django and Jinja2 templates ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There may be a chance that the Jinja2 parser is used to parse Django templates if you have a mixture of Django and Jinja2 templates in the same location(s). This should not be a problem since the Jinja2 parser will likely raise a template syntax error, causing Compressor to skip the errorneous template safely. (Vice versa for Django parser). Templates of both engines can be compressed like this: - ``./manage.py compress --engine django --engine jinja2`` However, it is still recommended that you do not mix Django and Jinja2 templates in the same project. Limitations ^^^^^^^^^^^ - Does not support ``{% import %}`` and similar blocks within ``{% compress %}`` blocks. - Does not support ``{{super()}}``. - All other filters, globals and language constructs such as ``{% if %}``, ``{% with %}`` and ``{% for %}`` are tested and should run fine. Jinja2 templates location ^^^^^^^^^^^^^^^^^^^^^^^^^ IMPORTANT: For Compressor to discover the templates for offline compression, there must be a template loader that implements the ``get_template_sources`` method, and is in the ``TEMPLATE_LOADERS`` setting. If you're using Jinja2, you're likely to have a Jinja2 template loader in the ``TEMPLATE_LOADERS`` setting, otherwise Django won't know how to load Jinja2 templates. By default, if you don't override the ``TEMPLATE_LOADERS`` setting, it will include the app directories loader that searches for templates under the ``templates`` directory in each app. If the app directories loader is in use and your Jinja2 templates are in the ``/templates`` directories, Compressor will be able to find the Jinja2 templates. However, if you have Jinja2 templates in other location(s), you could include the filesystem loader (``django.template.loaders.filesystem.Loader``) in the ``TEMPLATE_LOADERS`` setting and specify the custom location in the ``TEMPLATE_DIRS`` setting. Using your custom loader ^^^^^^^^^^^^^^^^^^^^^^^^ You should configure ``TEMPLATE_LOADERS`` as such:: TEMPLATE_LOADERS = ( 'your_app.Loader', ... other loaders (optional) ... ) You could implement the `get_template_sources` method in your loader or make use of the Django's builtin loaders to report the Jinja2 template location(s). .. _Jinja2: http://jinja.pocoo.org/docs/ django-compressor-4.4/docs/make.bat000066400000000000000000000106651444665704600174020ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-compressor.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-compressor.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end django-compressor-4.4/docs/quickstart.txt000066400000000000000000000052031444665704600207200ustar00rootroot00000000000000Quickstart ========== Installation ------------ * Install Django Compressor with your favorite Python package manager:: pip install django_compressor * Add ``'compressor'`` to your ``INSTALLED_APPS`` setting:: INSTALLED_APPS = ( # other apps "compressor", ) * See the list of :ref:`settings` to modify Django Compressor's default behaviour and make adjustments for your website. * In case you use Django's staticfiles_ contrib app you have to add Django Compressor's file finder to the ``STATICFILES_FINDERS`` setting, like this: .. code-block:: python STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # other finders.. 'compressor.finders.CompressorFinder', ) * Define :attr:`COMPRESS_ROOT ` in settings if you don't have already ``STATIC_ROOT`` or if you want it in a different folder. .. _staticfiles: http://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ .. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles .. _dependencies: Optional Dependencies --------------------- - BeautifulSoup_ For the :attr:`parser ` ``compressor.parser.BeautifulSoupParser`` and ``compressor.parser.LxmlParser``:: pip install beautifulsoup4 - lxml_ For the :attr:`parser ` ``compressor.parser.LxmlParser``, also requires libxml2_:: STATIC_DEPS=true pip install lxml - html5lib_ For the :attr:`parser ` ``compressor.parser.Html5LibParser``:: pip install html5lib - `Calmjs`_ For the :ref:`Calmjs filter ` ``compressor.filters.jsmin.CalmjsFilter``:: pip install calmjs.parse - `csscompressor`_ For the :ref:`csscompressor filter ` ``compressor.filters.cssmin.CSSCompressorFilter``:: pip install csscompressor - `brotli`_ For brotli file compressor `compressor.storage.BrotliCompressorFileStorage`:: pip install brotli .. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/ .. _lxml: http://lxml.de/ .. _libxml2: http://xmlsoft.org/ .. _html5lib: https://github.com/html5lib/html5lib-python .. _`Slim It`: https://github.com/rspivak/slimit .. _`Calmjs`: https://github.com/calmjs/calmjs.parse .. _django-appconf: http://pypi.python.org/pypi/django-appconf/ .. _versiontools: http://pypi.python.org/pypi/versiontools/ .. _csscompressor: https://pypi.org/project/csscompressor/ .. _brotli: https://pypi.python.org/pypi/brotli django-compressor-4.4/docs/reactjs.txt000066400000000000000000000042371444665704600201670ustar00rootroot00000000000000.. _reactjs_support: Facebook React Support ====================== Assuming you have `npm` available, you can install `babel` via `npm install -g babel` and integrate React with Django Compressor by following the `react-tools installation instructions`_ and adding an appropriate ``COMPRESS_PRECOMPILERS`` setting: .. code-block:: django COMPRESS_PRECOMPILERS = ( ('text/jsx', 'cat {infile} | babel > {outfile}'), ) .. _react-tools installation instructions: http://facebook.github.io/react/docs/tooling-integration.html#productionizing-precompiled-jsx If the above approach is not suitable for you, compiling React's jsx files can be done by creating a custom precompressor. Requirements ------------ * PyReact>=0.5.2 for compiling jsx files * PyExecJS>=1.1.0 required by PyReact (automatically installed when using pip) * A Javascript runtime : options include PyV8, Node.js, PhantomJS among others The full list of supported javascript engines can be found here: https://github.com/doloopwhile/PyExecJS Installation ------------ 1. Place the following code in a Python file (e.g. ``third_party/react_compressor.py``). Also make sure that ``third_party/__init__.py`` exists so the directory is recognized as a Python package. .. code-block:: django from compressor.filters import FilterBase from react import jsx class ReactFilter(FilterBase): def __init__(self, content, *args, **kwargs): self.content = content kwargs.pop('filter_type') super().__init__(content, *args, **kwargs) def input(self, **kwargs): return jsx.transform_string(self.content) 2. In your Django settings, add the following line: .. code-block:: django COMPRESS_PRECOMPILERS = ( ('text/jsx', 'third_party.react_compressor.ReactFilter'), ) Where ``third_party.react_compressor.ReactFilter`` is the full name of your ``ReactFilter`` class. Troubleshooting --------------- If you get "file not found" errors, open your Python command line and make sure you are able to import your ``ReactFilter`` class: .. code-block:: django __import__('third_party.react_compressor.ReactFilter') django-compressor-4.4/docs/remote-storages.txt000066400000000000000000000077641444665704600216640ustar00rootroot00000000000000.. _remote_storages: Remote Storages --------------- In some cases it's useful to use a CDN_ for serving static files such as those generated by Django Compressor. Due to the way Django Compressor processes files, it requires the files to be processed (in the ``{% compress %}`` block) to be available in a local file system cache. Django Compressor provides hooks to automatically have compressed files pushed to a remote storage backend. Simply set the storage backend that saves the result to a remote service (see :attr:`~django.conf.settings.COMPRESS_STORAGE`). django-storages ^^^^^^^^^^^^^^^ So assuming your CDN is `Amazon S3`_, you can use the boto3_ storage backend from the 3rd party app `django-storages`_. Some required settings are:: AWS_ACCESS_KEY_ID = 'XXXXXXXXXXXXXXXXXXXXX' AWS_SECRET_ACCESS_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' AWS_STORAGE_BUCKET_NAME = 'compressor-test' Next, you need to specify the new CDN base URL and update the URLs to the files in your templates which you want to compress:: COMPRESS_URL = "http://compressor-test.s3.amazonaws.com/" .. note:: For staticfiles just set ``STATIC_URL = COMPRESS_URL`` The storage backend to save the compressed files needs to be changed, too:: COMPRESS_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' Using staticfiles ^^^^^^^^^^^^^^^^^ If you are using Django's staticfiles_ contrib app, you'll need to use a temporary filesystem cache for Django Compressor to know which files to compress. Since staticfiles provides a management command to collect static files from various locations which uses a storage backend, this is where both apps can be integrated. #. Make sure the :attr:`~django.conf.settings.COMPRESS_ROOT` and STATIC_ROOT_ settings are equal since both apps need to look at the same directories when doing their job. #. You need to create a subclass of the remote storage backend you want to use; below is an example of the boto3 S3 storage backend from django-storages_:: from django.core.files.storage import get_storage_class from storages.backends.s3boto3 import S3Boto3Storage class CachedS3Boto3Storage(S3Boto3Storage): """ S3 storage backend that saves the files locally, too. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.local_storage = get_storage_class( "compressor.storage.CompressorFileStorage")() def save(self, name, content): self.local_storage.save(name, content) super().save(name, self.local_storage._open(name)) return name #. Set your :attr:`~django.conf.settings.COMPRESS_STORAGE`, STATICFILES_STORAGE_ and :attr:`~django.conf.settings.COMPRESS_OFFLINE_MANIFEST_STORAGE` settings to the dotted path of your custom cached storage backend, e.g. ``'mysite.storage.CachedS3Boto3Storage'``. #. To have Django correctly render the URLs to your static files, set the STATIC_URL_ setting to the same value as :attr:`~django.conf.settings.COMPRESS_URL` (e.g. ``"http://compressor-test.s3.amazonaws.com/"``). In the end it might look like this:: STATIC_ROOT = '/path/to/staticfiles' COMPRESS_ROOT = STATIC_ROOT STATICFILES_STORAGE = 'mysite.storage.CachedS3BotoStorage' COMPRESS_STORAGE = STATICFILES_STORAGE COMPRESS_OFFLINE_MANIFEST_STORAGE = STATICFILES_STORAGE STATIC_URL = 'https://compressor-test.s3.amazonaws.com/' COMPRESS_URL = STATIC_URL .. _CDN: http://en.wikipedia.org/wiki/Content_delivery_network .. _Amazon S3: https://s3.amazonaws.com/ .. _boto3: http://boto3.readthedocs.io/ .. _django-storages: http://github.com/jschneier/django-storages .. _staticfiles: http://docs.djangoproject.com/en/dev/howto/static-files/ .. _STATIC_ROOT: http://docs.djangoproject.com/en/dev/ref/settings/#static-root .. _STATIC_URL: http://docs.djangoproject.com/en/dev/ref/settings/#static-url .. _STATICFILES_STORAGE: http://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-storage django-compressor-4.4/docs/scenarios.txt000066400000000000000000000050371444665704600205210ustar00rootroot00000000000000.. _scenarios: Common Deployment Scenarios =========================== This document presents the most typical scenarios in which Django Compressor can be configured, and should help you decide which method you may want to use for your stack. In-Request Compression ---------------------- This is the default method of compression. Where-in Django Compressor will go through the steps outlined in :ref:`behind_the_scenes`. You will find in-request compression beneficial if: * Using a single server setup, where the application and static files are on the same machine. * Prefer a simple configuration. By default, there is no configuration required. Caveats ------- * If deploying to a multi-server setup and using :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS`, each binary is required to be installed on each application server. * Application servers may not have permissions to write to your static directories. For example, if deploying to a CDN (e.g. Amazon S3) Offline Compression ------------------- This method decouples the compression outside of the request (see :ref:`behind_the_Scenes`) and can prove beneficial in the speed, and in many scenarios, the maintainability of your deployment. You will find offline compression beneficial if: * Using a multi-server setup. A common scenario for this may be multiple application servers and a single static file server (CDN included). With offline compression, you typically run ``manage.py compress`` on a single utility server, meaning you only maintain :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS` binaries in one location. * You store compressed files on a CDN. * You want the best possible performance. Caveats ------- * If your templates have complex logic in how template inheritance is done (e.g. ``{% extends context_variable %}``), then this becomes a problem, as offline compression will not have the context, unless you set it in :attr:`~django.conf.settings.COMPRESS_OFFLINE_CONTEXT` * Due to the way the manifest file is used, while deploying across a multi-server setup, your application may use old templates with a new manifest, possibly rendering your pages incoherent. The current suggested solution for this is to change the :attr:`~django.conf.settings.COMPRESS_OFFLINE_MANIFEST` path for each new version of your code. This will ensure that the old code uses old compressed output, and the new one appropriately as well. Every setup is unique, and your scenario may differ slightly. Choose what is the most sane to maintain for your situation. django-compressor-4.4/docs/settings.txt000066400000000000000000000463111444665704600203730ustar00rootroot00000000000000.. _settings: Settings ======== .. currentmodule:: django.conf.settings Django Compressor has a number of settings that control its behavior. They've been given sensible defaults. Base settings ------------- .. attribute:: COMPRESS_ENABLED :default: the opposite of ``DEBUG`` Boolean that decides if compression will happen. To test compression when ``DEBUG`` is ``True`` ``COMPRESS_ENABLED`` must also be set to ``True``. When ``COMPRESS_ENABLED`` is ``False`` the input will be rendered without any compression except for code with a mimetype matching one listed in the :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS` setting. These matching files are still passed to the precompiler before rendering. An example for some javascript and coffeescript. .. code-block:: django {% load compress %} {% compress js %} .. attribute:: COMPRESS_URL :Default: ``STATIC_URL`` Controls the URL that linked files will be read from and compressed files will be written to. .. attribute:: COMPRESS_ROOT :Default: ``STATIC_ROOT`` Controls the absolute file path that linked static will be read from and compressed static will be written to when using the default :attr:`~django.conf.settings.COMPRESS_STORAGE` ``compressor.storage.CompressorFileStorage``. .. attribute:: COMPRESS_OUTPUT_DIR :Default: ``'CACHE'`` Controls the directory inside :attr:`~django.conf.settings.COMPRESS_ROOT` that compressed files will be written to. Backend settings ---------------- .. attribute:: COMPRESS_FILTERS :default: ``{'css': ['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.rCSSMinFilter'], 'js': ['compressor.filters.jsmin.rJSMinFilter']}`` A mapping of resource kinds to the list of filters to apply. The key is used to refer to this resource type in templates when using the `{% compress [resource_kind] %}` template tag. The value is a list of filters to apply in the given order for that resource. This library currently includes filters for CSS and Javascript. - CSS Filters .. _compress_css_filters: :default: ``['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.rCSSMinFilter']`` A list of filters that will be applied to CSS. Possible options for CSS filters are (including their settings): - ``compressor.filters.css_default.CssAbsoluteFilter`` A filter that normalizes the URLs used in ``url()`` CSS statements. This is necessary since the output css files produced by Django Compressor are in a different location than the source files and relative paths might have become invalid. The filter also appends a hash as query string to the normalized URLs to help with cache busting. .. attribute:: COMPRESS_CSS_HASHING_METHOD The method to use when calculating the query string. Either ``None``, ``'mtime'`` (default) or ``'content'``. Use the ``None`` if you want to completely disable that feature, and the ``'content'`` in case you're using multiple servers to serve your content. - ``compressor.filters.css_default.CssRelativeFilter`` An alternative to ``CssAbsoluteFilter``. It uses a relative instead of an absolute path to prefix URLs. Specifically, the prefix will be ``'../' * (N + 1)`` where ``N`` is the *depth* of ``settings.COMPRESS_OUTPUT_DIR`` folder (i.e. ``1`` for ``'CACHE'``, or ``2``for ``CACHE/data`` etc). This can be useful if you don't want to hard-code :attr:`~django.conf.settings.COMPRESS_URL` into CSS code. - ``compressor.filters.datauri.CssDataUriFilter`` A filter for embedding media as `data: URIs`_ in the CSS. .. attribute:: COMPRESS_DATA_URI_MAX_SIZE Only files that are smaller than this in bytes value will be embedded. - ``compressor.filters.yui.YUICSSFilter`` A filter that passes the CSS content to the `YUI compressor`_. .. attribute:: COMPRESS_YUI_BINARY The YUI compressor filesystem path. Make sure to also prepend this setting with ``java -jar`` if you use that kind of distribution. .. attribute:: COMPRESS_YUI_CSS_ARGUMENTS The arguments passed to the compressor. - ``compressor.filters.yuglify.YUglifyCSSFilter`` A filter that passes the CSS content to the `yUglify compressor`_. .. attribute:: COMPRESS_YUGLIFY_BINARY The yUglify compressor filesystem path. .. attribute:: COMPRESS_YUGLIFY_CSS_ARGUMENTS The arguments passed to the compressor. Defaults to --terminal. .. _csscompressor_filter: - ``compressor.filters.cssmin.CSSCompressorFilter`` A filter that uses Yury Selivanov's Python port of the YUI CSS compression algorithm csscompressor_. - ``compressor.filters.cssmin.rCSSMinFilter`` A filter that uses the cssmin implementation rCSSmin_ to compress CSS (installed by default). - ``compressor.filters.cleancss.CleanCSSFilter`` A filter that passes the CSS content to the `clean-css`_ tool. .. attribute:: COMPRESS_CLEAN_CSS_BINARY The clean-css binary filesystem path. .. attribute:: COMPRESS_CLEAN_CSS_ARGUMENTS The arguments passed to clean-css. .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme .. _csscompressor: http://pypi.python.org/pypi/csscompressor/ .. _rCSSmin: http://opensource.perlig.de/rcssmin/ .. _`clean-css`: https://github.com/GoalSmashers/clean-css/ - ``compressor.filters.template.TemplateFilter`` A filter that renders the CSS content with Django templating system. .. attribute:: COMPRESS_TEMPLATE_FILTER_CONTEXT The context to render your css files with. - Javascript Filters .. _compress_js_filters: :Default: ``['compressor.filters.jsmin.rJSMinFilter']`` A list of filters that will be applied to javascript. Possible options are: - ``compressor.filters.jsmin.rJSMinFilter`` A filter that uses the jsmin implementation rJSmin_ to compress JavaScript code (installed by default). .. _calmjs_filter: - ``compressor.filters.jsmin.CalmjsFilter`` A filter that uses the jsmin implementation `Calmjs`_ to compress JavaScript code. - ``compressor.filters.closure.ClosureCompilerFilter`` A filter that uses `Google Closure compiler`_. .. attribute:: COMPRESS_CLOSURE_COMPILER_BINARY The Closure compiler filesystem path. Make sure to also prepend this setting with ``java -jar`` if you use that kind of distribution. .. attribute:: COMPRESS_CLOSURE_COMPILER_ARGUMENTS The arguments passed to the compiler. - ``compressor.filters.yui.YUIJSFilter`` A filter that passes the JavaScript code to the `YUI compressor`_. .. attribute:: COMPRESS_YUI_BINARY :noindex: The YUI compressor filesystem path. .. attribute:: COMPRESS_YUI_JS_ARGUMENTS :noindex: The arguments passed to the compressor. - ``compressor.filters.yuglify.YUglifyJSFilter`` A filter that passes the JavaScript code to the `yUglify compressor`_. .. attribute:: COMPRESS_YUGLIFY_BINARY :noindex: The yUglify compressor filesystem path. .. attribute:: COMPRESS_YUGLIFY_JS_ARGUMENTS :noindex: The arguments passed to the compressor. - ``compressor.filters.template.TemplateFilter`` A filter that renders the JavaScript code with Django templating system. .. attribute:: COMPRESS_TEMPLATE_FILTER_CONTEXT :noindex: The context to render your JavaScript code with. .. _rJSmin: http://opensource.perlig.de/rjsmin/ .. _`Google Closure compiler`: http://code.google.com/closure/compiler/ .. _`YUI compressor`: http://developer.yahoo.com/yui/compressor/ .. _`yUglify compressor`: https://github.com/yui/yuglify .. _`Slim It`: https://github.com/rspivak/slimit .. _`Calmjs`: https://github.com/calmjs/calmjs.parse .. attribute:: COMPRESS_PRECOMPILERS :Default: ``()`` An iterable of two-tuples whose first item is the mimetype of the files or hunks you want to compile with the command or filter specified as the second item: #. mimetype The mimetype of the file or inline code that should be compiled. #. command_or_filter The command to call on each of the files. Modern Python string formatting will be provided for the two placeholders ``{infile}`` and ``{outfile}`` whose existence in the command string also triggers the actual creation of those temporary files. If not given in the command string, Django Compressor will use ``stdin`` and ``stdout`` respectively instead. Alternatively, you may provide the fully qualified class name of a filter you wish to use as a precompiler. Example:: COMPRESS_PRECOMPILERS = ( ('text/coffeescript', 'coffee --compile --stdio'), ('text/less', 'lessc {infile} {outfile}'), ('text/x-sass', 'sass {infile} {outfile}'), ('text/x-scss', 'sass --scss {infile} {outfile}'), ('text/stylus', 'stylus < {infile} > {outfile}'), ('text/foobar', 'path.to.MyPrecompilerFilter'), ) .. note:: Depending on the implementation, some precompilers might not support outputting to something else than ``stdout``, so you'll need to omit the ``{outfile}`` parameter when working with those. For instance, if you are using the Ruby version of lessc, you'll need to set up the precompiler like this:: ('text/less', 'lessc {infile}'), With that setting (and CoffeeScript_ installed), you could add the following code to your templates: .. code-block:: django {% load compress %} {% compress js %} {% endcompress %} This would give you something like this:: The same works for less_, too: .. code-block:: django {% load compress %} {% compress css %} {% endcompress %} Which would be rendered something like:: .. _less: http://lesscss.org/ .. _CoffeeScript: http://coffeescript.org/ .. attribute:: COMPRESS_STORAGE :Default: ``'compressor.storage.CompressorFileStorage'`` The dotted path to a Django Storage backend to be used to save the compressed files. Django Compressor ships with some additional storage backends: * ``'compressor.storage.GzipCompressorFileStorage'`` A subclass of the default storage backend, which will additionally create ``*.gz`` files of each of the compressed files. * ``'compressor.storage.BrotliCompressorFileStorage'`` A subclass of the default storage backend, which will additionally create ``*.br`` files of each of the compressed files. It is using the maximum level of compression (11) so compression speed will be low. .. attribute:: COMPRESS_PARSER :Default: ``'compressor.parser.AutoSelectParser'`` The backend to use when parsing the JavaScript or Stylesheet files. The ``AutoSelectParser`` picks the ``lxml`` based parser when available, and falls back to ``HtmlParser`` if ``lxml`` is not available. ``LxmlParser`` is the fastest available parser, but ``HtmlParser`` is not much slower. ``AutoSelectParser`` adds a slight overhead, but in most cases it won't be necessary to change the default parser. The other two included parsers are considerably slower and should only be used if absolutely necessary. .. warning:: In some cases the ``compressor.parser.HtmlParser`` parser isn't able to parse invalid HTML in JavaScript or CSS content. As a workaround you should use one of the more forgiving parsers, e.g. the ``BeautifulSoupParser``. The backends included in Django Compressor: - ``compressor.parser.AutoSelectParser`` - ``compressor.parser.LxmlParser`` - ``compressor.parser.HtmlParser`` - ``compressor.parser.BeautifulSoupParser`` - ``compressor.parser.Html5LibParser`` See :ref:`dependencies` for more info about the packages you need for each parser. Caching settings ---------------- .. attribute:: COMPRESS_CACHE_BACKEND :Default: ``"default"`` The cache to use by Django Compressor. Must be a cache alias specified in your ``CACHES`` setting. .. attribute:: COMPRESS_REBUILD_TIMEOUT :Default: ``2592000`` (30 days in seconds) The period of time after which the compressed files are rebuilt even if no file changes are detected. .. attribute:: COMPRESS_MINT_DELAY :Default: ``30`` (seconds) The upper bound on how long any compression should take to run. Prevents dog piling, should be a lot smaller than :attr:`~django.conf.settings.COMPRESS_REBUILD_TIMEOUT`. .. attribute:: COMPRESS_MTIME_DELAY :Default: ``10`` The amount of time (in seconds) to cache the modification timestamp of a file. Should be smaller than :attr:`~django.conf.settings.COMPRESS_REBUILD_TIMEOUT` and :attr:`~django.conf.settings.COMPRESS_MINT_DELAY`. .. attribute:: COMPRESS_CACHEABLE_PRECOMPILERS :Default: ``()`` An iterable of precompiler mimetypes as defined in :attr:`~django.conf.settings.COMPRESS_PRECOMPILERS` for which the compiler output can be cached based solely on the contents of the input file. This lets Django Compressor avoid recompiling unchanged files. Caching is appropriate for compilers such as CoffeeScript where files are compiled one-to-one, but not for compilers such as SASS that have an ``import`` mechanism for including one file from another. If caching is enabled for such a compiler, Django Compressor will not know to recompile files when a file they import is modified. .. attribute:: COMPRESS_DEBUG_TOGGLE :Default: None The name of the GET variable that toggles the debug mode and prevents Django Compressor from performing the actual compression. Only useful for debugging. .. warning:: Don't use this option in production! An easy convention is to only set it depending on the ``DEBUG`` setting:: if DEBUG: COMPRESS_DEBUG_TOGGLE = 'whatever' .. note:: This only works for pages that are rendered using the RequestContext_ and the ``django.core.context_processors.request`` context processor. .. _RequestContext: http://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.RequestContext .. attribute:: COMPRESS_CACHE_KEY_FUNCTION :Default: ``'compressor.cache.simple_cachekey'`` The function to use when generating the cache key. The function must take one argument which is the partial key based on the source's hex digest. It must return the full key as a string. Offline settings ---------------- .. attribute:: COMPRESS_OFFLINE :Default: ``False`` Boolean that decides if compression should be done outside of the request/response loop. See :ref:`offline_compression` for details. .. attribute:: COMPRESS_OFFLINE_TIMEOUT :Default: ``31536000`` (1 year in seconds) The period of time with which the ``compress`` management command stores the pre-compressed the contents of ``{% compress %}`` template tags in the cache. .. attribute:: COMPRESS_OFFLINE_CONTEXT :Default: ``{'STATIC_URL': settings.STATIC_URL}`` The context to be used by the ``compress`` management command when rendering the contents of ``{% compress %}`` template tags and saving the result in the offline cache. If available, the ``STATIC_URL`` setting is also added to the context. .. note:: It is also possible to perform offline compression for multiple contexts by providing a list or tuple of dictionaries, or by providing a dotted string pointing to a generator function. This makes it easier to generate contexts dynamically for situations where a user might be able to select a different theme in their user profile, or be served different stylesheets based on other criteria. An example of multiple offline contexts by providing a list or tuple:: # project/settings.py: COMPRESS_OFFLINE_CONTEXT = [ {'THEME': 'plain', 'STATIC_URL': STATIC_URL}, {'THEME': 'fancy', 'STATIC_URL': STATIC_URL}, # ... ] An example of multiple offline contexts generated dynamically:: # project/settings.py: COMPRESS_OFFLINE_CONTEXT = 'project.module.offline_context' # project/module.py: from django.conf import settings def offline_context(): from project.models import Company for theme in set(Company.objects.values_list('theme', flat=True)): yield {'THEME': theme, 'STATIC_URL': settings.STATIC_URL} .. attribute:: COMPRESS_OFFLINE_MANIFEST :Default: ``manifest.json`` The name of the file to be used for saving the names of the files compressed offline. .. attribute:: COMPRESS_OFFLINE_MANIFEST_STORAGE :Default: ``compressor.storage.OfflineManifestFileStorage`` The dotted path to a Django Storage backend to be used to save the offline manifest. By default, the file configured with :attr:`~django.conf.settings.COMPRESS_OFFLINE_MANIFEST` will be stored into :attr:`~django.conf.settings.COMPRESS_OUTPUT_DIR`. An example to output the manifest into the project's root directory:: # project/settings.py: COMPRESS_OFFLINE_MANIFEST_STORAGE = 'project.module.PrivateOfflineManifestFileStorage' # project/module.py: from compressor.storage import OfflineManifestFileStorage from django.conf import settings class PrivateOfflineManifestFileStorage(OfflineManifestFileStorage): def __init__(self, *args, **kwargs): super().__init__(settings.BASE_DIR, None, *args, **kwargs) django-compressor-4.4/docs/usage.txt000066400000000000000000000207521444665704600176400ustar00rootroot00000000000000.. _usage: Usage ===== .. code-block:: django {% load compress %} {% compress [ [block_name]] %} {% endcompress %} Examples -------- Basic example: .. code-block:: django {% compress css %} {% endcompress %} Result: .. code-block:: django Adding the ``inline`` parameter will put the content directly to the rendered page instead of a file: .. code-block:: django {% compress js inline %} {% endcompress %} Result: .. code-block:: django Adding the ``preload`` parameter will generate the preload tag for the compressed resource in the template: .. code-block:: django {% compress js preload %} {% endcompress %} Result: .. code-block:: django Specifying a ``block_name`` will change the output filename. It can also be accessed in the :ref:`post_compress signal ` in the ``context`` parameter. .. code-block:: django {% compress js file base %} {% endcompress %} Result: .. code-block:: django Javascript ``async`` and ``defer`` are supported: .. code-block:: django {% compress js %} {% endcompress %} {% compress js %} {% endcompress %} Result: .. code-block:: django .. note:: Remember that django-compressor will try to :ref:`group outputs by media `. Linked files **must** be accessible via :attr:`~django.conf.settings.COMPRESS_URL`. If the :attr:`~django.conf.settings.COMPRESS_ENABLED` setting is ``False`` (defaults to the opposite of DEBUG) the ``compress`` template tag does nothing and simply returns exactly what it was given. .. note:: If you've configured any :attr:`precompilers `, setting :attr:`~django.conf.settings.COMPRESS_ENABLED` to ``False`` won't affect the processing of those files. Only the :attr:`~django.conf.settings.COMPRESS_FILTERS` will be disabled. If both DEBUG and :attr:`~django.conf.settings.COMPRESS_ENABLED` are set to ``True``, incompressible files (off-site or non existent) will throw an exception. If DEBUG is ``False`` these files will be silently stripped. .. warning:: For production sites it is **strongly recommended** to use a real cache backend such as memcached_ to speed up the checks of compressed files. Make sure you set your Django cache backend appropriately (also see :attr:`~django.conf.settings.COMPRESS_CACHE_BACKEND` and Django's `caching documentation`_). .. _memcached: http://memcached.org/ .. _caching documentation: https://docs.djangoproject.com/en/stable/topics/cache/#memcached .. _offline_compression: Offline Compression ------------------- Django Compressor has the ability to run the compression "offline", i.e. outside of the request/response loop -- independent from user requests. If offline compression is enabled, no new files are generated during a request and the ``{% compress %}`` tag simply inserts links to the files in the offline cache (see :ref:`behind_the_scenes` for details). This results in better performance and enables certain deployment scenarios (see :ref:`scenarios`). To use offline compression, enable the :attr:`django.conf.settings.COMPRESS_OFFLINE` setting and then run the ``compress`` management command to compress your assets and update the offline cache. The command parses all templates that can be found with the template loader (as specified in the TEMPLATE_LOADERS_ setting) and looks for ``{% compress %}`` blocks. It then will use the context as defined in :attr:`django.conf.settings.COMPRESS_OFFLINE_CONTEXT` to render its content. So if you use any variables inside the ``{% compress %}`` blocks, make sure to list all values you require in ``COMPRESS_OFFLINE_CONTEXT``. It's similar to a template context and should be used if a variable is used in the blocks, e.g.: .. code-block:: django {% load compress %} {% compress js %} {% endcompress %} Since this template requires a variable (``greeting``) you need to specify this in your settings before using the ``compress`` management command:: COMPRESS_OFFLINE_CONTEXT = { 'greeting': 'Hello there!', } The result of running the ``compress`` management command will be cached in a file called ``manifest.json`` using the :attr:`configured storage ` to be able to be transferred from your development computer to the server easily. .. _TEMPLATE_LOADERS: http://docs.djangoproject.com/en/stable/ref/settings/#template-loaders .. _signals: Signals ------- .. function:: compressor.signals.post_compress(sender, type, mode, context) Django Compressor includes a ``post_compress`` signal that enables you to listen for changes to your compressed CSS/JS. This is useful, for example, if you need the exact filenames for use in an HTML5 manifest file. The signal sends the following arguments: ``sender`` Either :class:`compressor.css.CssCompressor` or :class:`compressor.js.JsCompressor`. .. versionchanged:: 1.2 The sender is now one of the supported Compressor classes for easier limitation to only one of them, previously it was a string named ``'django-compressor'``. ``type`` Either "``js``" or "``css``". ``mode`` Either "``file``" or "``inline``". ``context`` The context dictionary used to render the output of the compress template tag. If ``mode`` is "``file``" the dictionary named ``compressed`` in the context will contain a "``url``" key that maps to the relative URL for the compressed asset. If ``type`` is "``css``", the dictionary named ``compressed`` in the context will additionally contain a "``media``" key with a value of ``None`` if no media attribute is specified on the link/style tag and equal to that attribute if one is specified. Additionally, ``context['compressed']['name']`` will be the third positional argument to the template tag, if provided. .. note:: When compressing CSS, the ``post_compress`` signal will be called once for every different media attribute on the tags within the ``{% compress %}`` tag in question. .. _css_notes: CSS Notes --------- All relative ``url()`` bits specified in linked CSS files are automatically converted to absolute URLs while being processed. Any local absolute URLs (those starting with a ``'/'``) are left alone. Stylesheets that are ``@import``'d are not compressed into the main file. They are left alone. If the media attribute is set on