diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 76cd1aab8c..2e29023cab 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -25,6 +25,7 @@ Changelog * Docs: Document `restriction_type` field on PageViewRestriction (Shlomo Markowitz) * Docs: Document Wagtail's bug bounty policy (Jake Howard) * Docs: Fix incorrect Sphinx-style code references to use MyST style (Byron Peebles) + * Docs: Document the fact that `Orderable` is not required for inline panels (Bojan Mihelac) * Maintenance: Use `DjangoJSONEncoder` instead of custom `LazyStringEncoder` to serialize Draftail config (Sage Abdullah) * Maintenance: Refactor image chooser pagination to check `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` at runtime (Matt Westcott) * Maintenance: Exclude the `client/scss` directory in Tailwind content config to speed up CSS compilation (Sage Abdullah) diff --git a/docs/releases/6.2.md b/docs/releases/6.2.md index 17a54c3449..d67df6044f 100644 --- a/docs/releases/6.2.md +++ b/docs/releases/6.2.md @@ -43,6 +43,7 @@ depth: 1 * Document `restriction_type` field on PageViewRestriction (Shlomo Markowitz) * Document Wagtail's bug bounty policy (Jake Howard) * Fix incorrect Sphinx-style code references to use MyST style (Byron Peebles) + * Document the fact that `Orderable` is not required for inline panels (Bojan Mihelac) ### Maintenance diff --git a/docs/topics/pages.md b/docs/topics/pages.md index d550be9021..d678668741 100644 --- a/docs/topics/pages.md +++ b/docs/topics/pages.md @@ -333,10 +333,7 @@ class BlogPage(Page): Wagtail allows the nesting of other models within a page. This is useful for creating repeated fields, such as related links or items to display in a carousel. Inline model content is also versioned with the rest of the page. -Each inline model requires the following: - -- It must inherit from {class}`wagtail.models.Orderable` -- It must have a `ParentalKey` to the parent model +An inline model must have a `ParentalKey` pointing to the parent model. It can also inherit from {class}`wagtail.models.Orderable` to allow reordering of items in the admin interface. ````{note} The model inlining feature is provided by [django-modelcluster](https://github.com/wagtail/django-modelcluster) and the `ParentalKey` field type must be imported from there: diff --git a/wagtail/admin/tests/pages/test_create_page.py b/wagtail/admin/tests/pages/test_create_page.py index d19cef777a..6f5b1d5f57 100644 --- a/wagtail/admin/tests/pages/test_create_page.py +++ b/wagtail/admin/tests/pages/test_create_page.py @@ -1563,6 +1563,10 @@ class TestInlinePanelWithTags(WagtailTestUtils, TestCase): "comments-INITIAL_FORMS": 0, "comments-MIN_NUM_FORMS": 0, "comments-MAX_NUM_FORMS": 1000, + "social_links-TOTAL_FORMS": 0, + "social_links-INITIAL_FORMS": 0, + "social_links-MIN_NUM_FORMS": 0, + "social_links-MAX_NUM_FORMS": 1000, } response = self.client.post( reverse( @@ -1578,6 +1582,49 @@ class TestInlinePanelWithTags(WagtailTestUtils, TestCase): self.assertEqual(new_page.addresses.first().tags.count(), 2) +class TestNonOrderableInlinePanel(WagtailTestUtils, TestCase): + # https://github.com/wagtail/wagtail/issues/11887 + + def setUp(self): + self.root_page = Page.objects.get(id=2) + self.user = self.login() + + def test_create(self): + post_data = { + "title": "Mr Benn", + "slug": "mr-benn", + "first_name": "William", + "last_name": "Benn", + "addresses-TOTAL_FORMS": 0, + "addresses-INITIAL_FORMS": 0, + "addresses-MIN_NUM_FORMS": 0, + "addresses-MAX_NUM_FORMS": 1000, + "action-publish": "Publish", + "comments-TOTAL_FORMS": 0, + "comments-INITIAL_FORMS": 0, + "comments-MIN_NUM_FORMS": 0, + "comments-MAX_NUM_FORMS": 1000, + "social_links-TOTAL_FORMS": 1, + "social_links-INITIAL_FORMS": 0, + "social_links-MIN_NUM_FORMS": 0, + "social_links-MAX_NUM_FORMS": 1000, + "social_links-0-url": "https://twitter.com/mrbenn", + "social_links-0-kind": "twitter", + } + response = self.client.post( + reverse( + "wagtailadmin_pages:add", + args=("tests", "personpage", self.root_page.id), + ), + post_data, + ) + self.assertRedirects( + response, reverse("wagtailadmin_explore", args=(self.root_page.id,)) + ) + new_page = PersonPage.objects.get(slug="mr-benn") + self.assertEqual(new_page.social_links.count(), 1) + + class TestInlinePanelNonFieldErrors(WagtailTestUtils, TestCase): """ Test that non field errors will render for InlinePanels diff --git a/wagtail/admin/tests/test_edit_handlers.py b/wagtail/admin/tests/test_edit_handlers.py index de7ffec30a..efeeefc33d 100644 --- a/wagtail/admin/tests/test_edit_handlers.py +++ b/wagtail/admin/tests/test_edit_handlers.py @@ -52,6 +52,7 @@ from wagtail.test.testapp.models import ( FormPageWithRedirect, GalleryPage, PageChooserModel, + PersonPage, RestaurantPage, RestaurantTag, SimplePage, @@ -1522,6 +1523,42 @@ class TestInlinePanel(WagtailTestUtils, TestCase): ) +class TestNonOrderableInlinePanel(WagtailTestUtils, TestCase): + fixtures = ["test.json"] + + def setUp(self): + self.request = get_dummy_request() + user = AnonymousUser() # technically, Anonymous users cannot access the admin + self.request.user = user + + def test_render(self): + """ + Check that the inline panel renders the panels set on the model + when no 'panels' parameter is passed in the InlinePanel definition + """ + social_link_object_list = ObjectList( + [ + InlinePanel( + "social_links", + label="Social Links", + ) + ] + ).bind_to_model(PersonPage) + PersonPageForm = social_link_object_list.get_form_class() + + person_page = PersonPage() + form = PersonPageForm(instance=person_page) + panel = social_link_object_list.get_bound_panel( + instance=person_page, form=form, request=self.request + ) + result = panel.render_html() + # rendered panel must not contain hidden fields for ORDER + self.assertNotInHTML( + 'id="id_social_links-__prefix__-ORDER"', + result, + ) + + class TestInlinePanelGetComparison(TestCase): fixtures = ["test.json"] diff --git a/wagtail/test/testapp/migrations/0038_sociallink.py b/wagtail/test/testapp/migrations/0038_sociallink.py new file mode 100644 index 0000000000..fe403767d3 --- /dev/null +++ b/wagtail/test/testapp/migrations/0038_sociallink.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.13 on 2024-06-12 12:06 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields +import wagtail.search.index + + +class Migration(migrations.Migration): + + dependencies = [ + ("tests", "0037_testpermissionedgenericsetting_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="SocialLink", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("url", models.URLField()), + ( + "kind", + models.CharField( + choices=[("twitter", "Twitter"), ("facebook", "Facebook")], + max_length=30, + ), + ), + ( + "person", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="social_links", + to="tests.personpage", + verbose_name="Person", + ), + ), + ], + options={ + "verbose_name": "Social link", + "verbose_name_plural": "Social links", + }, + bases=(wagtail.search.index.Indexed, models.Model), + ), + ] diff --git a/wagtail/test/testapp/models.py b/wagtail/test/testapp/models.py index 813f4d41e8..0d8811169d 100644 --- a/wagtail/test/testapp/models.py +++ b/wagtail/test/testapp/models.py @@ -2097,6 +2097,7 @@ class PersonPage(Page): "Person", ), InlinePanel("addresses", label="Address"), + InlinePanel("social_links", label="Social links"), ] class Meta: @@ -2133,6 +2134,29 @@ class AddressTag(TaggedItemBase): ) +class SocialLink(index.Indexed, ClusterableModel): + url = models.URLField() + kind = models.CharField( + max_length=30, + choices=[ + ("twitter", "Twitter"), + ("facebook", "Facebook"), + ], + ) + person = ParentalKey( + to="tests.PersonPage", related_name="social_links", verbose_name="Person" + ) + + panels = [ + FieldPanel("url"), + FieldPanel("kind"), + ] + + class Meta: + verbose_name = "Social link" + verbose_name_plural = "Social links" + + class RestaurantPage(Page): tags = ClusterTaggableManager(through="tests.TaggedRestaurant", blank=True)