Ticket #15252: 15252.3.diff
File 15252.3.diff, 40.3 KB (added by , 13 years ago) |
---|
-
django/contrib/admin/templates/admin/auth/user/change_password.html
diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html index c280f50..b18b0aa 100644
a b 1 1 {% extends "admin/base_site.html" %} 2 {% load i18n static admin_modify %}2 {% load i18n admin_static admin_modify %} 3 3 {% load url from future %} 4 4 {% block extrahead %}{{ block.super }} 5 5 {% url 'admin:jsi18n' as jsi18nurl %} -
django/contrib/admin/templates/admin/base.html
diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 9282440..4b3c429 100644
a b 1 {% load static %}{% load url from future %}<!DOCTYPE html>1 {% load admin_static %}{% load url from future %}<!DOCTYPE html> 2 2 <html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}> 3 3 <head> 4 4 <title>{% block title %}{% endblock %}</title> -
django/contrib/admin/templates/admin/change_form.html
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index c5e428d..56661e9 100644
a b 1 1 {% extends "admin/base_site.html" %} 2 {% load i18n static admin_modify %}2 {% load i18n admin_static admin_modify %} 3 3 {% load url from future %} 4 4 5 5 {% block extrahead %}{{ block.super }} -
django/contrib/admin/templates/admin/change_list.html
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 29af47f..24c6d8c 100644
a b 1 1 {% extends "admin/base_site.html" %} 2 {% load i18n static admin_list %}2 {% load i18n admin_static admin_list %} 3 3 {% load url from future %} 4 4 {% block extrastyle %} 5 5 {{ block.super }} -
django/contrib/admin/templates/admin/change_list_results.html
diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html index 233d4e1..b1db647 100644
a b 1 {% load i18n static %}1 {% load i18n admin_static %} 2 2 {% if result_hidden_fields %} 3 3 <div class="hiddenfields">{# DIV for HTML validation #} 4 4 {% for item in result_hidden_fields %}{{ item }}{% endfor %} -
django/contrib/admin/templates/admin/edit_inline/stacked.html
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 7e69450..476e261 100644
a b 1 {% load i18n static %}1 {% load i18n admin_static %} 2 2 <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> 3 3 <h2>{{ inline_admin_formset.opts.verbose_name_plural|title }}</h2> 4 4 {{ inline_admin_formset.formset.management_form }} -
django/contrib/admin/templates/admin/edit_inline/tabular.html
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 8294227..29db95a 100644
a b 1 {% load i18n static admin_modify %}1 {% load i18n admin_static admin_modify %} 2 2 <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> 3 3 <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> 4 4 {{ inline_admin_formset.formset.management_form }} -
django/contrib/admin/templates/admin/index.html
diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 0f81a1a..7164220 100644
a b 1 1 {% extends "admin/base_site.html" %} 2 {% load i18n static %}2 {% load i18n admin_static %} 3 3 4 4 {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />{% endblock %} 5 5 -
django/contrib/admin/templates/admin/login.html
diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html index a95cfd8..dbaa119 100644
a b 1 1 {% extends "admin/base_site.html" %} 2 {% load i18n static %}2 {% load i18n admin_static %} 3 3 4 4 {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}" />{% endblock %} 5 5 -
django/contrib/admin/templates/admin/search_form.html
diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html index 2b23a21..162b54a 100644
a b 1 {% load i18n static %}1 {% load i18n admin_static %} 2 2 {% if cl.search_fields %} 3 3 <div id="toolbar"><form id="changelist-search" action="" method="get"> 4 4 <div><!-- DIV needed for valid HTML --> -
django/contrib/admin/templatetags/admin_list.py
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 7a3a9b8..0f5eafc 100644
a b import datetime 3 3 from django.contrib.admin.util import lookup_field, display_for_field, label_for_field 4 4 from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE, 5 5 ORDER_VAR, PAGE_VAR, SEARCH_VAR) 6 from django.contrib.admin.templatetags.admin_static import static 6 7 from django.core.exceptions import ObjectDoesNotExist 7 8 from django.db import models 8 from django.templatetags.static import static9 9 from django.utils import formats 10 10 from django.utils.html import escape, conditional_escape 11 11 from django.utils.safestring import mark_safe -
new file django/contrib/admin/templatetags/admin_static.py
diff --git a/django/contrib/admin/templatetags/admin_static.py b/django/contrib/admin/templatetags/admin_static.py new file mode 100644 index 0000000..5ea3ba5
- + 1 from django.conf import settings 2 from django.template import Library 3 4 register = Library() 5 6 if 'django.contrib.staticfiles' in settings.INSTALLED_APPS: 7 from django.contrib.staticfiles.templatetags.staticfiles import static 8 else: 9 from django.templatetags.static import static 10 11 static = register.simple_tag(static) -
django/contrib/admin/widgets.py
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 038351e..d7dc1a5 100644
a b from django import forms 7 7 from django.core.urlresolvers import reverse 8 8 from django.forms.widgets import RadioFieldRenderer 9 9 from django.forms.util import flatatt 10 from django.templatetags.static import static11 10 from django.utils.html import escape 12 11 from django.utils.text import Truncator 13 12 from django.utils.translation import ugettext as _ 14 13 from django.utils.safestring import mark_safe 15 14 from django.utils.encoding import force_unicode 15 from django.contrib.admin.templatetags.admin_static import static 16 16 17 17 18 class FilteredSelectMultiple(forms.SelectMultiple): 18 19 """ … … class FilteredSelectMultiple(forms.SelectMultiple): 31 32 super(FilteredSelectMultiple, self).__init__(attrs, choices) 32 33 33 34 def render(self, name, value, attrs=None, choices=()): 34 if attrs is None: attrs = {} 35 if attrs is None: 36 attrs = {} 35 37 attrs['class'] = 'selectfilter' 36 if self.is_stacked: attrs['class'] += 'stacked' 38 if self.is_stacked: 39 attrs['class'] += 'stacked' 37 40 output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] 38 41 output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {') 39 42 # TODO: "id_" is hard-coded here. This should instead use the correct -
django/contrib/staticfiles/management/commands/collectstatic.py
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index dd597ad..ead95ac 100644
a b import os 4 4 import sys 5 5 from optparse import make_option 6 6 7 from django.conf import settings 8 from django.core.files.storage import FileSystemStorage, get_storage_class 7 from django.core.files.storage import FileSystemStorage 9 8 from django.core.management.base import CommandError, NoArgsCommand 10 9 from django.utils.encoding import smart_str, smart_unicode 11 10 12 from django.contrib.staticfiles import finders 11 from django.contrib.staticfiles import finders, storage 13 12 14 13 15 14 class Command(NoArgsCommand): … … class Command(NoArgsCommand): 18 17 locations to the settings.STATIC_ROOT. 19 18 """ 20 19 option_list = NoArgsCommand.option_list + ( 21 make_option('--noinput', action='store_false', dest='interactive', 22 default=True, help="Do NOT prompt the user for input of any kind."), 20 make_option('--noinput', 21 action='store_false', dest='interactive', default=True, 22 help="Do NOT prompt the user for input of any kind."), 23 23 make_option('-i', '--ignore', action='append', default=[], 24 24 dest='ignore_patterns', metavar='PATTERN', 25 25 help="Ignore files or directories matching this glob-style " 26 26 "pattern. Use multiple times to ignore more."), 27 make_option('-n', '--dry-run', action='store_true', dest='dry_run', 28 default=False, help="Do everything except modify the filesystem."), 29 make_option('-c', '--clear', action='store_true', dest='clear', 30 default=False, help="Clear the existing files using the storage " 31 "before trying to copy or link the original file."), 32 make_option('-l', '--link', action='store_true', dest='link', 33 default=False, help="Create a symbolic link to each file instead of copying."), 27 make_option('-n', '--dry-run', 28 action='store_true', dest='dry_run', default=False, 29 help="Do everything except modify the filesystem."), 30 make_option('-c', '--clear', 31 action='store_true', dest='clear', default=False, 32 help="Clear the existing files using the storage " 33 "before trying to copy or link the original file."), 34 make_option('-l', '--link', 35 action='store_true', dest='link', default=False, 36 help="Create a symbolic link to each file instead of copying."), 34 37 make_option('--no-default-ignore', action='store_false', 35 38 dest='use_default_ignore_patterns', default=True, 36 39 help="Don't ignore the common private glob-style patterns 'CVS', " 37 40 "'.*' and '*~'."), 38 41 ) 39 help = "Collect static files from apps and other locations in a single location." 42 help = "Collect static files in a single location." 43 storage = storage.configured_storage 40 44 41 45 def __init__(self, *args, **kwargs): 42 46 super(NoArgsCommand, self).__init__(*args, **kwargs) 43 47 self.copied_files = [] 44 48 self.symlinked_files = [] 45 49 self.unmodified_files = [] 46 self.storage = get_storage_class(settings.STATICFILES_STORAGE)()47 50 try: 48 51 self.storage.path('') 49 52 except NotImplementedError: … … Type 'yes' to continue, or 'no' to cancel: """ 104 107 105 108 handler = { 106 109 True: self.link_file, 107 False: self.copy_file 110 False: self.copy_file, 108 111 }[self.symlink] 109 112 110 113 for finder in finders.get_finders(): … … Type 'yes' to continue, or 'no' to cancel: """ 116 119 prefixed_path = path 117 120 handler(path, prefixed_path, storage) 118 121 119 actual_count = len(self.copied_files) + len(self.symlinked_files) 122 modified_files = self.copied_files + self.symlinked_files 123 actual_count = len(modified_files) 124 125 # Here we check if the storage backend has a post_process method 126 # and pass it the list of modified files, if possible. 127 if hasattr(self.storage, 'post_process'): 128 self.storage.post_process(modified_files) 129 120 130 unmodified_count = len(self.unmodified_files) 121 131 if self.verbosity >= 1: 122 132 self.stdout.write(smart_str(u"\n%s static file%s %s %s%s.\n" … … Type 'yes' to continue, or 'no' to cancel: """ 146 156 for f in files: 147 157 fpath = os.path.join(path, f) 148 158 if self.dry_run: 149 self.log(u"Pretending to delete '%s'" % smart_unicode(fpath), level=1) 159 self.log(u"Pretending to delete '%s'" % 160 smart_unicode(fpath), level=1) 150 161 else: 151 162 self.log(u"Deleting '%s'" % smart_unicode(fpath), level=1) 152 163 self.storage.delete(fpath) … … Type 'yes' to continue, or 'no' to cancel: """ 159 170 if self.storage.exists(prefixed_path): 160 171 try: 161 172 # When was the target file modified last time? 162 target_last_modified = self.storage.modified_time(prefixed_path) 173 target_last_modified = \ 174 self.storage.modified_time(prefixed_path) 163 175 except (OSError, NotImplementedError): 164 176 # The storage doesn't support ``modified_time`` or failed 165 177 pass … … Type 'yes' to continue, or 'no' to cancel: """ 177 189 full_path = None 178 190 # Skip the file if the source file is younger 179 191 if target_last_modified >= source_last_modified: 180 if not ((self.symlink and full_path and not os.path.islink(full_path)) or 181 (not self.symlink and full_path and os.path.islink(full_path))): 192 if not ((self.symlink and full_path 193 and not os.path.islink(full_path)) or 194 (not self.symlink and full_path 195 and os.path.islink(full_path))): 182 196 if prefixed_path not in self.unmodified_files: 183 197 self.unmodified_files.append(prefixed_path) 184 198 self.log(u"Skipping '%s' (not modified)" % path) -
django/contrib/staticfiles/storage.py
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index a69ae23..fdb8b3d 100644
a b 1 import hashlib 1 2 import os 3 import posixpath 4 import re 5 2 6 from django.conf import settings 7 from django.core.cache import (get_cache, InvalidCacheBackendError, 8 cache as default_cache) 3 9 from django.core.exceptions import ImproperlyConfigured 4 from django.core.files.storage import FileSystemStorage 10 from django.core.files.storage import FileSystemStorage, get_storage_class 11 from django.utils.encoding import force_unicode 12 from django.utils.functional import LazyObject 5 13 from django.utils.importlib import import_module 6 14 7 15 from django.contrib.staticfiles import utils … … class StaticFilesStorage(FileSystemStorage): 27 35 raise ImproperlyConfigured("You're using the staticfiles app " 28 36 "without having set the STATIC_URL setting.") 29 37 utils.check_settings() 30 super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs) 38 super(StaticFilesStorage, self).__init__(location, base_url, 39 *args, **kwargs) 40 41 42 class CachedFilesMixin(object): 43 cached_patterns = [ 44 r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", 45 r"""(@import\s*["']\s*(.*?)["'])""", 46 ] 47 48 def __init__(self, *args, **kwargs): 49 super(CachedFilesMixin, self).__init__(*args, **kwargs) 50 self.saved_files = [] 51 try: 52 self.cache = get_cache('staticfiles') 53 except InvalidCacheBackendError: 54 # Use the default backend 55 self.cache = default_cache 56 self._cached_patterns = [] 57 for pattern in self.cached_patterns: 58 self._cached_patterns.append(re.compile(pattern)) 59 60 def hashed_name(self, name, content=None): 61 if content is None: 62 if not self.exists(name): 63 raise ValueError("The file '%s' could not be found using %r." % 64 (name, self)) 65 try: 66 content = self.open(name) 67 except IOError, e: 68 # Handle directory paths 69 return name 70 path, filename = os.path.split(name) 71 root, ext = os.path.splitext(filename) 72 # Get the MD5 hash of the file 73 md5 = hashlib.md5() 74 for chunk in content.chunks(): 75 md5.update(chunk) 76 md5sum = md5.hexdigest()[:12] 77 return os.path.join(path, u"%s.%s%s" % (root, md5sum, ext)) 78 79 def cache_key(self, name): 80 return u'staticfiles:cache:%s' % name 81 82 def url(self, name, force=False): 83 """ 84 Returns the real URL in DEBUG mode. 85 """ 86 if settings.DEBUG and not force: 87 return super(CachedFilesMixin, self).url(name) 88 cache_key = self.cache_key(name) 89 hashed_name = self.cache.get(cache_key) 90 if hashed_name is None: 91 hashed_name = self.hashed_name(name) 92 return super(CachedFilesMixin, self).url(hashed_name) 93 94 def save(self, name, content): 95 original_name = super(CachedFilesMixin, self).save(name, content) 96 97 # Return the name if the file is already there 98 hashed_name = self.hashed_name(original_name, content) 99 if os.path.exists(hashed_name): 100 return hashed_name 101 102 # or save the file with the hashed name 103 saved_name = self._save(hashed_name, content) 104 105 # Use filenames with forward slashes, even on Windows 106 hashed_name = force_unicode(saved_name.replace('\\', '/')) 107 self.cache.set(self.cache_key(name), hashed_name) 108 self.saved_files.append((name, hashed_name)) 109 return hashed_name 110 111 def url_converter(self, name): 112 """ 113 Returns the custom URL converter for the given file name. 114 """ 115 def converter(matchobj): 116 """ 117 Converts the matched URL depending on the parent level (`..`) 118 and returns the normalized and hashed URL using the url method 119 of the storage. 120 """ 121 matched, url = matchobj.groups() 122 # Completely ignore http(s) prefixed URLs 123 if url.startswith(('http', 'https')): 124 return matched 125 name_parts = name.split('/') 126 # Using posix normpath here to remove duplicates 127 result = url_parts = posixpath.normpath(url).split('/') 128 level = url.count('..') 129 if level: 130 result = name_parts[:-level-1] + url_parts[level:] 131 elif name_parts[:-1]: 132 result = name_parts[:-1] + url_parts[-1:] 133 joined_result = '/'.join(result) 134 hashed_url = self.url(joined_result, force=True) 135 # Return the hashed and normalized version to the file 136 return 'url("%s")' % hashed_url 137 return converter 138 139 def path_level(self, (name, hashed_name)): 140 return len(name.split('/')) 141 142 def post_process(self, paths): 143 """ 144 Post process method called by the collectstatic management command. 145 """ 146 self.cache.delete_many([self.cache_key(path) for path in paths]) 147 for name, hashed_name in sorted( 148 self.saved_files, key=self.path_level, reverse=True): 149 with self.open(name) as original_file: 150 content = original_file.read() 151 for pattern in self._cached_patterns: 152 content = pattern.sub(self.url_converter(name), content) 153 with open(self.path(hashed_name), 'w') as hashed_file: 154 hashed_file.write(content) 155 156 157 class CachedStaticFilesStorage(CachedFilesMixin, StaticFilesStorage): 158 """ 159 A static file system storage backend which also saves 160 hashed copies of the files it saves. 161 """ 162 pass 31 163 32 164 33 165 class AppStaticStorage(FileSystemStorage): … … class AppStaticStorage(FileSystemStorage): 47 179 mod_path = os.path.dirname(mod.__file__) 48 180 location = os.path.join(mod_path, self.source_dir) 49 181 super(AppStaticStorage, self).__init__(location, *args, **kwargs) 182 183 184 class ConfiguredStorage(LazyObject): 185 def _setup(self): 186 self._wrapped = get_storage_class(settings.STATICFILES_STORAGE)() 187 188 configured_storage = ConfiguredStorage() -
new file django/contrib/staticfiles/templatetags/staticfiles.py
diff --git a/django/contrib/staticfiles/templatetags/__init__.py b/django/contrib/staticfiles/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django/contrib/staticfiles/templatetags/staticfiles.py b/django/contrib/staticfiles/templatetags/staticfiles.py new file mode 100644 index 0000000..80cf53e
- + 1 from django import template 2 from django.contrib.staticfiles.storage import configured_storage 3 4 register = template.Library() 5 6 7 @register.simple_tag 8 def static(path): 9 """ 10 A template tag that returns the URL to a file 11 using staticfiles' storage backend 12 """ 13 return configured_storage.url(path) -
docs/howto/static-files.txt
diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 16f8ac4..465b5ba 100644
a b Basic usage 70 70 71 71 <img src="{{ STATIC_URL }}images/hi.jpg" /> 72 72 73 See :ref:`staticfiles-in-templates` for more details, includingan73 See :ref:`staticfiles-in-templates` for more details, **including** an 74 74 alternate method using a template tag. 75 75 76 76 Deploying static files in a nutshell … … A far better way is to use the value of the :setting:`STATIC_URL` setting 143 143 directly in your templates. This means that a switch of static files servers 144 144 only requires changing that single value. Much better! 145 145 146 ``staticfiles`` includes two built-in ways of getting atthis setting in your146 Django includes multiple built-in ways of using this setting in your 147 147 templates: a context processor and a template tag. 148 148 149 149 With a context processor … … but in views written by hand you'll need to explicitly use ``RequestContext`` 180 180 To see how that works, and to read more details, check out 181 181 :ref:`subclassing-context-requestcontext`. 182 182 183 Another option is the :ttag:`get_static_prefix` template tag that is part of 184 Django's core. 185 183 186 With a template tag 184 187 ------------------- 185 188 186 To easily link to static files Django ships with a :ttag:`static` template tag. 189 The more powerful tool is the :ttag:`static<staticfiles-static>` template 190 tag. It builds the URL for the given relative path by using the configured 191 :setting:`STATICFILES_STORAGE` storage. 187 192 188 193 .. code-block:: html+django 189 194 190 {% load static %}195 {% load staticfiles %} 191 196 <img src="{% static "images/hi.jpg" %}" /> 192 197 193 198 It is also able to consume standard context variables, e.g. assuming a … … It is also able to consume standard context variables, e.g. assuming a 195 200 196 201 .. code-block:: html+django 197 202 198 {% load static %}203 {% load staticfiles %} 199 204 <link rel="stylesheet" href="{% static user_stylesheet %}" type="text/css" media="screen" /> 200 205 201 Another option is the :ttag:`get_static_prefix` template tag. You can use 202 this if you're not using :class:`~django.template.RequestContext` (and 203 therefore not relying on the ``django.core.context_processors.static`` 204 context processor), or if you need more control over exactly where and how 205 :setting:`STATIC_URL` is injected into the template. Here's an example: 206 207 .. code-block:: html+django 208 209 {% load static %} 210 <img src="{% get_static_prefix %}images/hi.jpg" /> 211 212 There's also a second form you can use to avoid extra processing if you need 213 the value multiple times: 214 215 .. code-block:: html+django 206 .. note:: 216 207 217 {% load static %} 218 {% get_static_prefix as STATIC_PREFIX %} 208 There is also a template tag named :ttag:`static` in Django's core set 209 of :ref:`built in template tags<ref-templates-builtins-tags>` which has 210 the same argument signature but only uses `urlparse.urljoin()`_ with the 211 :setting:`STATIC_URL` setting and the given path. This has the 212 disadvantage of not being able to easily switch the storage backend 213 without changing the templates, so in doubt use the ``staticfiles`` 214 :ttag:`static<staticfiles-static>` 215 template tag. 219 216 220 <img src="{{ STATIC_PREFIX }}images/hi.jpg" /> 221 <img src="{{ STATIC_PREFIX }}images/hi2.jpg" /> 217 .. _`urlparse.urljoin()`: http://docs.python.org/library/urlparse.html#urlparse.urljoin 222 218 223 219 .. _staticfiles-development: 224 220 -
docs/ref/contrib/staticfiles.txt
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 5ab3c1e..abf8c58 100644
a b Example usage:: 237 237 238 238 django-admin.py runserver --insecure 239 239 240 .. currentmodule:: None 240 Storages 241 ======== 242 243 StaticFilesStorage 244 ------------------ 245 246 .. class:: storage.StaticFilesStorage 247 248 A subclass of the :class:`~django.core.files.storage.FileSystemStorage` 249 storage backend that uses the :setting:`STATIC_ROOT` setting as the base 250 file system location and the :setting:`STATIC_URL` setting respectively 251 as the base URL. 252 253 CachedStaticFilesStorage 254 ------------------------ 255 256 .. class:: storage.CachedStaticFilesStorage 257 258 A subclass of the :class:`~django.contrib.staticfiles.storage.StaticFilesStorage` 259 storage backend which caches the files it saves by appending the MD5 hash of 260 the file's content to the filename. For example, the file ``css/styles.css`` 261 would also be saved as ``css/styles.55e7cbb9ba48.css``. 262 263 The purpose of this storage is to keep serving the old files in case some 264 pages still refer to those files, e.g. because they are cached by you or a 265 3rd party proxy server. Additionally, it's very helpful if you want to apply 266 `far future Expires headers`_ to the deployed files to speed up the load time 267 for subsequent page visits. 268 269 The storage backend automatically replaces the paths found in the saved files 270 matching other saved files with the path of the cached copy. The regular 271 expressions used to find those paths 272 (``django.contrib.staticfiles.storage.CachedStaticFilesStorage.cached_patterns``) 273 by default cover the `@import`_ rule and `url()`_ statement of `Cascading 274 Style Sheets`_. For example, the ``'css/styles.css'`` file with the content 275 276 .. code-block:: css+django 277 278 @import url("../admin/css/base.css"); 279 280 would be replaced by calling the :meth:`~django.core.files.storage.Storage.url` 281 method of the ``CachedStaticFilesStorage`` storage backend, ultimatively 282 saving a ``'css/styles.55e7cbb9ba48.css'`` file with the following content: 283 284 .. code-block:: css+django 285 286 @import url("/static/admin/css/base.27e20196a850.css"); 287 288 To enable the ``CachedStaticFilesStorage`` you have to make sure the 289 following requirements are met: 290 291 * the :setting:`STATICFILES_STORAGE` setting is set to 292 ``'django.contrib.staticfiles.storage.CachedStaticFilesStorage'`` 293 * the :setting:`DEBUG` setting is set to ``False`` 294 * you use the ``staticfiles`` :ttag:`static<staticfiles-static>` template tag 295 to refer to your static files in your templates 296 * you've collected all your static files by using the :djadmin:`collectstatic` 297 management command 298 299 Since creating the MD5 hash can be a performance burden to your website during 300 runtime, ``staticfiles`` will automatically try to cache the hashed name for 301 each file path using Django's :doc:`caching framework</topics/cache>`. If you 302 want to override certain options of the cache backend the storage uses, 303 simply specify a custom entry in the :setting:`CACHES` setting named 304 ``'staticfiles'``. It falls back to using the default cache backend. 305 306 .. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires 307 .. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import 308 .. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri 309 .. _`Cascading Style Sheets`: http://www.w3.org/Style/CSS/ 310 311 .. currentmodule:: django.contrib.staticfiles.templatetags.staticfiles 312 313 Template tags 314 ============= 315 316 static 317 ------ 318 319 .. templatetag:: staticfiles-static 320 321 .. versionadded:: 1.4 322 323 Uses the configued :setting:`STATICFILES_STORAGE` storage to create the 324 full URL for the given relative path, e.g.: 325 326 .. code-block:: html+django 327 328 {% load static from staticfiles %} 329 <img src="{% static "css/base.css" %}" /> 330 331 The previous example is equal to calling the ``url`` method of an instance of 332 :setting:`STATICFILES_STORAGE` with ``"css/base.css"``. This is especially 333 useful when using a non-local storage backend to deploy files as documented 334 in :ref:`staticfiles-from-cdn`. 241 335 242 336 Other Helpers 243 337 ============= … … files: 251 345 with :class:`~django.template.RequestContext` contexts. 252 346 253 347 - The builtin template tag :ttag:`static` which takes a path and 254 joins it with thethe static prefix :setting:`STATIC_URL`.348 urljoins it with the static prefix :setting:`STATIC_URL`. 255 349 256 350 - The builtin template tag :ttag:`get_static_prefix` which populates a 257 351 template variable with the static prefix :setting:`STATIC_URL` to be -
docs/ref/templates/builtins.txt
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 5c08c66..a7d548a 100644
a b static 2353 2353 2354 2354 .. highlight:: html+django 2355 2355 2356 To link to static files Django ships with a :ttag:`static` template tag. You2357 can use this regardless if you're using :class:`~django.template.RequestContext` 2358 or not.2356 To link to static files that are saved in :setting:`STATIC_ROOT` Django ships 2357 with a :ttag:`static` template tag. You can use this regardless if you're 2358 using :class:`~django.template.RequestContext` or not. 2359 2359 2360 2360 .. code-block:: html+django 2361 2361 … … It is also able to consume standard context variables, e.g. assuming a 2370 2370 {% load static %} 2371 2371 <link rel="stylesheet" href="{% static user_stylesheet %}" type="text/css" media="screen" /> 2372 2372 2373 .. note:: 2374 2375 The :mod:`staticfiles<django.contrib.staticfiles>` contrib app also ships 2376 with a :ttag:`static template tag<staticfiles-static>` which uses 2377 ``staticfiles'`` :setting:`STATICFILES_STORAGE` to build the URL of the 2378 given path. Use that instead if you have an advanced use case such as 2379 :ref:`using a cloud service to serve static files<staticfiles-from-cdn>`:: 2380 2381 {% load static from staticfiles %} 2382 <img src="{% static "images/hi.jpg" %}" /> 2383 2373 2384 .. templatetag:: get_static_prefix 2374 2385 2375 2386 get_static_prefix -
docs/releases/1.4.txt
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 2723b42..99f08d3 100644
a b Additionally, it's now possible to define translatable URL patterns using 212 212 :ref:`url-internationalization` for more information about the language prefix 213 213 and how to internationalize URL patterns. 214 214 215 ``static`` template tag 216 ~~~~~~~~~~~~~~~~~~~~~~~ 217 218 The :mod:`staticfiles<django.contrib.staticfiles>` contrib app has now a new 219 :ttag:`static template tag<staticfiles-static>` to refer to files saved with 220 the :setting:`STATICFILES_STORAGE` storage backend. It'll use the storage 221 ``url`` method and therefore supports advanced features such as 222 :ref:`serving files from a cloud service<staticfiles-from-cdn>`. 223 224 ``CachedStaticFilesStorage`` storage backend 225 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 226 227 Additional to the `static template tag`_ the 228 :mod:`staticfiles<django.contrib.staticfiles>` contrib app now has a 229 :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` which 230 caches the files it saves (when running the :djadmin:`collectstatic` 231 management command) by appending the MD5 hash of the file's content to the 232 filename. For example, the file ``css/styles.css`` would also be saved as 233 ``css/styles.55e7cbb9ba48.css`` 234 235 See the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` 236 docs for more information. 237 215 238 Minor features 216 239 ~~~~~~~~~~~~~~ 217 240 -
new file tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css
diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/absolute.css new file mode 100644 index 0000000..e64e7cc
- + 1 @import url("/static/cached/styles.css"); -
new file tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css
diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/denorm.css new file mode 100644 index 0000000..27b9a34
- + 1 @import url("..//cached///styles.css"); -
new file tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css
diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/other.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/other.css new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/relative.css new file mode 100644 index 0000000..40c4a25
- + 1 @import url("../cached/styles.css"); 2 @import url("absolute.css"); 3 No newline at end of file -
new file tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css
diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/styles.css new file mode 100644 index 0000000..84936d1
- + 1 @import url("cached/other.css"); 2 No newline at end of file -
new file tests/regressiontests/staticfiles_tests/project/documents/cached/url.css
diff --git a/tests/regressiontests/staticfiles_tests/project/documents/cached/url.css b/tests/regressiontests/staticfiles_tests/project/documents/cached/url.css new file mode 100644 index 0000000..184e254
- + 1 @import url("https://www.djangoproject.com/m/css/base.css"); 2 No newline at end of file -
new file tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt
diff --git a/tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt b/tests/regressiontests/staticfiles_tests/project/site_media/static/testfile.txt new file mode 100644 index 0000000..4d92dbe
- + 1 Test! 2 No newline at end of file -
tests/regressiontests/staticfiles_tests/tests.py
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py index 77b771d..ed276bb 100644
a b import sys 8 8 import tempfile 9 9 from StringIO import StringIO 10 10 11 from django.template import loader, Context 11 12 from django.conf import settings 12 from django.core.exceptions import ImproperlyConfigured 13 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation 13 14 from django.core.files.storage import default_storage 14 15 from django.core.management import call_command 15 16 from django.test import TestCase … … class StaticFilesTestCase(TestCase): 48 49 def assertFileNotFound(self, filepath): 49 50 self.assertRaises(IOError, self._get_file, filepath) 50 51 52 def render_template(self, template, **kwargs): 53 if isinstance(template, basestring): 54 template = loader.get_template_from_string(template) 55 return template.render(Context(kwargs)).strip() 56 57 def assertTemplateRenders(self, template, result, **kwargs): 58 self.assertEqual(self.render_template(template, **kwargs), result) 59 60 def assertTemplateRaises(self, exc, template, result, **kwargs): 61 self.assertRaises(exc, self.assertTemplateRenders, template, result, **kwargs) 62 63 51 64 StaticFilesTestCase = override_settings( 52 65 DEBUG = True, 53 66 MEDIA_URL = '/media/', … … TestBuildStaticNonLocalStorage = override_settings( 257 270 )(TestBuildStaticNonLocalStorage) 258 271 259 272 273 class TestBuildStaticCachedStorage(BuildStaticTestCase): 274 """ 275 Tests for the Cache busting storage 276 """ 277 @classmethod 278 def setUpClass(cls): 279 storage.configured_storage._wrapped = empty 280 281 def tearDown(self): 282 storage.configured_storage._wrapped = empty 283 284 def cached_file_path(self, relpath): 285 template = "{%% load static from staticfiles %%}{%% static '%s' %%}" 286 fullpath = self.render_template(template % relpath) 287 return fullpath.replace(settings.STATIC_URL, '') 288 289 def test_template_tag_return(self): 290 """ 291 Test the CachedStaticFilesStorage backend. 292 """ 293 self.assertTemplateRaises(SuspiciousOperation, """ 294 {% load static from staticfiles %}{% static "does/not/exist.png" %} 295 """, "/static/does/not/exist.png") 296 self.assertTemplateRenders(""" 297 {% load static from staticfiles %}{% static "test/file.txt" %} 298 """, "/static/test/file.dad0999e4f8f.txt") 299 self.assertTemplateRenders(""" 300 {% load static from staticfiles %}{% static "cached/styles.css" %} 301 """, "/static/cached/styles.5653c259030b.css") 302 303 def test_template_tag_simple_content(self): 304 relpath = self.cached_file_path("cached/styles.css") 305 self.assertEqual(relpath, "cached/styles.5653c259030b.css") 306 with storage.configured_storage.open(relpath) as relfile: 307 content = relfile.read() 308 self.assertFalse("cached/other.css" in content) 309 self.assertTrue("/static/cached/other.d41d8cd98f00.css" in content) 310 311 def test_template_tag_absolute(self): 312 relpath = self.cached_file_path("cached/absolute.css") 313 self.assertEqual(relpath, "cached/absolute.cc80cb5e2eb1.css") 314 with storage.configured_storage.open(relpath) as relfile: 315 content = relfile.read() 316 self.assertFalse("/static/cached/styles.css" in content) 317 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 318 319 def test_template_tag_denorm(self): 320 relpath = self.cached_file_path("cached/denorm.css") 321 self.assertEqual(relpath, "cached/denorm.363de96e9b4b.css") 322 with storage.configured_storage.open(relpath) as relfile: 323 content = relfile.read() 324 self.assertFalse("..//cached///styles.css" in content) 325 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 326 327 def test_template_tag_relative(self): 328 relpath = self.cached_file_path("cached/relative.css") 329 self.assertEqual(relpath, "cached/relative.298ff891a8d4.css") 330 with storage.configured_storage.open(relpath) as relfile: 331 content = relfile.read() 332 self.assertFalse("../cached/styles.css" in content) 333 self.assertFalse('@import "styles.css"' in content) 334 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 335 336 def test_template_tag_url(self): 337 relpath = self.cached_file_path("cached/url.css") 338 self.assertEqual(relpath, "cached/url.615e21601e4b.css") 339 with storage.configured_storage.open(relpath) as relfile: 340 self.assertTrue("https://" in relfile.read()) 341 342 343 TestBuildStaticCachedStorage = override_settings( 344 STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage', 345 )(TestBuildStaticCachedStorage) 346 347 260 348 if sys.platform != 'win32': 261 349 class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults): 262 350 """ … … class TestStaticfilesDirsType(TestCase): 418 506 TestStaticfilesDirsType = override_settings( 419 507 STATICFILES_DIRS = 'a string', 420 508 )(TestStaticfilesDirsType) 509 510 511 class TestTemplateTag(StaticFilesTestCase): 512 513 def test_template_tag(self): 514 self.assertTemplateRenders(""" 515 {% load static from staticfiles %}{% static "does/not/exist.png" %} 516 """, "/static/does/not/exist.png") 517 self.assertTemplateRenders(""" 518 {% load static from staticfiles %}{% static "testfile.txt" %} 519 """, "/static/testfile.txt")