From b38141ad829418f66b1653a0467b4a9d4b9f8de1 Mon Sep 17 00:00:00 2001 From: alexkiro <1538458+alexkiro@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:30:52 +0300 Subject: [PATCH] Allow customization of preview device sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartosz CieliƄski --- docs/reference/pages/model_reference.md | 10 ++++ .../shared/side_panels/preview.html | 31 ++++++---- wagtail/admin/tests/pages/test_preview.py | 21 +++++++ wagtail/models/__init__.py | 57 +++++++++++++++++++ wagtail/snippets/tests/test_snippets.py | 45 +++++++++++++++ ...reviewsizesmodel_custompreviewsizespage.py | 52 +++++++++++++++++ wagtail/test/testapp/models.py | 53 +++++++++++++++++ 7 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py diff --git a/docs/reference/pages/model_reference.md b/docs/reference/pages/model_reference.md index 7951215f22..5f91fa6fac 100644 --- a/docs/reference/pages/model_reference.md +++ b/docs/reference/pages/model_reference.md @@ -202,6 +202,12 @@ See also [django-treebeard](https://django-treebeard.readthedocs.io/en/latest/in .. autoattribute:: preview_modes + .. autoattribute:: default_preview_mode + + .. autoattribute:: preview_sizes + + .. autoattribute:: default_preview_size + .. automethod:: serve_preview .. automethod:: get_parent @@ -576,6 +582,10 @@ Pages already include this mixin, so there is no need to add it. .. autoattribute:: preview_modes .. autoattribute:: default_preview_mode + + .. autoattribute:: preview_sizes + + .. autoattribute:: default_preview_size .. automethod:: is_previewable diff --git a/wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html b/wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html index eede986f2c..d0bde17562 100644 --- a/wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html +++ b/wagtail/admin/templates/wagtailadmin/shared/side_panels/preview.html @@ -12,18 +12,25 @@ >
{% block size_buttons %} - - - + {% for size in object.preview_sizes %} + + {% endfor %} {% endblock %} {% block new_tab_button %} diff --git a/wagtail/admin/tests/pages/test_preview.py b/wagtail/admin/tests/pages/test_preview.py index 9942e7a77b..dcdc5b46e1 100644 --- a/wagtail/admin/tests/pages/test_preview.py +++ b/wagtail/admin/tests/pages/test_preview.py @@ -9,6 +9,7 @@ from freezegun import freeze_time from wagtail.admin.views.pages.preview import PreviewOnEdit from wagtail.models import Page from wagtail.test.testapp.models import ( + CustomPreviewSizesPage, EventCategory, MultiPreviewModesPage, SimplePage, @@ -415,6 +416,26 @@ class TestPreview(WagtailTestUtils, TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, template) + def test_preview_sizes(self): + page = CustomPreviewSizesPage(title="Custom preview size") + self.home_page.add_child(instance=page) + edit_url = reverse("wagtailadmin_pages:edit", args=(page.id,)) + + response = self.client.get(edit_url) + self.assertEqual(response.status_code, 200) + soup = self.get_soup(response.content) + + radios = soup.select('input[type="radio"][name="preview-size"]') + self.assertEqual(len(radios), 2) + + self.assertEqual("412", radios[0]["data-device-width"]) + self.assertEqual("Custom mobile preview", radios[0]["aria-label"]) + self.assertFalse(radios[0].has_attr("checked")) + + self.assertEqual("1280", radios[1]["data-device-width"]) + self.assertEqual("Original desktop", radios[1]["aria-label"]) + self.assertTrue(radios[1].has_attr("checked")) + class TestEnablePreview(WagtailTestUtils, TestCase): def setUp(self): diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py index 940a276627..d99c6ded97 100644 --- a/wagtail/models/__init__.py +++ b/wagtail/models/__init__.py @@ -805,6 +805,26 @@ class PreviewableMixin: full_url = property(get_full_url) DEFAULT_PREVIEW_MODES = [("", _("Default"))] + DEFAULT_PREVIEW_SIZES = [ + { + "name": "mobile", + "icon": "mobile-alt", + "device_width": 375, + "label": _("Preview in mobile size"), + }, + { + "name": "tablet", + "icon": "tablet-alt", + "device_width": 768, + "label": _("Preview in tablet size"), + }, + { + "name": "desktop", + "icon": "desktop", + "device_width": 1280, + "label": _("Preview in desktop size"), + }, + ] @property def preview_modes(self): @@ -828,6 +848,43 @@ class PreviewableMixin: """ return self.preview_modes[0][0] + @property + def preview_sizes(self): + """ + Returns a list of dictionaries, each representing a preview size option for this object. + Override this property to customize the preview sizes. + Each dictionary in the list should include the following keys: + + - `name`: A string representing the internal name of the preview size. + - `icon`: A string specifying the icon's name for the preview size button. + - `device_width`: An integer indicating the device's width in pixels. + - `label`: A string for the aria label on the preview size button. + + .. code-block:: python + + @property + def preview_sizes(self): + return [ + { + "name": "mobile", + "icon": "mobile-icon", + "device_width": 320, + "label": "Preview in mobile size" + }, + # Add more preview size dictionaries as needed. + ] + """ + return PreviewableMixin.DEFAULT_PREVIEW_SIZES + + @property + def default_preview_size(self): + """ + The default preview size name to use in live preview. + Defaults to ``"mobile"``, which is the first one defined in ``preview_sizes``. + If ``preview_sizes`` is empty, an ``IndexError`` will be raised. + """ + return self.preview_sizes[0]["name"] + def is_previewable(self): """Returns ``True`` if at least one preview mode is specified in ``preview_modes``.""" return bool(self.preview_modes) diff --git a/wagtail/snippets/tests/test_snippets.py b/wagtail/snippets/tests/test_snippets.py index 3c8ee9cbea..c08a5af8c7 100644 --- a/wagtail/snippets/tests/test_snippets.py +++ b/wagtail/snippets/tests/test_snippets.py @@ -63,10 +63,12 @@ from wagtail.test.testapp.models import ( AdvertWithCustomPrimaryKey, AdvertWithCustomUUIDPrimaryKey, AdvertWithTabbedInterface, + CustomPreviewSizesModel, DraftStateCustomPrimaryKeyModel, DraftStateModel, FullFeaturedSnippet, MultiPreviewModesModel, + PreviewableModel, RevisableChildModel, RevisableModel, SnippetChooserModel, @@ -1991,6 +1993,49 @@ class TestSnippetEditView(BaseTestSnippetEditView): html=True, ) + def test_previewable_snippet(self): + self.test_snippet = PreviewableModel.objects.create( + text="Preview-enabled snippet" + ) + response = self.get() + self.assertEqual(response.status_code, 200) + soup = self.get_soup(response.content) + + radios = soup.select('input[type="radio"][name="preview-size"]') + self.assertEqual(len(radios), 3) + + self.assertEqual( + [ + "Preview in mobile size", + "Preview in tablet size", + "Preview in desktop size", + ], + [radio["aria-label"] for radio in radios], + ) + + self.assertEqual("375", radios[0]["data-device-width"]) + self.assertTrue(radios[0].has_attr("checked")) + + def test_custom_preview_sizes(self): + self.test_snippet = CustomPreviewSizesModel.objects.create( + text="Preview-enabled with custom sizes", + ) + + response = self.get() + self.assertEqual(response.status_code, 200) + soup = self.get_soup(response.content) + + radios = soup.select('input[type="radio"][name="preview-size"]') + self.assertEqual(len(radios), 2) + + self.assertEqual("412", radios[0]["data-device-width"]) + self.assertEqual("Custom mobile preview", radios[0]["aria-label"]) + self.assertFalse(radios[0].has_attr("checked")) + + self.assertEqual("1280", radios[1]["data-device-width"]) + self.assertEqual("Original desktop", radios[1]["aria-label"]) + self.assertTrue(radios[1].has_attr("checked")) + class TestEditTabbedSnippet(BaseTestSnippetEditView): def setUp(self): diff --git a/wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py b/wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py new file mode 100644 index 0000000000..07b7813f5e --- /dev/null +++ b/wagtail/test/testapp/migrations/0044_custompreviewsizesmodel_custompreviewsizespage.py @@ -0,0 +1,52 @@ +# Generated by Django 5.0.9 on 2024-10-16 05:17 + +import django.db.models.deletion +import wagtail.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("tests", "0043_customimage_description"), + ("wagtailcore", "0094_alter_page_locale"), + ] + + operations = [ + migrations.CreateModel( + name="CustomPreviewSizesModel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.TextField()), + ], + bases=(wagtail.models.PreviewableMixin, models.Model), + ), + migrations.CreateModel( + name="CustomPreviewSizesPage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + ] diff --git a/wagtail/test/testapp/models.py b/wagtail/test/testapp/models.py index 55efdf0b40..abcb953490 100644 --- a/wagtail/test/testapp/models.py +++ b/wagtail/test/testapp/models.py @@ -215,6 +215,31 @@ class MultiPreviewModesPage(Page): return super().get_preview_template(request, mode_name) +class CustomPreviewSizesPage(Page): + template = "tests/simple_page.html" + + @property + def preview_sizes(self): + return [ + { + "name": "custom-mobile", + "icon": "mobile-alt", + "device_width": 412, + "label": "Custom mobile preview", + }, + { + "name": "desktop", + "icon": "desktop", + "device_width": 1280, + "label": "Original desktop", + }, + ] + + @property + def default_preview_size(self): + return "desktop" + + # Page with Excluded Fields when copied class PageWithExcludedCopyField(Page): content = models.TextField() @@ -1083,6 +1108,34 @@ class PreviewableModel(PreviewableMixin, ClusterableModel): register_snippet(PreviewableModel) +class CustomPreviewSizesModel(PreviewableMixin, models.Model): + text = models.TextField() + + @property + def preview_sizes(self): + return [ + { + "name": "custom-mobile", + "icon": "mobile-alt", + "device_width": 412, + "label": "Custom mobile preview", + }, + { + "name": "desktop", + "icon": "desktop", + "device_width": 1280, + "label": "Original desktop", + }, + ] + + @property + def default_preview_size(self): + return "desktop" + + +register_snippet(CustomPreviewSizesModel) + + class MultiPreviewModesModel(PreviewableMixin, RevisionMixin, models.Model): text = models.TextField()