Ticket #15252: 15252.2.diff
File 15252.2.diff, 12.5 KB (added by , 13 years ago) |
---|
-
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 24b29e4..3f4cecd 100644
a b 1 1 import os 2 2 import sys 3 import shutil4 3 from optparse import make_option 5 4 6 5 from django.conf import settings … … Type 'yes' to continue, or 'no' to cancel: """) 88 87 else: 89 88 self.copy_file(path, prefixed_path, storage, **options) 90 89 91 actual_count = len(self.copied_files) + len(self.symlinked_files) 90 modified_files = self.copied_files + self.symlinked_files 91 actual_count = len(modified_files) 92 93 # Here we check if the storage backend has a post_process method 94 # and pass it the list of modified files, if possible. 95 if hasattr(self.storage, 'post_process'): 96 self.storage.post_process(modified_files) 97 92 98 unmodified_count = len(self.unmodified_files) 93 99 if self.verbosity >= 1: 94 100 self.stdout.write(smart_str(u"\n%s static file%s %s to '%s'%s.\n" … … Type 'yes' to continue, or 'no' to cancel: """) 196 202 os.makedirs(os.path.dirname(full_path)) 197 203 except OSError: 198 204 pass 199 shutil.copy2(source_path, full_path) 200 else: 201 source_file = source_storage.open(path) 202 self.storage.save(prefixed_path, source_file) 205 source_file = source_storage.open(path) 206 self.storage.save(prefixed_path, source_file) 203 207 if not prefixed_path in self.copied_files: 204 208 self.copied_files.append(prefixed_path) -
django/contrib/staticfiles/storage.py
diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index a69ae23..1942385 100644
a b 1 import hashlib 1 2 import os 3 import re 4 2 5 from django.conf import settings 3 from django.core.exceptions import ImproperlyConfigured 4 from django.core.files.storage import FileSystemStorage 6 from django.core.cache import get_cache, InvalidCacheBackendError, cache as default_cache 7 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation 8 from django.core.files.storage import FileSystemStorage, get_storage_class 9 from django.core.files.base import ContentFile 10 from django.template import Template, Context 5 11 from django.utils.importlib import import_module 12 from django.utils.encoding import force_unicode 13 from django.utils.functional import LazyObject 14 15 from django.contrib.staticfiles.utils import check_settings 16 6 17 7 from django.contrib.staticfiles import utils 18 urltag_re = re.compile(r""" 19 url\( 20 (\s*) # allow whitespace wrapping (and capture) 21 ( # capture actual url 22 [^\)\\\r\n]*? # don't allow newlines, closing paran, escape chars (1) 23 (?:\\. # process all escapes here instead 24 [^\)\\\r\n]*? # proceed, with previous restrictions (1) 25 )* # repeat until end 26 ) 27 (\s*) # whitespace again (and capture) 28 \) 29 """, re.VERBOSE) 8 30 9 31 10 32 class StaticFilesStorage(FileSystemStorage): … … class StaticFilesStorage(FileSystemStorage): 26 48 if base_url is None: 27 49 raise ImproperlyConfigured("You're using the staticfiles app " 28 50 "without having set the STATIC_URL setting.") 29 utils.check_settings()51 check_settings() 30 52 super(StaticFilesStorage, self).__init__(location, base_url, *args, **kwargs) 31 53 32 54 55 class CacheBustingMixin(object): 56 57 def __init__(self, *args, **kwargs): 58 super(CacheBustingMixin, self).__init__(*args, **kwargs) 59 self.processed_files = [] 60 try: 61 self.cache = get_cache('staticfiles') 62 except InvalidCacheBackendError: 63 # Use the default backend 64 self.cache = default_cache 65 66 def hashed_filename(self, name, content=None): 67 if content is None: 68 if not self.exists(name): 69 raise SuspiciousOperation("Attempted access to '%s' denied." % self.path(name)) 70 content = self.open(self.path(name)) 71 path, filename = os.path.split(name) 72 root, ext = os.path.splitext(filename) 73 # Get the MD5 hash of the file 74 md5 = hashlib.md5() 75 for chunk in content.chunks(): 76 md5.update(chunk) 77 md5sum = md5.hexdigest()[:12] 78 return os.path.join(path, u"%s.%s%s" % (root, md5sum, ext)) 79 80 def cache_key(self, name): 81 return 'staticfiles:cache:%s' % name 82 83 def url(self, name): 84 cache_key = self.cache_key(name) 85 hashed_name = self.cache.get(cache_key, self.hashed_filename(name)) 86 return super(CacheBustingMixin, self).url(hashed_name) 87 88 def save(self, name, content): 89 original_name = super(CacheBustingMixin, self).save(name, content) 90 hashed_name = self.hashed_filename(original_name, content) 91 # Return the name if the file is already there 92 if os.path.exists(hashed_name): 93 return hashed_name 94 # Save the file 95 rendered_content = Template(content.read()).render(Context({})) 96 hashed_name = self._save(hashed_name, ContentFile(rendered_content)) 97 # Use filenames with forward slashes, even on Windows 98 hashed_name = force_unicode(hashed_name.replace('\\', '/')) 99 self.cache.set(self.cache_key(name), hashed_name) 100 self.processed_files.append((name, hashed_name)) 101 return hashed_name 102 103 def post_process(self, modified_files): 104 """ 105 Post process method called by the collectstatic management command. 106 """ 107 cached_files = [self.cache_key(path) for path in modified_files] 108 self.cache.delete_many(cached_files) 109 110 def path_level((name, hashed_name)): 111 return len(name.split(os.sep)) 112 113 for name, hashed_name in sorted( 114 self.processed_files, key=path_level, reverse=True): 115 116 def url_converter(matchobj): 117 url = matchobj.groups()[1] 118 # normalize the url we got 119 if url[:1] in '"\'': 120 url = url[1:] 121 if url[-1:] in '"\'': 122 url = url[:-1] 123 rel_level = url.count(os.pardir) 124 if rel_level: 125 url_parts = (name.split('/')[:-rel_level-1] + 126 url.split('/')[rel_level:]) 127 url = self.url('/'.join(url_parts)) 128 return "url('%s')" % url 129 130 original = self.open(name) 131 converted = urltag_re.sub(url_converter, original.read()) 132 hashed = self.path(hashed_name) 133 with open(hashed, 'w') as hashed_file: 134 hashed_file.write(converted) 135 136 class CachedStaticFilesStorage(CacheBustingMixin, StaticFilesStorage): 137 pass 138 139 33 140 class AppStaticStorage(FileSystemStorage): 34 141 """ 35 142 A file system storage backend that takes an app module and works … … class AppStaticStorage(FileSystemStorage): 47 154 mod_path = os.path.dirname(mod.__file__) 48 155 location = os.path.join(mod_path, self.source_dir) 49 156 super(AppStaticStorage, self).__init__(location, *args, **kwargs) 157 158 159 160 class ConfiguredStorage(LazyObject): 161 def _setup(self): 162 self._wrapped = get_storage_class(settings.STATICFILES_STORAGE)() 163 164 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..42b4f6b
- + 1 from django import template 2 from django.contrib.staticfiles import 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 storage.configured_storage.url(path) -
django/core/cache/__init__.py
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index b76ddcf..b97c746 100644
a b def parse_backend_conf(backend, **kwargs): 126 126 location = args.pop('LOCATION', '') 127 127 return backend, location, args 128 128 else: 129 # Trying to import the given backend, in case it's a dotted path130 mod_path, cls_name = backend.rsplit('.', 1)131 129 try: 130 # Trying to import the given backend, in case it's a dotted path 131 mod_path, cls_name = backend.rsplit('.', 1) 132 132 mod = importlib.import_module(mod_path) 133 133 backend_cls = getattr(mod, cls_name) 134 except (AttributeError, ImportError ):134 except (AttributeError, ImportError, ValueError): 135 135 raise InvalidCacheBackendError("Could not find backend '%s'" % backend) 136 136 location = kwargs.pop('LOCATION', '') 137 137 return backend, location, kwargs -
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 4c0c53b..a2e0b71 100644
a b import sys 7 7 import tempfile 8 8 from StringIO import StringIO 9 9 10 from django.template import loader, Context 10 11 from django.conf import settings 11 from django.core.exceptions import ImproperlyConfigured 12 from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation 12 13 from django.core.files.storage import default_storage 13 14 from django.core.management import call_command 14 15 from django.test import TestCase … … class StaticFilesTestCase(TestCase): 50 51 def assertFileNotFound(self, filepath): 51 52 self.assertRaises(IOError, self._get_file, filepath) 52 53 54 def assertTemplateRenders(self, template, result, **kwargs): 55 if isinstance(template, basestring): 56 template = loader.get_template_from_string(template) 57 self.assertEqual(template.render(Context(kwargs)), result) 58 59 def assertTemplateRaises(self, exc, template, result, **kwargs): 60 self.assertRaises(exc, self.assertTemplateRenders, template, result, **kwargs) 61 62 53 63 StaticFilesTestCase = override_settings( 54 64 DEBUG = True, 55 65 MEDIA_URL = '/media/', … … TestBuildStaticNonLocalStorage = override_settings( 245 255 )(TestBuildStaticNonLocalStorage) 246 256 247 257 258 class TestBuildStaticCachedStorage(BuildStaticTestCase, TestDefaults): 259 """ 260 Tests for the Cache busting storage 261 """ 262 @classmethod 263 def tearDownClass(cls): 264 """ 265 Resetting the global storage for staticfiles 266 """ 267 storage.configured_storage = storage.ConfiguredStorage() 268 269 def test_template_tag(self): 270 self.assertTemplateRaises(SuspiciousOperation, """{% load static from staticfiles %}{% static "does/not/exist.png" %}""", "/static/does/not/exist.png") 271 self.assertTemplateRenders("""{% load static from staticfiles %}{% static "test/file.txt" %}""", "/static/test/file.dad0999e4f8f.txt") 272 273 274 TestBuildStaticCachedStorage = override_settings( 275 STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage' 276 )(TestBuildStaticCachedStorage) 277 278 248 279 if sys.platform != 'win32': 249 280 class TestBuildStaticLinks(BuildStaticTestCase, TestDefaults): 250 281 """ … … class TestStaticfilesDirsType(TestCase): 406 437 TestStaticfilesDirsType = override_settings( 407 438 STATICFILES_DIRS = 'a string', 408 439 )(TestStaticfilesDirsType) 440 441 442 class TestTemplateTag(StaticFilesTestCase): 443 444 def test_template_tag(self): 445 self.assertTemplateRenders("""{% load static from staticfiles %}{% static "does/not/exist.png" %}""", "/static/does/not/exist.png") 446 self.assertTemplateRenders("""{% load static from staticfiles %}{% static "testfile.txt" %}""", "/static/testfile.txt")