Skip to content

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

Open
@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 <[email protected]>
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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions