diff --git a/django/forms/utils.py b/django/forms/utils.py index f5fc0515a5a..7d4cd90b578 100644 --- a/django/forms/utils.py +++ b/django/forms/utils.py @@ -29,16 +29,17 @@ def flatatt(attrs): The result is passed through 'mark_safe'. """ + key_value_attrs = [] boolean_attrs = [] - for attr, value in list(attrs.items()): - if value is True: - boolean_attrs.append((attr,)) - del attrs[attr] - elif value is False: - del attrs[attr] + for attr, value in attrs.items(): + if isinstance(value, bool): + if value: + boolean_attrs.append((attr,)) + else: + key_value_attrs.append((attr, value)) return ( - format_html_join('', ' {0}="{1}"', sorted(attrs.items())) + + format_html_join('', ' {0}="{1}"', sorted(key_value_attrs)) + format_html_join('', ' {0}', sorted(boolean_attrs)) ) diff --git a/tests/forms_tests/tests/test_util.py b/tests/forms_tests/tests/test_util.py index 68ecd50e993..c37affeb311 100644 --- a/tests/forms_tests/tests/test_util.py +++ b/tests/forms_tests/tests/test_util.py @@ -27,6 +27,23 @@ class FormsUtilTestCase(TestCase): self.assertEqual(flatatt({'class': "news", 'title': "Read this", 'required': False}), ' class="news" title="Read this"') self.assertEqual(flatatt({}), '') + def test_flatatt_no_side_effects(self): + """ + Fixes #23883 -- Check that flatatt does not modify the dict passed in + """ + attrs = {'foo': 'bar', 'true': True, 'false': False} + attrs_copy = copy.copy(attrs) + self.assertEqual(attrs, attrs_copy) + + first_run = flatatt(attrs) + self.assertEqual(attrs, attrs_copy) + self.assertEqual(first_run, ' foo="bar" true') + + second_run = flatatt(attrs) + self.assertEqual(attrs, attrs_copy) + + self.assertEqual(first_run, second_run) + def test_validation_error(self): ################### # ValidationError #