diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py index 60c4e7cd6d..2170647433 100644 --- a/django/contrib/staticfiles/management/commands/collectstatic.py +++ b/django/contrib/staticfiles/management/commands/collectstatic.py @@ -221,12 +221,16 @@ class Command(BaseCommand): smart_text(fpath), level=1) else: self.log("Deleting '%s'" % smart_text(fpath), level=1) - full_path = self.storage.path(fpath) - if not os.path.exists(full_path) and os.path.lexists(full_path): - # Delete broken symlinks - os.unlink(full_path) - else: + try: + full_path = self.storage.path(fpath) + except NotImplementedError: self.storage.delete(fpath) + else: + if not os.path.exists(full_path) and os.path.lexists(full_path): + # Delete broken symlinks + os.unlink(full_path) + else: + self.storage.delete(fpath) for d in dirs: self.clear_dir(os.path.join(path, d)) diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt index d74a2d9abe..98dd1799a8 100644 --- a/docs/releases/1.9.5.txt +++ b/docs/releases/1.9.5.txt @@ -27,3 +27,6 @@ Bugfixes earlier versions of Django. * Fixed a memory leak in the cached template loader (:ticket:`26306`). + +* Fixed a regression that caused ``collectstatic --clear`` to fail if the + storage doesn't implement ``path()`` (:ticket:`26297`). diff --git a/tests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py index 3e972552f2..06caebfcd2 100644 --- a/tests/staticfiles_tests/storage.py +++ b/tests/staticfiles_tests/storage.py @@ -1,5 +1,8 @@ +import errno +import os from datetime import datetime +from django.conf import settings from django.contrib.staticfiles.storage import CachedStaticFilesStorage from django.core.files import storage from django.utils import timezone @@ -22,6 +25,40 @@ class DummyStorage(storage.Storage): return datetime.datetime(1970, 1, 1, tzinfo=timezone.utc) +class PathNotImplementedStorage(storage.Storage): + + def _save(self, name, content): + return 'dummy' + + def _path(self, name): + return os.path.join(settings.STATIC_ROOT, name) + + def exists(self, name): + return os.path.exists(self._path(name)) + + def listdir(self, path): + path = self._path(path) + directories, files = [], [] + for entry in os.listdir(path): + if os.path.isdir(os.path.join(path, entry)): + directories.append(entry) + else: + files.append(entry) + return directories, files + + def delete(self, name): + name = self._path(name) + if os.path.exists(name): + try: + os.remove(name) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def path(self, name): + raise NotImplementedError + + class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage): def file_hash(self, name, content=None): diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index 4700977666..fa47f12b49 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -170,6 +170,11 @@ class TestCollectionClear(CollectionTestCase): shutil.rmtree(six.text_type(settings.STATIC_ROOT)) super(TestCollectionClear, self).run_collectstatic(clear=True) + @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.PathNotImplementedStorage') + def test_handle_path_notimplemented(self): + self.run_collectstatic() + self.assertFileNotFound('cleared.txt') + class TestCollectionExcludeNoDefaultIgnore(CollectionTestCase, TestDefaults): """