summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Bligh <blighj@users.noreply.github.com>2025-07-26 13:50:34 +0100
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-08-05 16:37:43 +0200
commit6142e3f347c6f9415165d385d3eba4211a052d96 (patch)
treecabe81a42409fed427f3776b447de41728f58d51
parent65377325855ab6faa7ac487e336e9902c6268c96 (diff)
Fixed #26583 -- Silenced individual clashing name warnings in collectstatic's default verbosity.
Made collectstatic report individual destination conflicts only at verbosity 2+. Made verbosity level 1 report a summary count of skipped files.
-rw-r--r--django/contrib/staticfiles/management/commands/collectstatic.py13
-rw-r--r--docs/releases/6.0.txt4
-rw-r--r--tests/staticfiles_tests/test_management.py52
3 files changed, 56 insertions, 13 deletions
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index d5cd3f56ca..7f1d612bf0 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -25,6 +25,7 @@ class Command(BaseCommand):
self.symlinked_files = []
self.unmodified_files = []
self.post_processed_files = []
+ self.skipped_files = []
self.storage = staticfiles_storage
self.style = no_style()
@@ -134,12 +135,13 @@ class Command(BaseCommand):
found_files[prefixed_path] = (storage, path)
handler(path, prefixed_path, storage)
else:
+ self.skipped_files.append(prefixed_path)
self.log(
"Found another file with the destination path '%s'. It "
"will be ignored since only the first encountered file "
"is collected. If this is not what you want, make sure "
"every static file has a unique path." % prefixed_path,
- level=1,
+ level=2,
)
# Storage backends may define a post_process() method.
@@ -165,6 +167,7 @@ class Command(BaseCommand):
"modified": self.copied_files + self.symlinked_files,
"unmodified": self.unmodified_files,
"post_processed": self.post_processed_files,
+ "skipped": self.skipped_files,
}
def handle(self, **options):
@@ -212,9 +215,10 @@ class Command(BaseCommand):
modified_count = len(collected["modified"])
unmodified_count = len(collected["unmodified"])
post_processed_count = len(collected["post_processed"])
+ skipped_count = len(collected["skipped"])
return (
"\n%(modified_count)s %(identifier)s %(action)s"
- "%(destination)s%(unmodified)s%(post_processed)s."
+ "%(destination)s%(unmodified)s%(post_processed)s%(skipped)s."
) % {
"modified_count": modified_count,
"identifier": "static file" + ("" if modified_count == 1 else "s"),
@@ -232,6 +236,11 @@ class Command(BaseCommand):
and ", %s post-processed" % post_processed_count
or ""
),
+ "skipped": (
+ ", %s skipped due to conflict" % skipped_count
+ if collected["skipped"]
+ else ""
+ ),
}
def log(self, msg, level=2):
diff --git a/docs/releases/6.0.txt b/docs/releases/6.0.txt
index 828e2d0354..4afc172da9 100644
--- a/docs/releases/6.0.txt
+++ b/docs/releases/6.0.txt
@@ -470,6 +470,10 @@ Miscellaneous
* The minimum supported version of ``asgiref`` is increased from 3.8.1 to
3.9.1.
+* The :djadmin:`collectstatic` command now reports only a summary of skipped
+ files due to conflicts when ``--verbosity`` is 1. To see warnings for each
+ conflicting destination path, set the ``--verbosity`` flag to 2 or higher.
+
.. _deprecated-features-6.0:
Features deprecated in 6.0
diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py
index e8873915e6..9555c54093 100644
--- a/tests/staticfiles_tests/test_management.py
+++ b/tests/staticfiles_tests/test_management.py
@@ -508,15 +508,17 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
# looking for was emitted.
warning_string = "Found another file"
- def _collectstatic_output(self, **kwargs):
+ def _collectstatic_output(self, verbosity=3, **kwargs):
"""
- Run collectstatic, and capture and return the output. We want to run
- the command at highest verbosity, which is why we can't
- just call e.g. BaseCollectionTestCase.run_collectstatic()
+ Run collectstatic, and capture and return the output.
"""
out = StringIO()
call_command(
- "collectstatic", interactive=False, verbosity=3, stdout=out, **kwargs
+ "collectstatic",
+ interactive=False,
+ verbosity=verbosity,
+ stdout=out,
+ **kwargs,
)
return out.getvalue()
@@ -527,9 +529,10 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
output = self._collectstatic_output(clear=True)
self.assertNotIn(self.warning_string, output)
- def test_warning(self):
+ def test_warning_at_verbosity_2(self):
"""
- There is a warning when there are duplicate destinations.
+ There is a warning when there are duplicate destinations at verbosity
+ 2+.
"""
with tempfile.TemporaryDirectory() as static_dir:
duplicate = os.path.join(static_dir, "test", "file.txt")
@@ -538,15 +541,42 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
f.write("duplicate of file.txt")
with self.settings(STATICFILES_DIRS=[static_dir]):
- output = self._collectstatic_output(clear=True)
+ output = self._collectstatic_output(clear=True, verbosity=2)
self.assertIn(self.warning_string, output)
- os.remove(duplicate)
+ def test_no_warning_at_verbosity_1(self):
+ """
+ There is no individual warning at verbosity 1, but summary is shown.
+ """
+ with tempfile.TemporaryDirectory() as static_dir:
+ duplicate = os.path.join(static_dir, "test", "file.txt")
+ os.mkdir(os.path.dirname(duplicate))
+ with open(duplicate, "w+") as f:
+ f.write("duplicate of file.txt")
- # Make sure the warning went away again.
with self.settings(STATICFILES_DIRS=[static_dir]):
- output = self._collectstatic_output(clear=True)
+ output = self._collectstatic_output(clear=True, verbosity=1)
self.assertNotIn(self.warning_string, output)
+ self.assertIn("1 skipped due to conflict", output)
+
+ def test_summary_multiple_conflicts(self):
+ """
+ Summary shows correct count for multiple conflicts.
+ """
+ with tempfile.TemporaryDirectory() as static_dir:
+ duplicate1 = os.path.join(static_dir, "test", "file.txt")
+ os.makedirs(os.path.dirname(duplicate1))
+ with open(duplicate1, "w+") as f:
+ f.write("duplicate of file.txt")
+ duplicate2 = os.path.join(static_dir, "test", "file1.txt")
+ with open(duplicate2, "w+") as f:
+ f.write("duplicate of file1.txt")
+ duplicate3 = os.path.join(static_dir, "test", "nonascii.css")
+ shutil.copy2(duplicate1, duplicate3)
+
+ with self.settings(STATICFILES_DIRS=[static_dir]):
+ output = self._collectstatic_output(clear=True, verbosity=1)
+ self.assertIn("3 skipped due to conflict", output)
@override_settings(