From 85a34f44d652d13c14d095d947b9013e02d09246 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Fri, 7 Oct 2011 17:02:36 +0200 Subject: [PATCH 1/9] Added option into collectstatic command to ignore errors raised on post-processing phase when files are missing. --- staticfiles/management/commands/collectstatic.py | 3 +++ staticfiles/storage.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index fc323fc..2d99719 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -41,6 +41,9 @@ class Command(NoArgsCommand): dest='use_default_ignore_patterns', default=True, help="Don't ignore the common private glob-style patterns 'CVS', " "'.*' and '*~'."), + make_option('--ignore-errors', action='store_true', + dest='ignore_errors', default=False, + help="Ignore post-processing errors raised on missing file."), ) help = "Collect static files in a single location." diff --git a/staticfiles/storage.py b/staticfiles/storage.py index 7fe7bd7..d1318f4 100644 --- a/staticfiles/storage.py +++ b/staticfiles/storage.py @@ -128,7 +128,7 @@ def url(self, name, force=False): hashed_name = self.hashed_name(name) return super(CachedFilesMixin, self).url(hashed_name) - def url_converter(self, name): + def url_converter(self, name, fail_silently=False): """ Returns the custom URL converter for the given file name. """ @@ -160,7 +160,12 @@ def converter(matchobj): else: start, end = 1, sub_level - 1 joined_result = '/'.join(name_parts[:-start] + url_parts[end:]) - hashed_url = self.url(joined_result, force=True) + try: + hashed_url = self.url(joined_result, force=True) + except ValueError: + if not fail_silently: + raise + hashed_url = url # Return the hashed and normalized version to the file return 'url("%s")' % hashed_url return converter @@ -169,6 +174,7 @@ def post_process(self, paths, dry_run=False, **options): """ Post process the given list of files (called from collectstatic). """ + fail_silently = options['ignore_errors'] processed_files = [] # don't even dare to process the files if we're in dry run mode if dry_run: @@ -195,7 +201,7 @@ def post_process(self, paths, dry_run=False, **options): # to apply each replacement pattern on the content if name in processing_paths: - converter = self.url_converter(name) + converter = self.url_converter(name, fail_silently) for patterns in self._patterns.values(): for pattern in patterns: content = pattern.sub(converter, content) From a9355b3803a9ac62240d6f7239a1c2041b243b62 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Fri, 2 Mar 2012 18:48:18 +0100 Subject: [PATCH 2/9] Fixed silent error mode. --- staticfiles/management/commands/collectstatic.py | 3 ++- staticfiles/storage.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 863010d..851b4d0 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -82,6 +82,7 @@ def set_options(self, **options): ignore_patterns += ['CVS', '.*', '*~'] self.ignore_patterns = list(set(ignore_patterns)) self.post_process = options['post_process'] + self.fail_silently = options['ignore_errors'] def collect(self): """ @@ -119,7 +120,7 @@ def collect(self): # method and pass it the list of modified files. if self.post_process and hasattr(self.storage, 'post_process'): processor = self.storage.post_process(found_files, - dry_run=self.dry_run) + dry_run=self.dry_run, fail_silently=self.fail_silently) for original_path, processed_path, processed in processor: if processed: self.log(u"Post-processed '%s' as '%s" % diff --git a/staticfiles/storage.py b/staticfiles/storage.py index f12f20d..b42b63d 100644 --- a/staticfiles/storage.py +++ b/staticfiles/storage.py @@ -220,7 +220,7 @@ def converter(matchobj): return 'url("%s")' % unquote(hashed_url) return converter - def post_process(self, paths, dry_run=False, **options): + def post_process(self, paths, dry_run=False, fail_silently=False): """ Post process the given list of files (called from collectstatic). @@ -234,7 +234,6 @@ def post_process(self, paths, dry_run=False, **options): If either of these are performed on a file, then that file is considered post-processed. """ - fail_silently = options['ignore_errors'] # don't even dare to process the files if we're in dry run mode if dry_run: return From c17bb27fa65d98645fe68ab25e70ea5d49e7a562 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Sat, 24 Mar 2012 18:59:16 +0100 Subject: [PATCH 3/9] pep8 cleaning --- setup.py | 6 ++++-- staticfiles/context_processors.py | 2 ++ staticfiles/management/commands/findstatic.py | 1 + staticfiles/templatetags/static.py | 3 +++ staticfiles/utils.py | 5 ++++- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index b245fa9..bc15633 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,11 @@ from distutils.util import convert_path from setuptools import setup, find_packages + def read(*parts): return open(os.path.join(os.path.dirname(__file__), *parts)).read() + def find_version(*file_paths): version_file = read(*file_paths) version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", @@ -22,6 +24,7 @@ def find_version(*file_paths): standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', './dist', 'EGG-INFO', '*.egg-info') + # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # Note: you may want to copy this into your setup.py file verbatim, as @@ -102,11 +105,10 @@ def find_package_data( break if bad_name: continue - out.setdefault(package, []).append(prefix+name) + out.setdefault(package, []).append(prefix + name) return out - setup( name="django-staticfiles", version=find_version("staticfiles", "__init__.py"), diff --git a/staticfiles/context_processors.py b/staticfiles/context_processors.py index d466770..e113bca 100644 --- a/staticfiles/context_processors.py +++ b/staticfiles/context_processors.py @@ -1,12 +1,14 @@ import warnings from django.conf import settings + def static(request): """ Adds static-related context variables to the context. """ return {'STATIC_URL': settings.STATIC_URL} + def static_url(request): warnings.warn( "The context processor 'staticfiles.context_processors.static_url' " diff --git a/staticfiles/management/commands/findstatic.py b/staticfiles/management/commands/findstatic.py index 4f496f2..cd2cafe 100644 --- a/staticfiles/management/commands/findstatic.py +++ b/staticfiles/management/commands/findstatic.py @@ -6,6 +6,7 @@ from staticfiles import finders + class Command(LabelCommand): help = "Finds the absolute paths for the given static file(s)." args = "[file ...]" diff --git a/staticfiles/templatetags/static.py b/staticfiles/templatetags/static.py index 636fbfd..9b4357c 100644 --- a/staticfiles/templatetags/static.py +++ b/staticfiles/templatetags/static.py @@ -3,6 +3,7 @@ register = template.Library() + class PrefixNode(template.Node): def __repr__(self): @@ -47,6 +48,7 @@ def render(self, context): context[self.varname] = prefix return '' + @register.tag def get_static_prefix(parser, token): """ @@ -65,6 +67,7 @@ def get_static_prefix(parser, token): """ return PrefixNode.handle_token(parser, token, "STATIC_URL") + @register.tag def get_media_prefix(parser, token): """ diff --git a/staticfiles/utils.py b/staticfiles/utils.py index a72e53f..869ffa3 100644 --- a/staticfiles/utils.py +++ b/staticfiles/utils.py @@ -17,6 +17,7 @@ def get_files_for_app(app, ignore_patterns=None): "instead.", DeprecationWarning) return AppStaticStorage(app).get_files(ignore_patterns) + def get_app_prefix(app): """ Return the path name that should be prepended to files for this app. @@ -28,6 +29,7 @@ def get_app_prefix(app): "instead.", DeprecationWarning) return AppStaticStorage(app).get_prefix() + def matches_patterns(path, patterns=None): """ Return True or False depending on whether the ``path`` should be @@ -40,6 +42,7 @@ def matches_patterns(path, patterns=None): return True return False + def get_filtered_patterns(storage, ignore_patterns=None, location=''): """ Return a filtered list of patterns that match the storage location. @@ -62,6 +65,7 @@ def get_filtered_patterns(storage, ignore_patterns=None, location=''): ignore_filtered.append(tail) return ignore_filtered + def get_files(storage, ignore_patterns=None, location=''): """ Recursively walk the storage directories yielding the paths @@ -84,4 +88,3 @@ def get_files(storage, ignore_patterns=None, location=''): dir = os.path.join(location, dir) for fn in get_files(storage, ignore_patterns, dir): yield fn - From 257bcec24f6b6c95bd4f492a54914c2c4eed6912 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Sat, 24 Mar 2012 22:17:54 +0100 Subject: [PATCH 4/9] Added test for --ignore-errors option. --- .../management/commands/collectstatic.py | 4 +-- .../tests/project/documents/cached/faulty.css | 1 + staticfiles/tests/tests.py | 35 +++++++++++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 staticfiles/tests/project/documents/cached/faulty.css diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 851b4d0..45758cb 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -44,7 +44,7 @@ class Command(NoArgsCommand): help="Don't ignore the common private glob-style patterns 'CVS', " "'.*' and '*~'."), make_option('--ignore-errors', action='store_true', - dest='ignore_errors', default=False, + dest='fail_silently', default=False, help="Ignore post-processing errors raised on missing file."), ) help = "Collect static files in a single location." @@ -82,7 +82,7 @@ def set_options(self, **options): ignore_patterns += ['CVS', '.*', '*~'] self.ignore_patterns = list(set(ignore_patterns)) self.post_process = options['post_process'] - self.fail_silently = options['ignore_errors'] + self.fail_silently = options['fail_silently'] def collect(self): """ diff --git a/staticfiles/tests/project/documents/cached/faulty.css b/staticfiles/tests/project/documents/cached/faulty.css new file mode 100644 index 0000000..1a75fee --- /dev/null +++ b/staticfiles/tests/project/documents/cached/faulty.css @@ -0,0 +1 @@ +@import url("img/does_not_exists.png"); \ No newline at end of file diff --git a/staticfiles/tests/tests.py b/staticfiles/tests/tests.py index d60cfad..affceeb 100644 --- a/staticfiles/tests/tests.py +++ b/staticfiles/tests/tests.py @@ -136,6 +136,7 @@ def run_collectstatic(self, **kwargs): ignore_patterns = [ '*.ignoreme', os.path.join('test', '*.ignoreme2'), os.path.join( settings.TEST_ROOT, 'apps', 'test', 'static', 'test', '*.ignoreme3')] + kwargs.setdefault('fail_silently', True) call_command('collectstatic', interactive=False, verbosity='0', ignore_patterns=ignore_patterns, **kwargs) @@ -268,7 +269,7 @@ def test_staticfiles_ignore_patterns(self): class TestCollectionClear(CollectionTestCase): """ - Test the ``--clear`` option of the ``collectstatic`` managemenet command. + Test the ``--clear`` option of the ``collectstatic`` management command. """ def run_collectstatic(self, **kwargs): clear_filepath = os.path.join(settings.STATIC_ROOT, 'cleared.txt') @@ -459,6 +460,13 @@ def test_cache_invalidation(self): cached_name = storage.staticfiles_storage.cache.get(cache_key) self.assertEqual(cached_name, hashed_name) + def test_ignored_file(self): + relpath = self.cached_file_path("cached/faulty.css") + self.assertEqual(relpath, "cached/faulty.c376691faf10.css") + with storage.staticfiles_storage.open(relpath) as relfile: + content = relfile.read() + self.assertIn('@import url("img/does_not_exists.png");', content) + def test_post_processing(self): """Test that post_processing behaves correctly. @@ -476,7 +484,8 @@ def test_post_processing(self): 'dry_run': False, 'post_process': True, 'use_default_ignore_patterns': True, - 'ignore_patterns': ['*.ignoreme'] + 'ignore_patterns': ['*.ignoreme'], + 'fail_silently': True, } collectstatic_cmd = CollectstaticCommand() @@ -485,6 +494,28 @@ def test_post_processing(self): self.assertTrue(u'cached/css/window.css' in stats['post_processed']) self.assertTrue(u'cached/css/img/window.png' in stats['unmodified']) + def test_post_processing_fail(self): + """Test that post_processing behaves correctly. + + Missing files raise a ValueError on post-processing when errors aren't + explicitely silenced. + """ + collectstatic_args = { + 'interactive': False, + 'verbosity': '0', + 'link': False, + 'clear': False, + 'dry_run': False, + 'post_process': True, + 'use_default_ignore_patterns': True, + 'ignore_patterns': ['*.ignoreme'], + 'fail_silently': False, + } + + collectstatic_cmd = CollectstaticCommand() + collectstatic_cmd.set_options(**collectstatic_args) + self.assertRaises(ValueError, collectstatic_cmd.collect) + if sys.platform != 'win32': class TestCollectionLinks(CollectionTestCase, TestDefaults): From 7fe2983798d6dee5fb0ffe46e21f08b88d066725 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Sat, 24 Mar 2012 22:20:32 +0100 Subject: [PATCH 5/9] Added --ignore-errors documentation. --- docs/commands.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/commands.rst b/docs/commands.rst index dadbb14..00069a5 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -50,6 +50,12 @@ Some commonly used options are: method of the configured :attr:`~django.conf.settings.STATICFILES_STORAGE` storage backend. +``--ignore-errors`` + + .. versionadded:: 1.2 + + Ignore post-processing errors raised on missing file. + For a full list of options, refer to the collectstatic management command help by running:: From a24a980c91217e3f86b977e65a4ceefc92970124 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Sat, 24 Mar 2012 22:25:23 +0100 Subject: [PATCH 6/9] Enforced to ignore errors by default on test case. --- staticfiles/tests/tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/staticfiles/tests/tests.py b/staticfiles/tests/tests.py index affceeb..d094ab3 100644 --- a/staticfiles/tests/tests.py +++ b/staticfiles/tests/tests.py @@ -136,9 +136,8 @@ def run_collectstatic(self, **kwargs): ignore_patterns = [ '*.ignoreme', os.path.join('test', '*.ignoreme2'), os.path.join( settings.TEST_ROOT, 'apps', 'test', 'static', 'test', '*.ignoreme3')] - kwargs.setdefault('fail_silently', True) call_command('collectstatic', interactive=False, verbosity='0', - ignore_patterns=ignore_patterns, **kwargs) + ignore_patterns=ignore_patterns, fail_silently=True, **kwargs) def _get_file(self, filepath): assert filepath, 'filepath is empty.' From 2eea2800b100ce1d4fc1ddf71355c63f6f20202b Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Sat, 24 Mar 2012 22:29:04 +0100 Subject: [PATCH 7/9] Typo --- docs/commands.rst | 2 +- staticfiles/management/commands/collectstatic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/commands.rst b/docs/commands.rst index 00069a5..f4c1dba 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -54,7 +54,7 @@ Some commonly used options are: .. versionadded:: 1.2 - Ignore post-processing errors raised on missing file. + Ignore post-processing error raised on missing file. For a full list of options, refer to the collectstatic management command help by running:: diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 45758cb..57e894f 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -45,7 +45,7 @@ class Command(NoArgsCommand): "'.*' and '*~'."), make_option('--ignore-errors', action='store_true', dest='fail_silently', default=False, - help="Ignore post-processing errors raised on missing file."), + help="Ignore post-processing error raised on missing file."), ) help = "Collect static files in a single location." requires_model_validation = False From fc722e607940463c8be05f4624344ad76b2fb7d2 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Wed, 4 Apr 2012 14:57:19 +0200 Subject: [PATCH 8/9] Typo --- docs/helpers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index 9fc9535..185142e 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -33,7 +33,7 @@ static .. versionadded:: 1.1 -Uses the configued :attr:`~django.conf.settings.STATICFILES_STORAGE` storage +Uses the configured :attr:`~django.conf.settings.STATICFILES_STORAGE` storage to create the full URL for the given relative path, e.g.:: {% load staticfiles %} From 9b0579f96044848eb9910b2e9e6fbe4506b6bca6 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Wed, 4 Apr 2012 17:10:41 +0200 Subject: [PATCH 9/9] Fixed issue in collectstatic that was processing too much files. When a file exists in many directories included in STATICFILES_DIRS, every files found were processed, and the last one was used fro the post-processing phase. According to the precedence, only the first file found should be (post-)processed. --- staticfiles/management/commands/collectstatic.py | 6 ++++-- staticfiles/tests/tests.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 57e894f..db13371 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -113,8 +113,10 @@ def collect(self): prefixed_path = os.path.join(storage.prefix, path) else: prefixed_path = path - found_files[prefixed_path] = (storage, path) - handler(path, prefixed_path, storage) + # Process only not already processed files. + if prefixed_path not in found_files: + found_files[prefixed_path] = (storage, path) + handler(path, prefixed_path, storage) # Here we check if the storage backend has a post_process # method and pass it the list of modified files. diff --git a/staticfiles/tests/tests.py b/staticfiles/tests/tests.py index d094ab3..c7a7ba6 100644 --- a/staticfiles/tests/tests.py +++ b/staticfiles/tests/tests.py @@ -361,7 +361,7 @@ def test_template_tag_return(self): "does/not/exist.png", "/static/does/not/exist.png") self.assertStaticRenders("test/file.txt", - "/static/test/file.ea5bccaf16d5.txt") + "/static/test/file.dad0999e4f8f.txt") self.assertStaticRenders("cached/styles.css", "/static/cached/styles.93b1147e8552.css") @@ -466,6 +466,15 @@ def test_ignored_file(self): content = relfile.read() self.assertIn('@import url("img/does_not_exists.png");', content) + def test_path_with_precedence(self): + relpath = self.cached_file_path("test/file.txt") + self.assertEqual(relpath, "test/file.dad0999e4f8f.txt") + with storage.staticfiles_storage.open( + "test/file.dad0999e4f8f.txt") as relfile: + content = relfile.read() + self.assertNotIn("In app media directory.", content) + self.assertIn("In STATICFILES_DIRS directory.", content) + def test_post_processing(self): """Test that post_processing behaves correctly.