Skip to content

INTPYTHON-602 Fix loading of embedded model fields that use database converters #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions django_mongodb_backend/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def get_db_converters(self, expression):
converters.append(self.convert_datetimefield_value)
elif internal_type == "DecimalField":
converters.append(self.convert_decimalfield_value)
elif internal_type == "EmbeddedModelField":
converters.append(self.convert_embeddedmodelfield_value)
elif internal_type == "JSONField":
converters.append(self.convert_jsonfield_value)
elif internal_type == "TimeField":
Expand Down Expand Up @@ -150,6 +152,16 @@ def convert_durationfield_value(self, value, expression, connection):
value = datetime.timedelta(milliseconds=int(str(value)))
return value

def convert_embeddedmodelfield_value(self, value, expression, connection):
if value is not None:
# Apply database converters to each field of the embedded model.
for field in expression.output_field.embedded_model._meta.fields:
field_expr = Expression(output_field=field)
converters = connection.ops.get_db_converters(field_expr)
for converter in converters:
value[field.attname] = converter(value[field.attname], field_expr, connection)
return value

def convert_jsonfield_value(self, value, expression, connection):
"""
Convert dict data to a string so that JSONField.from_db_value() can
Expand Down
3 changes: 3 additions & 0 deletions docs/source/releases/5.1.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Django MongoDB Backend 5.1.x
- Fixed the results of queries that use the ``tzinfo`` parameter of the
``Trunc`` database functions.
- Added support for ``QuerySet.dates()`` and ``datetimes()``.
- Fixed loading of ``QuerySet`` results for embedded models that have fields
that use database converters. For example, a crash for ``DecimalField``:
``ValidationError: ['“1” value must be a decimal number.']``).

.. _django-mongodb-backend-5.1.0-beta-2:

Expand Down
3 changes: 3 additions & 0 deletions docs/source/releases/5.2.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ Bug fixes
- Fixed the results of queries that use the ``tzinfo`` parameter of the
``Trunc`` database functions.
- Added support for ``QuerySet.dates()`` and ``datetimes()``.
- Fixed loading of ``QuerySet`` results for embedded models that have fields
that use database converters. For example, a crash for ``DecimalField``:
``ValidationError: ['“1” value must be a decimal number.']``).
6 changes: 6 additions & 0 deletions tests/model_fields_/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ class Data(EmbeddedModel):
auto_now = models.DateTimeField(auto_now=True)
auto_now_add = models.DateTimeField(auto_now_add=True)
json_value = models.JSONField()
decimal = models.DecimalField(max_digits=9, decimal_places="2", null=True, blank=True)
nested_data = EmbeddedModelField("NestedData", null=True, blank=True)


class NestedData(EmbeddedModel):
decimal = models.DecimalField(max_digits=9, decimal_places="2", null=True, blank=True)


class Address(EmbeddedModel):
Expand Down
25 changes: 24 additions & 1 deletion tests/model_fields_/test_embedded_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Data,
Holder,
Library,
NestedData,
)
from .utils import truncate_ms

Expand Down Expand Up @@ -93,7 +94,16 @@ def test_pre_save(self):
class QueryingTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.objs = [Holder.objects.create(data=Data(integer=x)) for x in range(6)]
cls.objs = [
Holder.objects.create(
data=Data(
integer=x,
decimal=f"{x}.5",
nested_data=NestedData(decimal=f"{x}.5"),
)
)
for x in range(6)
]

def test_exact(self):
self.assertCountEqual(Holder.objects.filter(data__integer=3), [self.objs[3]])
Expand All @@ -113,6 +123,19 @@ def test_gte(self):
def test_range(self):
self.assertCountEqual(Holder.objects.filter(data__integer__range=(2, 4)), self.objs[2:5])

def test_exact_decimal(self):
# EmbeddedModelField lookups call
# DatabaseOperations.adapt__<foo>field_value().
self.assertCountEqual(Holder.objects.filter(data__decimal="3.5"), [self.objs[3]])

def test_lt_decimal(self):
self.assertCountEqual(Holder.objects.filter(data__decimal__lt="3"), self.objs[0:3])

def test_exact_decimal_nested(self):
self.assertCountEqual(
Holder.objects.filter(data__nested_data__decimal="3.5"), [self.objs[3]]
)

def test_order_by_embedded_field(self):
qs = Holder.objects.filter(data__integer__gt=3).order_by("-data__integer")
self.assertSequenceEqual(qs, list(reversed(self.objs[4:])))
Expand Down
Loading