Fix check for duplicate FormField.clean_names

WagtailAdminFormPageForm.clean checks for duplicate fields by generating
a clean_name from a FormField's current label, catching duplicate labels.

However, once saved, a FormField's clean_name cannot change. It's
possible for clean_names to clash if a a field is renamed and add a new
field with the same name.

Updated duplicate check to take existing clean_name into account.

Refactored logic to avoid nested loops.
pull/9284/head
Dan Bentley 2022-09-23 14:43:40 +01:00 zatwierdzone przez LB (Ben Johnston)
rodzic eb981b4896
commit dbe87bdc5d
4 zmienionych plików z 71 dodań i 22 usunięć

Wyświetl plik

@ -51,6 +51,7 @@ Changelog
* Fix: Ensure `for_user` argument is passed to the form class when previewing pages (Matt Westcott)
* Fix: Ensure the capitalisation of the `timesince_simple` tag is consistently added in the template based on usage in context (Stefan Hammer)
* Fix: Add missing translation usage for the `timesince_last_update` and ensure the translated labels can be easier to work with in Transifex (Stefan Hammer)
* Fix: Add additional checks for duplicate form field `clean_name` values in the Form Builder validation and increase performance of checks (Dan Bentley)
4.0.3 (xx.xx.xxxx) - IN DEVELOPMENT
~~~~~~~~~~~~~~~~~~

Wyświetl plik

@ -76,6 +76,7 @@ This feature was developed by Karl Hobley and Matt Westcott.
* Ensure `for_user` argument is passed to the form class when previewing pages (Matt Westcott)
* Ensure the capitalisation of the `timesince_simple` tag is consistently added in the template based on usage in context (Stefan Hammer)
* Add missing translation usage for the `timesince_last_update` and ensure the translated labels can be easier to work with in Transifex (Stefan Hammer)
* Add additional checks for duplicate form field `clean_name` values in the Form Builder validation and increase performance of checks (Dan Bentley)
## Upgrade considerations

Wyświetl plik

@ -174,29 +174,35 @@ class SelectDateForm(django.forms.Form):
class WagtailAdminFormPageForm(WagtailAdminPageForm):
def clean(self):
super().clean()
# Check for dupe form field labels - fixes #585
# Check for duplicate form fields by comparing their internal clean_names
if "form_fields" in self.formsets:
_forms = self.formsets["form_fields"].forms
for f in _forms:
f.is_valid()
forms = self.formsets["form_fields"].forms
for form in forms:
form.is_valid()
for i, form in enumerate(_forms):
if "label" in form.changed_data:
label = form.cleaned_data.get("label")
clean_name = form.instance.get_field_clean_name()
for idx, ff in enumerate(_forms):
# Exclude self
ff_clean_name = ff.instance.get_field_clean_name()
if idx != i and clean_name == ff_clean_name:
form.add_error(
"label",
django.forms.ValidationError(
_(
"There is another field with the label %s, please change one of them."
)
% label
),
)
# Use existing clean_name or generate for new fields.
# clean_name is set in FormField.save
clean_names = [
f.instance.clean_name or f.instance.get_field_clean_name()
for f in forms
]
duplicate_clean_name = next(
(n for n in clean_names if clean_names.count(n) > 1), None
)
if duplicate_clean_name:
duplicate_form_field = next(
f
for f in self.formsets["form_fields"].forms
if f.instance.get_field_clean_name() == duplicate_clean_name
)
duplicate_form_field.add_error(
"label",
django.forms.ValidationError(
_(
"There is another field with the label %s, please change one of them."
% duplicate_form_field.instance.label
)
),
)

Wyświetl plik

@ -29,6 +29,7 @@ from wagtail.test.testapp.models import (
FormPageWithCustomSubmissionListView,
)
from wagtail.test.utils import WagtailTestUtils
from wagtail.test.utils.form_data import inline_formset, nested_form_data
class TestFormResponsesPanel(TestCase):
@ -1735,6 +1736,46 @@ class TestDuplicateFormFieldLabels(TestCase, WagtailTestUtils):
text="There is another field with the label duplicate 1, please change one of them.",
)
def test_rename_existing_field_and_add_new_field_with_clashing_clean_name(
self,
):
"""Ensure duplicate field names are checked against existing field clean_names."""
form_page = FormPage(
title="Form page!",
form_fields=[FormField(label="Test field", field_type="singleline")],
)
self.root_page.add_child(instance=form_page)
# Rename existing field and add new field with label matching original field
post_data = nested_form_data(
{
"title": "Form page!",
"slug": "form-page",
"form_fields": inline_formset(
[
{
"id": form_page.form_fields.first().pk,
"label": "Other field",
"field_type": "singleline",
},
{"id": "", "label": "Test field", "field_type": "singleline"},
],
initial=1,
),
"action-publish": "action-publish",
}
)
response = self.client.post(
reverse("wagtailadmin_pages:edit", args=[form_page.pk]), post_data
)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
text="There is another field with the label Test field, please change one of them.",
)
class TestPreview(TestCase, WagtailTestUtils):