├── .gitignore ├── COPYING ├── README.rst ├── __init__.py ├── coverage_settings.py ├── coverage_test_runner.py ├── django_static ├── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── django_static.py ├── tests.py └── views.py ├── exampleapp ├── __init__.py ├── models.py ├── templates │ ├── page.html │ └── showicons.html ├── tests.py ├── urls.py └── views.py ├── gorun_settings.py ├── manage.py ├── media ├── css │ ├── reset.css │ ├── styles.css │ ├── sub-module2.css │ └── sub │ │ └── module.css ├── delete_all_timestamped_files.py ├── images │ ├── icon1.png │ ├── icon2.png │ ├── icon3.png │ ├── peterbe.jpg │ └── sky.jpg └── javascript │ ├── canbe.js │ ├── combined.min.js │ ├── jquery-1.3.2.min.js │ ├── script.js │ └── shit_code.js ├── settings.py ├── setup.py └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | dist/* 3 | .venv 4 | *.pyc 5 | *~ 6 | media/css/styles.*.css 7 | media/images/sky.*.jpg 8 | media/images/peterbe.*.jpg 9 | media/javascript/script.*.js 10 | media/javascript/jquery-1.3.2.min.*.js 11 | media/javascript/canbe_combined.min.*.js 12 | media/images/icon1.*.png 13 | media/images/icon2.*.png 14 | media/images/icon1_icon2.*.png 15 | media/images/icon3.*.png 16 | media/javascript/shit_code.*.js 17 | media/css/reset_styles.*.css 18 | media/css/reset.*.css 19 | coverage_report/fcgi_settings.py 20 | kill_fcgi.sh 21 | run_fcgi.sh 22 | coverage_report/ 23 | fcgi_settings.py 24 | media/css/sub/module.*.css 25 | media/css/sub-module2.*.css 26 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Peter Bengtsson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | About django-static 2 | =================== 3 | 4 | .. contents:: 5 | 6 | What it does 7 | ------------ 8 | 9 | ``django_static`` is a Django app that enables as various template tags 10 | for better serving your static content. It basically rewrites 11 | references to static files and where applicable it does whitespace 12 | optmization of the content. By making references to static content 13 | unique (timestamp included in the name) you can be very aggressive 14 | with your cache-control settings without ever having to worry about 15 | upgrading your code and worrying about visitors using an older version. 16 | 17 | The five template tags it enables are the following: 18 | 19 | 1. ``staticfile`` Takes the timestamp of the file, and makes a copy by 20 | symlinking as you define. You use it like this:: 21 | 22 | 23 | 24 | and the following is rendered:: 25 | 26 | 27 | 28 | ...assuming the epoch timestamp of the file is 123456789. 29 | 30 | 2. ``slimfile`` Works the same as ``staticfile`` but instead of copying 31 | the file as a symlink it actually rewrites the file and compresses 32 | it through `slimmer `__. This of 33 | course only works for ``.js`` and ``.css`` files but it works 34 | wonderfully fast and is careful enough to not break things. The 35 | cool thing about doing this for ``.css`` files it finds all relative 36 | images inside and applies ``staticfile`` for all of them too. You use 37 | it just like ``staticfile``:: 38 | 39 | 41 | 42 | 3. ``slimcontent`` is used to whitespace compress content right in the 43 | template. It requires a format parameter which can be ``"js"``, 44 | ``"css"`` or ``"html"``. So, for example for some inline CSS content 45 | you do this:: 46 | 47 | 54 | 55 | ...and you get this:: 56 | 57 | 60 | 61 | 4. ``staticall`` combines all files between the tags into one and 62 | makes the same symlinking as ``staticfile``. Write this:: 63 | 64 | {% staticall %} 65 | 66 | 67 | {% endstaticall %} 68 | 69 | ...and you get this:: 70 | 71 | 72 | 73 | 5. ``slimall`` does the same compression ``slimfile`` does but also 74 | combines the files as ``staticall``. Use it like ``staticall``:: 75 | 76 | {% slimall %} 77 | 78 | 79 | {% endslimall %} 80 | 81 | ``staticall`` and ``slimall`` fully support ``async`` or ``defer`` 82 | JavaScript attributes. Meaning this:: 83 | 84 | {% slimall %} 85 | 86 | 87 | {% endslimall %} 88 | 89 | ...will give you this:: 90 | 91 | 92 | 93 | Be careful not to mix the two attributes within the same blocks 94 | or you might get unexpected results. 95 | 96 | Configuration 97 | ------------- 98 | 99 | ``django_static`` will be disabled by default. It's not until you set 100 | ``DJANGO_STATIC = True`` in your settings module that it actually starts 101 | to work for you. 102 | 103 | By default, when ``django_static`` slims files or makes symlinks with 104 | timestamps in the filename, it does this into the same directory as 105 | where the original file is. If you don't like that you can override 106 | the save location by setting 107 | ``DJANGO_STATIC_SAVE_PREFIX = "/tmp/django-static"`` 108 | 109 | If you, for the sake of setting up your nginx/varnish/apache2, want 110 | change the name the files get you can set 111 | ``DJANGO_STATIC_NAME_PREFIX = "/cache-forever"`` as this will make it easier 112 | to write a rewrite rule/regular expression that in 113 | nginx/varnish/apache2 deliberately sets extra aggressive caching. 114 | 115 | Another option is to let django_static take care of setting your 116 | ``MEDIA_URL``. You could do this:: 117 | 118 | 119 | 120 | But if you're feeling lazy and what django_static to automatically 121 | take care of it set ``DJANGO_STATIC_MEDIA_URL``. In settings.py:: 122 | 123 | DJANGO_STATIC_MEDIA_URL = "//static.example.com" 124 | 125 | In your template:: 126 | 127 | 128 | 129 | And you get this result:: 130 | 131 | 132 | 133 | Right out of the box, ``DJANGO_STATIC_MEDIA_URL`` will not be active 134 | if ``DJANGO_STATIC = False``. If you want it to be, set 135 | ``DJANGO_STATIC_MEDIA_URL_ALWAYS = True``. 136 | 137 | By default django_static will look for source files in ``MEDIA_ROOT``, 138 | but it is possible tell django_static to look in all directories listed 139 | in ``DJANGO_STATIC_MEDIA_ROOTS``. The first match will be used. 140 | 141 | There is also a setting ``DJANGO_STATIC_USE_SYMLINK`` that can be set to 142 | ``False`` to force django_static to copy files instead of symlinking them. 143 | 144 | Advanced configuration with DJANGO_STATIC_USE_MANIFEST_FILE 145 | ----------------------------------------------------------- 146 | 147 | If you enable, in your settings, a variable called 148 | ``DJANGO_STATIC_USE_MANIFEST_FILE`` you can save filenames to 149 | manifest.json which is stored in the first match directory in 150 | ``DJANGO_STATIC_MEDIA_ROOTS``. This is for the usecase where we want to 151 | manually upload css and javascript files to CDN. On production, where DEBUG=False, 152 | django-static will pick the filenames from manifest.json file instead of 153 | doing all the calculations. 154 | 155 | 156 | Advanced configuration with DJANGO_STATIC_FILE_PROXY 157 | ---------------------------------------------------- 158 | 159 | If you enable, in your settings, a variable called 160 | ``DJANGO_STATIC_FILE_PROXY`` you can make all static URIs that 161 | ``django_static`` generates go though one function. So that you, for 162 | example, can do something with the information such as uploading to a 163 | CDN. To get started set the config:: 164 | 165 | DJANGO_STATIC_FILE_PROXY = 'mycdn.cdn_uploader_file_proxy' 166 | 167 | This is expected to be the equivalent of this import statement:: 168 | 169 | from mycdn import cdn_uploader_file_proxy 170 | 171 | Where ``mycdn`` is a python module (e.g. ``mycdn.py``) and 172 | ``cdn_uploader_file_proxy`` is a regular python function. Here's the 173 | skeleton for that function:: 174 | 175 | def cdn_uploader_file_proxy(uri, **kwargs): 176 | return uri 177 | 178 | Now, it's inside those keyword arguments that you get the juicy gossip 179 | about what ``django_static`` has done with the file. These are the 180 | pieces of information you will always get inside those keyword 181 | argments:: 182 | 183 | new = False 184 | checked = False 185 | changed = False 186 | notfound = False 187 | 188 | The names hopefully speak for themselves. They become ``True`` depending 189 | on what ``django_static`` has done. For example, if you change your 190 | ``foo.js`` and re-run the template it's not ``new`` but it will be ``checked`` 191 | and ``changed``. The possibly most important keyword argument you might 192 | get is ``filepath``. This is set whenever ``django_static`` actually does 193 | its magic on a static file. So, for example you might write a function 194 | like this:: 195 | 196 | on_my_cdn = {} 197 | 198 | def cdn_uploader_file_proxy(uri, filepath=None, new=False, 199 | changed=False, **kwargs): 200 | if filepath and (new or changed): 201 | on_my_cdn[uri] = upload_to_my_cdn(filepath) 202 | 203 | return on_my_cdn.get(uri, uri) 204 | 205 | Advanced configuration with DJANGO_STATIC_FILENAME_GENERATOR 206 | ------------------------------------------------------------ 207 | 208 | By default, django-static generates filenames for your combined files 209 | using timestamps. You can use your own filename generating function 210 | by setting it in settings, like so:: 211 | 212 | DJANGO_STATIC_FILENAME_GENERATOR = 'myapp.filename_generator' 213 | 214 | This is expected to be the equivalent of this import statement:: 215 | 216 | from myapp import filename_generator 217 | 218 | Where ``myapp`` is a python module, and ``filename_generator`` is a regular 219 | python function. Here's the skeleton for that function:: 220 | 221 | def filename_generator(file_parts, new_m_time): 222 | return ''.join([file_parts[0], '.%s' % new_m_time, file_parts[1]]) 223 | 224 | 225 | Advanced configuration with DJANGO_STATIC_COMBINE_FILENAMES_GENERATOR 226 | --------------------------------------------------------------------- 227 | 228 | By default, django-static generates filenames for your combined files 229 | by concatenating the file names. You can also use your own filename 230 | generating function by setting it in settings, like so:: 231 | 232 | DJANGO_STATIC_COMBINE_FILENAMES_GENERATOR = 'myapp.combine_filenames' 233 | 234 | This is expected to be the equivalent of this import statement:: 235 | 236 | from myapp import combine_filenames 237 | 238 | Where ``myapp`` is a python module, and ``combine_filenames`` is a regular 239 | python function. Here's the skeleton for that function:: 240 | 241 | path = None 242 | names = [] 243 | extension = None 244 | timestamps = [] 245 | for filename in filenames: 246 | name = os.path.basename(filename) 247 | if not extension: 248 | extension = os.path.splitext(name)[1] 249 | elif os.path.splitext(name)[1] != extension: 250 | raise ValueError("Can't combine multiple file extensions") 251 | 252 | for each in re.finditer('\.\d{10}\.', name): 253 | timestamps.append(int(each.group().replace('.',''))) 254 | name = name.replace(each.group(), '.') 255 | name = os.path.splitext(name)[0] 256 | names.append(name) 257 | 258 | if path is None: 259 | path = os.path.dirname(filename) 260 | else: 261 | if len(os.path.dirname(filename)) < len(path): 262 | path = os.path.dirname(filename) 263 | 264 | 265 | new_filename = '_'.join(names) 266 | if timestamps: 267 | new_filename += ".%s" % max(timestamps) 268 | 269 | new_filename = new_filename[:max_length] 270 | new_filename += extension 271 | 272 | return os.path.join(path, new_filename) 273 | 274 | 275 | Compression Filters 276 | ------------------- 277 | 278 | Default (cssmin) 279 | ~~~~~~~~~~~~~~~~ 280 | 281 | django-static uses cssmin by default if it is installed. 282 | Get the source here: https://github.com/zacharyvoase/cssmin 283 | 284 | Using jsmin 285 | ~~~~~~~~~~~ 286 | 287 | If you would like to use jsmin instead of default js_slimmer, you just need to set 288 | the variable in your settings.py file:: 289 | 290 | DJANGO_STATIC_JSMIN = True 291 | 292 | 293 | Using Google Closure Compiler 294 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 295 | 296 | If you want to use the `Google Closure 297 | Compiler `__ to optimize your 298 | Javascript files you first have to download the compiler.jar file and 299 | make sure your systam can run java. Suppose you download it in 300 | /usr/local/bin, the set this variable in your settings.py file:: 301 | 302 | DJANGO_STATIC_CLOSURE_COMPILER = '/usr/local/bin/compiler.jar' 303 | 304 | If for some reason the compiler chokes on your Javascript it won't 305 | halt the serving of the file but it won't be whitespace optimized and 306 | the error will be inserted into the resulting Javascript file as a big 307 | comment block. 308 | 309 | Using the YUI Compressor 310 | ~~~~~~~~~~~~~~~~~~~~~~~~ 311 | 312 | The `YUI Compressor `__ is 313 | both a Javascript and CSS compressor which requires a java runtime. 314 | Just like the Google Closure Compiler, you need to download the jar 315 | file and then set something like this in your settings.py:: 316 | 317 | DJANGO_STATIC_YUI_COMPRESSOR = '/path/to/yuicompressor-2.4.2.jar' 318 | 319 | If you configure the Google Closure Compiler **and** YUI Compressor, 320 | the Google Closure Compiler will be first choice for Javascript 321 | compression. 322 | 323 | Using the slimmer 324 | ~~~~~~~~~~~~~~~~~ 325 | 326 | `slimmer `__ is an all python 327 | package that is capable of whitespace optimizing CSS, HTML, XHTML and 328 | Javascript. It's faster than the YUI Compressor and Google Closure but 329 | that speed difference is due to the start-stop time of bridging the 330 | Java files. 331 | 332 | How to hook this up with nginx 333 | ------------------------------ 334 | 335 | Read `this blog entry on 336 | peterbe.com `__ 337 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/__init__.py -------------------------------------------------------------------------------- /coverage_settings.py: -------------------------------------------------------------------------------- 1 | from settings import * 2 | 3 | TEST_RUNNER='coverage_test_runner.test_runner_with_coverage' 4 | 5 | COVERAGE_MODULES = [ 6 | 'django_static.templatetags.django_static', 7 | ] 8 | -------------------------------------------------------------------------------- /coverage_test_runner.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | import os 3 | import shutil 4 | from django.test.simple import DjangoTestSuiteRunner 5 | 6 | from django.conf import settings 7 | 8 | 9 | import coverage 10 | def test_runner_with_coverage(test_labels, verbosity=1, interactive=True, 11 | failfast=None, extra_tests=[]): 12 | """ 13 | Custom test runner. Follows the django.test.simple.run_tests() interface. 14 | """ 15 | # Start code coverage before anything else if necessary 16 | if hasattr(settings, 'COVERAGE_MODULES'): 17 | cov = coverage.coverage() 18 | #coverage.use_cache(0) # Do not cache any of the coverage.py stuff 19 | cov.use_cache(0) # Do not cache any of the coverage.py stuff 20 | cov.start() 21 | 22 | test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) 23 | test_results = test_runner.run_tests(test_labels, extra_tests=extra_tests) 24 | 25 | # Stop code coverage after tests have completed 26 | if hasattr(settings, 'COVERAGE_MODULES'): 27 | cov.stop() 28 | 29 | # Print code metrics header 30 | print '' 31 | print '----------------------------------------------------------------------' 32 | print ' Unit Test Code Coverage Results' 33 | print '----------------------------------------------------------------------' 34 | 35 | # Report code coverage metrics 36 | if hasattr(settings, 'COVERAGE_MODULES'): 37 | coverage_modules = [] 38 | for module in settings.COVERAGE_MODULES: 39 | coverage_modules.append(__import__(module, globals(), locals(), [''])) 40 | cov.report(coverage_modules, show_missing=1) 41 | cov.html_report(coverage_modules, directory='coverage_report') 42 | # Print code metrics footer 43 | print '----------------------------------------------------------------------' 44 | 45 | return test_results 46 | -------------------------------------------------------------------------------- /django_static/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.5.6' # remember to match with setup.py 2 | -------------------------------------------------------------------------------- /django_static/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /django_static/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /django_static/templatetags/django_static.py: -------------------------------------------------------------------------------- 1 | # python 2 | import os 3 | import re 4 | import sys 5 | import stat 6 | import shutil 7 | import codecs 8 | from collections import defaultdict 9 | from cStringIO import StringIO 10 | from subprocess import Popen, PIPE 11 | import warnings 12 | import fcntl 13 | import json 14 | 15 | # django 16 | from django import template 17 | from django.conf import settings 18 | from django.template import TemplateSyntaxError 19 | 20 | register = template.Library() 21 | 22 | try: 23 | import slimmer 24 | except ImportError: 25 | slimmer = None 26 | 27 | try: 28 | import cssmin 29 | except ImportError: 30 | cssmin = None 31 | 32 | try: 33 | import jsmin 34 | except ImportError: 35 | jsmin = None 36 | 37 | ################################################################################ 38 | # The reason we're setting all of these into `settings` is so that in the code 39 | # we can do things like `if settings.DJANGO_STATIC:` rather than the verbose 40 | # and ugly `getattr(settings, 'DJANGO_STATIC')`. 41 | # And the reason why these aren't set as constants variables is to make the code 42 | # much easier to test because in the unit tests we can then do 43 | # settings.DJANGO_STATIC_SAVE_PREFIX = '/tmp/test' and stuff like that. 44 | settings.DJANGO_STATIC_USE_SYMLINK = getattr(settings, "DJANGO_STATIC_USE_SYMLINK", True) 45 | settings.DJANGO_STATIC = getattr(settings, 'DJANGO_STATIC', False) 46 | settings.DJANGO_STATIC_SAVE_PREFIX = getattr(settings, 'DJANGO_STATIC_SAVE_PREFIX', '') 47 | settings.DJANGO_STATIC_NAME_PREFIX = getattr(settings, 'DJANGO_STATIC_NAME_PREFIX', '') 48 | settings.DJANGO_STATIC_NAME_MAX_LENGTH = getattr(settings, 'DJANGO_STATIC_NAME_MAX_LENGTH', 40) 49 | settings.DJANGO_STATIC_MEDIA_URL = \ 50 | getattr(settings, "DJANGO_STATIC_MEDIA_URL", None) 51 | settings.DJANGO_STATIC_MEDIA_URL_ALWAYS = \ 52 | getattr(settings, "DJANGO_STATIC_MEDIA_URL_ALWAYS", False) 53 | 54 | settings.DJANGO_STATIC_MEDIA_ROOTS = getattr(settings, "DJANGO_STATIC_MEDIA_ROOTS", 55 | [settings.MEDIA_ROOT]) 56 | settings.DJANGO_STATIC_USE_MANIFEST_FILE = \ 57 | getattr(settings, "DJANGO_STATIC_USE_MANIFEST_FILE", False) 58 | 59 | if sys.platform == "win32": 60 | _CAN_SYMLINK = False 61 | else: 62 | _CAN_SYMLINK = settings.DJANGO_STATIC_USE_SYMLINK 63 | 64 | # Wheree the mapping filename -> annotated_filename is kept 65 | 66 | if settings.DJANGO_STATIC_USE_MANIFEST_FILE: 67 | _MANIFEST_PATH = os.path.join(settings.DJANGO_STATIC_MEDIA_ROOTS[0], 'manifest.json') 68 | else: 69 | _FILE_MAP = {} 70 | 71 | ## These two methods are put here if someone wants to access the django_static 72 | ## functionality from code rather than from a django template 73 | ## E.g. 74 | ## from django_static import slimfile 75 | ## print slimfile('/css/foo.js') 76 | 77 | def slimfile(filename): 78 | return _static_file(filename, 79 | symlink_if_possible=_CAN_SYMLINK, 80 | optimize_if_possible=True) 81 | 82 | def staticfile(filename): 83 | return _static_file(filename, 84 | symlink_if_possible=_CAN_SYMLINK, 85 | optimize_if_possible=False) 86 | 87 | 88 | def _load_file_proxy(): 89 | # This is a function so that it can be unit tested more easily 90 | try: 91 | file_proxy_name = settings.DJANGO_STATIC_FILE_PROXY 92 | if not file_proxy_name: 93 | #warnings.warn("Empty DJANGO_STATIC_FILE_PROXY settings") 94 | raise AttributeError 95 | from django.utils.importlib import import_module 96 | _module_name, _function_name = file_proxy_name.rsplit('.', 1) 97 | file_proxy_module = import_module(_module_name) 98 | return getattr(file_proxy_module, _function_name) 99 | except AttributeError: 100 | def file_proxy_nothing(uri, *args, **kwargs): 101 | return uri 102 | return file_proxy_nothing 103 | file_proxy = _load_file_proxy() 104 | 105 | def _load_filename_generator(): 106 | filename_generator = getattr(settings, 'DJANGO_STATIC_FILENAME_GENERATOR', None) 107 | if filename_generator: 108 | from django.utils.importlib import import_module 109 | _module_name, _function_name = filename_generator.rsplit('.', 1) 110 | file_generator_module = import_module(_module_name) 111 | return getattr(file_generator_module, _function_name) 112 | def default_filename_generator(apart, new_m_time): 113 | new_filename = ''.join([apart[0], '.%s' % new_m_time, apart[1]]) 114 | return new_filename 115 | return default_filename_generator 116 | 117 | _generate_filename = _load_filename_generator() 118 | 119 | # this defines what keyword arguments you can always expect to get from in the 120 | # file proxy function you've defined. 121 | fp_default_kwargs = dict(new=False, changed=False, checked=False, notfound=False) 122 | 123 | 124 | class SlimContentNode(template.Node): 125 | 126 | def __init__(self, nodelist, format=None): 127 | self.nodelist = nodelist 128 | self.format = format 129 | 130 | def render(self, context): 131 | code = self.nodelist.render(context) 132 | if slimmer is None: 133 | return code 134 | 135 | if self.format not in ('css','js','html','xhtml'): 136 | self.format = slimmer.guessSyntax(code) 137 | 138 | if self.format == 'css': 139 | return slimmer.css_slimmer(code) 140 | elif self.format in ('js', 'javascript'): 141 | return slimmer.js_slimmer(code) 142 | elif self.format == 'xhtml': 143 | return slimmer.xhtml_slimmer(code) 144 | elif self.format == 'html': 145 | return slimmer.html_slimmer(code) 146 | else: 147 | raise TemplateSyntaxError("Unrecognized format for slimming content") 148 | 149 | return code 150 | 151 | 152 | 153 | @register.tag(name='slimcontent') 154 | def do_slimcontent(parser, token): 155 | nodelist = parser.parse(('endslimcontent',)) 156 | parser.delete_first_token() 157 | 158 | _split = token.split_contents() 159 | format = '' 160 | if len(_split) > 1: 161 | tag_name, format = _split 162 | if not (format[0] == format[-1] and format[0] in ('"', "'")): 163 | raise template.TemplateSyntaxError, \ 164 | "%r tag's argument should be in quotes" % tag_name 165 | 166 | return SlimContentNode(nodelist, format[1:-1]) 167 | 168 | 169 | 170 | @register.tag(name='slimfile') 171 | def slimfile_node(parser, token): 172 | """For example: 173 | {% slimfile "/js/foo.js" %} 174 | or 175 | {% slimfile "/js/foo.js" as variable_name %} 176 | Or for multiples: 177 | {% slimfile "/foo.js; /bar.js" %} 178 | or 179 | {% slimfile "/foo.js; /bar.js" as variable_name %} 180 | """ 181 | return staticfile_node(parser, token, optimize_if_possible=True) 182 | 183 | 184 | @register.tag(name='staticfile') 185 | def staticfile_node(parser, token, optimize_if_possible=False): 186 | """For example: 187 | {% staticfile "/js/foo.js" %} 188 | or 189 | {% staticfile "/js/foo.js" as variable_name %} 190 | Or for multiples: 191 | {% staticfile "/foo.js; /bar.js" %} 192 | or 193 | {% staticfile "/foo.js; /bar.js" as variable_name %} 194 | """ 195 | args = token.split_contents() 196 | tag = args[0] 197 | 198 | if len(args) == 4 and args[-2] == 'as': 199 | context_name = args[-1] 200 | args = args[:-2] 201 | else: 202 | context_name = None 203 | 204 | filename = parser.compile_filter(args[1]) 205 | 206 | return StaticFileNode(filename, 207 | symlink_if_possible=_CAN_SYMLINK, 208 | optimize_if_possible=optimize_if_possible, 209 | context_name=context_name) 210 | 211 | 212 | class StaticFileNode(template.Node): 213 | 214 | def __init__(self, filename_var, 215 | optimize_if_possible=False, 216 | symlink_if_possible=False, 217 | context_name=None): 218 | self.filename_var = filename_var 219 | self.optimize_if_possible = optimize_if_possible 220 | self.symlink_if_possible = symlink_if_possible 221 | self.context_name = context_name 222 | 223 | def render(self, context): 224 | filename = self.filename_var.resolve(context) 225 | if not settings.DJANGO_STATIC: 226 | if settings.DJANGO_STATIC_MEDIA_URL_ALWAYS: 227 | return settings.DJANGO_STATIC_MEDIA_URL + filename 228 | return filename 229 | new_filename = _static_file([x.strip() for x in filename.split(';')], 230 | optimize_if_possible=self.optimize_if_possible, 231 | symlink_if_possible=self.symlink_if_possible) 232 | if self.context_name: 233 | context[self.context_name] = new_filename 234 | return '' 235 | return new_filename 236 | 237 | 238 | @register.tag(name='slimall') 239 | def do_slimallfiles(parser, token): 240 | nodelist = parser.parse(('endslimall',)) 241 | parser.delete_first_token() 242 | 243 | return StaticFilesNode(nodelist, 244 | symlink_if_possible=_CAN_SYMLINK, 245 | optimize_if_possible=True) 246 | 247 | 248 | @register.tag(name='staticall') 249 | def do_staticallfiles(parser, token): 250 | nodelist = parser.parse(('endstaticall',)) 251 | parser.delete_first_token() 252 | 253 | return StaticFilesNode(nodelist, 254 | symlink_if_possible=_CAN_SYMLINK, 255 | optimize_if_possible=False) 256 | 257 | 258 | 259 | 260 | SCRIPTS_REGEX = re.compile('') 261 | STYLES_REGEX = re.compile('', re.M|re.DOTALL) 262 | IMG_REGEX = re.compile('', re.M|re.DOTALL) 263 | ASYNC_DEFER_REGEX = re.compile('async|defer') 264 | 265 | class StaticFilesNode(template.Node): 266 | """find all static files in the wrapped code and run staticfile (or 267 | slimfile) on them all by analyzing the code. 268 | """ 269 | def __init__(self, nodelist, optimize_if_possible=False, 270 | symlink_if_possible=False): 271 | self.nodelist = nodelist 272 | self.optimize_if_possible = optimize_if_possible 273 | 274 | self.symlink_if_possible = symlink_if_possible 275 | 276 | def render(self, context): 277 | """inspect the code and look for files that can be turned into combos. 278 | Basically, the developer could type this: 279 | {% slimall %} 280 | 281 | 282 | {% endslimall %} 283 | And it should be reconsidered like this: 284 | 285 | which we already have routines for doing. 286 | """ 287 | code = self.nodelist.render(context) 288 | if not settings.DJANGO_STATIC: 289 | # Append MEDIA_URL if set 290 | # quick and dirty 291 | if settings.DJANGO_STATIC_MEDIA_URL_ALWAYS: 292 | for match in STYLES_REGEX.finditer(code): 293 | for filename in match.groups(): 294 | code = (code.replace(filename, 295 | settings.DJANGO_STATIC_MEDIA_URL + filename)) 296 | 297 | for match in SCRIPTS_REGEX.finditer(code): 298 | for filename in match.groups(): 299 | code = (code.replace(filename, 300 | settings.DJANGO_STATIC_MEDIA_URL + filename)) 301 | 302 | return code 303 | 304 | return code 305 | 306 | new_js_filenames = [] 307 | for match in SCRIPTS_REGEX.finditer(code): 308 | whole_tag = match.group() 309 | async_defer = ASYNC_DEFER_REGEX.search(whole_tag) 310 | for filename in match.groups(): 311 | 312 | optimize_if_possible = self.optimize_if_possible 313 | if optimize_if_possible and \ 314 | (filename.endswith('.min.js') or filename.endswith('.minified.js')): 315 | # Override! Because we simply don't want to run slimmer 316 | # on files that have the file extension .min.js 317 | optimize_if_possible = False 318 | 319 | new_js_filenames.append(filename) 320 | code = code.replace(whole_tag, '') 321 | 322 | # Now, we need to combine these files into one 323 | if new_js_filenames: 324 | new_js_filename = _static_file(new_js_filenames, 325 | optimize_if_possible=optimize_if_possible, 326 | symlink_if_possible=self.symlink_if_possible) 327 | else: 328 | new_js_filename = None 329 | 330 | new_image_filenames = [] 331 | def image_replacer(match): 332 | tag = match.group() 333 | for filename in match.groups(): 334 | new_filename = _static_file(filename, 335 | symlink_if_possible=self.symlink_if_possible) 336 | if new_filename != filename: 337 | tag = tag.replace(filename, new_filename) 338 | return tag 339 | 340 | code = IMG_REGEX.sub(image_replacer, code) 341 | 342 | new_css_filenames = defaultdict(list) 343 | 344 | # It's less trivial with CSS because we can't combine those that are 345 | # of different media 346 | media_regex = re.compile('media=["\']([^"\']+)["\']') 347 | for match in STYLES_REGEX.finditer(code): 348 | whole_tag = match.group() 349 | try: 350 | media_type = media_regex.findall(whole_tag)[0] 351 | except IndexError: 352 | media_type = '' 353 | 354 | for filename in match.groups(): 355 | new_css_filenames[media_type].append(filename) 356 | code = code.replace(whole_tag, '') 357 | 358 | # Now, we need to combine these files into one 359 | new_css_filenames_combined = {} 360 | if new_css_filenames: 361 | for media_type, filenames in new_css_filenames.items(): 362 | r = _static_file(filenames, 363 | optimize_if_possible=self.optimize_if_possible, 364 | symlink_if_possible=self.symlink_if_possible) 365 | new_css_filenames_combined[media_type] = r 366 | 367 | 368 | if new_js_filename: 369 | # Now is the time to apply the name prefix if there is one 370 | if async_defer: 371 | new_tag = ('' % 372 | (async_defer.group(0), new_js_filename)) 373 | else: 374 | new_tag = '' % new_js_filename 375 | code = "%s%s" % (new_tag, code) 376 | 377 | for media_type, new_css_filename in new_css_filenames_combined.items(): 378 | extra_params = '' 379 | if media_type: 380 | extra_params += ' media="%s"' % media_type 381 | new_tag = '' % \ 382 | (extra_params, new_css_filename) 383 | code = "%s%s" % (new_tag, code) 384 | 385 | return code 386 | 387 | REFERRED_CSS_URLS_REGEX = re.compile('''url\(((?!["']?data:)[^\)]+)\)''') 388 | REFERRED_CSS_URLLESS_IMPORTS_REGEX = re.compile('@import\s+[\'"]([^\'"]+)[\'"]') 389 | 390 | def _static_file(filename, 391 | optimize_if_possible=False, 392 | symlink_if_possible=False, 393 | warn_no_file=True): 394 | """ 395 | """ 396 | if not settings.DJANGO_STATIC: 397 | return file_proxy(filename, disabled=True) 398 | 399 | def wrap_up(filename): 400 | if settings.DJANGO_STATIC_MEDIA_URL_ALWAYS: 401 | return settings.DJANGO_STATIC_MEDIA_URL + filename 402 | elif settings.DJANGO_STATIC_MEDIA_URL: 403 | return settings.DJANGO_STATIC_MEDIA_URL + filename 404 | return filename 405 | 406 | is_combined_files = isinstance(filename, list) 407 | if is_combined_files and len(filename) == 1: 408 | # e.g. passed a list of files but only one so treat it like a 409 | # single file 410 | filename = filename[0] 411 | is_combined_files = False 412 | 413 | if is_combined_files: 414 | map_key = ';'.join(filename) 415 | else: 416 | map_key = filename 417 | 418 | if settings.DJANGO_STATIC_USE_MANIFEST_FILE: 419 | new_filename, m_time = _get(_MANIFEST_PATH, map_key) 420 | else: 421 | new_filename, m_time = _FILE_MAP.get(map_key, (None, None)) 422 | 423 | 424 | # we might already have done a conversion but the question is 425 | # if the file has changed. This we only want 426 | # to bother with when in DEBUG mode because it adds one more 427 | # unnecessary operation. 428 | if new_filename: 429 | if settings.DEBUG: 430 | # need to check if the original has changed 431 | old_new_filename = new_filename 432 | new_filename = None 433 | else: 434 | # This is really fast and only happens when NOT in DEBUG mode 435 | # since it doesn't do any comparison 436 | return file_proxy(wrap_up(new_filename), **fp_default_kwargs) 437 | else: 438 | # This is important so that we can know that there wasn't an 439 | # old file which will help us know we don't need to delete 440 | # the old one 441 | old_new_filename = None 442 | 443 | 444 | if not new_filename: 445 | if is_combined_files: 446 | # It's a list! We have to combine it into one file 447 | new_file_content = StringIO() 448 | each_m_times = [] 449 | extension = None 450 | for each in filename: 451 | filepath, path = _find_filepath_in_roots(each) 452 | if not filepath: 453 | raise OSError("Failed to find %s in %s" % (each, 454 | ",".join(settings.DJANGO_STATIC_MEDIA_ROOTS))) 455 | 456 | if extension: 457 | if os.path.splitext(filepath)[1] != extension: 458 | raise ValueError("Mismatching file extension in combo %r" % \ 459 | each) 460 | else: 461 | extension = os.path.splitext(filepath)[1] 462 | each_m_times.append(os.stat(filepath)[stat.ST_MTIME]) 463 | new_file_content.write(open(filepath, 'r').read().strip()) 464 | new_file_content.write('\n') 465 | 466 | filename = _combine_filenames(filename, settings.DJANGO_STATIC_NAME_MAX_LENGTH) 467 | # Set the root path of the combined files to the first entry 468 | # in the MEDIA_ROOTS list. This way django-static behaves a 469 | # little more predictible. 470 | path = settings.DJANGO_STATIC_MEDIA_ROOTS[0] 471 | new_m_time = max(each_m_times) 472 | 473 | else: 474 | filepath, path = _find_filepath_in_roots(filename) 475 | if not filepath: 476 | if warn_no_file: 477 | msg = "Can't find file %s in %s" % \ 478 | (filename, ",".join(settings.DJANGO_STATIC_MEDIA_ROOTS)) 479 | warnings.warn(msg) 480 | return file_proxy(wrap_up(filename), 481 | **dict(fp_default_kwargs, 482 | filepath=filepath, 483 | notfound=True)) 484 | 485 | new_m_time = os.stat(filepath)[stat.ST_MTIME] 486 | 487 | if m_time: 488 | # we had the filename in the map 489 | if m_time != new_m_time: 490 | # ...but it has changed! 491 | m_time = None 492 | else: 493 | # ...and it hasn't changed! 494 | return file_proxy(wrap_up(old_new_filename)) 495 | 496 | if not m_time: 497 | # We did not have the filename in the map OR it has changed 498 | apart = os.path.splitext(filename) 499 | new_filename = _generate_filename(apart, new_m_time) 500 | fileinfo = (settings.DJANGO_STATIC_NAME_PREFIX + new_filename, 501 | new_m_time) 502 | 503 | 504 | if settings.DJANGO_STATIC_USE_MANIFEST_FILE: 505 | _set(_MANIFEST_PATH, map_key, fileinfo) 506 | else: 507 | _FILE_MAP[map_key] = fileinfo 508 | 509 | 510 | if old_new_filename: 511 | old_new_filename = old_new_filename.replace( 512 | settings.DJANGO_STATIC_NAME_PREFIX, '') 513 | old_new_filepath = _filename2filepath(old_new_filename, 514 | settings.DJANGO_STATIC_SAVE_PREFIX or path) 515 | if not os.path.isdir(os.path.dirname(old_new_filepath)): 516 | _mkdir(os.path.dirname(old_new_filepath)) 517 | 518 | if os.path.isfile(old_new_filepath): 519 | os.remove(old_new_filepath) 520 | new_filepath = _filename2filepath(new_filename, 521 | settings.DJANGO_STATIC_SAVE_PREFIX or path) 522 | 523 | if not os.path.isdir(os.path.dirname(new_filepath)): 524 | _mkdir(os.path.dirname(new_filepath)) 525 | 526 | 527 | # Files are either slimmered or symlinked or just copied. Basically, only 528 | # .css and .js can be slimmered but not all are. For example, an already 529 | # minified say jquery.min.js doesn't need to be slimmered nor does it need 530 | # to be copied. 531 | # If you're on windows, it will always have to do a copy. 532 | # When symlinking, what the achievement is is that it gives the file a 533 | # unique and different name than the original. 534 | # 535 | # The caller of this method is responsible for dictacting if we're should 536 | # slimmer and if we can symlink. 537 | if optimize_if_possible: 538 | # Then we expect to be able to modify the content and we will 539 | # definitely need to write a new file. 540 | if is_combined_files: 541 | content = new_file_content.getvalue().decode('utf-8') 542 | else: 543 | #content = open(filepath).read() 544 | content = codecs.open(filepath, 'r', 'utf-8').read() 545 | if new_filename.endswith('.js') and has_optimizer(JS): 546 | content = optimize(content, JS) 547 | elif new_filename.endswith('.css') and has_optimizer(CSS): 548 | content = optimize(content, CSS) 549 | 550 | # and _static_file() all images refered in the CSS file itself 551 | def replacer(match): 552 | this_filename = match.groups()[0] 553 | 554 | if (this_filename.startswith('"') and this_filename.endswith('"')) or \ 555 | (this_filename.startswith("'") and this_filename.endswith("'")): 556 | this_filename = this_filename[1:-1] 557 | # It's really quite common that the CSS file refers to the file 558 | # that doesn't exist because if you refer to an image in CSS for 559 | # a selector you never use you simply don't suffer. 560 | # That's why we say not to warn on nonexisting files 561 | 562 | replace_with = this_filename 563 | 564 | if not (this_filename.startswith('/') or \ 565 | (this_filename.startswith('http') and '://' in this_filename)): 566 | # if the referenced filename is something like 567 | # 'images/foo.jpg' or 'sub/module.css' then we need to copy the 568 | # current relative directory 569 | replace_with = this_filename 570 | this_filename = os.path.join(os.path.dirname(filename), this_filename) 571 | optimize_again = optimize_if_possible and \ 572 | this_filename.lower().endswith('.css') or False 573 | new_filename = _static_file(this_filename, 574 | symlink_if_possible=symlink_if_possible, 575 | optimize_if_possible=optimize_again, 576 | warn_no_file=settings.DEBUG and True or False) 577 | return match.group().replace(replace_with, new_filename) 578 | 579 | content = REFERRED_CSS_URLS_REGEX.sub(replacer, content) 580 | content = REFERRED_CSS_URLLESS_IMPORTS_REGEX.sub(replacer, content) 581 | 582 | elif slimmer or cssmin: 583 | raise ValueError( 584 | "Unable to slimmer file %s. Unrecognized extension" % new_filename) 585 | #print "** STORING:", new_filepath 586 | codecs.open(new_filepath, 'w', 'utf-8').write(content) 587 | elif symlink_if_possible and not is_combined_files: 588 | #print "** SYMLINK:", filepath, '-->', new_filepath 589 | 590 | # The reason we have to do this strange while loop is that it can 591 | # happen that in between the time it takes to destroy symlink till you 592 | # can create it, another thread or process might be trying to do the 593 | # exact same thing with just a fraction of a second difference, thus 594 | # making it possible to, at the time of creating the symlink, that it's 595 | # already there which will raise an OSError. 596 | # 597 | # This is quite possible when Django for example starts multiple fcgi 598 | # threads roughly all at the same time. An alternative approach would 599 | # be to store the global variable _FILE_MAP in a cache or something 600 | # which would effectively make it thread safe but that has the annoying 601 | # disadvantage that it remains in the cache between server restarts and 602 | # for a production environment, server restarts very often happen 603 | # because you have upgraded the code (and the static files). So, an 604 | # alternative is to use a cache so that thread number 2, number 3 etc 605 | # gets the file mappings of the first thread and then let this cache 606 | # only last for a brief amount of time. That amount of time would 607 | # basically be equivalent of the time the sys admin or developer would 608 | # have to wait between new code deployment and refreshed symlinks for 609 | # the static files. That feels convoluted and complex so I've decided 610 | # to instead use this rather obtuse while loop which is basically built 611 | # to try X number of times. If it still fails after X number of attempts 612 | # there's something else wrong with the IO which needs to bubble up. 613 | _max_attempts = 10 614 | while True: 615 | try: 616 | if os.path.lexists(new_filepath): 617 | # since in the other cases we write a new file, it doesn't matter 618 | # that the file existed before. 619 | # That's not the case with symlinks 620 | os.unlink(new_filepath) 621 | 622 | os.symlink(filepath, new_filepath) 623 | break 624 | except OSError: 625 | _max_attempts -= 1 626 | if _max_attempts <= 0: 627 | raise 628 | elif is_combined_files: 629 | #print "** STORING COMBO:", new_filepath 630 | open(new_filepath, 'w').write(new_file_content.getvalue()) 631 | else: 632 | # straight copy 633 | #print "** STORING COPY:", new_filepath 634 | shutil.copyfile(filepath, new_filepath) 635 | 636 | return file_proxy(wrap_up(settings.DJANGO_STATIC_NAME_PREFIX + new_filename), 637 | **dict(fp_default_kwargs, new=True, 638 | filepath=new_filepath, checked=True)) 639 | 640 | 641 | def _mkdir(newdir): 642 | """works the way a good mkdir should :) 643 | - already exists, silently complete 644 | - regular file in the way, raise an exception 645 | - parent directory(ies) does not exist, make them as well 646 | """ 647 | if os.path.isdir(newdir): 648 | pass 649 | elif os.path.isfile(newdir): 650 | raise OSError("a file with the same name as the desired " \ 651 | "dir, '%s', already exists." % newdir) 652 | else: 653 | head, tail = os.path.split(newdir) 654 | if head and not os.path.isdir(head): 655 | _mkdir(head) 656 | if tail: 657 | os.mkdir(newdir) 658 | 659 | 660 | def _find_filepath_in_roots(filename): 661 | """Look for filename in all MEDIA_ROOTS, and return the first one found.""" 662 | for root in settings.DJANGO_STATIC_MEDIA_ROOTS: 663 | filepath = _filename2filepath(filename, root) 664 | if os.path.isfile(filepath): 665 | return filepath, root 666 | # havent found it in DJANGO_STATIC_MEDIA_ROOTS look for apps' files if we're 667 | # in DEBUG mode 668 | if settings.DEBUG: 669 | try: 670 | from django.contrib.staticfiles import finders 671 | absolute_path = finders.find(filename) 672 | if absolute_path: 673 | root, filepath = os.path.split(absolute_path) 674 | return absolute_path, root 675 | except ImportError: 676 | pass 677 | return None, None 678 | 679 | def _filename2filepath(filename, media_root): 680 | # The reason we're doing this is because the templates will 681 | # look something like this: 682 | # src="{{ MEDIA_URL }}/css/foo.css" 683 | # and if (and often happens in dev mode) MEDIA_URL will 684 | # just be '' 685 | 686 | if filename.startswith('/'): 687 | path = os.path.join(media_root, filename[1:]) 688 | else: 689 | path = os.path.join(media_root, filename) 690 | 691 | return path 692 | 693 | 694 | 695 | def default_combine_filenames_generator(filenames, max_length=40): 696 | """Return a new filename to use as the combined file name for a 697 | bunch of files. 698 | A precondition is that they all have the same file extension 699 | 700 | Given that the list of files can have different paths, we aim to use the 701 | most common path. 702 | 703 | Example: 704 | /somewhere/else/foo.js 705 | /somewhere/bar.js 706 | /somewhere/different/too/foobar.js 707 | The result will be 708 | /somewhere/foo_bar_foobar.js 709 | 710 | Another thing to note, if the filenames have timestamps in them, combine 711 | them all and use the highest timestamp. 712 | 713 | """ 714 | path = None 715 | names = [] 716 | extension = None 717 | timestamps = [] 718 | for filename in filenames: 719 | name = os.path.basename(filename) 720 | if not extension: 721 | extension = os.path.splitext(name)[1] 722 | elif os.path.splitext(name)[1] != extension: 723 | raise ValueError("Can't combine multiple file extensions") 724 | 725 | for each in re.finditer('\.\d{10}\.', name): 726 | timestamps.append(int(each.group().replace('.',''))) 727 | name = name.replace(each.group(), '.') 728 | name = os.path.splitext(name)[0] 729 | names.append(name) 730 | 731 | if path is None: 732 | path = os.path.dirname(filename) 733 | else: 734 | if len(os.path.dirname(filename)) < len(path): 735 | path = os.path.dirname(filename) 736 | 737 | 738 | new_filename = '_'.join(names) 739 | if timestamps: 740 | new_filename += ".%s" % max(timestamps) 741 | 742 | new_filename = new_filename[:max_length] 743 | new_filename += extension 744 | 745 | return os.path.join(path, new_filename) 746 | 747 | 748 | def _load_combine_filenames_generator(): 749 | combine_filenames_generator = getattr(settings, 'DJANGO_STATIC_COMBINE_FILENAMES_GENERATOR', None) 750 | if combine_filenames_generator: 751 | from django.utils.importlib import import_module 752 | _module_name, _function_name = combine_filenames_generator.rsplit('.', 1) 753 | combine_filenames_generator_module = import_module(_module_name) 754 | return getattr(combine_filenames_generator_module, _function_name) 755 | return default_combine_filenames_generator 756 | 757 | _combine_filenames = _load_combine_filenames_generator() 758 | 759 | 760 | CSS = 'css' 761 | JS = 'js' 762 | 763 | def has_optimizer(type_): 764 | if type_ == CSS: 765 | if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): 766 | return True 767 | return slimmer is not None or cssmin is not None 768 | elif type_ == JS: 769 | if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): 770 | return True 771 | if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): 772 | return True 773 | if getattr(settings, 'DJANGO_STATIC_JSMIN', None): 774 | assert jsmin is not None, "jsmin not installed" 775 | return True 776 | return slimmer is not None or cssmin is not None 777 | else: 778 | raise ValueError("Invalid type %r" % type_) 779 | 780 | def optimize(content, type_): 781 | if type_ == CSS: 782 | if cssmin is not None: 783 | return _run_cssmin(content) 784 | elif getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): 785 | return _run_yui_compressor(content, type_) 786 | return slimmer.css_slimmer(content) 787 | elif type_ == JS: 788 | if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): 789 | return _run_closure_compiler(content) 790 | if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): 791 | return _run_yui_compressor(content, type_) 792 | if getattr(settings, 'DJANGO_STATIC_JSMIN', None): 793 | return _run_jsmin(content) 794 | return slimmer.js_slimmer(content) 795 | else: 796 | raise ValueError("Invalid type %r" % type_) 797 | 798 | CLOSURE_COMMAND_TEMPLATE = "java -jar %(jarfile)s" 799 | def _run_closure_compiler(jscode): 800 | cmd = CLOSURE_COMMAND_TEMPLATE % {'jarfile': settings.DJANGO_STATIC_CLOSURE_COMPILER} 801 | proc = Popen(cmd, shell=True, stdout=PIPE, stdin=PIPE, stderr=PIPE) 802 | try: 803 | (stdoutdata, stderrdata) = proc.communicate(jscode) 804 | if stderrdata: 805 | # Check if there are real errors. 806 | if re.search('[1-9]\d* error(s)', stderrdata) is None: 807 | # Suppress the loud stderr output of closure compiler. 808 | stderrdata = None 809 | except OSError, msg: # pragma: no cover 810 | # see comment on OSErrors inside _run_yui_compressor() 811 | stderrdata = \ 812 | "OSError: %s. Try again by making a small change and reload" % msg 813 | if stderrdata: 814 | return "/* ERRORS WHEN RUNNING CLOSURE COMPILER\n" + stderrdata + '\n*/\n' + jscode 815 | 816 | return stdoutdata 817 | 818 | YUI_COMMAND_TEMPLATE = "java -jar %(jarfile)s --type=%(type)s" 819 | def _run_yui_compressor(code, type_): 820 | cmd = YUI_COMMAND_TEMPLATE % \ 821 | {'jarfile': settings.DJANGO_STATIC_YUI_COMPRESSOR, 822 | 'type': type_} 823 | proc = Popen(cmd, shell=True, stdout=PIPE, stdin=PIPE, stderr=PIPE) 824 | try: 825 | (stdoutdata, stderrdata) = proc.communicate(code) 826 | except OSError, msg: # pragma: no cover 827 | # Sometimes, for unexplicable reasons, you get a Broken pipe when 828 | # running the popen instance. It's always non-deterministic problem 829 | # so it probably has something to do with concurrency or something 830 | # really low level. 831 | stderrdata = \ 832 | "OSError: %s. Try again by making a small change and reload" % msg 833 | 834 | if stderrdata: 835 | return "/* ERRORS WHEN RUNNING YUI COMPRESSOR\n" + stderrdata + '\n*/\n' + code 836 | 837 | return stdoutdata 838 | 839 | 840 | def _run_cssmin(code): 841 | output = cssmin.cssmin(code) 842 | return output 843 | 844 | def _run_jsmin(code): 845 | output = jsmin.jsmin(code) 846 | return output 847 | 848 | def _get(file, key): 849 | with _touchopen(file, "r") as f: 850 | previous_value = f.read() 851 | f.close() 852 | if not previous_value: 853 | data = json.loads('{}') 854 | else: 855 | data = json.loads(previous_value.decode('utf8')) 856 | return data.get(key, (None, None)) 857 | 858 | def _set(file, key, value): 859 | with _touchopen(file, "r+") as f: 860 | # Acquire a non-blocking exclusive lock 861 | fcntl.lockf(f, fcntl.LOCK_EX) 862 | 863 | # Read a previous value if present 864 | previous_value = f.read() 865 | if not previous_value: 866 | data = json.loads('{}') 867 | else: 868 | data = json.loads(previous_value.decode('utf8')) 869 | 870 | data[key] = value 871 | 872 | # Write the new value and truncate 873 | f.seek(0) 874 | f.write(json.dumps(data, indent=4).encode('utf8')) 875 | f.truncate() 876 | f.close() 877 | 878 | def _touchopen(filename, *args, **kwargs): 879 | fd = os.open(filename, os.O_RDWR | os.O_CREAT) 880 | 881 | return os.fdopen(fd, *args, **kwargs) 882 | -------------------------------------------------------------------------------- /django_static/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | 3 | import codecs 4 | import re 5 | import os 6 | import stat 7 | import sys 8 | import time 9 | from tempfile import mkdtemp, gettempdir 10 | from unittest import TestCase 11 | from shutil import rmtree 12 | import warnings 13 | 14 | import django_static.templatetags.django_static as _django_static 15 | from django_static.templatetags.django_static import _static_file, _combine_filenames 16 | 17 | def _slim_file(x, symlink_if_possible=False,): 18 | return _django_static._static_file(x, optimize_if_possible=True, 19 | symlink_if_possible=symlink_if_possible) 20 | 21 | try: 22 | import slimmer 23 | except ImportError: 24 | slimmer = None 25 | 26 | try: 27 | import cssmin 28 | except ImportError: 29 | cssmin = None 30 | 31 | if cssmin is None and slimmer is None: 32 | import warnings 33 | warnings.warn("Can't run tests that depend on slimmer/cssmin") 34 | 35 | 36 | from django.conf import settings 37 | from django.template import Template 38 | from django.template import Context 39 | import django.template 40 | 41 | ## Monkey patch the {% load ... %} tag to always reload our code 42 | ## so it can pick up any changes to "settings.py" that happens during 43 | ## unit tests 44 | #_original_get_library = django.template.get_library 45 | #def get_library_wrapper(library_name): 46 | # if library_name == "django_static": 47 | # reload(sys.modules['django_static.templatetags.django_static']) 48 | # return _original_get_library(library_name) 49 | #django.template.get_library = get_library_wrapper 50 | #reload(sys.modules['django.template.defaulttags']) 51 | 52 | _GIF_CONTENT = 'R0lGODlhBgAJAJEDAGmaywBUpv///////yH5BAEAAAMALAAAAAAGAAkAAAIRnBFwITEoGoyBRWnb\ns27rBRQAOw==\n' 53 | _GIF_CONTENT_DIFFERENT = 'R0lGODlhBAABAJEAANHV3ufr7qy9xGyiyCH5BAAAAAAALAAAAAAEAAEAAAIDnBAFADs=\n' 54 | 55 | #TEST_MEDIA_ROOT = os.path.join(gettempdir(), 'fake_media_root') 56 | #_original_MEDIA_ROOT = settings.MEDIA_ROOT 57 | #_MISSING = id(get_library_wrapper) # get semi-random mark 58 | _marker = object() 59 | _saved_settings = [] 60 | for name in [ "DEBUG", 61 | "DJANGO_STATIC", 62 | "DJANGO_STATIC_SAVE_PREFIX", 63 | "DJANGO_STATIC_NAME_PREFIX", 64 | "DJANGO_STATIC_MEDIA_URL", 65 | "DJANGO_STATIC_MEDIA_URL_ALWAYS", 66 | "DJANGO_STATIC_FILE_PROXY", 67 | "DJANGO_STATIC_USE_SYMLINK", 68 | "DJANGO_STATIC_CLOSURE_COMPILER", 69 | "DJANGO_STATIC_MEDIA_ROOTS", 70 | "DJANGO_STATIC_YUI_COMPRESSOR"]: 71 | _saved_settings.append((name, getattr(settings, name, _marker))) 72 | 73 | class TestDjangoStatic(TestCase): 74 | 75 | # NOTE! The reason we keep chaning names in the tests is because of the 76 | # global object _FILE_MAP in django_static.py (which is questionable) 77 | 78 | 79 | def _notice_file(self, filepath): 80 | assert os.path.isfile(filepath) 81 | self.__added_filepaths.append(filepath) 82 | 83 | def setUp(self): 84 | _django_static._FILE_MAP = {} 85 | self.__added_dirs = [] 86 | self.__added_filepaths = [] 87 | #if not os.path.isdir(TEST_MEDIA_ROOT): 88 | # os.mkdir(TEST_MEDIA_ROOT) 89 | 90 | # All tests is going to run off this temp directory 91 | settings.MEDIA_ROOT = self._mkdir() 92 | 93 | # Set all django-static settings to known values so it isn't 94 | # dependant on the real values in settings.py 95 | settings.DJANGO_STATIC = True 96 | settings.DJANGO_STATIC_SAVE_PREFIX = "" 97 | settings.DJANGO_STATIC_NAME_PREFIX = "" 98 | settings.DJANGO_STATIC_MEDIA_URL = "" 99 | settings.DJANGO_STATIC_MEDIA_URL_ALWAYS = False 100 | settings.DJANGO_STATIC_USE_SYMLINK = True 101 | settings.DJANGO_STATIC_FILE_PROXY = None 102 | settings.DJANGO_STATIC_CLOSURE_COMPILER = None 103 | settings.DJANGO_STATIC_YUI_COMPRESSOR = None 104 | #if hasattr(settings, "DJANGO_STATIC_MEDIA_ROOTS"): 105 | # del settings.DJANGO_STATIC_MEDIA_ROOTS 106 | settings.DJANGO_STATIC_MEDIA_ROOTS = [settings.MEDIA_ROOT] 107 | 108 | super(TestDjangoStatic, self).setUp() 109 | 110 | def _mkdir(self): 111 | dir = mkdtemp() 112 | self.__added_dirs.append(dir) 113 | return dir 114 | 115 | def tearDown(self): 116 | for filepath in self.__added_filepaths: 117 | if os.path.isfile(filepath): 118 | os.remove(filepath) 119 | 120 | # restore things for other potential tests 121 | for name, value in _saved_settings: 122 | if value == _marker and hasattr(settings, name): 123 | delattr(settings, name) 124 | else: 125 | setattr(settings, name, value) 126 | 127 | for dir in self.__added_dirs: 128 | if dir.startswith(gettempdir()): 129 | rmtree(dir) 130 | 131 | super(TestDjangoStatic, self).tearDown() 132 | 133 | 134 | 135 | 136 | def test__combine_filenames(self): 137 | """test the private function _combine_filenames()""" 138 | 139 | filenames = ['/somewhere/else/foo.js', 140 | '/somewhere/bar.js', 141 | '/somewhere/different/too/foobar.js'] 142 | expect = '/somewhere/foo_bar_foobar.js' 143 | 144 | self.assertEqual(_django_static._combine_filenames(filenames), expect) 145 | 146 | filenames = ['/foo.1243892792.js', 147 | '/bar.1243893111.js', 148 | '/foobar.js'] 149 | expect = '/foo_bar_foobar.1243893111.js' 150 | self.assertEqual(_django_static._combine_filenames(filenames), expect) 151 | 152 | 153 | def test__combine_long_filenames(self): 154 | """test the private function _combine_filenames()""" 155 | 156 | filenames = ['/jquery_something_%s.js' % x 157 | for x in range(10)] 158 | expect = '/jquery_something_0_jquery_something_1_jq.js' 159 | 160 | self.assertEqual(_django_static._combine_filenames(filenames), expect) 161 | 162 | 163 | def test_staticfile_django_static_off(self): 164 | """You put this in the template: 165 | {% staticfile "/js/foo.js" %} 166 | but then you disable DJANGO_STATIC so you should get 167 | /js/foo.js 168 | """ 169 | settings.DEBUG = True 170 | settings.DJANGO_STATIC = False 171 | 172 | filename = "/foo.js" 173 | test_filepath = settings.MEDIA_ROOT + filename 174 | open(test_filepath, 'w').write('samplecode()\n') 175 | 176 | media_root_files_before = os.listdir(settings.MEDIA_ROOT) 177 | template_as_string = """{% load django_static %} 178 | {% staticfile "/foo.js" %} 179 | """ 180 | template = Template(template_as_string) 181 | context = Context() 182 | rendered = template.render(context).strip() 183 | self.assertEqual(rendered, u"/foo.js") 184 | 185 | 186 | def test_staticall_django_static_off(self): 187 | """You put this in the template: 188 | {% staticall %} 189 | 190 | {% endstaticall %} 191 | but then you disable DJANGO_STATIC so you should get 192 | /js/foo.js 193 | """ 194 | settings.DEBUG = True 195 | settings.DJANGO_STATIC = False 196 | 197 | filename = "/foo.js" 198 | test_filepath = settings.MEDIA_ROOT + filename 199 | open(test_filepath, 'w').write('samplecode()\n') 200 | 201 | media_root_files_before = os.listdir(settings.MEDIA_ROOT) 202 | template_as_string = """{% load django_static %} 203 | {% staticall %} 204 | 205 | {% endstaticall %} 206 | """ 207 | template = Template(template_as_string) 208 | context = Context() 209 | rendered = template.render(context).strip() 210 | self.assertEqual(rendered, u'') 211 | 212 | def test_staticall_with_already_minified_files(self): 213 | """You put this in the template: 214 | {% staticall %} 215 | 216 | {% endstaticall %} 217 | but then you disable DJANGO_STATIC so you should get 218 | /js/foo.js 219 | """ 220 | if slimmer is None and cssmin is None: 221 | return 222 | 223 | settings.DEBUG = False 224 | settings.DJANGO_STATIC = True 225 | 226 | filename = "/foo.js" 227 | test_filepath = settings.MEDIA_ROOT + filename 228 | open(test_filepath, 'w').write(""" 229 | function (var) { return var++; } 230 | """) 231 | 232 | filename = "/jquery.min.js" 233 | test_filepath = settings.MEDIA_ROOT + filename 234 | open(test_filepath, 'w').write(""" 235 | function jQuery() { return ; } 236 | """) 237 | 238 | media_root_files_before = os.listdir(settings.MEDIA_ROOT) 239 | template_as_string = """{% load django_static %} 240 | {% slimall %} 241 | 242 | 243 | {% endslimall %} 244 | """ 245 | template = Template(template_as_string) 246 | context = Context() 247 | rendered = template.render(context).strip() 248 | regex = re.compile('/foo_jquery\.min\.\d+\.js') 249 | self.assertTrue(regex.findall(rendered)) 250 | self.assertEqual(rendered.count(''), 1) 252 | new_filename = regex.findall(rendered)[0] 253 | 254 | # check that the parts of the content was slimmed 255 | self.assertTrue(os.path.basename(new_filename) in \ 256 | os.listdir(settings.MEDIA_ROOT)) 257 | 258 | def test_staticall_with_image_tags(self): 259 | """test when there are image tags in a block of staticall like this: 260 | 261 | {% staticall %} 262 | 263 | 264 | {% endstaticall %} 265 | 266 | And you should expect something like this: 267 | 268 | 269 | 270 | 271 | """ 272 | 273 | open(settings.MEDIA_ROOT + '/img100.gif', 'w').write(_GIF_CONTENT) 274 | open(settings.MEDIA_ROOT + '/img200.gif', 'w').write(_GIF_CONTENT) 275 | 276 | template_as_string = """{% load django_static %} 277 | {% staticall %} 278 | 279 | 280 | {% endstaticall %} 281 | """ 282 | template = Template(template_as_string) 283 | context = Context() 284 | rendered = template.render(context).strip() 285 | self.assertTrue(re.findall('img100\.\d+\.gif', rendered)) 286 | self.assertTrue(re.findall('img200\.\d+\.gif', rendered)) 287 | 288 | def _assertProcessedFileExists(self, dir, org_name): 289 | head, tail = os.path.splitext(org_name) 290 | filename_re = re.compile(r"^%s\.\d+\%s$" % (head, tail)) 291 | files = os.listdir(dir) 292 | match = [ f for f in files if filename_re.match(f) ] 293 | self.assertEqual(len(match), 1) 294 | 295 | def test_static_in_multiple_media_roots(self): 296 | """Test that static files in multiple media roots works. 297 | 298 | {% staticall %} 299 | 300 | 301 | {% endstaticall %} 302 | 303 | The source files are stored in different MEDIA_ROOTs. 304 | First, run without DJANGO_STATIC_SAVE_PREFIX set and verify 305 | that the files are stored in the source directories. 306 | Then, set DJANGO_STATIC_SAVE_PREFIX and verify that the 307 | processed files are stored there. 308 | """ 309 | 310 | dir1 = settings.MEDIA_ROOT 311 | dir2 = self._mkdir() 312 | settings.DJANGO_STATIC_MEDIA_ROOTS = [ dir1, dir2 ] 313 | dir3 = self._mkdir() 314 | 315 | open(dir1 + '/img100.gif', 'w').write(_GIF_CONTENT) 316 | open(dir2 + '/img200.gif', 'w').write(_GIF_CONTENT) 317 | 318 | template_as_string = """{% load django_static %} 319 | {% staticall %} 320 | 321 | 322 | {% endstaticall %} 323 | """ 324 | template = Template(template_as_string) 325 | context = Context() 326 | template.render(context).strip() 327 | self._assertProcessedFileExists(dir1, "img100.gif") 328 | self._assertProcessedFileExists(dir2, "img200.gif") 329 | 330 | 331 | def test_static_in_multiple_media_roots_with_save_prefix(self): 332 | """same as test_static_in_multiple_media_roots() but with a save 333 | prefix. """ 334 | 335 | dir1 = settings.MEDIA_ROOT 336 | dir2 = self._mkdir() 337 | settings.DJANGO_STATIC_MEDIA_ROOTS = [ dir1, dir2 ] 338 | dir3 = self._mkdir() 339 | settings.DJANGO_STATIC_SAVE_PREFIX = dir3 340 | 341 | open(dir1 + '/img100.gif', 'w').write(_GIF_CONTENT) 342 | open(dir2 + '/img200.gif', 'w').write(_GIF_CONTENT) 343 | 344 | template_as_string = """{% load django_static %} 345 | {% staticall %} 346 | 347 | 348 | {% endstaticall %} 349 | """ 350 | template = Template(template_as_string) 351 | context = Context() 352 | template.render(context).strip() 353 | self._assertProcessedFileExists(dir3, "img100.gif") 354 | self._assertProcessedFileExists(dir3, "img200.gif") 355 | 356 | 357 | def test_combined_files_in_multiple_media_roots(self): 358 | """Test that static files in multiple media roots works. 359 | 360 | {% staticall %} 361 | 362 | 363 | {% endstaticall %} 364 | 365 | The source files are stored in different MEDIA_ROOTs. 366 | First, run without DJANGO_STATIC_SAVE_PREFIX set and verify 367 | that the files are stored in the source directories. 368 | Then, set DJANGO_STATIC_SAVE_PREFIX and verify that the 369 | processed files are stored there. 370 | """ 371 | dir1 = settings.MEDIA_ROOT 372 | dir2 = self._mkdir() 373 | settings.DJANGO_STATIC_MEDIA_ROOTS = [dir1, dir2] 374 | 375 | open(dir1 + '/test_A.js', 'w').write("var A=1;") 376 | open(dir2 + '/test_B.js', 'w').write("var B=1;") 377 | 378 | template_as_string = """{% load django_static %} 379 | {% slimfile "/test_A.js;/test_B.js" %} 380 | """ 381 | template = Template(template_as_string) 382 | context = Context() 383 | template.render(context).strip() 384 | # The result will always be saved in the first dir in 385 | # MEDIA_ROOTS unless DJANGO_STATIC_SAVE_PREFIX is set 386 | self._assertProcessedFileExists(dir1, "test_A_test_B.js") 387 | 388 | def test_combined_files_in_multiple_media_roots_with_save_prefix(self): 389 | """copy of test_combined_files_in_multiple_media_roots() but this time 390 | with a save prefix""" 391 | dir1 = settings.MEDIA_ROOT 392 | dir2 = self._mkdir() 393 | settings.DJANGO_STATIC_MEDIA_ROOTS = [dir1, dir2] 394 | 395 | open(dir1 + '/test_A.js', 'w').write("var A=1;") 396 | open(dir2 + '/test_B.js', 'w').write("var B=1;") 397 | 398 | template_as_string = """{% load django_static %} 399 | {% slimfile "/test_A.js;/test_B.js" %} 400 | """ 401 | dir3 = self._mkdir() 402 | settings.DJANGO_STATIC_SAVE_PREFIX = dir3 403 | 404 | template = Template(template_as_string) 405 | context = Context() 406 | template.render(context).strip() 407 | self._assertProcessedFileExists(dir3, "test_A_test_B.js") 408 | 409 | 410 | def test_staticfile_single_debug_on(self): 411 | """Most basic test 412 | {% staticfile "/js/jquery-1.9.9.min.js" %} 413 | it should become 414 | /js/jquery-1.9.9.min.1257xxxxxx.js 415 | and unlike slimfile() this file should either be a symlink or 416 | a copy that hasn't changed. 417 | """ 418 | settings.DEBUG = True 419 | settings.DJANGO_STATIC = True 420 | 421 | self._test_staticfile_single('/jquery.min.js', 422 | 'function () { return 1; }') 423 | 424 | 425 | def test_staticfile_single_debug_off(self): 426 | """Most basic test 427 | {% staticfile "/js/jquery-1.9.9.min.js" %} 428 | it should become 429 | /js/jquery-1.9.9.min.1257xxxxxx.js 430 | and unlike slimfile() this file should either be a symlink or 431 | a copy that hasn't changed. 432 | """ 433 | settings.DEBUG = False 434 | settings.DJANGO_STATIC = True 435 | 436 | self._test_staticfile_single('/jquery-2.min.js', 437 | 'function () { return 1; }') 438 | 439 | def test_staticfile_single_debug_off_with_media_url(self): 440 | """Most basic test 441 | {% staticfile "/js/jquery-1.9.9.min.js" %} 442 | it should become 443 | http://static.example.com/js/jquery-1.9.9.min.1257xxxxxx.js 444 | and unlike slimfile() this file should either be a symlink or 445 | a copy that hasn't changed. 446 | """ 447 | settings.DEBUG = False 448 | settings.DJANGO_STATIC = True 449 | settings.DJANGO_STATIC_MEDIA_URL = media_url = 'http://static.example.com' 450 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 451 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'special') 452 | 453 | self._test_staticfile_single('/jquery-3.min.js', 454 | 'function () { return 1; }', 455 | media_url=media_url, 456 | name_prefix='/infinity', 457 | save_prefix='special') 458 | 459 | def test_staticfile_single_debug_off_with_name_and_save_prefix_with_media_url(self): 460 | """Most basic test 461 | {% staticfile "/js/jquery-1.9.9.min.js" %} 462 | it should become 463 | http://static.example.com/js/jquery-1.9.9.min.1257xxxxxx.js 464 | and unlike slimfile() this file should either be a symlink or 465 | a copy that hasn't changed. 466 | """ 467 | settings.DEBUG = False 468 | settings.DJANGO_STATIC = True 469 | settings.DJANGO_STATIC_MEDIA_URL = media_url = 'http://static.example.com' 470 | 471 | self._test_staticfile_single('/jquery-4.min.js', 472 | 'function () { return 1; }', 473 | media_url=media_url) 474 | 475 | def assertFilenamesAlmostEqual(self, name1, name2): 476 | # Occasionally we get a failure because the clock ticked to 477 | # the next second after the file was rendered. 478 | # Thanks https://github.com/slinkp for this contribution! 479 | 480 | name1, name2 = name1.strip(), name2.strip() 481 | timestamp_re = re.compile(r'[^\.]\.([0-9]+)\..*') 482 | t1 = int(timestamp_re.search(name1).group(1)) 483 | t2 = int(timestamp_re.search(name2).group(1)) 484 | self.assert_(t1 - t2 in (-1, 0, 1), 485 | "Filenames %r and %r timestamps differ by more than 1" % (name1, name2)) 486 | 487 | 488 | def _test_staticfile_single(self, filename, code, name_prefix='', save_prefix='', 489 | media_url=''): 490 | 491 | test_filepath = settings.MEDIA_ROOT + filename 492 | open(test_filepath, 'w').write(code + '\n') 493 | 494 | media_root_files_before = os.listdir(settings.MEDIA_ROOT) 495 | 496 | template_as_string = '{% load django_static %}\n' 497 | template_as_string += '{% staticfile "' + filename + '" %}' 498 | template = Template(template_as_string) 499 | context = Context() 500 | rendered = template.render(context).strip() 501 | bits = filename.rsplit('.', 1) 502 | now = int(time.time()) 503 | new_filename = bits[0] + '.%s.' % now + bits[-1] 504 | self.assertFilenamesAlmostEqual(rendered, media_url + name_prefix + new_filename) 505 | 506 | if save_prefix: 507 | save_dir = os.path.join(os.path.join(settings.MEDIA_ROOT, save_prefix)) 508 | if os.path.basename(new_filename) not in os.listdir(save_dir): 509 | # most likely because the clock ticked whilst running the test 510 | ts = re.findall('\d{2,10}', new_filename)[0] 511 | previous_second = int(ts) - 1 512 | other_new_filename = new_filename.replace(ts, str(previous_second)) 513 | self.assertTrue(os.path.basename(other_new_filename) in os.listdir(save_dir)) 514 | else: 515 | self.assertTrue(os.path.basename(new_filename) in os.listdir(save_dir)) 516 | # The content of the new file should be the same 517 | new_file = os.listdir(save_dir)[0] 518 | new_file_path = os.path.join(save_dir, new_file) 519 | if sys.platform != "win32": 520 | self.assertTrue(os.path.islink(new_file_path)) 521 | new_code = open(new_file_path).read() 522 | self.assertTrue(len(new_code.strip()) == len(code.strip())) 523 | else: 524 | media_root_files_after = os.listdir(settings.MEDIA_ROOT) 525 | # assuming the file isn't in a sub directory 526 | if len(new_filename.split('/')) <= 2: 527 | self.assertEqual(len(media_root_files_before) + 1, 528 | len(media_root_files_after)) 529 | 530 | # Content shouldn't change 531 | new_file = [x for x in media_root_files_after 532 | if x not in media_root_files_before][0] 533 | new_file_path = os.path.join(settings.MEDIA_ROOT, new_file) 534 | if sys.platform != "win32": 535 | self.assertTrue(os.path.islink(new_file_path)) 536 | new_code = open(new_file_path).read() 537 | self.assertEqual(len(new_code.strip()), len(code.strip())) 538 | 539 | # Run it again just to check that it still works 540 | rendered = template.render(context).strip() 541 | self.assertFilenamesAlmostEqual(rendered, media_url + name_prefix + new_filename) 542 | 543 | # pretend that time has passed and 10 seconds has lapsed then re-run the 544 | # rendering again and depending on settings.DEBUG this is noticed 545 | # or not. 546 | 547 | from posix import stat_result 548 | def fake_stat(arg): 549 | if arg == test_filepath: 550 | faked = list(orig_os_stat(arg)) 551 | faked[stat.ST_MTIME] = faked[stat.ST_MTIME] + 10 552 | return stat_result(faked) 553 | else: 554 | return orig_os_stat(arg) 555 | orig_os_stat = os.stat 556 | os.stat = fake_stat 557 | 558 | rendered = template.render(context).strip() 559 | if settings.DEBUG: 560 | new_filename = bits[0] + '.%s.' % (now + 10) + bits[1] 561 | self.assertFilenamesAlmostEqual(rendered, media_url + name_prefix + new_filename) 562 | 563 | if settings.DEBUG: 564 | 565 | # when time passes and a new file is created, it's important to test 566 | # that the previously created one is deleted 567 | if save_prefix: 568 | # If you use a save prefix, presumbly the directory where the 569 | # timestamped files are saved didn't exist before so we can 570 | # assume that the file existed before where none 571 | files_now = os.listdir(os.path.join(settings.MEDIA_ROOT, save_prefix)) 572 | self.assertEqual(len(files_now), 1) 573 | else: 574 | self.assertEqual(len(media_root_files_before) + 1, 575 | len(os.listdir(settings.MEDIA_ROOT))) 576 | self.assertNotEqual(sorted(media_root_files_after), 577 | sorted(os.listdir(settings.MEDIA_ROOT))) 578 | 579 | 580 | 581 | def test_slimfile_single_debug_on(self): 582 | """Most basic test 583 | {% slimfile "/js/foo.js" %} 584 | it should become: 585 | /js/foo.1257xxxxxxx.js 586 | """ 587 | settings.DEBUG = True 588 | settings.DJANGO_STATIC = True 589 | 590 | self._test_slimfile_single('/test.js', 591 | 'function () { return 1; }') 592 | 593 | def test_slimfile_single_debug_off(self): 594 | """Most basic test 595 | {% slimfile "/js/foo.js" %} 596 | it should become: 597 | /js/foo.1257xxxxxxx.js 598 | """ 599 | settings.DEBUG = False 600 | settings.DJANGO_STATIC = True 601 | 602 | self._test_slimfile_single('/testing.js', 603 | 'var a = function() { return ; }') 604 | 605 | def test_slimfile_single_debug_off_with_name_prefix(self): 606 | """Most basic test 607 | {% slimfile "/js/foo.js" %} 608 | it should become: 609 | /myprefix/js/foo.1257xxxxxxx.js 610 | """ 611 | settings.DEBUG = False 612 | settings.DJANGO_STATIC = True 613 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 614 | 615 | self._test_slimfile_single('/testing123.js', 616 | 'var a = function() { return ; }', 617 | name_prefix='/infinity') 618 | 619 | def test_slimfile_single_debug_on_with_name_prefix(self): 620 | """Most basic test 621 | {% slimfile "/js/foo.js" %} 622 | it should become: 623 | /myprefix/js/foo.1257xxxxxxx.js 624 | """ 625 | settings.DEBUG = True 626 | settings.DJANGO_STATIC = True 627 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 628 | 629 | self._test_slimfile_single('/testing321.js', 630 | 'var a = function() { return ; }', 631 | name_prefix='/infinity') 632 | 633 | 634 | def test_slimfile_single_debug_off_with_save_prefix(self): 635 | """Most basic test 636 | {% slimfile "/js/foo.js" %} 637 | it should become: 638 | /myprefix/js/foo.1257xxxxxxx.js 639 | """ 640 | settings.DEBUG = False 641 | settings.DJANGO_STATIC = True 642 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'special') 643 | 644 | self._test_slimfile_single('/testingXXX.js', 645 | 'var a = function() { return ; }', 646 | save_prefix='special') 647 | 648 | def test_slimfile_single_debug_on_with_save_prefix(self): 649 | """Most basic test 650 | {% slimfile "/js/foo.js" %} 651 | it should become: 652 | /myprefix/js/foo.1257xxxxxxx.js 653 | """ 654 | settings.DEBUG = True 655 | settings.DJANGO_STATIC = True 656 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'special') 657 | 658 | self._test_slimfile_single('/testingAAA.js', 659 | 'var a = function() { return ; }', 660 | save_prefix='special') 661 | 662 | 663 | def _test_slimfile_single(self, filename, code, name_prefix='', save_prefix=''): 664 | if slimmer is None and cssmin is None: 665 | return 666 | 667 | test_filepath = settings.MEDIA_ROOT + filename 668 | open(test_filepath, 'w').write(code + '\n') 669 | 670 | 671 | media_root_files_before = os.listdir(settings.MEDIA_ROOT) 672 | 673 | template_as_string = '{% load django_static %}\n' 674 | template_as_string += '{% slimfile "' + filename + '" %}' 675 | template = Template(template_as_string) 676 | context = Context() 677 | rendered = template.render(context).strip() 678 | bits = filename.split('.') 679 | now = int(time.time()) 680 | new_filename = bits[0] + '.%s.' % now + bits[1] 681 | self.assertFilenamesAlmostEqual(rendered, name_prefix + new_filename) 682 | 683 | if save_prefix: 684 | save_dir = os.path.join(os.path.join(settings.MEDIA_ROOT, save_prefix)) 685 | if os.path.basename(new_filename) not in os.listdir(save_dir): 686 | # Because the clock has ticked in the time this test was running 687 | # have to manually check some things. 688 | # This might be what you have on your hands: 689 | # new_filename 690 | # /testingXXX.1291842804.js 691 | # os.path.basename(new_filename) 692 | # testingXXX.1291842804.js 693 | # os.listdir(save_dir) 694 | # ['testingXXX.1291842803.js'] 695 | ts = re.findall('\d{2,10}', new_filename)[0] 696 | previous_second = int(ts) - 1 697 | other_new_filename = new_filename.replace(ts, str(previous_second)) 698 | self.assertTrue(os.path.basename(other_new_filename) in os.listdir(save_dir)) 699 | 700 | else: 701 | self.assertTrue(os.path.basename(new_filename) in os.listdir(save_dir)) 702 | # The content of the new file should be smaller 703 | 704 | new_file = os.path.join(save_dir, os.listdir(save_dir)[0]) 705 | new_code = open(new_file).read() 706 | self.assertTrue(len(new_code.strip()) < len(code.strip())) 707 | else: 708 | media_root_files_after = os.listdir(settings.MEDIA_ROOT) 709 | # assuming the file isn't in a sub directory 710 | if len(new_filename.split('/')) <= 2: 711 | self.assertEqual(len(media_root_files_before) + 1, 712 | len(media_root_files_after)) 713 | 714 | # The content of the newly saved file should have been whitespace 715 | # optimized so we can expect it to contain less bytes 716 | new_file = [x for x in media_root_files_after 717 | if x not in media_root_files_before][0] 718 | new_code = open(os.path.join(settings.MEDIA_ROOT, new_file)).read() 719 | self.assertTrue(len(new_code) < len(code)) 720 | 721 | # Run it again just to check that it still works 722 | rendered = template.render(context).strip() 723 | self.assertFilenamesAlmostEqual(rendered, name_prefix + new_filename) 724 | 725 | # pretend that time has passed and 10 seconds has lapsed then re-run the 726 | # rendering again and depending on settings.DEBUG this is noticed 727 | # or not. 728 | 729 | #time.sleep(1) 730 | from posix import stat_result 731 | def fake_stat(arg): 732 | if arg == test_filepath: 733 | faked = list(orig_os_stat(arg)) 734 | faked[stat.ST_MTIME] = faked[stat.ST_MTIME] + 10 735 | return stat_result(faked) 736 | else: 737 | return orig_os_stat(arg) 738 | orig_os_stat = os.stat 739 | os.stat = fake_stat 740 | 741 | rendered = template.render(context).strip() 742 | if settings.DEBUG: 743 | new_filename = bits[0] + '.%s.' % (now + 10) + bits[1] 744 | self.assertFilenamesAlmostEqual(rendered, name_prefix + new_filename) 745 | 746 | if settings.DEBUG: 747 | 748 | # when time passes and a new file is created, it's important to test 749 | # that the previously created one is deleted 750 | if save_prefix: 751 | # If you use a save prefix, presumbly the directory where the 752 | # timestamped files are saved didn't exist before so we can 753 | # assume that the file existed before where none 754 | files_now = os.listdir(os.path.join(settings.MEDIA_ROOT, save_prefix)) 755 | self.assertEqual(len(files_now), 1) 756 | else: 757 | self.assertEqual(len(media_root_files_before) + 1, 758 | len(os.listdir(settings.MEDIA_ROOT))) 759 | self.assertNotEqual(sorted(media_root_files_after), 760 | sorted(os.listdir(settings.MEDIA_ROOT))) 761 | 762 | def test_slimfile_multiple_debug_on(self): 763 | """Where there are multiple files instead if just one: 764 | {% slimfile "/js/foo.js; /js/bar.js" %} 765 | it should become: 766 | /js/foo_bar.1257xxxxx.js 767 | """ 768 | settings.DEBUG = True 769 | settings.DJANGO_STATIC = True 770 | #settings.DJANGO_STATIC_SAVE_PREFIX = TEST_SAVE_PREFIX 771 | #settings.DJANGO_STATIC_NAME_PREFIX = '' 772 | 773 | filenames = ('/test_1.js', '/test_2.js') 774 | codes = ('function (var1, var2) { return var1+var2; }', 775 | 'var xxxxx = "yyyy" ;') 776 | 777 | self._test_slimfile_multiple(filenames, codes) 778 | 779 | def test_slimfile_multiple_debug_off(self): 780 | """This is effectively the same as test_slimfile_multiple_debug_on() 781 | with the exception that this time with DEBUG=False which basically 782 | means that it assumes that the filename doesn't change if the 783 | filename is mapped at all. 784 | """ 785 | settings.DEBUG = False 786 | settings.DJANGO_STATIC = True 787 | #settings.DJANGO_STATIC_SAVE_PREFIX = TEST_SAVE_PREFIX 788 | #settings.DJANGO_STATIC_NAME_PREFIX = '' 789 | 790 | filenames = ('/test_A.js', '/test_B.js') 791 | codes = ('function (var1, var2) { return var1+var2; }', 792 | 'var xxxxx = "yyyy" ;') 793 | 794 | self._test_slimfile_multiple(filenames, codes) 795 | 796 | def test_slimfile_multiple_debug_on_with_name_prefix(self): 797 | """same as test_slimfile_multiple_debug_on() but this time with a 798 | name prefix. 799 | """ 800 | settings.DEBUG = True 801 | settings.DJANGO_STATIC = True 802 | #settings.DJANGO_STATIC_SAVE_PREFIX = TEST_SAVE_PREFIX 803 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 804 | 805 | filenames = ('/test_X.js', '/test_Y.js') 806 | codes = ('function (var1, var2) { return var1+var2; }', 807 | 'var xxxxx = "yyyy" ;') 808 | 809 | self._test_slimfile_multiple(filenames, codes, name_prefix='/infinity') 810 | 811 | def test_slimfile_multiple_debug_off_with_name_prefix(self): 812 | """same as test_slimfile_multiple_debug_on() but this time with a 813 | name prefix. 814 | """ 815 | settings.DEBUG = False 816 | settings.DJANGO_STATIC = True 817 | #settings.DJANGO_STATIC_SAVE_PREFIX = TEST_SAVE_PREFIX 818 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 819 | 820 | filenames = ('/test_P.js', '/test_Q.js') 821 | codes = ('function (var1, var2) { return var1+var2; }', 822 | 'var xxxxx = "yyyy" ;') 823 | 824 | self._test_slimfile_multiple(filenames, codes, name_prefix='/infinity') 825 | 826 | def test_slimfile_multiple_debug_on_with_save_prefix(self): 827 | """same as test_slimfile_multiple_debug_on() but this time with a 828 | name prefix. 829 | """ 830 | settings.DEBUG = True 831 | settings.DJANGO_STATIC = True 832 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'forever') 833 | 834 | filenames = ('/test_a.js', '/test_b.js') 835 | codes = ('function (var1, var2) { return var1+var2; }', 836 | 'var xxxxx = "yyyy" ;') 837 | 838 | self._test_slimfile_multiple(filenames, codes, save_prefix='forever') 839 | 840 | def test_slimfile_multiple_debug_on_with_name_and_save_prefix(self): 841 | """same as test_slimfile_multiple_debug_on() but this time with a 842 | name prefix. 843 | """ 844 | settings.DEBUG = True 845 | settings.DJANGO_STATIC = True 846 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'forever') 847 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 848 | 849 | filenames = ('/test_111.js', '/test_222.js') 850 | codes = ('function (var1, var2) { return var1+var2; }', 851 | 'var xxxxx = "yyyy" ;') 852 | 853 | self._test_slimfile_multiple(filenames, codes, 854 | name_prefix='/infinity', 855 | save_prefix='forever') 856 | 857 | 858 | def _test_slimfile_multiple(self, filenames, codes, name_prefix='', save_prefix=None): 859 | 860 | test_filepaths = [] 861 | for i, filename in enumerate(filenames): 862 | test_filepath = settings.MEDIA_ROOT + filename 863 | test_filepaths.append(test_filepath) 864 | open(test_filepath, 'w')\ 865 | .write(codes[i] + '\n') 866 | 867 | now = int(time.time()) 868 | 869 | template_as_string = '{% load django_static %}\n' 870 | template_as_string += '{% slimfile "' + '; '.join(filenames) + '" %}' 871 | # First do it without DJANGO_STATIC_MEDIA_URL set 872 | 873 | template = Template(template_as_string) 874 | context = Context() 875 | rendered = template.render(context).strip() 876 | expect_filename = _django_static._combine_filenames(filenames) 877 | bits = expect_filename.split('.') 878 | expect_filename = expect_filename[:-3] 879 | expect_filename += '.%s%s' % (now, os.path.splitext(filenames[0])[1]) 880 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 881 | 882 | if save_prefix: 883 | new_filenames_set = os.listdir(os.path.join(settings.MEDIA_ROOT, save_prefix)) 884 | self.assertEqual(len(new_filenames_set), 1) 885 | else: 886 | filenames_set = set(os.path.basename(x) for x in filenames) 887 | # what we expect in the media root is all the original 888 | # filenames plus the newly created one 889 | new_filenames_set = set(os.listdir(settings.MEDIA_ROOT)) 890 | self.assertEqual(new_filenames_set & filenames_set, filenames_set) 891 | self.assertEqual(len(filenames_set) + 1, len(new_filenames_set)) 892 | 893 | rendered = template.render(context).strip() 894 | 895 | template_as_string = '{% load django_static %}\n' 896 | template_as_string += '{% slimfile "' + '; '.join(filenames) + '" as new_src %}\n' 897 | template_as_string += '{{ new_src }}' 898 | 899 | template = Template(template_as_string) 900 | context = Context() 901 | rendered = template.render(context).strip() 902 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 903 | 904 | from posix import stat_result 905 | def fake_stat(arg): 906 | if arg in test_filepaths: 907 | faked = list(orig_os_stat(arg)) 908 | faked[stat.ST_MTIME] = faked[stat.ST_MTIME] + 10 909 | return stat_result(faked) 910 | else: 911 | return orig_os_stat(arg) 912 | orig_os_stat = os.stat 913 | os.stat = fake_stat 914 | 915 | rendered = template.render(context).strip() 916 | if settings.DEBUG: 917 | expect_filename = bits[0] + '.%s.' % (now + 10) + bits[-1] 918 | else: 919 | # then it shouldn't change. 920 | # This effectively means that if you have a live server, and you 921 | # make some changes to the, say, CSS files your Django templates 922 | # won't notice this until after you restart Django. 923 | pass 924 | 925 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 926 | 927 | 928 | def test_staticfile_multiple_debug_on(self): 929 | """Where there are multiple files instead if just one: 930 | {% slimfile "/js/foo.js; /js/bar.js" %} 931 | it should become: 932 | /js/foo_bar.1257xxxxx.js 933 | """ 934 | settings.DEBUG = True 935 | settings.DJANGO_STATIC = True 936 | 937 | filenames = ('/test_33.js', '/test_44.js') 938 | codes = ('function (var1, var2) { return var1+var2; }', 939 | 'var xxxxx = "yyyy" ;') 940 | 941 | self._test_staticfile_multiple(filenames, codes) 942 | 943 | 944 | def _test_staticfile_multiple(self, filenames, codes, name_prefix='', 945 | save_prefix=None): 946 | 947 | test_filepaths = [] 948 | for i, filename in enumerate(filenames): 949 | test_filepath = settings.MEDIA_ROOT + filename 950 | test_filepaths.append(test_filepath) 951 | open(test_filepath, 'w')\ 952 | .write(codes[i] + '\n') 953 | 954 | now = int(time.time()) 955 | 956 | template_as_string = '{% load django_static %}\n' 957 | template_as_string += '{% staticfile "' + '; '.join(filenames) + '" %}' 958 | 959 | template = Template(template_as_string) 960 | context = Context() 961 | rendered = template.render(context).strip() 962 | expect_filename = _django_static._combine_filenames(filenames) 963 | bits = expect_filename.split('.') 964 | expect_filename = expect_filename[:-3] 965 | expect_filename += '.%s%s' % (now, os.path.splitext(filenames[0])[1]) 966 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 967 | 968 | if save_prefix: 969 | new_filenames_set = os.listdir(os.path.join(settings.MEDIA_ROOT, save_prefix)) 970 | self.assertEqual(len(new_filenames_set), 1) 971 | else: 972 | filenames_set = set(os.path.basename(x) for x in filenames) 973 | # what we expect in the media root is all the original 974 | # filenames plus the newly created one 975 | new_filenames_set = set(os.listdir(settings.MEDIA_ROOT)) 976 | self.assertEqual(new_filenames_set & filenames_set, filenames_set) 977 | self.assertEqual(len(filenames_set) + 1, len(new_filenames_set)) 978 | 979 | new_file = [x for x in new_filenames_set 980 | if x not in filenames_set][0] 981 | new_file_path = os.path.join(settings.MEDIA_ROOT, new_file) 982 | 983 | # the file shouldn't become a symlink 984 | if sys.platform != "win32": 985 | self.assertTrue(os.path.lexists(new_file_path)) 986 | self.assertTrue(not os.path.islink(new_file_path)) 987 | 988 | rendered = template.render(context).strip() 989 | 990 | template_as_string = '{% load django_static %}\n' 991 | template_as_string += '{% staticfile "' + '; '.join(filenames) + '" as new_src %}\n' 992 | template_as_string += '{{ new_src }}' 993 | 994 | template = Template(template_as_string) 995 | context = Context() 996 | rendered = template.render(context).strip() 997 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 998 | 999 | from posix import stat_result 1000 | def fake_stat(arg): 1001 | if arg in test_filepaths: 1002 | faked = list(orig_os_stat(arg)) 1003 | faked[stat.ST_MTIME] = faked[stat.ST_MTIME] + 10 1004 | return stat_result(faked) 1005 | else: 1006 | return orig_os_stat(arg) 1007 | orig_os_stat = os.stat 1008 | os.stat = fake_stat 1009 | 1010 | rendered = template.render(context).strip() 1011 | if settings.DEBUG: 1012 | expect_filename = bits[0] + '.%s.' % (now + 10) + bits[-1] 1013 | else: 1014 | # then it shouldn't change. 1015 | # This effectively means that if you have a live server, and you 1016 | # make some changes to the, say, CSS files your Django templates 1017 | # won't notice this until after you restart Django. 1018 | pass 1019 | 1020 | self.assertFilenamesAlmostEqual(rendered, name_prefix + expect_filename) 1021 | 1022 | def test_staticall_basic(self): 1023 | settings.DEBUG = True 1024 | settings.DJANGO_STATIC = True 1025 | 1026 | filenames = ('/test_11.js', '/test_22.js') 1027 | codes = ('function (var1, var2) { return var1+var2; }', 1028 | 'var xxxxx = "yyyy" ;') 1029 | 1030 | self._test_staticall(filenames, codes) 1031 | 1032 | def test_staticall_one_file_only(self): 1033 | settings.DEBUG = True 1034 | settings.DJANGO_STATIC = True 1035 | 1036 | filenames = ('/test_abc.js',) 1037 | codes = ('function (var1, var2) { return var1+var2; }',) 1038 | 1039 | self._test_staticall(filenames, codes) 1040 | 1041 | def test_slimall_basic(self): 1042 | if slimmer is None and cssmin is None: 1043 | return 1044 | 1045 | settings.DEBUG = True 1046 | settings.DJANGO_STATIC = True 1047 | 1048 | filenames = ('/testxx.js', '/testyy.js') 1049 | codes = ('function (var1, var2) { return var1+var2; }', 1050 | 'var xxxxx = "yyyy" ;') 1051 | 1052 | self._test_slimall(filenames, codes) 1053 | 1054 | def test_slimall_basic_css(self): 1055 | if slimmer is None and cssmin is None: 1056 | return 1057 | 1058 | settings.DEBUG = True 1059 | settings.DJANGO_STATIC = True 1060 | 1061 | filenames = ('/adam.css', '/eve.css') 1062 | codes = ('body { gender: male; }', 1063 | 'footer { size: small; }') 1064 | 1065 | self._test_slimall(filenames, codes) 1066 | 1067 | 1068 | def test_slimall_css_files(self): 1069 | if slimmer is None and cssmin is None: 1070 | return 1071 | 1072 | settings.DEBUG = True 1073 | settings.DJANGO_STATIC = True 1074 | 1075 | filenames = ('/testxx.css', '/testyy.css') 1076 | codes = ('body { color:blue; }', 1077 | 'p { color:red; }') 1078 | 1079 | self._test_slimall(filenames, codes, 1080 | css_medias={'/testxx.css':'screen'}) 1081 | 1082 | def test_slimall_css_files_different_media(self): 1083 | settings.DEBUG = True 1084 | settings.DJANGO_STATIC = True 1085 | 1086 | filenames = ('/screen1.css', '/screen2.css', '/print_this.css') 1087 | codes = ('body { color:blue; }', 1088 | 'p { color:red; }', 1089 | 'body { margin: 0px; }') 1090 | 1091 | self._test_slimall(filenames, codes, 1092 | css_medias={'/print_this.css':'print'}) 1093 | 1094 | 1095 | def _test_slimall(self, filenames, codes, name_prefix='', media_url='', 1096 | save_prefix=None, 1097 | content_slimmed=True, 1098 | css_medias=None): 1099 | return self._test_staticall(filenames, codes, name_prefix=name_prefix, 1100 | media_url=media_url, save_prefix=save_prefix, 1101 | content_slimmed=True, 1102 | css_medias=css_medias) 1103 | 1104 | def _test_staticall(self, filenames, codes, name_prefix='', media_url='', 1105 | save_prefix=None, 1106 | content_slimmed=False, 1107 | css_medias=None): 1108 | 1109 | test_filepaths = [] 1110 | for i, filename in enumerate(filenames): 1111 | test_filepath = settings.MEDIA_ROOT + filename 1112 | test_filepaths.append(test_filepath) 1113 | open(test_filepath, 'w')\ 1114 | .write(codes[i] + '\n') 1115 | 1116 | now = int(time.time()) 1117 | template_as_string = '{% load django_static %}\n' 1118 | if content_slimmed: 1119 | template_as_string += '{% slimall %}\n' 1120 | else: 1121 | template_as_string += '{% staticall %}\n' 1122 | 1123 | for filename in filenames: 1124 | if filename.endswith('.js'): 1125 | template_as_string += '\n' % filename 1126 | elif filename.endswith('.css'): 1127 | if css_medias and css_medias.get(filename): 1128 | template_as_string += '\n' %\ 1129 | (css_medias.get(filename), filename) 1130 | else: 1131 | template_as_string += '\n' %\ 1132 | filename 1133 | else: 1134 | raise NotImplementedError('????') 1135 | 1136 | if content_slimmed: 1137 | template_as_string += '{% endslimall %}' 1138 | else: 1139 | template_as_string += '{% endstaticall %}' 1140 | 1141 | if filenames[0].endswith('.js'): 1142 | assert template_as_string.count(' 1355 | """ 1356 | template = Template(template_as_string) 1357 | context = Context() 1358 | rendered = template.render(context).strip() 1359 | self.assertTrue( 1360 | re.findall('', rendered) 1361 | ) 1362 | 1363 | 1364 | def test_staticfile_css_with_image_urls(self): 1365 | """staticfile a css file that contains image urls""" 1366 | settings.DEBUG = False 1367 | settings.DJANGO_STATIC = True 1368 | 1369 | 1370 | filename = "/medis.css" 1371 | test_filepath = settings.MEDIA_ROOT + filename 1372 | open(test_filepath, 'w').write(""" 1373 | h1 { 1374 | background-image: url('/img1.gif'); 1375 | } 1376 | h2 { 1377 | background-image: url("/img2.gif"); 1378 | } 1379 | h3 { 1380 | background-image: url(/img3.gif); 1381 | } 1382 | h9 { 1383 | background-image: url(/img9.gif); 1384 | } 1385 | """) 1386 | 1387 | open(settings.MEDIA_ROOT + '/img1.gif', 'w').write(_GIF_CONTENT) 1388 | open(settings.MEDIA_ROOT + '/img2.gif', 'w').write(_GIF_CONTENT) 1389 | open(settings.MEDIA_ROOT + '/img3.gif', 'w').write(_GIF_CONTENT) 1390 | # deliberately no img9.gif 1391 | 1392 | template_as_string = """{% load django_static %} 1393 | {% slimfile "/medis.css" %} 1394 | """ 1395 | template = Template(template_as_string) 1396 | context = Context() 1397 | rendered = template.render(context).strip() 1398 | self.assertTrue(re.findall('medis\.\d+\.css', rendered)) 1399 | 1400 | # open the file an expect that it did staticfile for the images 1401 | # within 1402 | new_filename = re.findall('/medis\.\d+\.css', rendered)[0] 1403 | new_filepath = os.path.join(settings.MEDIA_ROOT, 1404 | os.path.basename(new_filename)) 1405 | content = open(new_filepath).read() 1406 | self.assertTrue(re.findall('/img1\.\d+\.gif', content)) 1407 | self.assertTrue(re.findall('/img2\.\d+\.gif', content)) 1408 | self.assertTrue(re.findall('/img3\.\d+\.gif', content)) 1409 | self.assertTrue(re.findall('/img9\.gif', content)) 1410 | 1411 | def test_shortcut_functions(self): 1412 | """you can use slimfile() and staticfile() without using template tags""" 1413 | 1414 | settings.DEBUG = True 1415 | settings.DJANGO_STATIC = True 1416 | 1417 | filename = "/foo101.js" 1418 | test_filepath = settings.MEDIA_ROOT + filename 1419 | open(test_filepath, 'w').write(""" 1420 | function test() { 1421 | return "done"; 1422 | } 1423 | """) 1424 | 1425 | filename = "/foo102.js" 1426 | test_filepath = settings.MEDIA_ROOT + filename 1427 | open(test_filepath, 'w').write(""" 1428 | function test() { 1429 | return "done"; 1430 | } 1431 | """) 1432 | 1433 | reload(sys.modules['django_static.templatetags.django_static']) 1434 | from django_static.templatetags.django_static import slimfile, staticfile 1435 | result = staticfile('/foo101.js') 1436 | self.assertTrue(re.findall('/foo101\.\d+\.js', result)) 1437 | 1438 | result = slimfile('/foo102.js') 1439 | self.assertTrue(re.findall('/foo102\.\d+\.js', result)) 1440 | if slimmer is not None or cssmin is not None: 1441 | # test the content 1442 | new_filepath = os.path.join(settings.MEDIA_ROOT, 1443 | os.path.basename(result)) 1444 | content = open(new_filepath).read() 1445 | self.assertEqual(content, 'function test(){return "done";}') 1446 | 1447 | def test_staticfile_on_nonexistant_file(self): 1448 | """should warn and do nothing if try to staticfile a file that doesn't exist""" 1449 | 1450 | import django_static.templatetags.django_static 1451 | #from django_static.templatetags.django_static import staticfile 1452 | class MockedWarnings: 1453 | def warn(self, msg, *a, **k): 1454 | self.msg = msg 1455 | 1456 | mocked_warnings = MockedWarnings() 1457 | django_static.templatetags.django_static.warnings = mocked_warnings 1458 | result = django_static.templatetags.django_static.staticfile('/tralalal.js') 1459 | self.assertEqual(result, '/tralalal.js') 1460 | self.assertTrue(mocked_warnings.msg) 1461 | self.assertTrue(mocked_warnings.msg.count('tralalal.js')) 1462 | 1463 | def test_has_optimizer(self): 1464 | """test the utility function has_optimizer(type)""" 1465 | from django_static.templatetags.django_static import has_optimizer 1466 | 1467 | # definitely if you have defined a DJANGO_STATIC_YUI_COMPRESSOR 1468 | settings.DJANGO_STATIC_YUI_COMPRESSOR = 'sure' 1469 | self.assertTrue(has_optimizer('css')) 1470 | del settings.DJANGO_STATIC_YUI_COMPRESSOR 1471 | 1472 | self.assertEqual(has_optimizer('css'), bool(slimmer or cssmin)) 1473 | 1474 | # for javascript 1475 | settings.DJANGO_STATIC_YUI_COMPRESSOR = 'sure' 1476 | settings.DJANGO_STATIC_CLOSURE_COMPILER = 'sure' 1477 | 1478 | self.assertTrue(has_optimizer('js')) 1479 | del settings.DJANGO_STATIC_CLOSURE_COMPILER 1480 | self.assertTrue(has_optimizer('js')) 1481 | del settings.DJANGO_STATIC_YUI_COMPRESSOR 1482 | 1483 | self.assertEqual(has_optimizer('js'), bool(slimmer or cssmin)) 1484 | 1485 | self.assertRaises(ValueError, has_optimizer, 'uh') 1486 | 1487 | def test_running_closure_compiler(self): 1488 | settings.DJANGO_STATIC_CLOSURE_COMPILER = 'mocked' 1489 | 1490 | import django_static.templatetags.django_static 1491 | optimize = django_static.templatetags.django_static.optimize 1492 | 1493 | class MockedPopen: 1494 | return_error = False 1495 | 1496 | def __init__(self, cmd, *a, **__): 1497 | self.cmd = cmd 1498 | 1499 | def communicate(self, code): 1500 | self.code_in = code 1501 | if self.return_error: 1502 | return '', 'Something wrong!' 1503 | else: 1504 | return code.strip().upper(), None 1505 | 1506 | class BadMockedPopen(MockedPopen): 1507 | return_error = True 1508 | 1509 | old_Popen = django_static.templatetags.django_static.Popen 1510 | django_static.templatetags.django_static.Popen = MockedPopen 1511 | 1512 | code = 'function() { return 1 + 2; }' 1513 | new_code = optimize(code, 'js') 1514 | self.assertTrue(new_code.startswith('FUNCTION')) 1515 | 1516 | django_static.templatetags.django_static.Popen = BadMockedPopen 1517 | 1518 | new_code = optimize(code, 'js') 1519 | self.assertTrue(code in new_code) 1520 | self.assertTrue(new_code.find('/*') < new_code.find('Something wrong!') < \ 1521 | new_code.find('*/')) 1522 | 1523 | del settings.DJANGO_STATIC_CLOSURE_COMPILER 1524 | django_static.templatetags.django_static.Popen = MockedPopen 1525 | 1526 | new_code = optimize(code, 'js') 1527 | 1528 | django_static.templatetags.django_static.Popen = old_Popen 1529 | 1530 | 1531 | def test_running_yui_compressor(self): 1532 | if hasattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER'): 1533 | del settings.DJANGO_STATIC_CLOSURE_COMPILER 1534 | 1535 | settings.DJANGO_STATIC_YUI_COMPRESSOR = 'mocked' 1536 | 1537 | import django_static.templatetags.django_static 1538 | optimize = django_static.templatetags.django_static.optimize 1539 | 1540 | class MockedPopen: 1541 | return_error = False 1542 | 1543 | def __init__(self, cmd, *a, **__): 1544 | self.cmd = cmd 1545 | 1546 | def communicate(self, code): 1547 | self.code_in = code 1548 | if self.return_error: 1549 | return '', 'Something wrong!' 1550 | else: 1551 | return code.strip().upper(), None 1552 | 1553 | class BadMockedPopen(MockedPopen): 1554 | return_error = True 1555 | 1556 | old_Popen = django_static.templatetags.django_static.Popen 1557 | django_static.templatetags.django_static.Popen = MockedPopen 1558 | 1559 | code = 'function() { return 1 + 2; }' 1560 | new_code = optimize(code, 'js') 1561 | self.assertTrue(new_code.startswith('FUNCTION')) 1562 | 1563 | django_static.templatetags.django_static.Popen = BadMockedPopen 1564 | 1565 | new_code = optimize(code, 'js') 1566 | self.assertTrue(code in new_code) 1567 | self.assertTrue(new_code.find('/*') < new_code.find('Something wrong!') < \ 1568 | new_code.find('*/')) 1569 | 1570 | django_static.templatetags.django_static.Popen = MockedPopen 1571 | 1572 | code = 'body { font: big; }' 1573 | new_code = optimize(code, 'css') 1574 | self.assertTrue(new_code.startswith('body')) 1575 | 1576 | new_code = optimize(code, 'css') 1577 | del settings.DJANGO_STATIC_YUI_COMPRESSOR 1578 | 1579 | django_static.templatetags.django_static.Popen = old_Popen 1580 | 1581 | def test_load_file_proxy(self): 1582 | 1583 | settings.DEBUG = False 1584 | func = _django_static._load_file_proxy 1585 | 1586 | if hasattr(settings, 'DJANGO_STATIC_FILE_PROXY'): 1587 | del settings.DJANGO_STATIC_FILE_PROXY 1588 | 1589 | proxy_function = func() 1590 | self.assertEqual(proxy_function.func_name, 'file_proxy_nothing') 1591 | # this function will always return the first argument 1592 | self.assertEqual(proxy_function('anything', 'other', shit=123), 'anything') 1593 | self.assertEqual(proxy_function(None), None) 1594 | self.assertEqual(proxy_function(123), 123) 1595 | self.assertRaises(TypeError, proxy_function) 1596 | self.assertRaises(TypeError, proxy_function, keyword='argument') 1597 | 1598 | # the same would happen if DJANGO_STATIC_FILE_PROXY is defined but 1599 | # set to nothing 1600 | settings.DJANGO_STATIC_FILE_PROXY = None 1601 | proxy_function = func() 1602 | self.assertEqual(proxy_function.func_name, 'file_proxy_nothing') 1603 | 1604 | # Now set it to something sensible 1605 | # This becomes the equivalent of `from django_static.tests import fake_file_proxy` 1606 | # Test that that works to start with 1607 | settings.DJANGO_STATIC_FILE_PROXY = 'django_static.tests.fake_file_proxy' 1608 | 1609 | proxy_function = func() 1610 | self.assertNotEqual(proxy_function.func_name, 'file_proxy_nothing') 1611 | 1612 | # but that's not enough, now we need to "monkey patch" this usage 1613 | _django_static.file_proxy = proxy_function 1614 | 1615 | # now with this set up it will start to proxy and staticfile() or slimfile() 1616 | 1617 | open(settings.MEDIA_ROOT + '/img100.gif', 'w').write(_GIF_CONTENT) 1618 | 1619 | template_as_string = """{% load django_static %} 1620 | {% staticfile "/img100.gif" %} 1621 | """ 1622 | template = Template(template_as_string) 1623 | context = Context() 1624 | rendered = template.render(context).strip() 1625 | self.assertEqual(_last_fake_file_uri, rendered) 1626 | 1627 | # we can expect that a keyword argument called 'filepath' was used 1628 | self.assertTrue('filepath' in _last_fake_file_keyword_arguments) 1629 | # the filepath should point to the real file 1630 | self.assertTrue(os.path.isfile(_last_fake_file_keyword_arguments['filepath'])) 1631 | 1632 | self.assertTrue('new' in _last_fake_file_keyword_arguments) 1633 | self.assertTrue(_last_fake_file_keyword_arguments['new']) 1634 | 1635 | self.assertTrue('changed' in _last_fake_file_keyword_arguments) 1636 | self.assertFalse(_last_fake_file_keyword_arguments['changed']) 1637 | 1638 | self.assertTrue('checked' in _last_fake_file_keyword_arguments) 1639 | self.assertTrue(_last_fake_file_keyword_arguments['checked']) 1640 | 1641 | # if you run it again, because we're not in debug mode the second time 1642 | # the file won't be checked if it has changed 1643 | assert not settings.DEBUG 1644 | rendered = template.render(context).strip() 1645 | self.assertEqual(_last_fake_file_uri, rendered) 1646 | self.assertFalse(_last_fake_file_keyword_arguments['new']) 1647 | self.assertFalse(_last_fake_file_keyword_arguments['checked']) 1648 | self.assertFalse(_last_fake_file_keyword_arguments['changed']) 1649 | 1650 | # What if DJANGO_STATIC = False 1651 | # It should still go through the configured file proxy function 1652 | settings.DJANGO_STATIC = False 1653 | rendered = template.render(context).strip() 1654 | self.assertEqual(rendered, '/img100.gif') 1655 | self.assertFalse(_last_fake_file_keyword_arguments['checked']) 1656 | self.assertFalse(_last_fake_file_keyword_arguments['changed']) 1657 | 1658 | def test_file_proxy_with_name_prefix(self): 1659 | # Test it with a name prefix 1660 | settings.DEBUG = True 1661 | settings.DJANGO_STATIC = True 1662 | settings.DJANGO_STATIC_NAME_PREFIX = '/love-cache' 1663 | 1664 | open(settings.MEDIA_ROOT + '/imgXXX.gif', 'w').write(_GIF_CONTENT) 1665 | 1666 | # set up the file proxy 1667 | settings.DJANGO_STATIC_FILE_PROXY = 'django_static.tests.fake_file_proxy' 1668 | func = _django_static._load_file_proxy 1669 | proxy_function = func() 1670 | # but that's not enough, now we need to "monkey patch" this usage 1671 | _django_static.file_proxy = proxy_function 1672 | 1673 | template_as_string = """{% load django_static %} 1674 | {% staticfile "/imgXXX.gif" %} 1675 | """ 1676 | template = Template(template_as_string) 1677 | context = Context() 1678 | rendered = template.render(context).strip() 1679 | 1680 | self.assertEqual(_last_fake_file_uri, rendered) 1681 | self.assertTrue(_last_fake_file_keyword_arguments['new']) 1682 | self.assertTrue('filepath' in _last_fake_file_keyword_arguments) 1683 | 1684 | 1685 | def test_cross_optimizing_imported_css(self): 1686 | """Most basic test 1687 | {% slimfile "/css/foobar.css" %} 1688 | it should become 1689 | /foobar.123xxxxxxx.css 1690 | 1691 | But foobar.css will have to contain these: 1692 | 1693 | @import "/css/one.css"; 1694 | @import "two.css"; 1695 | @import url(/css/three.css); 1696 | @import url('two.css'); 1697 | 1698 | Also, in them we're going to refer to images. 1699 | """ 1700 | settings.DEBUG = False 1701 | settings.DJANGO_STATIC = True 1702 | 1703 | 1704 | filename = 'css/foobar.css' 1705 | if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css')): 1706 | os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css')) 1707 | if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')): 1708 | os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')) 1709 | 1710 | test_filepath = os.path.join(settings.MEDIA_ROOT, filename) 1711 | open(test_filepath, 'w').write(""" 1712 | @import "/css/one.css"; 1713 | @import "two.css"; 1714 | @import url(/css/deeper/three.css); 1715 | @import url('four.css'); 1716 | """) 1717 | template_as_string = """ 1718 | {% load django_static %} 1719 | {% slimfile "/css/foobar.css" %} 1720 | """ 1721 | 1722 | # now we need to create all of those mock files 1723 | open(settings.MEDIA_ROOT + '/css/one.css', 'w').write(""" 1724 | /* COMMENT ONE */ 1725 | p { background-image: url('one.gif'); } 1726 | """) 1727 | 1728 | open(settings.MEDIA_ROOT + '/css/two.css', 'w').write(""" 1729 | /* COMMENT TWO */ 1730 | p { background-image: url(two.gif); } 1731 | """) 1732 | 1733 | open(settings.MEDIA_ROOT + '/css/deeper/three.css', 'w').write(""" 1734 | /* COMMENT THREE */ 1735 | p { background-image: url("three.gif"); } 1736 | """) 1737 | 1738 | open(settings.MEDIA_ROOT + '/css/four.css', 'w').write(""" 1739 | /* COMMENT FOUR */ 1740 | p { background-image: url("/four.gif"); } 1741 | """) 1742 | 1743 | # now we need to create the images 1744 | open(settings.MEDIA_ROOT + '/css/one.gif', 'w').write(_GIF_CONTENT) 1745 | open(settings.MEDIA_ROOT + '/css/two.gif', 'w').write(_GIF_CONTENT) 1746 | open(settings.MEDIA_ROOT + '/css/deeper/three.gif', 'w').write(_GIF_CONTENT) 1747 | open(settings.MEDIA_ROOT + '/four.gif', 'w').write(_GIF_CONTENT) 1748 | 1749 | template = Template(template_as_string) 1750 | context = Context() 1751 | rendered = template.render(context).strip() 1752 | 1753 | self.assertTrue(re.findall('/css/foobar\.\d+.css', rendered)) 1754 | foobar_content = open(settings.MEDIA_ROOT + rendered).read() 1755 | self.assertTrue(not foobar_content.count('\n')) 1756 | self.assertTrue(re.findall('@import "/css/one\.\d+\.css";', foobar_content)) 1757 | # notice how we add the '/css/' path to this one! 1758 | # it was '@import "two.css";' originally 1759 | self.assertTrue(re.findall('@import "/css/two\.\d+\.css";', foobar_content)) 1760 | self.assertTrue(re.findall('@import url\(/css/deeper/three\.\d+\.css\);', foobar_content)) 1761 | self.assertTrue(re.findall('@import url\(\'/css/four\.\d+\.css\'\);', foobar_content)) 1762 | 1763 | # now lets study the results of each of these files 1764 | filename_one = re.findall('one\.\d+\.css', foobar_content)[0] 1765 | filename_two = re.findall('two\.\d+\.css', foobar_content)[0] 1766 | filename_three = re.findall('three\.\d+\.css', foobar_content)[0] 1767 | filename_four = re.findall('four\.\d+\.css', foobar_content)[0] 1768 | 1769 | content_one = open(settings.MEDIA_ROOT + '/css/' + filename_one).read() 1770 | self.assertTrue('COMMENT ONE' not in content_one) 1771 | self.assertTrue(re.findall('one\.\d+\.gif', content_one)) 1772 | image_filename_one = re.findall('one\.\d+\.gif', content_one)[0] 1773 | 1774 | content_two = open(settings.MEDIA_ROOT + '/css/' + filename_two).read() 1775 | self.assertTrue('COMMENT TWO' not in content_one) 1776 | self.assertTrue(re.findall('two\.\d+\.gif', content_two)) 1777 | image_filename_two = re.findall('two\.\d+\.gif', content_two)[0] 1778 | 1779 | content_three = open(settings.MEDIA_ROOT + '/css/deeper/' + filename_three).read() 1780 | self.assertTrue('COMMENT THREE' not in content_three) 1781 | self.assertTrue(re.findall('three\.\d+\.gif', content_three)) 1782 | image_filename_three = re.findall('three\.\d+\.gif', content_three)[0] 1783 | 1784 | content_four = open(settings.MEDIA_ROOT + '/css/' + filename_four).read() 1785 | self.assertTrue('COMMENT FOUR' not in content_four) 1786 | self.assertTrue(re.findall('four\.\d+\.gif', content_four)) 1787 | image_filename_four = re.findall('four\.\d+\.gif', content_four)[0] 1788 | 1789 | # now check that these images were actually created 1790 | self.assertTrue(image_filename_one in os.listdir(settings.MEDIA_ROOT + '/css')) 1791 | self.assertTrue(image_filename_two in os.listdir(settings.MEDIA_ROOT + '/css')) 1792 | self.assertTrue(image_filename_three in os.listdir(settings.MEDIA_ROOT + '/css/deeper')) 1793 | self.assertTrue(image_filename_four in os.listdir(settings.MEDIA_ROOT)) 1794 | 1795 | 1796 | 1797 | def test_cross_optimizing_imported_css_with_save_prefix_and_name_prefix(self): 1798 | """This test is entirely copied from test_cross_optimizing_imported_css() 1799 | but with SAVE_PREFIX and NAME_PREFIX added. 1800 | """ 1801 | settings.DEBUG = False 1802 | settings.DJANGO_STATIC = True 1803 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 1804 | settings.DJANGO_STATIC_NAME_PREFIX = '/infinity' 1805 | settings.DJANGO_STATIC_SAVE_PREFIX = os.path.join(settings.MEDIA_ROOT, 'special') 1806 | 1807 | filename = 'css/foobar.css' 1808 | if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css')): 1809 | os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css')) 1810 | if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')): 1811 | os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')) 1812 | 1813 | test_filepath = os.path.join(settings.MEDIA_ROOT, filename) 1814 | open(test_filepath, 'w').write(""" 1815 | @import "/css/one.css"; 1816 | @import "two.css"; 1817 | @import url(/css/deeper/three.css); 1818 | @import url('four.css'); 1819 | """) 1820 | template_as_string = """ 1821 | {% load django_static %} 1822 | {% slimfile "/css/foobar.css" %} 1823 | """ 1824 | 1825 | # now we need to create all of those mock files 1826 | open(settings.MEDIA_ROOT + '/css/one.css', 'w').write(""" 1827 | /* COMMENT ONE */ 1828 | p { background-image: url('one.gif'); } 1829 | """) 1830 | 1831 | open(settings.MEDIA_ROOT + '/css/two.css', 'w').write(""" 1832 | /* COMMENT TWO */ 1833 | p { background-image: url(two.gif); } 1834 | """) 1835 | 1836 | open(settings.MEDIA_ROOT + '/css/deeper/three.css', 'w').write(""" 1837 | /* COMMENT THREE */ 1838 | p { background-image: url("three.gif"); } 1839 | """) 1840 | 1841 | open(settings.MEDIA_ROOT + '/css/four.css', 'w').write(""" 1842 | /* COMMENT FOUR */ 1843 | p { background-image: url("/four.gif"); } 1844 | """) 1845 | 1846 | # now we need to create the images 1847 | open(settings.MEDIA_ROOT + '/css/one.gif', 'w').write(_GIF_CONTENT) 1848 | open(settings.MEDIA_ROOT + '/css/two.gif', 'w').write(_GIF_CONTENT) 1849 | open(settings.MEDIA_ROOT + '/css/deeper/three.gif', 'w').write(_GIF_CONTENT) 1850 | open(settings.MEDIA_ROOT + '/four.gif', 'w').write(_GIF_CONTENT) 1851 | 1852 | template = Template(template_as_string) 1853 | context = Context() 1854 | rendered = template.render(context).strip() 1855 | 1856 | self.assertTrue(re.findall('/infinity/css/foobar\.\d+.css', rendered)) 1857 | foobar_content = open(settings.MEDIA_ROOT + '/special' + \ 1858 | rendered.replace('/infinity','')).read() 1859 | self.assertTrue(not foobar_content.count('\n')) 1860 | self.assertTrue(re.findall('@import "/infinity/css/one\.\d+\.css";', foobar_content)) 1861 | # notice how we add the '/css/' path to this one! 1862 | # it was '@import "two.css";' originally 1863 | self.assertTrue(re.findall('@import "/infinity/css/two\.\d+\.css";', foobar_content)) 1864 | self.assertTrue(re.findall('@import url\(/infinity/css/deeper/three\.\d+\.css\);', foobar_content)) 1865 | self.assertTrue(re.findall('@import url\(\'/infinity/css/four\.\d+\.css\'\);', foobar_content)) 1866 | 1867 | # now lets study the results of each of these files 1868 | filename_one = re.findall('one\.\d+\.css', foobar_content)[0] 1869 | filename_two = re.findall('two\.\d+\.css', foobar_content)[0] 1870 | filename_three = re.findall('three\.\d+\.css', foobar_content)[0] 1871 | filename_four = re.findall('four\.\d+\.css', foobar_content)[0] 1872 | 1873 | content_one = open(settings.MEDIA_ROOT + '/special/css/' + filename_one).read() 1874 | self.assertTrue('COMMENT ONE' not in content_one) 1875 | self.assertTrue(re.findall('one\.\d+\.gif', content_one)) 1876 | image_filename_one = re.findall('one\.\d+\.gif', content_one)[0] 1877 | 1878 | content_two = open(settings.MEDIA_ROOT + '/special/css/' + filename_two).read() 1879 | self.assertTrue('COMMENT TWO' not in content_one) 1880 | self.assertTrue(re.findall('two\.\d+\.gif', content_two)) 1881 | image_filename_two = re.findall('two\.\d+\.gif', content_two)[0] 1882 | 1883 | content_three = open(settings.MEDIA_ROOT + '/special/css/deeper/' + filename_three).read() 1884 | self.assertTrue('COMMENT THREE' not in content_three) 1885 | self.assertTrue(re.findall('three\.\d+\.gif', content_three)) 1886 | image_filename_three = re.findall('three\.\d+\.gif', content_three)[0] 1887 | 1888 | content_four = open(settings.MEDIA_ROOT + '/special/css/' + filename_four).read() 1889 | self.assertTrue('COMMENT FOUR' not in content_four) 1890 | self.assertTrue(re.findall('four\.\d+\.gif', content_four)) 1891 | image_filename_four = re.findall('four\.\d+\.gif', content_four)[0] 1892 | 1893 | # now check that these images were actually created 1894 | self.assertTrue(image_filename_one in os.listdir(settings.MEDIA_ROOT + '/special/css')) 1895 | self.assertTrue(image_filename_two in os.listdir(settings.MEDIA_ROOT + '/special/css')) 1896 | self.assertTrue(image_filename_three in os.listdir(settings.MEDIA_ROOT + '/special/css/deeper')) 1897 | self.assertTrue(image_filename_four in os.listdir(settings.MEDIA_ROOT + '/special')) 1898 | 1899 | def test_slimming_non_ascii_css(self): 1900 | """ 1901 | https://github.com/peterbe/django-static/issues/#issue/11 1902 | """ 1903 | settings.DEBUG = False 1904 | settings.DJANGO_STATIC = True 1905 | 1906 | 1907 | filename = 'fax.css' 1908 | #if not os.path.isdir(os.path.join(settings.MEDIA_ROOT): 1909 | # os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css')) 1910 | # if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')): 1911 | # os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')) 1912 | 1913 | test_filepath = os.path.join(settings.MEDIA_ROOT, filename) 1914 | codecs.open(test_filepath, 'w', 'utf8').write(u""" 1915 | @font-face { 1916 | font-family: 'da39a3ee5e'; 1917 | src: url('da39a3ee5e.eot'); 1918 | src: local('☺'), url(data:font/woff;charset=utf-8;base64,[ snip ]) format('truetype'); 1919 | font-weight: normal; 1920 | font-style: normal; 1921 | } 1922 | """) 1923 | 1924 | template_as_string = """ 1925 | {% load django_static %} 1926 | {% slimfile "/fax.css" %} 1927 | """ 1928 | template = Template(template_as_string) 1929 | context = Context() 1930 | rendered = template.render(context).strip() 1931 | self.assertTrue(re.findall('/fax\.\d+.css', rendered)) 1932 | content = codecs.open(settings.MEDIA_ROOT + rendered, 'r', 'utf-8').read() 1933 | 1934 | if slimmer is None and cssmin is None: 1935 | return 1936 | self.assertTrue(u"src:url('/da39a3ee5e.eot');src:local('\u263a')," in content) 1937 | 1938 | def test_slimming_non_ascii_import_css(self): 1939 | """ 1940 | https://github.com/peterbe/django-static/issues/#issue/11 1941 | """ 1942 | settings.DEBUG = False 1943 | settings.DJANGO_STATIC = True 1944 | 1945 | 1946 | filename = 'pax.css' 1947 | #if not os.path.isdir(os.path.join(settings.MEDIA_ROOT): 1948 | # os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css')) 1949 | # if not os.path.isdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')): 1950 | # os.mkdir(os.path.join(settings.MEDIA_ROOT, 'css', 'deeper')) 1951 | 1952 | test_filepath = os.path.join(settings.MEDIA_ROOT, filename) 1953 | codecs.open(test_filepath, 'w', 'utf8').write('@import "bax.css";\n') 1954 | 1955 | test_filepath = os.path.join(settings.MEDIA_ROOT, 'bax.css') 1956 | codecs.open(test_filepath, 'w', 'utf8').write(u""" 1957 | @font-face { 1958 | font-family: 'da39a3ee5e'; 1959 | src: url('da39a3ee5e.eot'); 1960 | src: local('☺'), url(data:font/woff;charset=utf-8;base64,[ snip ]) format('truetype'); 1961 | font-weight: normal; 1962 | font-style: normal; 1963 | } 1964 | """) 1965 | 1966 | template_as_string = """ 1967 | {% load django_static %} 1968 | {% slimfile "/pax.css" %} 1969 | """ 1970 | template = Template(template_as_string) 1971 | context = Context() 1972 | rendered = template.render(context).strip() 1973 | self.assertTrue(re.findall('/pax\.\d+.css', rendered)) 1974 | content = open(settings.MEDIA_ROOT + rendered).read() 1975 | self.assertTrue(re.findall('/bax\.\d+.css', content)) 1976 | generated_filename = re.findall('(/bax\.\d+.css)', content)[0] 1977 | # open the generated new file 1978 | content = codecs.open(settings.MEDIA_ROOT + generated_filename, 'r', 'utf-8').read() 1979 | if slimmer is None and cssmin is None: 1980 | return 1981 | self.assertTrue(u"src:url('/da39a3ee5e.eot');src:local('\u263a')," in content) 1982 | 1983 | def test_ignoring_data_uri_scheme(self): 1984 | settings.DEBUG = False 1985 | settings.DJANGO_STATIC = True 1986 | 1987 | html = u""" 1988 | 1989 | 1990 | 1991 | 1992 | """ 1993 | 1994 | css = u""" 1995 | @font-face { 1996 | src: url('da39a3ee5e.eot'); 1997 | src: local('☺'), url(data:font/woff;charset=utf-8;base64,[ snip ]) format('truetype'); 1998 | } 1999 | 2000 | a { 2001 | background: url(pax.jpg) 2002 | } 2003 | 2004 | strong { 2005 | background: url('data_yadda.jpg') 2006 | } 2007 | """ 2008 | 2009 | # first test some html 2010 | re_html = re.findall(_django_static.IMG_REGEX, html) 2011 | self.assertEqual(re_html, [u'pax.jpg', u'data_foo.jpg']) 2012 | 2013 | # test some css 2014 | re_css = re.findall(_django_static.REFERRED_CSS_URLS_REGEX, css) 2015 | self.assertEqual(re_css, [u"'da39a3ee5e.eot'", u'pax.jpg', u"'data_yadda.jpg'"]) 2016 | 2017 | def test_slimall_with_defer(self): 2018 | settings.DEBUG = False 2019 | settings.DJANGO_STATIC = True 2020 | 2021 | template_as_string = u""" 2022 | {% load django_static %} 2023 | {% slimall %} 2024 | 2025 | 2026 | {% endslimall %} 2027 | """ 2028 | 2029 | filename = "/foo.js" 2030 | test_filepath = settings.MEDIA_ROOT + filename 2031 | open(test_filepath, 'w').write(""" 2032 | function (var) { return var++; } 2033 | """) 2034 | 2035 | filename = "/bar.js" 2036 | test_filepath = settings.MEDIA_ROOT + filename 2037 | open(test_filepath, 'w').write(""" 2038 | function (var) { return var++; } 2039 | """) 2040 | 2041 | template = Template(template_as_string) 2042 | context = Context() 2043 | rendered = template.render(context).strip() 2044 | self.assertTrue(rendered.startswith(u' 2081 | {% endslimall %} 2082 | """ 2083 | template = Template(template_as_string) 2084 | context = Context() 2085 | rendered = template.render(context).strip() 2086 | self.assertEqual(rendered, u'') 2087 | 2088 | settings.DJANGO_STATIC = True 2089 | rendered = template.render(context).strip() 2090 | self.assertTrue(rendered.startswith(u' 69 | 70 | 91 | 92 | 93 | 94 | {% slimall %} 95 | 96 | 97 | {% endslimall %} 98 | 99 | 100 | -------------------------------------------------------------------------------- /exampleapp/templates/showicons.html: -------------------------------------------------------------------------------- 1 | {% load django_static %} 2 | -------------------------------------------------------------------------------- /exampleapp/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /exampleapp/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | 4 | import views 5 | 6 | urlpatterns = patterns('', 7 | url(r'^/?$', views.page, name="page"), 8 | 9 | ) -------------------------------------------------------------------------------- /exampleapp/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render_to_response 2 | from django.template import RequestContext 3 | 4 | def page(request): 5 | return render_to_response('page.html', locals(), 6 | context_instance=RequestContext(request)) 7 | -------------------------------------------------------------------------------- /gorun_settings.py: -------------------------------------------------------------------------------- 1 | DIRECTORIES = ( 2 | (('django_static/tests.py', 3 | 'django_static/templatetags/django_static.py'), 4 | # './manage.py test django_static.TestDjangoStatic'), 5 | # './manage.py test django_static.TestDjangoStatic.test_slimfile_multiple_basic'), 6 | # './manage.py test django_static.TestDjangoStatic.test_cross_optimizing_imported_css'), 7 | './manage.py test django_static.TestDjangoStatic.test_cross_optimizing_imported_css_with_save_prefix_and_name_prefix'), 8 | # './manage.py test django_static.TestDjangoStatic.test_staticall_basic'), 9 | # './manage.py test django_static.TestDjangoStatic'), 10 | # './manage.py test django_static.TestDjangoStatic.test_slimall_basic_css'), 11 | # './manage.py test django_static.TestDjangoStatic.test_slimfiles_scripts'), 12 | # './manage.py test django_static.TestDjangoStatic.test_slimfiles_styles'), 13 | # './manage.py test django_static.TestDjangoStatic.test_slimfiles_scripts_and_styles'), 14 | # './manage.py test django_static.TestDjangoStatic.test_slimfiles_scripts_and_styles_with_name_prefix'), 15 | # './manage.py test django_static.TestDjangoStatic.test_slimfiles_scripts_and_styles_with_save_and_name_prefix_with_media_url'), 16 | # './manage.py test django_static.TestDjangoStatic.test__combine_filenames'), 17 | ) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /media/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ */ 2 | /* v1.0 | 20080212 */ 3 | 4 | html, body, div, span, applet, object, iframe, 5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 6 | a, abbr, acronym, address, big, cite, code, 7 | del, dfn, em, font, img, ins, kbd, q, s, samp, 8 | small, strike, strong, sub, sup, tt, var, 9 | b, u, i, center, 10 | dl, dt, dd, ol, ul, li, 11 | fieldset, form, label, legend, 12 | table, caption, tbody, tfoot, thead, tr, th, td { 13 | margin: 0; 14 | padding: 0; 15 | border: 0; 16 | outline: 0; 17 | font-size: 100%; 18 | vertical-align: baseline; 19 | background: transparent; 20 | } 21 | body { 22 | line-height: 1; 23 | } 24 | ol, ul { 25 | list-style: none; 26 | } 27 | blockquote, q { 28 | quotes: none; 29 | } 30 | blockquote:before, blockquote:after, 31 | q:before, q:after { 32 | content: ''; 33 | content: none; 34 | } 35 | 36 | /* remember to define focus styles! */ 37 | :focus { 38 | outline: 0; 39 | } 40 | 41 | /* remember to highlight inserts somehow! */ 42 | ins { 43 | text-decoration: none; 44 | } 45 | del { 46 | text-decoration: line-through; 47 | } 48 | 49 | /* tables still need 'cellspacing="0"' in the markup */ 50 | table { 51 | border-collapse: collapse; 52 | border-spacing: 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /media/css/styles.css: -------------------------------------------------------------------------------- 1 | @import "sub/module.css"; /* valid */ 2 | @import url("/css/sub-module2.css"); /* also valid */ 3 | body { 4 | margin:0; 5 | padding:0; 6 | background:#59aef0 url("/images/sky.jpg") no-repeat; 7 | } 8 | 9 | #content { 10 | padding:20px 60px; 11 | } 12 | p, li { 13 | font-family:serif, Arial; 14 | } -------------------------------------------------------------------------------- /media/css/sub-module2.css: -------------------------------------------------------------------------------- 1 | h4.sub { 2 | font-style:italic; 3 | } -------------------------------------------------------------------------------- /media/css/sub/module.css: -------------------------------------------------------------------------------- 1 | h4{ 2 | text-decoration:line-through; 3 | } -------------------------------------------------------------------------------- /media/delete_all_timestamped_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | def delete_here(dir): 4 | for f in os.listdir(dir): 5 | if os.path.isdir(os.path.join(dir, f)): 6 | delete_here(os.path.join(dir, f)) 7 | elif re.findall('\.\d{10}\.', f): 8 | print os.path.join(dir, f) 9 | os.remove(os.path.join(dir, f)) 10 | if __name__ == '__main__': 11 | delete_here(os.path.abspath(os.path.dirname(__file__))) 12 | -------------------------------------------------------------------------------- /media/images/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/media/images/icon1.png -------------------------------------------------------------------------------- /media/images/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/media/images/icon2.png -------------------------------------------------------------------------------- /media/images/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/media/images/icon3.png -------------------------------------------------------------------------------- /media/images/peterbe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/media/images/peterbe.jpg -------------------------------------------------------------------------------- /media/images/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/django-static/05c0d2f302274b9d3f7df6ad92bd861889316ebd/media/images/sky.jpg -------------------------------------------------------------------------------- /media/javascript/canbe.js: -------------------------------------------------------------------------------- 1 | // This file is going to be combined with another 2 | // one when it's wrapped in a slimfiles tag. 3 | // 4 | var dummy = " Test "; 5 | -------------------------------------------------------------------------------- /media/javascript/combined.min.js: -------------------------------------------------------------------------------- 1 | var x=function(){foo()}; // already minified -------------------------------------------------------------------------------- /media/javascript/jquery-1.3.2.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); 13 | /* 14 | * Sizzle CSS Selector Engine - v0.9.3 15 | * Copyright 2009, The Dojo Foundation 16 | * Released under the MIT, BSD, and GPL Licenses. 17 | * More information: http://sizzlejs.com/ 18 | */ 19 | (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); -------------------------------------------------------------------------------- /media/javascript/script.js: -------------------------------------------------------------------------------- 1 | /* Example comment */ 2 | $(function () { 3 | 4 | // Hide all images first 5 | $('img.peter:visible').hide(); 6 | 7 | // Example of something silly to do 8 | setTimeout(function() { 9 | $('img.peter:hidden').fadeIn("slow"); 10 | }, 2*1000); 11 | 12 | 13 | }); -------------------------------------------------------------------------------- /media/javascript/shit_code.js: -------------------------------------------------------------------------------- 1 | function a=b() ; return {] -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for django_static project. 2 | import os 3 | OUR_ROOT = os.path.realpath(os.path.dirname(__file__)) 4 | 5 | DEBUG = True 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@domain.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', 17 | 'NAME': ':memory:', 18 | } 19 | } 20 | # good bye Django 1.1! 21 | #DATABASE_ENGINE = 'sqlite3' 22 | #DATABASE_NAME = TEST_DATABASE_NAME = ':memory:' 23 | 24 | CACHE_BACKEND = 'locmem:///' 25 | 26 | 27 | # Local time zone for this installation. Choices can be found here: 28 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 29 | # although not all choices may be available on all operating systems. 30 | # If running in a Windows environment this must be set to the same as your 31 | # system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # Absolute path to the directory that holds media. 45 | # Example: "/home/media/media.lawrence.com/" 46 | MEDIA_ROOT = os.path.join(OUR_ROOT, 'media') 47 | 48 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 49 | # trailing slash if there is a path component (optional in other cases). 50 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 51 | MEDIA_URL = '' 52 | 53 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 54 | # trailing slash. 55 | # Examples: "http://foo.com/media/", "/media/". 56 | ADMIN_MEDIA_PREFIX = '/media/' 57 | 58 | # Make this unique, and don't share it with anybody. 59 | SECRET_KEY = 'nsma0=bc56bwj7h*_*4g0)iibz-uc1^l5$e6df1&yh(r1u*pv8' 60 | 61 | # List of callables that know how to import templates from various sources. 62 | TEMPLATE_LOADERS = ( 63 | 'django.template.loaders.filesystem.load_template_source', 64 | 'django.template.loaders.app_directories.load_template_source', 65 | # 'django.template.loaders.eggs.load_template_source', 66 | ) 67 | 68 | MIDDLEWARE_CLASSES = ( 69 | 'django.middleware.common.CommonMiddleware', 70 | 'django.contrib.sessions.middleware.SessionMiddleware', 71 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 72 | ) 73 | 74 | ROOT_URLCONF = 'urls' 75 | 76 | TEMPLATE_DIRS = ( 77 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 78 | # Always use forward slashes, even on Windows. 79 | # Don't forget to use absolute paths, not relative paths. 80 | ) 81 | 82 | INSTALLED_APPS = ( 83 | #'django.contrib.auth', 84 | #'django.contrib.contenttypes', 85 | #'django.contrib.sessions', 86 | #'django.contrib.sites', 87 | 'django_static', 88 | 'exampleapp', 89 | ) 90 | 91 | 92 | 93 | 94 | DJANGO_STATIC = True 95 | 96 | #DJANGO_STATIC_CLOSURE_COMPILER = '/home/peterbe/tmp/compiler-latest/compiler.jar' 97 | #DJANGO_STATIC_YUI_COMPRESSOR = '/home/peterbe/tmp/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar' 98 | #DJANGO_STATIC_JSMIN = True 99 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | 5 | # django-static doesn't have a version but this setup.py does 6 | VERSION = '1.5.6' # remember to match with django_static/__init__.py 7 | 8 | import os 9 | long_description = open(os.path.join(os.path.dirname(__file__), 10 | 'README.rst')).read() 11 | 12 | 13 | setup( 14 | name='django-static', 15 | version=VERSION, 16 | url='https://github.com/peterbe/django-static', 17 | download_url='https://pypi.python.org/pypi/django-static/', 18 | description='Template tags for better serving static files from templates in Django', 19 | long_description=long_description, 20 | author='Peter Bengtsson', 21 | author_email='mail@peterbe.com', 22 | platforms=['any'], 23 | license='BSD', 24 | packages=[ 25 | 'django_static', 26 | 'django_static.templatetags', 27 | ], 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'Environment :: Web Environment', 31 | 'Framework :: Django', 32 | 'Intended Audience :: Developers', 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python', 35 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | from settings import MEDIA_ROOT 4 | 5 | 6 | urlpatterns = patterns('', 7 | # Example: 8 | (r'^exampleapp/', include('exampleapp.urls')), 9 | 10 | # medi 11 | url(r'^images/(?P.*)$', 'django.views.static.serve', 12 | {'document_root': MEDIA_ROOT + '/images'}), 13 | url(r'^css/(?P.*)$', 'django.views.static.serve', 14 | {'document_root': MEDIA_ROOT + '/css'}), 15 | url(r'^javascript/(?P.*)$', 'django.views.static.serve', 16 | {'document_root': MEDIA_ROOT + '/javascript'}), 17 | 18 | ) 19 | --------------------------------------------------------------------------------