From 49275c548887769cd70bbd85a3b125491f0c4062 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Mon, 10 Feb 2020 22:40:07 +0100 Subject: [PATCH] Fixed #30261 -- Prevented Form._html_output() from mutating errors if hidden fields have errors. --- django/forms/forms.py | 3 ++- django/forms/utils.py | 5 +++++ tests/forms_tests/tests/test_forms.py | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/django/forms/forms.py b/django/forms/forms.py index a601467e18..81c59dea33 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -191,7 +191,8 @@ class BaseForm: def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Output HTML. Used by as_table(), as_ul(), as_p()." - top_errors = self.non_field_errors() # Errors that should be displayed above all fields. + # Errors that should be displayed above all fields. + top_errors = self.non_field_errors().copy() output, hidden_fields = [], [] for name, field in self.fields.items(): diff --git a/django/forms/utils.py b/django/forms/utils.py index 5ae137943a..fbe79f1142 100644 --- a/django/forms/utils.py +++ b/django/forms/utils.py @@ -92,6 +92,11 @@ class ErrorList(UserList, list): def as_data(self): return ValidationError(self.data).error_list + def copy(self): + copy = super().copy() + copy.error_class = self.error_class + return copy + def get_json_data(self, escape_html=False): errors = [] for error in self.as_data(): diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index e4a8127e15..b62b32de4b 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -1245,6 +1245,22 @@ value="Should escape < & > and <script>alert('xss')< self.assertTrue(f.has_error(NON_FIELD_ERRORS, 'password_mismatch')) self.assertFalse(f.has_error(NON_FIELD_ERRORS, 'anything')) + def test_html_output_with_hidden_input_field_errors(self): + class TestForm(Form): + hidden_input = CharField(widget=HiddenInput) + + def clean(self): + self.add_error(None, 'Form error') + + f = TestForm(data={}) + error_dict = { + 'hidden_input': ['This field is required.'], + '__all__': ['Form error'], + } + self.assertEqual(f.errors, error_dict) + f.as_table() + self.assertEqual(f.errors, error_dict) + def test_dynamic_construction(self): # It's possible to construct a Form dynamically by adding to the self.fields # dictionary in __init__(). Don't forget to call Form.__init__() within the