Skip to content

Value.as_mql() doesn't call output_field.get_db_prep_save() #282

@timgraham

Description

@timgraham

Unlike Value.as_sql(), Value.as_mql() doesn't call output_field.get_db_prep_save(). This hasn't been a problem on any tests except for a new one in Django 5.2, model_fields.test_jsonfield.TestSaveLoad.test_bulk_update_custom_get_prep_value.

I'm not sure how needful this fix is, and since the patch I wrote breaks another test (perhaps revealing a server-side bug), I'm just going to leave it here for now.

commit 5c7433e1f6e94b89ef87b3ba505c491e1f8fe84e
Author: Tim Graham <timograham@gmail.com>
Date:   Fri Feb 21 20:01:42 2025 -0500

    fix model_fields.test_jsonfield.TestSaveLoad.test_bulk_update_custom_get_prep_value
    
    https://github.com/django/django/commit/9525135698bd4f97cf1431776ef52ae393dfb3c0

diff --git a/django_mongodb_backend/expressions.py b/django_mongodb_backend/expressions.py
index e5ef335..2bf7e0e 100644
--- a/django_mongodb_backend/expressions.py
+++ b/django_mongodb_backend/expressions.py
@@ -186,6 +186,12 @@ def when(self, compiler, connection):
 
 def value(self, compiler, connection):  # noqa: ARG001
     value = self.value
+    output_field = self._output_field_or_none
+    if output_field is not None:
+        if self.for_save:
+            value = output_field.get_db_prep_save(value, connection=connection)
+        else:
+            value = output_field.get_db_prep_value(value, connection=connection)
     if isinstance(value, int):
         # Wrap numbers in $literal to prevent ambiguity when Value appears in
         # $project.
diff --git a/django_mongodb_backend/features.py b/django_mongodb_backend/features.py
index 1317a72..bb709b1 100644
--- a/django_mongodb_backend/features.py
+++ b/django_mongodb_backend/features.py
@@ -98,6 +98,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         "prefetch_related.tests.NestedPrefetchTests.test_nested_prefetch_is_not_overwritten_by_related_object",
         "prefetch_related.tests.NullableTest.test_prefetch_nullable",
         "prefetch_related.tests.Ticket19607Tests.test_bug",
+        # {'$project': {'name': Decimal128('1')} is broken? (gives None)
+        "expressions.tests.ValueTests.test_output_field_decimalfield",
     }
     # $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
     _django_test_expected_failures_bitwise = {
diff --git a/tests/expressions_/test_value.py b/tests/expressions_/test_value.py
index c57c2f0..ad131a7 100644
--- a/tests/expressions_/test_value.py
+++ b/tests/expressions_/test_value.py
@@ -3,6 +3,7 @@ import uuid
 from decimal import Decimal
 
 from bson import Decimal128
+from django.db import connection
 from django.db.models import Value
 from django.test import SimpleTestCase
 
@@ -10,34 +11,36 @@ from django.test import SimpleTestCase
 class ValueTests(SimpleTestCase):
     def test_date(self):
         self.assertEqual(
-            Value(datetime.date(2025, 1, 1)).as_mql(None, None),
+            Value(datetime.date(2025, 1, 1)).as_mql(None, connection),
             datetime.datetime(2025, 1, 1),
         )
 
     def test_datetime(self):
         self.assertEqual(
-            Value(datetime.datetime(2025, 1, 1, 9, 8, 7)).as_mql(None, None),
+            Value(datetime.datetime(2025, 1, 1, 9, 8, 7)).as_mql(None, connection),
             datetime.datetime(2025, 1, 1, 9, 8, 7),
         )
 
     def test_decimal(self):
-        self.assertEqual(Value(Decimal("1.0")).as_mql(None, None), Decimal128("1.0"))
+        self.assertEqual(Value(Decimal("1.0")).as_mql(None, connection), Decimal128("1.0"))
 
     def test_time(self):
         self.assertEqual(
-            Value(datetime.time(9, 8, 7)).as_mql(None, None),
+            Value(datetime.time(9, 8, 7)).as_mql(None, connection),
             datetime.datetime(1, 1, 1, 9, 8, 7),
         )
 
     def test_timedelta(self):
-        self.assertEqual(Value(datetime.timedelta(3600)).as_mql(None, None), 311040000000.0)
+        self.assertEqual(
+            Value(datetime.timedelta(3600)).as_mql(None, connection), {"$literal": 311040000000}
+        )
 
     def test_int(self):
-        self.assertEqual(Value(1).as_mql(None, None), {"$literal": 1})
+        self.assertEqual(Value(1).as_mql(None, connection), {"$literal": 1})
 
     def test_str(self):
-        self.assertEqual(Value("foo").as_mql(None, None), "foo")
+        self.assertEqual(Value("foo").as_mql(None, connection), "foo")
 
     def test_uuid(self):
         value = uuid.UUID(int=1)
-        self.assertEqual(Value(value).as_mql(None, None), "00000000000000000000000000000001")
+        self.assertEqual(Value(value).as_mql(None, connection), "00000000000000000000000000000001")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions