Skip to content

Commit 248e2b6

Browse files
committed
Add tests for .deconstruct() methods returning a different field class path
1 parent 3fa8308 commit 248e2b6

File tree

4 files changed

+137
-53
lines changed

4 files changed

+137
-53
lines changed

tests/testapp/custom_fields.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,23 @@ def deconstruct(self):
2020
# TranslatedField should preserve the runtime choices, not the ones from deconstruct
2121
kwargs["choices"] = [("", "")]
2222
return name, path, args, kwargs
23+
24+
25+
class CustomPathTextField(models.TextField):
26+
"""
27+
TextField that overrides the path in deconstruct() method.
28+
29+
This is similar to django-prose-editor's implementation where the field
30+
returns a different path in deconstruct() to ensure it's reconstructed as
31+
a standard TextField rather than the custom subclass.
32+
"""
33+
34+
def __init__(self, *args, **kwargs):
35+
super().__init__(*args, **kwargs)
36+
37+
def deconstruct(self):
38+
name, _path, args, kwargs = super().deconstruct()
39+
# Override the path to always use the base TextField class
40+
# This simulates a field with custom behavior that reports a base field type in migrations
41+
# TranslatedField preserves the original field class, but uses the path for migrations
42+
return name, "django.db.models.TextField", args, kwargs

tests/testapp/field_types_models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.db import models
22
from django.utils.translation import gettext_lazy as _
33

4-
from testapp.custom_fields import ChoicesCharField
4+
from testapp.custom_fields import ChoicesCharField, CustomPathTextField
55
from translated_fields import TranslatedField
66

77

@@ -112,5 +112,13 @@ class CustomFieldModel(models.Model):
112112
)
113113
)
114114

115+
# Custom field that changes its path in deconstruct
116+
custom_path_text = TranslatedField(
117+
CustomPathTextField(
118+
_("custom path text field"),
119+
help_text=_("This is a text field with custom path in deconstruct()"),
120+
)
121+
)
122+
115123
def __str__(self):
116124
return self.custom_choices

tests/testapp/test_custom_field_debug.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

tests/testapp/test_custom_fields.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.forms import modelform_factory
33
from django.utils.translation import override
44

5+
from testapp.custom_fields import CustomPathTextField
56
from testapp.field_types_models import CustomFieldModel
67

78

@@ -134,3 +135,110 @@ def test_custom_field_valid_options(option):
134135
model.save()
135136
assert model.custom_choices_en == option
136137
assert model.custom_choices_de == option
138+
139+
140+
@pytest.mark.django_db
141+
def test_custom_path_field_type_preserved():
142+
"""
143+
Test that custom field classes are preserved even when the path is overridden.
144+
145+
This test passes unexpectedly. It seems TranslatedField actually does preserve
146+
the field class, which is good! The path from deconstruct() is used only for
147+
migrations, but the actual field instance in the model is of the correct type.
148+
149+
This shows that TranslatedField doesn't use the path from deconstruct() when
150+
creating the field instances, but rather the original class.
151+
"""
152+
# Get the field instances
153+
custom_en = CustomFieldModel._meta.get_field("custom_path_text_en")
154+
custom_de = CustomFieldModel._meta.get_field("custom_path_text_de")
155+
156+
# Check that the field instances are of the custom field type
157+
assert isinstance(custom_en, CustomPathTextField)
158+
assert isinstance(custom_de, CustomPathTextField)
159+
160+
# Get the deconstruct values
161+
name_en, path_en, args_en, kwargs_en = custom_en.deconstruct()
162+
name_de, path_de, args_de, kwargs_de = custom_de.deconstruct()
163+
164+
# Confirm that deconstruct returns the base TextField path
165+
assert path_en == "django.db.models.TextField"
166+
assert path_de == "django.db.models.TextField"
167+
168+
# Confirm we're using the right class despite the wrong path
169+
assert type(custom_en) is CustomPathTextField
170+
assert type(custom_de) is CustomPathTextField
171+
172+
173+
@pytest.mark.django_db
174+
def test_custom_path_field_form_generation():
175+
"""
176+
Test form field generation for custom fields.
177+
178+
This test shows that while the model field instance is correctly preserved as CustomPathTextField,
179+
when forms are generated, Django uses the field's formfield() method which returns a standard
180+
form field type. This is expected behavior and not a bug in TranslatedField.
181+
"""
182+
form_class = modelform_factory(
183+
CustomFieldModel, fields=["custom_path_text_en", "custom_path_text_de"]
184+
)
185+
form = form_class()
186+
187+
# Get the form fields
188+
field_en = form.fields["custom_path_text_en"]
189+
field_de = form.fields["custom_path_text_de"]
190+
191+
# Get the model fields for comparison
192+
model_field_en = CustomFieldModel._meta.get_field("custom_path_text_en")
193+
model_field_de = CustomFieldModel._meta.get_field("custom_path_text_de")
194+
195+
# Check that the model field is our custom type
196+
assert isinstance(model_field_en, CustomPathTextField)
197+
assert isinstance(model_field_de, CustomPathTextField)
198+
199+
# Model field class is correctly preserved
200+
assert type(model_field_en).__name__ == "CustomPathTextField"
201+
assert type(model_field_de).__name__ == "CustomPathTextField"
202+
203+
# Form field is based on standard Django form fields - these assertions should pass
204+
# Django maps TextField to Textarea widget by default
205+
from django.forms import CharField
206+
207+
assert isinstance(field_en, CharField)
208+
assert field_en.widget.__class__.__name__ == "Textarea"
209+
210+
# This is Django's standard behavior - model field's formfield() method determines
211+
# what form field type is used, not TranslatedField's behavior
212+
assert field_en.__class__.__name__ == "CharField"
213+
assert field_de.__class__.__name__ == "CharField"
214+
215+
216+
@pytest.mark.django_db
217+
def test_custom_path_field_model_usage():
218+
"""
219+
Test using the custom path text field with a model instance.
220+
221+
This test verifies that the basic functionality of the model works properly
222+
with the CustomPathTextField when its path is overridden in deconstruct().
223+
"""
224+
# Create a model instance with values for the custom path text field
225+
text_en = "English text content"
226+
text_de = "Deutscher Textinhalt"
227+
228+
model = CustomFieldModel.objects.create(
229+
custom_choices_en="a",
230+
custom_choices_de="a",
231+
custom_path_text_en=text_en,
232+
custom_path_text_de=text_de,
233+
)
234+
235+
# Check that the values are correctly stored
236+
assert model.custom_path_text_en == text_en
237+
assert model.custom_path_text_de == text_de
238+
239+
# Check that the translated descriptor works
240+
with override("en"):
241+
assert model.custom_path_text == text_en
242+
243+
with override("de"):
244+
assert model.custom_path_text == text_de

0 commit comments

Comments
 (0)