summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-01-03 19:03:08 -0500
committerTim Graham <timograham@gmail.com>2017-01-04 13:10:03 -0500
commitda9b36c52d403a8de77682f4892d8890075f1289 (patch)
tree9bd17919c10f275f865b96443e39920bff0b5694
parenta72fa7de3eddeb8791b5fdbc1f6576cae39d5ff5 (diff)
downloaddjango-da9b36c52d403a8de77682f4892d8890075f1289.tar.gz
[1.10.x] Fixed #27658 -- Prevented collectstatic from overwriting newer files in remote storages.
Thanks revimi for the initial patch. Backport of c85831e4b7b5a7e4249df10327175b7251cb012d from master
-rw-r--r--django/contrib/staticfiles/management/commands/collectstatic.py18
-rw-r--r--docs/releases/1.10.5.txt3
-rw-r--r--tests/staticfiles_tests/cases.py3
-rw-r--r--tests/staticfiles_tests/storage.py10
-rw-r--r--tests/staticfiles_tests/test_management.py17
5 files changed, 46 insertions, 5 deletions
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index db29ff73cc..5dcf35fe4e 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -269,12 +269,24 @@ class Command(BaseCommand):
# The full path of the target file
if self.local:
full_path = self.storage.path(prefixed_path)
+ # If it's --link mode and the path isn't a link (i.e.
+ # the previous collectstatic wasn't with --link) or if
+ # it's non-link mode and the path is a link (i.e. the
+ # previous collectstatic was with --link), the old
+ # links/files must be deleted so it's not safe to skip
+ # unmodified files.
+ can_skip_unmodified_files = not (self.symlink ^ os.path.islink(full_path))
else:
full_path = None
- # Skip the file if the source file is younger
+ # In remote storages, skipping is only based on the
+ # modified times since symlinks aren't relevant.
+ can_skip_unmodified_files = True
# Avoid sub-second precision (see #14665, #19540)
- if (target_last_modified.replace(microsecond=0) >= source_last_modified.replace(microsecond=0) and
- full_path and not (self.symlink ^ os.path.islink(full_path))):
+ file_is_unmodified = (
+ target_last_modified.replace(microsecond=0) >=
+ source_last_modified.replace(microsecond=0)
+ )
+ if file_is_unmodified and can_skip_unmodified_files:
if prefixed_path not in self.unmodified_files:
self.unmodified_files.append(prefixed_path)
self.log("Skipping '%s' (not modified)" % path)
diff --git a/docs/releases/1.10.5.txt b/docs/releases/1.10.5.txt
index 5c9d40914b..3e75235c42 100644
--- a/docs/releases/1.10.5.txt
+++ b/docs/releases/1.10.5.txt
@@ -17,3 +17,6 @@ Bugfixes
* Fixed a regression in the ``timesince`` and ``timeuntil`` filters that caused
incorrect results for dates in a leap year (:ticket:`27637`).
+
+* Fixed a regression where ``collectstatic`` overwrote newer files in remote
+ storages (:ticket:`27658`).
diff --git a/tests/staticfiles_tests/cases.py b/tests/staticfiles_tests/cases.py
index abc4f1e81b..d2a4dcf2cb 100644
--- a/tests/staticfiles_tests/cases.py
+++ b/tests/staticfiles_tests/cases.py
@@ -82,7 +82,8 @@ class CollectionTestCase(BaseStaticFilesMixin, SimpleTestCase):
super(CollectionTestCase, self).tearDown()
def run_collectstatic(self, **kwargs):
- call_command('collectstatic', interactive=False, verbosity=0,
+ verbosity = kwargs.pop('verbosity', 0)
+ call_command('collectstatic', interactive=False, verbosity=verbosity,
ignore_patterns=['*.ignoreme'], **kwargs)
def _get_file(self, filepath):
diff --git a/tests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py
index 8c474591f3..01c5de07f0 100644
--- a/tests/staticfiles_tests/storage.py
+++ b/tests/staticfiles_tests/storage.py
@@ -1,6 +1,6 @@
import errno
import os
-from datetime import datetime
+from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.staticfiles.storage import CachedStaticFilesStorage
@@ -59,6 +59,14 @@ class PathNotImplementedStorage(storage.Storage):
raise NotImplementedError
+class NeverCopyRemoteStorage(PathNotImplementedStorage):
+ """
+ Return a future modified time for all files so that nothing is collected.
+ """
+ def get_modified_time(self, name):
+ return datetime.now() + timedelta(days=30)
+
+
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 5eebdc8939..ca71cb01bc 100644
--- a/tests/staticfiles_tests/test_management.py
+++ b/tests/staticfiles_tests/test_management.py
@@ -337,6 +337,23 @@ class TestCollectionNonLocalStorage(TestNoFilesCreated, CollectionTestCase):
pass
+class TestCollectionNeverCopyStorage(CollectionTestCase):
+
+ @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.NeverCopyRemoteStorage')
+ def test_skips_newer_files_in_remote_storage(self):
+ """
+ collectstatic skips newer files in a remote storage.
+ run_collectstatic() in setUp() copies the static files, then files are
+ always skipped after NeverCopyRemoteStorage is activated since
+ NeverCopyRemoteStorage.get_modified_time() returns a datetime in the
+ future to simulate an unmodified file.
+ """
+ stdout = six.StringIO()
+ self.run_collectstatic(stdout=stdout, verbosity=2)
+ output = force_text(stdout.getvalue())
+ self.assertIn("Skipping 'test.txt' (not modified)", output)
+
+
@unittest.skipUnless(symlinks_supported(), "Must be able to symlink to run this test.")
class TestCollectionLinks(TestDefaults, CollectionTestCase):
"""