1
0
mirror of https://github.com/django/django.git synced 2025-08-21 01:09:13 +00:00

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.
This commit is contained in:
James Bligh 2025-07-26 13:50:34 +01:00 committed by Sarah Boyce
parent 6537732585
commit 6142e3f347
3 changed files with 56 additions and 13 deletions

View File

@ -25,6 +25,7 @@ class Command(BaseCommand):
self.symlinked_files = [] self.symlinked_files = []
self.unmodified_files = [] self.unmodified_files = []
self.post_processed_files = [] self.post_processed_files = []
self.skipped_files = []
self.storage = staticfiles_storage self.storage = staticfiles_storage
self.style = no_style() self.style = no_style()
@ -134,12 +135,13 @@ class Command(BaseCommand):
found_files[prefixed_path] = (storage, path) found_files[prefixed_path] = (storage, path)
handler(path, prefixed_path, storage) handler(path, prefixed_path, storage)
else: else:
self.skipped_files.append(prefixed_path)
self.log( self.log(
"Found another file with the destination path '%s'. It " "Found another file with the destination path '%s'. It "
"will be ignored since only the first encountered file " "will be ignored since only the first encountered file "
"is collected. If this is not what you want, make sure " "is collected. If this is not what you want, make sure "
"every static file has a unique path." % prefixed_path, "every static file has a unique path." % prefixed_path,
level=1, level=2,
) )
# Storage backends may define a post_process() method. # Storage backends may define a post_process() method.
@ -165,6 +167,7 @@ class Command(BaseCommand):
"modified": self.copied_files + self.symlinked_files, "modified": self.copied_files + self.symlinked_files,
"unmodified": self.unmodified_files, "unmodified": self.unmodified_files,
"post_processed": self.post_processed_files, "post_processed": self.post_processed_files,
"skipped": self.skipped_files,
} }
def handle(self, **options): def handle(self, **options):
@ -212,9 +215,10 @@ class Command(BaseCommand):
modified_count = len(collected["modified"]) modified_count = len(collected["modified"])
unmodified_count = len(collected["unmodified"]) unmodified_count = len(collected["unmodified"])
post_processed_count = len(collected["post_processed"]) post_processed_count = len(collected["post_processed"])
skipped_count = len(collected["skipped"])
return ( return (
"\n%(modified_count)s %(identifier)s %(action)s" "\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, "modified_count": modified_count,
"identifier": "static file" + ("" if modified_count == 1 else "s"), "identifier": "static file" + ("" if modified_count == 1 else "s"),
@ -232,6 +236,11 @@ class Command(BaseCommand):
and ", %s post-processed" % post_processed_count and ", %s post-processed" % post_processed_count
or "" or ""
), ),
"skipped": (
", %s skipped due to conflict" % skipped_count
if collected["skipped"]
else ""
),
} }
def log(self, msg, level=2): def log(self, msg, level=2):

View File

@ -470,6 +470,10 @@ Miscellaneous
* The minimum supported version of ``asgiref`` is increased from 3.8.1 to * The minimum supported version of ``asgiref`` is increased from 3.8.1 to
3.9.1. 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: .. _deprecated-features-6.0:
Features deprecated in 6.0 Features deprecated in 6.0

View File

@ -508,15 +508,17 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
# looking for was emitted. # looking for was emitted.
warning_string = "Found another file" 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 Run collectstatic, and capture and return the output.
the command at highest verbosity, which is why we can't
just call e.g. BaseCollectionTestCase.run_collectstatic()
""" """
out = StringIO() out = StringIO()
call_command( call_command(
"collectstatic", interactive=False, verbosity=3, stdout=out, **kwargs "collectstatic",
interactive=False,
verbosity=verbosity,
stdout=out,
**kwargs,
) )
return out.getvalue() return out.getvalue()
@ -527,9 +529,10 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
output = self._collectstatic_output(clear=True) output = self._collectstatic_output(clear=True)
self.assertNotIn(self.warning_string, output) 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: with tempfile.TemporaryDirectory() as static_dir:
duplicate = os.path.join(static_dir, "test", "file.txt") duplicate = os.path.join(static_dir, "test", "file.txt")
@ -538,15 +541,42 @@ class TestCollectionOverwriteWarning(CollectionTestCase):
f.write("duplicate of file.txt") f.write("duplicate of file.txt")
with self.settings(STATICFILES_DIRS=[static_dir]): 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) 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]): 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.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( @override_settings(