Ticket #15252: 15252.4.diff
File 15252.4.diff, 60.3 KB (added by , 13 years ago) |
---|
-
django/contrib/admin/helpers.py
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index d15716b..04a3492 100644
a b 1 1 from django import forms 2 2 from django.contrib.admin.util import (flatten_fieldsets, lookup_field, 3 3 display_for_field, label_for_field, help_text_for_field) 4 from django.contrib.admin.templatetags.admin_static import static 4 5 from django.contrib.contenttypes.models import ContentType 5 6 from django.core.exceptions import ObjectDoesNotExist 6 7 from django.db.models.fields.related import ManyToManyRel … … class Fieldset(object): 75 76 def _media(self): 76 77 if 'collapse' in self.classes: 77 78 js = ['jquery.min.js', 'jquery.init.js', 'collapse.min.js'] 78 return forms.Media(js=[ 'admin/js/%s' % urlfor url in js])79 return forms.Media(js=[static('admin/js/%s' % url) for url in js]) 79 80 return forms.Media() 80 81 media = property(_media) 81 82 -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 35c3cde..81e8ae5 100644
a b from django.forms.models import (modelform_factory, modelformset_factory, 6 6 from django.contrib.contenttypes.models import ContentType 7 7 from django.contrib.admin import widgets, helpers 8 8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict 9 from django.contrib.admin.templatetags.admin_static import static 9 10 from django.contrib import messages 10 11 from django.views.decorators.csrf import csrf_protect 11 12 from django.core.exceptions import PermissionDenied, ValidationError … … class ModelAdmin(BaseModelAdmin): 350 351 return self.get_urls() 351 352 urls = property(urls) 352 353 353 def _media(self): 354 @property 355 def media(self): 354 356 js = [ 355 357 'core.js', 356 358 'admin/RelatedObjectLookups.js', … … class ModelAdmin(BaseModelAdmin): 363 365 js.extend(['urlify.js', 'prepopulate.min.js']) 364 366 if self.opts.get_ordered_objects(): 365 367 js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js']) 366 return forms.Media(js=['admin/js/%s' % url for url in js]) 367 media = property(_media) 368 return forms.Media(js=[static('admin/js/%s' % url) for url in js]) 368 369 369 370 def has_add_permission(self, request): 370 371 """ … … class InlineModelAdmin(BaseModelAdmin): 1322 1323 if self.verbose_name_plural is None: 1323 1324 self.verbose_name_plural = self.model._meta.verbose_name_plural 1324 1325 1325 def _media(self): 1326 @property 1327 def media(self): 1326 1328 js = ['jquery.min.js', 'jquery.init.js', 'inlines.min.js'] 1327 1329 if self.prepopulated_fields: 1328 1330 js.extend(['urlify.js', 'prepopulate.min.js']) 1329 1331 if self.filter_vertical or self.filter_horizontal: 1330 1332 js.extend(['SelectBox.js', 'SelectFilter2.js']) 1331 return forms.Media(js=['admin/js/%s' % url for url in js]) 1332 media = property(_media) 1333 return forms.Media(js=[static('admin/js/%s' % url) for url in js]) 1333 1334 1334 1335 def get_formset(self, request, obj=None, **kwargs): 1335 1336 """Returns a BaseInlineFormSet class for use in admin add/change views.""" -
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..0d1f2a9 100644
a b Form Widget classes specific to the Django admin site. 4 4 5 5 import copy 6 6 from django import forms 7 from django.contrib.admin.templatetags.admin_static import static 7 8 from django.core.urlresolvers import reverse 8 9 from django.forms.widgets import RadioFieldRenderer 9 10 from django.forms.util import flatatt 10 from django.templatetags.static import static11 11 from django.utils.html import escape 12 12 from django.utils.text import Truncator 13 13 from django.utils.translation import ugettext as _ 14 14 from django.utils.safestring import mark_safe 15 15 from django.utils.encoding import force_unicode 16 16 17 17 18 class FilteredSelectMultiple(forms.SelectMultiple): 18 19 """ 19 20 A SelectMultiple with a JavaScript filter interface. … … class FilteredSelectMultiple(forms.SelectMultiple): 21 22 Note that the resulting JavaScript assumes that the jsi18n 22 23 catalog has been loaded in the page 23 24 """ 24 class Media: 25 js = ["admin/js/%s" % path 26 for path in ["core.js", "SelectBox.js", "SelectFilter2.js"]] 25 @property 26 def media(self): 27 js = ["core.js", "SelectBox.js", "SelectFilter2.js"] 28 return forms.Media(js=[static("admin/js/%s" % path) for path in js]) 27 29 28 30 def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): 29 31 self.verbose_name = verbose_name … … class FilteredSelectMultiple(forms.SelectMultiple): 31 33 super(FilteredSelectMultiple, self).__init__(attrs, choices) 32 34 33 35 def render(self, name, value, attrs=None, choices=()): 34 if attrs is None: attrs = {} 36 if attrs is None: 37 attrs = {} 35 38 attrs['class'] = 'selectfilter' 36 if self.is_stacked: attrs['class'] += 'stacked' 39 if self.is_stacked: 40 attrs['class'] += 'stacked' 37 41 output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] 38 42 output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {') 39 43 # TODO: "id_" is hard-coded here. This should instead use the correct … … class FilteredSelectMultiple(forms.SelectMultiple): 43 47 return mark_safe(u''.join(output)) 44 48 45 49 class AdminDateWidget(forms.DateInput): 46 class Media: 47 js = ["admin/js/calendar.js", "admin/js/admin/DateTimeShortcuts.js"] 50 51 @property 52 def media(self): 53 js = ["calendar.js", "admin/DateTimeShortcuts.js"] 54 return forms.Media(js=[static("admin/js/%s" % path) for path in js]) 48 55 49 56 def __init__(self, attrs={}, format=None): 50 57 super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}, format=format) 51 58 52 59 class AdminTimeWidget(forms.TimeInput): 53 class Media: 54 js = ["admin/js/calendar.js", "admin/js/admin/DateTimeShortcuts.js"] 60 61 @property 62 def media(self): 63 js = ["calendar.js", "admin/DateTimeShortcuts.js"] 64 return forms.Media(js=[static("admin/js/%s" % path) for path in js]) 55 65 56 66 def __init__(self, attrs={}, format=None): 57 67 super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}, format=format) … … class RelatedFieldWidgetWrapper(forms.Widget): 232 242 memo[id(self)] = obj 233 243 return obj 234 244 235 def _media(self): 245 @property 246 def media(self): 236 247 return self.widget.media 237 media = property(_media)238 248 239 249 def render(self, name, value, *args, **kwargs): 240 250 rel_to = self.rel.to -
django/contrib/staticfiles/finders.py
diff --git a/django/contrib/staticfiles/finders.py b/django/contrib/staticfiles/finders.py index ffb96e0..45bf4a1 100644
a b class BaseFinder(object): 28 28 """ 29 29 raise NotImplementedError() 30 30 31 def list(self, ignore_patterns =[]):31 def list(self, ignore_patterns): 32 32 """ 33 33 Given an optional list of paths to ignore, this should return 34 34 a two item iterable consisting of the relative path and storage -
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..6ceddca 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 locationsin a single location."42 help = "Collect static files in a single location." 40 43 41 44 def __init__(self, *args, **kwargs): 42 45 super(NoArgsCommand, self).__init__(*args, **kwargs) 43 46 self.copied_files = [] 44 47 self.symlinked_files = [] 45 48 self.unmodified_files = [] 46 self.storage = get_storage_class(settings.STATICFILES_STORAGE)()49 self.storage = storage.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 113 found_files = [] 110 114 for finder in finders.get_finders(): 111 115 for path, storage in finder.list(self.ignore_patterns): 112 116 # Prefix the relative path if the source storage contains it … … Type 'yes' to continue, or 'no' to cancel: """ 114 118 prefixed_path = os.path.join(storage.prefix, path) 115 119 else: 116 120 prefixed_path = path 121 found_files.append(prefixed_path) 117 122 handler(path, prefixed_path, storage) 118 123 119 actual_count = len(self.copied_files) + len(self.symlinked_files) 124 # Here we check if the storage backend has a post_process 125 # method and pass it the list of modified files. 126 if hasattr(self.storage, 'post_process'): 127 post_processed = self.storage.post_process(found_files, **options) 128 for path in post_processed: 129 self.log(u"Post-processed '%s'" % path, level=1) 130 else: 131 post_processed = [] 132 133 modified_files = self.copied_files + self.symlinked_files 134 actual_count = len(modified_files) 120 135 unmodified_count = len(self.unmodified_files) 136 121 137 if self.verbosity >= 1: 122 self.stdout.write(smart_str(u"\n%s static file%s %s %s%s.\n" 123 % (actual_count, 124 actual_count != 1 and 's' or '', 125 self.symlink and 'symlinked' or 'copied', 126 destination_path and "to '%s'" 127 % destination_path or '', 128 unmodified_count and ' (%s unmodified)' 129 % unmodified_count or ''))) 138 template = ("\n%(actual_count)s %(identifier)s %(action)s" 139 "%(destination)s%(unmodified)s.\n") 140 summary = template % { 141 'actual_count': actual_count, 142 'identifier': 'static file' + (actual_count > 1 and 's' or ''), 143 'action': self.symlink and 'symlinked' or 'copied', 144 'destination': (destination_path and " to '%s'" 145 % destination_path or ''), 146 'unmodified': (self.unmodified_files and ', %s unmodified' 147 % unmodified_count or ''), 148 } 149 self.stdout.write(smart_str(summary)) 130 150 131 151 def log(self, msg, level=2): 132 152 """ … … Type 'yes' to continue, or 'no' to cancel: """ 146 166 for f in files: 147 167 fpath = os.path.join(path, f) 148 168 if self.dry_run: 149 self.log(u"Pretending to delete '%s'" % smart_unicode(fpath), level=1) 169 self.log(u"Pretending to delete '%s'" % 170 smart_unicode(fpath), level=1) 150 171 else: 151 172 self.log(u"Deleting '%s'" % smart_unicode(fpath), level=1) 152 173 self.storage.delete(fpath) … … Type 'yes' to continue, or 'no' to cancel: """ 159 180 if self.storage.exists(prefixed_path): 160 181 try: 161 182 # When was the target file modified last time? 162 target_last_modified = self.storage.modified_time(prefixed_path) 183 target_last_modified = \ 184 self.storage.modified_time(prefixed_path) 163 185 except (OSError, NotImplementedError): 164 186 # The storage doesn't support ``modified_time`` or failed 165 187 pass … … Type 'yes' to continue, or 'no' to cancel: """ 177 199 full_path = None 178 200 # Skip the file if the source file is younger 179 201 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))): 202 if not ((self.symlink and full_path 203 and not os.path.islink(full_path)) or 204 (not self.symlink and full_path 205 and os.path.islink(full_path))): 182 206 if prefixed_path not in self.unmodified_files: 183 207 self.unmodified_files.append(prefixed_path) 184 208 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..960f581 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.base import ContentFile 11 from django.core.files.storage import FileSystemStorage, get_storage_class 12 from django.utils.encoding import force_unicode 13 from django.utils.functional import LazyObject 5 14 from django.utils.importlib import import_module 15 from django.utils.datastructures import SortedDict 6 16 7 from django.contrib.staticfiles import utils17 from django.contrib.staticfiles.utils import check_settings, matches_patterns 8 18 9 19 10 20 class StaticFilesStorage(FileSystemStorage): … … class StaticFilesStorage(FileSystemStorage): 26 36 if base_url is None: 27 37 raise ImproperlyConfigured("You're using the staticfiles app " 28 38 "without having set the STATIC_URL setting.") 29 utils.check_settings() 30 super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs) 39 check_settings() 40 super(StaticFilesStorage, self).__init__(location, base_url, 41 *args, **kwargs) 42 43 44 class CachedFilesMixin(object): 45 patterns = ( 46 ("*.css", ( 47 r"""(url\(['"]{0,1}\s*(.*?)["']{0,1}\))""", 48 r"""(@import\s*["']\s*(.*?)["'])""", 49 )), 50 ) 51 52 def __init__(self, *args, **kwargs): 53 super(CachedFilesMixin, self).__init__(*args, **kwargs) 54 try: 55 self.cache = get_cache('staticfiles') 56 except InvalidCacheBackendError: 57 # Use the default backend 58 self.cache = default_cache 59 self._patterns = SortedDict() 60 for extension, patterns in self.patterns: 61 for pattern in patterns: 62 compiled = re.compile(pattern) 63 self._patterns.setdefault(extension, []).append(compiled) 64 65 def hashed_name(self, name, content=None): 66 if content is None: 67 if not self.exists(name): 68 raise ValueError("The file '%s' could not be found with %r." % 69 (name, self)) 70 try: 71 content = self.open(name) 72 except IOError: 73 # Handle directory paths 74 return name 75 path, filename = os.path.split(name) 76 root, ext = os.path.splitext(filename) 77 # Get the MD5 hash of the file 78 md5 = hashlib.md5() 79 for chunk in content.chunks(): 80 md5.update(chunk) 81 md5sum = md5.hexdigest()[:12] 82 return os.path.join(path, u"%s.%s%s" % (root, md5sum, ext)) 83 84 def cache_key(self, name): 85 return u'staticfiles:cache:%s' % name 86 87 def url(self, name, force=False): 88 """ 89 Returns the real URL in DEBUG mode. 90 """ 91 if settings.DEBUG and not force: 92 return super(CachedFilesMixin, self).url(name) 93 cache_key = self.cache_key(name) 94 hashed_name = self.cache.get(cache_key) 95 if hashed_name is None: 96 hashed_name = self.hashed_name(name) 97 return super(CachedFilesMixin, self).url(hashed_name) 98 99 def url_converter(self, name): 100 """ 101 Returns the custom URL converter for the given file name. 102 """ 103 def converter(matchobj): 104 """ 105 Converts the matched URL depending on the parent level (`..`) 106 and returns the normalized and hashed URL using the url method 107 of the storage. 108 """ 109 matched, url = matchobj.groups() 110 # Completely ignore http(s) prefixed URLs 111 if url.startswith(('http', 'https')): 112 return matched 113 name_parts = name.split('/') 114 # Using posix normpath here to remove duplicates 115 result = url_parts = posixpath.normpath(url).split('/') 116 level = url.count('..') 117 if level: 118 result = name_parts[:-level - 1] + url_parts[level:] 119 elif name_parts[:-1]: 120 result = name_parts[:-1] + url_parts[-1:] 121 joined_result = '/'.join(result) 122 hashed_url = self.url(joined_result, force=True) 123 # Return the hashed and normalized version to the file 124 return 'url("%s")' % hashed_url 125 return converter 126 127 def post_process(self, paths, dry_run=False, **options): 128 """ 129 Post process the given list of files (called from collectstatic). 130 """ 131 processed_files = [] 132 # don't even dare to process the files if we're in dry run mode 133 if dry_run: 134 return processed_files 135 136 # delete cache of all handled paths 137 self.cache.delete_many([self.cache_key(path) for path in paths]) 138 139 # only try processing the files we have patterns for 140 matches = lambda path: matches_patterns(path, self._patterns.keys()) 141 processing_paths = [path for path in paths if matches(path)] 142 143 # then sort the files by the directory level 144 path_level = lambda name: len(name.split('/')) 145 for name in sorted(paths, key=path_level, reverse=True): 146 with self.open(name) as original_file: 147 148 # first get the original's file content 149 content = original_file.read() 150 151 # and a hashed name for it 152 hashed_name = self.hashed_name(name, ContentFile(content)) 153 154 # then apply each replacement pattern on the content 155 if name in processing_paths: 156 converter = self.url_converter(name) 157 for patterns in self._patterns.values(): 158 for pattern in patterns: 159 content = pattern.sub(converter, content) 160 161 # and save the result 162 if self.exists(hashed_name): 163 self.delete(hashed_name) 164 saved_name = self._save(hashed_name, ContentFile(content)) 165 hashed_name = force_unicode(saved_name.replace('\\', '/')) 166 processed_files.append(hashed_name) 167 self.cache.set(self.cache_key(name), hashed_name) 168 169 return processed_files 170 171 class CachedStaticFilesStorage(CachedFilesMixin, StaticFilesStorage): 172 """ 173 A static file system storage backend which also saves 174 hashed copies of the files it saves. 175 """ 176 pass 31 177 32 178 33 179 class AppStaticStorage(FileSystemStorage): … … class AppStaticStorage(FileSystemStorage): 47 193 mod_path = os.path.dirname(mod.__file__) 48 194 location = os.path.join(mod_path, self.source_dir) 49 195 super(AppStaticStorage, self).__init__(location, *args, **kwargs) 196 197 198 class ConfiguredStorage(LazyObject): 199 def _setup(self): 200 self._wrapped = get_storage_class(settings.STATICFILES_STORAGE)() 201 202 staticfiles_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..788f06e
- + 1 from django import template 2 from django.contrib.staticfiles.storage import staticfiles_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 staticfiles_storage.url(path) -
django/contrib/staticfiles/utils.py
diff --git a/django/contrib/staticfiles/utils.py b/django/contrib/staticfiles/utils.py index 9ff4bc4..c175eda 100644
a b import fnmatch 3 3 from django.conf import settings 4 4 from django.core.exceptions import ImproperlyConfigured 5 5 6 def is_ignored(path, ignore_patterns=[]):6 def matches_patterns(path, ignore_patterns=[]): 7 7 """ 8 8 Return True or False depending on whether the ``path`` should be 9 9 ignored (if it matches any pattern in ``ignore_patterns``). … … def is_ignored(path, ignore_patterns=[]): 13 13 return True 14 14 return False 15 15 16 def get_files(storage, ignore_patterns= [], location=''):16 def get_files(storage, ignore_patterns=None, location=''): 17 17 """ 18 18 Recursively walk the storage directories yielding the paths 19 19 of all files that should be copied. 20 20 """ 21 if ignore_patterns is None: 22 ignore_patterns = [] 21 23 directories, files = storage.listdir(location) 22 24 for fn in files: 23 if is_ignored(fn, ignore_patterns):25 if matches_patterns(fn, ignore_patterns): 24 26 continue 25 27 if location: 26 28 fn = os.path.join(location, fn) 27 29 yield fn 28 30 for dir in directories: 29 if is_ignored(dir, ignore_patterns):31 if matches_patterns(dir, ignore_patterns): 30 32 continue 31 33 if location: 32 34 dir = os.path.join(location, dir) -
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..76b6aa7 100644
a b in a ``'downloads'`` subdirectory of :setting:`STATIC_ROOT`. 68 68 69 69 This would allow you to refer to the local file 70 70 ``'/opt/webfiles/stats/polls_20101022.tar.gz'`` with 71 ``'/static/downloads/polls_20101022.tar.gz'`` in your templates, e.g.:: 71 ``'/static/downloads/polls_20101022.tar.gz'`` in your templates, e.g.: 72 73 .. code-block:: html+django 72 74 73 75 <a href="{{ STATIC_URL }}downloads/polls_20101022.tar.gz"> 74 76 … … Files are searched by using the :setting:`enabled finders 141 143 :setting:`STATICFILES_DIRS` and in the ``'static'`` directory of apps 142 144 specified by the :setting:`INSTALLED_APPS` setting. 143 145 146 .. versionadded:: 1.4 147 148 The :djadmin:`collectstatic` management command calls the 149 :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process` 150 method of the :setting:`STATICFILES_STORAGE` after each run and passes 151 a list of paths that have been modified by the management command. It also 152 receives all command line options of :djadmin:`collectstatic`. This is used 153 by the :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` 154 by default. 155 144 156 Some commonly used options are: 145 157 146 158 .. django-admin-option:: --noinput … … Example usage:: 237 249 238 250 django-admin.py runserver --insecure 239 251 240 .. currentmodule:: None 252 Storages 253 ======== 254 255 StaticFilesStorage 256 ------------------ 257 258 .. class:: storage.StaticFilesStorage 259 260 A subclass of the :class:`~django.core.files.storage.FileSystemStorage` 261 storage backend that uses the :setting:`STATIC_ROOT` setting as the base 262 file system location and the :setting:`STATIC_URL` setting respectively 263 as the base URL. 264 265 .. method:: post_process(paths, **options) 266 267 .. versionadded:: 1.4 268 269 This method is called by the :djadmin:`collectstatic` management command 270 after each run and gets passed the paths of modified files, as well as the 271 command line options. 272 273 The :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage` 274 uses this behind the scenes to replace the paths with their hashed 275 counterparts and update the cache appropriately. 276 277 CachedStaticFilesStorage 278 ------------------------ 279 280 .. class:: storage.CachedStaticFilesStorage 281 282 .. versionadded:: 1.4 283 284 A subclass of the :class:`~django.contrib.staticfiles.storage.StaticFilesStorage` 285 storage backend which caches the files it saves by appending the MD5 hash 286 of the file's content to the filename. For example, the file 287 ``css/styles.css`` would also be saved as ``css/styles.55e7cbb9ba48.css``. 288 289 The purpose of this storage is to keep serving the old files in case some 290 pages still refer to those files, e.g. because they are cached by you or 291 a 3rd party proxy server. Additionally, it's very helpful if you want to 292 apply `far future Expires headers`_ to the deployed files to speed up the 293 load time for subsequent page visits. 294 295 The storage backend automatically replaces the paths found in the saved 296 files matching other saved files with the path of the cached copy (using 297 the :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process` 298 method). The regular expressions used to find those paths 299 (``django.contrib.staticfiles.storage.CachedStaticFilesStorage.cached_patterns``) 300 by default cover the `@import`_ rule and `url()`_ statement of `Cascading 301 Style Sheets`_. For example, the ``'css/styles.css'`` file with the 302 content 303 304 .. code-block:: css+django 305 306 @import url("../admin/css/base.css"); 307 308 would be replaced by calling the 309 :meth:`~django.core.files.storage.Storage.url` 310 method of the ``CachedStaticFilesStorage`` storage backend, ultimatively 311 saving a ``'css/styles.55e7cbb9ba48.css'`` file with the following 312 content: 313 314 .. code-block:: css+django 315 316 @import url("/static/admin/css/base.27e20196a850.css"); 317 318 To enable the ``CachedStaticFilesStorage`` you have to make sure the 319 following requirements are met: 320 321 * the :setting:`STATICFILES_STORAGE` setting is set to 322 ``'django.contrib.staticfiles.storage.CachedStaticFilesStorage'`` 323 * the :setting:`DEBUG` setting is set to ``False`` 324 * you use the ``staticfiles`` :ttag:`static<staticfiles-static>` template 325 tag to refer to your static files in your templates 326 * you've collected all your static files by using the 327 :djadmin:`collectstatic` management command 328 329 Since creating the MD5 hash can be a performance burden to your website 330 during runtime, ``staticfiles`` will automatically try to cache the 331 hashed name for each file path using Django's :doc:`caching 332 framework</topics/cache>`. If you want to override certain options of the 333 cache backend the storage uses, simply specify a custom entry in the 334 :setting:`CACHES` setting named ``'staticfiles'``. It falls back to using 335 the ``'default'`` cache backend. 336 337 .. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires 338 .. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import 339 .. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri 340 .. _`Cascading Style Sheets`: http://www.w3.org/Style/CSS/ 341 342 .. currentmodule:: django.contrib.staticfiles.templatetags.staticfiles 343 344 Template tags 345 ============= 346 347 static 348 ------ 349 350 .. templatetag:: staticfiles-static 351 352 .. versionadded:: 1.4 353 354 Uses the configued :setting:`STATICFILES_STORAGE` storage to create the 355 full URL for the given relative path, e.g.: 356 357 .. code-block:: html+django 358 359 {% load static from staticfiles %} 360 <img src="{% static "css/base.css" %}" /> 361 362 The previous example is equal to calling the ``url`` method of an instance of 363 :setting:`STATICFILES_STORAGE` with ``"css/base.css"``. This is especially 364 useful when using a non-local storage backend to deploy files as documented 365 in :ref:`staticfiles-from-cdn`. 241 366 242 367 Other Helpers 243 368 ============= … … files: 251 376 with :class:`~django.template.RequestContext` contexts. 252 377 253 378 - The builtin template tag :ttag:`static` which takes a path and 254 joins it with thethe static prefix :setting:`STATIC_URL`.379 urljoins it with the static prefix :setting:`STATIC_URL`. 255 380 256 381 - The builtin template tag :ttag:`get_static_prefix` which populates a 257 382 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..4056948 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 13 from django.core.exceptions import ImproperlyConfigured 13 14 from django.core.files.storage import default_storage … … from django.utils._os import rmtree_errorhandler 21 22 from django.contrib.staticfiles import finders, storage 22 23 23 24 TEST_ROOT = os.path.dirname(__file__) 25 TEST_SETTINGS = { 26 'DEBUG': True, 27 'MEDIA_URL': '/media/', 28 'STATIC_URL': '/static/', 29 'MEDIA_ROOT': os.path.join(TEST_ROOT, 'project', 'site_media', 'media'), 30 'STATIC_ROOT': os.path.join(TEST_ROOT, 'project', 'site_media', 'static'), 31 'STATICFILES_DIRS': ( 32 os.path.join(TEST_ROOT, 'project', 'documents'), 33 ('prefix', os.path.join(TEST_ROOT, 'project', 'prefixed')), 34 ), 35 'STATICFILES_FINDERS': ( 36 'django.contrib.staticfiles.finders.FileSystemFinder', 37 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 38 'django.contrib.staticfiles.finders.DefaultStorageFinder', 39 ), 40 } 24 41 25 42 26 class StaticFilesTestCase(TestCase):43 class BaseStaticFilesTestCase(object): 27 44 """ 28 45 Test case with a couple utility assertions. 29 46 """ … … class StaticFilesTestCase(TestCase): 32 49 # gets accessed (by some other test), it evaluates settings.MEDIA_ROOT, 33 50 # since we're planning on changing that we need to clear out the cache. 34 51 default_storage._wrapped = empty 52 storage.staticfiles_storage._wrapped = empty 35 53 36 54 # To make sure SVN doesn't hangs itself with the non-ASCII characters 37 55 # during checkout, we actually create one file dynamically. … … class StaticFilesTestCase(TestCase): 48 66 def assertFileNotFound(self, filepath): 49 67 self.assertRaises(IOError, self._get_file, filepath) 50 68 51 StaticFilesTestCase = override_settings( 52 DEBUG = True, 53 MEDIA_URL = '/media/', 54 STATIC_URL = '/static/', 55 MEDIA_ROOT = os.path.join(TEST_ROOT, 'project', 'site_media', 'media'), 56 STATIC_ROOT = os.path.join(TEST_ROOT, 'project', 'site_media', 'static'), 57 STATICFILES_DIRS = ( 58 os.path.join(TEST_ROOT, 'project', 'documents'), 59 ('prefix', os.path.join(TEST_ROOT, 'project', 'prefixed')), 60 ), 61 STATICFILES_FINDERS = ( 62 'django.contrib.staticfiles.finders.FileSystemFinder', 63 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 64 'django.contrib.staticfiles.finders.DefaultStorageFinder', 65 ), 66 )(StaticFilesTestCase) 69 def render_template(self, template, **kwargs): 70 if isinstance(template, basestring): 71 template = loader.get_template_from_string(template) 72 return template.render(Context(kwargs)).strip() 73 74 def assertTemplateRenders(self, template, result, **kwargs): 75 self.assertEqual(self.render_template(template, **kwargs), result) 67 76 77 def assertTemplateRaises(self, exc, template, result, **kwargs): 78 self.assertRaises(exc, self.assertTemplateRenders, template, result, **kwargs) 68 79 69 class BuildStaticTestCase(StaticFilesTestCase): 80 81 class StaticFilesTestCase(BaseStaticFilesTestCase, TestCase): 82 pass 83 StaticFilesTestCase = override_settings(**TEST_SETTINGS)(StaticFilesTestCase) 84 85 86 class BaseCollectionTestCase(BaseStaticFilesTestCase): 70 87 """ 71 Tests shared by all file -resolving features (collectstatic,88 Tests shared by all file finding features (collectstatic, 72 89 findstatic, and static serve view). 73 90 74 91 This relies on the asserts defined in UtilityAssertsTestCase, but … … class BuildStaticTestCase(StaticFilesTestCase): 76 93 all these tests. 77 94 """ 78 95 def setUp(self): 79 super(B uildStaticTestCase, self).setUp()96 super(BaseCollectionTestCase, self).setUp() 80 97 self.old_root = settings.STATIC_ROOT 81 98 settings.STATIC_ROOT = tempfile.mkdtemp() 82 99 self.run_collectstatic() … … class BuildStaticTestCase(StaticFilesTestCase): 86 103 87 104 def tearDown(self): 88 105 settings.STATIC_ROOT = self.old_root 89 super(B uildStaticTestCase, self).tearDown()106 super(BaseCollectionTestCase, self).tearDown() 90 107 91 108 def run_collectstatic(self, **kwargs): 92 109 call_command('collectstatic', interactive=False, verbosity='0', … … class BuildStaticTestCase(StaticFilesTestCase): 99 116 return f.read() 100 117 101 118 119 class CollectionTestCase(BaseCollectionTestCase, StaticFilesTestCase): 120 pass 121 122 102 123 class TestDefaults(object): 103 124 """ 104 125 A few standard test cases. … … class TestDefaults(object): 142 163 self.assertFileContains(u'test/camelCase.txt', u'camelCase') 143 164 144 165 145 class TestFindStatic( BuildStaticTestCase, TestDefaults):166 class TestFindStatic(CollectionTestCase, TestDefaults): 146 167 """ 147 168 Test ``findstatic`` management command. 148 169 """ … … class TestFindStatic(BuildStaticTestCase, TestDefaults): 171 192 lines = [l.strip() for l in sys.stdout.readlines()] 172 193 finally: 173 194 sys.stdout = _stdout 174 self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line195 self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line 175 196 self.assertTrue('project' in lines[1]) 176 197 self.assertTrue('apps' in lines[2]) 177 198 178 199 179 class Test BuildStatic(BuildStaticTestCase, TestDefaults):200 class TestCollection(CollectionTestCase, TestDefaults): 180 201 """ 181 202 Test ``collectstatic`` management command. 182 203 """ … … class TestBuildStatic(BuildStaticTestCase, TestDefaults): 195 216 self.assertFileNotFound('test/CVS') 196 217 197 218 198 class Test BuildStaticClear(BuildStaticTestCase):219 class TestCollectionClear(CollectionTestCase): 199 220 """ 200 221 Test the ``--clear`` option of the ``collectstatic`` managemenet command. 201 222 """ … … class TestBuildStaticClear(BuildStaticTestCase): 203 224 clear_filepath = os.path.join(settings.STATIC_ROOT, 'cleared.txt') 204 225 with open(clear_filepath, 'w') as f: 205 226 f.write('should be cleared') 206 super(Test BuildStaticClear, self).run_collectstatic(clear=True)227 super(TestCollectionClear, self).run_collectstatic(clear=True) 207 228 208 229 def test_cleared_not_found(self): 209 230 self.assertFileNotFound('cleared.txt') 210 231 211 232 212 class Test BuildStaticExcludeNoDefaultIgnore(BuildStaticTestCase, TestDefaults):233 class TestCollectionExcludeNoDefaultIgnore(CollectionTestCase, TestDefaults): 213 234 """ 214 235 Test ``--exclude-dirs`` and ``--no-default-ignore`` options of the 215 236 ``collectstatic`` management command. 216 237 """ 217 238 def run_collectstatic(self): 218 super(Test BuildStaticExcludeNoDefaultIgnore, self).run_collectstatic(239 super(TestCollectionExcludeNoDefaultIgnore, self).run_collectstatic( 219 240 use_default_ignore_patterns=False) 220 241 221 242 def test_no_common_ignore_patterns(self): … … class TestNoFilesCreated(object): 238 259 self.assertEqual(os.listdir(settings.STATIC_ROOT), []) 239 260 240 261 241 class Test BuildStaticDryRun(BuildStaticTestCase, TestNoFilesCreated):262 class TestCollectionDryRun(CollectionTestCase, TestNoFilesCreated): 242 263 """ 243 264 Test ``--dry-run`` option for ``collectstatic`` management command. 244 265 """ 245 266 def run_collectstatic(self): 246 super(Test BuildStaticDryRun, self).run_collectstatic(dry_run=True)267 super(TestCollectionDryRun, self).run_collectstatic(dry_run=True) 247 268 248 269 249 class Test BuildStaticNonLocalStorage(BuildStaticTestCase, TestNoFilesCreated):270 class TestCollectionNonLocalStorage(CollectionTestCase, TestNoFilesCreated): 250 271 """ 251 272 Tests for #15035 252 273 """ 253 274 pass 254 275 255 Test BuildStaticNonLocalStorage = override_settings(276 TestCollectionNonLocalStorage = override_settings( 256 277 STATICFILES_STORAGE='regressiontests.staticfiles_tests.storage.DummyStorage', 257 )(TestBuildStaticNonLocalStorage) 278 )(TestCollectionNonLocalStorage) 279 280 281 class TestCollectionCachedStorage(BaseCollectionTestCase, 282 BaseStaticFilesTestCase, TestCase): 283 """ 284 Tests for the Cache busting storage 285 """ 286 def cached_file_path(self, relpath): 287 template = "{%% load static from staticfiles %%}{%% static '%s' %%}" 288 fullpath = self.render_template(template % relpath) 289 return fullpath.replace(settings.STATIC_URL, '') 290 291 def test_template_tag_return(self): 292 """ 293 Test the CachedStaticFilesStorage backend. 294 """ 295 self.assertTemplateRaises(ValueError, """ 296 {% load static from staticfiles %}{% static "does/not/exist.png" %} 297 """, "/static/does/not/exist.png") 298 self.assertTemplateRenders(""" 299 {% load static from staticfiles %}{% static "test/file.txt" %} 300 """, "/static/test/file.dad0999e4f8f.txt") 301 self.assertTemplateRenders(""" 302 {% load static from staticfiles %}{% static "cached/styles.css" %} 303 """, "/static/cached/styles.5653c259030b.css") 304 305 def test_template_tag_simple_content(self): 306 relpath = self.cached_file_path("cached/styles.css") 307 self.assertEqual(relpath, "cached/styles.5653c259030b.css") 308 with storage.staticfiles_storage.open(relpath) as relfile: 309 content = relfile.read() 310 self.assertFalse("cached/other.css" in content, content) 311 self.assertTrue("/static/cached/other.d41d8cd98f00.css" in content) 312 313 def test_template_tag_absolute(self): 314 relpath = self.cached_file_path("cached/absolute.css") 315 self.assertEqual(relpath, "cached/absolute.cc80cb5e2eb1.css") 316 with storage.staticfiles_storage.open(relpath) as relfile: 317 content = relfile.read() 318 self.assertFalse("/static/cached/styles.css" in content) 319 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 320 321 def test_template_tag_denorm(self): 322 relpath = self.cached_file_path("cached/denorm.css") 323 self.assertEqual(relpath, "cached/denorm.363de96e9b4b.css") 324 with storage.staticfiles_storage.open(relpath) as relfile: 325 content = relfile.read() 326 self.assertFalse("..//cached///styles.css" in content) 327 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 328 329 def test_template_tag_relative(self): 330 relpath = self.cached_file_path("cached/relative.css") 331 self.assertEqual(relpath, "cached/relative.298ff891a8d4.css") 332 with storage.staticfiles_storage.open(relpath) as relfile: 333 content = relfile.read() 334 self.assertFalse("../cached/styles.css" in content) 335 self.assertFalse('@import "styles.css"' in content) 336 self.assertTrue("/static/cached/styles.5653c259030b.css" in content) 337 338 def test_template_tag_url(self): 339 relpath = self.cached_file_path("cached/url.css") 340 self.assertEqual(relpath, "cached/url.615e21601e4b.css") 341 with storage.staticfiles_storage.open(relpath) as relfile: 342 self.assertTrue("https://" in relfile.read()) 343 344 # we set DEBUG to False here since the template tag wouldn't work otherwise 345 TestCollectionCachedStorage = override_settings(**dict(TEST_SETTINGS, 346 STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage', 347 DEBUG=False, 348 ))(TestCollectionCachedStorage) 258 349 259 350 260 351 if sys.platform != 'win32': 261 class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults): 352 353 class TestCollectionLinks(CollectionTestCase, TestDefaults): 262 354 """ 263 355 Test ``--link`` option for ``collectstatic`` management command. 264 356 … … if sys.platform != 'win32': 267 359 ``--link`` does not change the file-selection semantics. 268 360 """ 269 361 def run_collectstatic(self): 270 super(Test BuildStaticLinks, self).run_collectstatic(link=True)362 super(TestCollectionLinks, self).run_collectstatic(link=True) 271 363 272 364 def test_links_created(self): 273 365 """ … … class TestServeStaticWithDefaultURL(TestServeStatic, TestDefaults): 312 404 """ 313 405 pass 314 406 407 315 408 class TestServeStaticWithURLHelper(TestServeStatic, TestDefaults): 316 409 """ 317 410 Test static asset serving view with staticfiles_urlpatterns helper. … … class TestMiscFinder(TestCase): 399 492 finders.FileSystemFinder)) 400 493 401 494 def test_get_finder_bad_classname(self): 402 self.assertRaises(ImproperlyConfigured, 403 finders.get_finder,'django.contrib.staticfiles.finders.FooBarFinder')495 self.assertRaises(ImproperlyConfigured, finders.get_finder, 496 'django.contrib.staticfiles.finders.FooBarFinder') 404 497 405 498 def test_get_finder_bad_module(self): 406 499 self.assertRaises(ImproperlyConfigured, 407 500 finders.get_finder, 'foo.bar.FooBarFinder') 408 501 409 410 class TestStaticfilesDirsType(TestCase):411 """412 We can't determine if STATICFILES_DIRS is set correctly just by looking at413 the type, but we can determine if it's definitely wrong.414 """415 502 def test_non_tuple_raises_exception(self): 416 self.assertRaises(ImproperlyConfigured, finders.FileSystemFinder) 503 """ 504 We can't determine if STATICFILES_DIRS is set correctly just by 505 looking at the type, but we can determine if it's definitely wrong. 506 """ 507 with self.settings(STATICFILES_DIRS='a string'): 508 self.assertRaises(ImproperlyConfigured, finders.FileSystemFinder) 509 510 511 class TestTemplateTag(StaticFilesTestCase): 417 512 418 TestStaticfilesDirsType = override_settings( 419 STATICFILES_DIRS = 'a string', 420 )(TestStaticfilesDirsType) 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")