diff --git a/django_mongodb_backend/operations.py b/django_mongodb_backend/operations.py index 4a63eccb7..d9eabb9af 100644 --- a/django_mongodb_backend/operations.py +++ b/django_mongodb_backend/operations.py @@ -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": @@ -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 diff --git a/docs/source/releases/5.1.x.rst b/docs/source/releases/5.1.x.rst index 3e164fa74..fac35eb16 100644 --- a/docs/source/releases/5.1.x.rst +++ b/docs/source/releases/5.1.x.rst @@ -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: diff --git a/docs/source/releases/5.2.x.rst b/docs/source/releases/5.2.x.rst index d6bdc937d..849c2ab29 100644 --- a/docs/source/releases/5.2.x.rst +++ b/docs/source/releases/5.2.x.rst @@ -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.']``). diff --git a/tests/model_fields_/models.py b/tests/model_fields_/models.py index ad573323b..2470f4bb8 100644 --- a/tests/model_fields_/models.py +++ b/tests/model_fields_/models.py @@ -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): diff --git a/tests/model_fields_/test_embedded_model.py b/tests/model_fields_/test_embedded_model.py index 700a3cf1c..004eae00d 100644 --- a/tests/model_fields_/test_embedded_model.py +++ b/tests/model_fields_/test_embedded_model.py @@ -24,6 +24,7 @@ Data, Holder, Library, + NestedData, ) from .utils import truncate_ms @@ -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]]) @@ -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__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:])))