diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c0a276e288..32f3130207 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -37,6 +37,7 @@ Changelog * Extract generic `HistoryView` from snippets and add it to `ModelViewSet` (Sage Abdullah) * Add generic `UsageView` to `ModelViewSet` (Sage Abdullah) * Add the ability to define listing buttons on generic `IndexView` (Sage Abdullah) + * Add a visual progress bar to the output of the `wagtail_update_image_renditions` management command (Faishal Manzar) * Fix: Ensure that StreamField's `FieldBlock`s correctly set the `required` and `aria-describedby` attributes (Storm Heg) * Fix: Avoid an error when the moderation panel (admin dashboard) contains both snippets and private pages (Matt Westcott) * Fix: When deleting collections, ensure the collection name is correctly shown in the success message (LB (Ben) Johnston) diff --git a/docs/releases/5.2.md b/docs/releases/5.2.md index 2c613ac9b0..d9f947a0a0 100644 --- a/docs/releases/5.2.md +++ b/docs/releases/5.2.md @@ -49,6 +49,7 @@ depth: 1 * Extract generic `HistoryView` from snippets and add it to `ModelViewSet` (Sage Abdullah) * Add generic `UsageView` to `ModelViewSet` (Sage Abdullah) * Add the ability to define listing buttons on generic `IndexView` (Sage Abdullah) + * Add a visual progress bar to the output of the `wagtail_update_image_renditions` management command (Faishal Manzar) ### Bug fixes diff --git a/wagtail/images/management/commands/wagtail_update_image_renditions.py b/wagtail/images/management/commands/wagtail_update_image_renditions.py index 3d75960023..660fa0da07 100644 --- a/wagtail/images/management/commands/wagtail_update_image_renditions.py +++ b/wagtail/images/management/commands/wagtail_update_image_renditions.py @@ -8,6 +8,17 @@ from wagtail.images import get_image_model logger = logging.getLogger(__name__) +def progress_bar(current, total, bar_length=50): + fraction = current / total + + arrow = int(fraction * bar_length - 1) * "-" + ">" + padding = int(bar_length - len(arrow)) * " " + + ending = "\n" if current == total else "\r" + + return (f"Progress: [{arrow}{padding}] {int(fraction*100)}%", ending) + + class Command(BaseCommand): """Command to create missing image renditions with the option to remove (purge) any existing ones.""" @@ -49,6 +60,7 @@ class Command(BaseCommand): self.style.HTTP_INFO(f"Regenerating {num_renditions} rendition(s)") ) + progress_bar_current = 1 for rendition in ( # Pre-calculate the ids of the renditions to change, # otherwise `.iterator` never ends. @@ -64,6 +76,10 @@ class Command(BaseCommand): # Delete the existing rendition rendition.delete() + _progress_bar = progress_bar(progress_bar_current, num_renditions) + self.stdout.write(_progress_bar[0], ending=_progress_bar[1]) + progress_bar_current = progress_bar_current + 1 + if not purge_only: # Create a new one rendition_image.get_rendition(rendition_filter) diff --git a/wagtail/images/tests/test_management_commands.py b/wagtail/images/tests/test_management_commands.py index 8030627d92..584aae5d5d 100644 --- a/wagtail/images/tests/test_management_commands.py +++ b/wagtail/images/tests/test_management_commands.py @@ -5,6 +5,7 @@ from io import StringIO from django.core import management from django.test import TestCase, override_settings +from ..management.commands.wagtail_update_image_renditions import progress_bar from .utils import Image, get_test_image_file # note .utils.Image already does get_image_model() @@ -49,6 +50,29 @@ class TestUpdateImageRenditions(TestCase): return output + def test_progress_bar(self): + total_rendition = 10 + out = StringIO() + for current in range(1, total_rendition + 1): + progress_bar_output = progress_bar(current, total_rendition)[0] + out.write(progress_bar_output) + out.seek(0) + expected_output = "".join( + [ + "Progress: [----> ] 10%", + "Progress: [---------> ] 20%", + "Progress: [--------------> ] 30%", + "Progress: [-------------------> ] 40%", + "Progress: [------------------------> ] 50%", + "Progress: [-----------------------------> ] 60%", + "Progress: [----------------------------------> ] 70%", + "Progress: [---------------------------------------> ] 80%", + "Progress: [--------------------------------------------> ] 90%", + "Progress: [------------------------------------------------->] 100%", + ] + ) + self.assertIn(expected_output, out.getvalue()) + def test_exits_early_for_no_renditions(self): self.delete_renditions() # checking when command is called without any arguments @@ -70,6 +94,7 @@ class TestUpdateImageRenditions(TestCase): self.assertEqual( output_string, f"Regenerating {total_renditions} rendition(s)\n" + f"Progress: [------------------------------------------------->] 100%\n" f"Successfully processed {total_renditions} rendition(s)\n", ) @@ -87,6 +112,7 @@ class TestUpdateImageRenditions(TestCase): self.assertEqual( output_string, f"Purging {total_renditions} rendition(s)\n" + f"Progress: [------------------------------------------------->] 100%\n" f"Successfully processed {total_renditions} rendition(s)\n", )