Fix field deepcopy to copy error_messages dict

Ensure Field.__deepcopy__ creates an independent copy of error_messages.
Prevents changes to error messages in one form field instance from affecting others.
Addresses shared mutable error_messages issue (#issue).
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 22:59:04 +00:00
parent 06909fe084
commit 2f38240802
2 changed files with 53 additions and 0 deletions

View file

@ -199,6 +199,7 @@ class Field:
result = copy.copy(self)
memo[id(self)] = result
result.widget = copy.deepcopy(self.widget, memo)
result.error_messages = self.error_messages.copy()
result.validators = self.validators[:]
return result

View file

@ -1,3 +1,5 @@
import copy
from django.forms import ChoiceField, Field, Form, Select
from django.test import SimpleTestCase
@ -35,6 +37,56 @@ class BasicFieldsTests(SimpleTestCase):
self.assertEqual(f.fields['field1'].widget.choices, [('1', '1')])
self.assertEqual(f.fields['field2'].widget.choices, [('2', '2')])
def test_field_deepcopies_error_messages(self):
"""
Test that __deepcopy__ creates a deep copy of the error_messages dict.
Modifying error_messages on one field instance should not affect other
instances.
"""
class MyForm(Form):
field1 = Field()
field2 = Field()
form1 = MyForm()
form2 = MyForm()
# Store original error message
original_error = str(form2.fields['field1'].error_messages['required'])
# Modify error_messages on form1's field1
form1.fields['field1'].error_messages['required'] = 'Custom error for form1'
# form2's field1 should not be affected
self.assertEqual(
form2.fields['field1'].error_messages['required'],
original_error
)
# form1's field2 should not be affected either
self.assertEqual(
form1.fields['field2'].error_messages['required'],
original_error
)
# Adding a new error message to form1's field1 should not affect form2
form1.fields['field1'].error_messages['custom'] = 'New custom error'
self.assertNotIn('custom', form2.fields['field1'].error_messages)
def test_field_direct_deepcopy_error_messages(self):
"""
Test that directly deepcopying a field creates independent error_messages.
"""
field1 = Field(error_messages={'required': 'Original required error'})
field2 = copy.deepcopy(field1)
# Modify field1's error_messages
field1.error_messages['required'] = 'Modified required error'
field1.error_messages['new_error'] = 'A new error'
# field2 should not be affected
self.assertEqual(field2.error_messages['required'], 'Original required error')
self.assertNotIn('new_error', field2.error_messages)
class DisabledFieldTests(SimpleTestCase):
def test_disabled_field_has_changed_always_false(self):