Skip to content
This repository has been archived by the owner on Jan 9, 2018. It is now read-only.

Added option into collectstatic command to ignore post-processing errors #17

Closed
wants to merge 11 commits into from
6 changes: 6 additions & 0 deletions docs/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 error raised on missing file.

For a full list of options, refer to the collectstatic management command help
by running::

Expand Down
2 changes: 1 addition & 1 deletion docs/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = ['\"]([^'\"]*)['\"]",
Expand All @@ -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
Expand Down Expand Up @@ -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"),
Expand Down
12 changes: 9 additions & 3 deletions staticfiles/management/commands/collectstatic.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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='fail_silently', default=False,
help="Ignore post-processing error raised on missing file."),
)
help = "Collect static files in a single location."
requires_model_validation = False
Expand Down Expand Up @@ -79,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['fail_silently']

def collect(self):
"""
Expand Down Expand Up @@ -109,14 +113,16 @@ 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.
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" %
Expand Down
14 changes: 9 additions & 5 deletions staticfiles/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def url(self, name, force=False):

return unquote(final_url)

def url_converter(self, name):
def url_converter(self, name, fail_silently=False):
"""
Returns the custom URL converter for the given file name.
"""
Expand Down Expand Up @@ -210,13 +210,17 @@ def converter(matchobj):
else:
start, end = 1, sub_level - 1
joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
hashed_url = self.url(unquote(joined_result), force=True)

try:
hashed_url = self.url(unquote(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")' % 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).

Expand Down Expand Up @@ -264,7 +268,7 @@ def post_process(self, paths, dry_run=False, **options):
# ..to apply each replacement pattern to the content
if name in adjustable_paths:
content = original_file.read()
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)
Expand Down
1 change: 1 addition & 0 deletions staticfiles/tests/project/documents/cached/faulty.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import url("img/does_not_exists.png");
47 changes: 43 additions & 4 deletions staticfiles/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def run_collectstatic(self, **kwargs):
'*.ignoreme', os.path.join('test', '*.ignoreme2'), os.path.join(
settings.TEST_ROOT, 'apps', 'test', 'static', 'test', '*.ignoreme3')]
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.'
Expand Down Expand Up @@ -268,7 +268,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')
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -459,6 +459,22 @@ 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_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.

Expand All @@ -476,7 +492,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()
Expand All @@ -485,6 +502,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):
Expand Down