浏览代码

Fixed #33966 -- Added support for using KeyTextTransform from lookup.

Allen Jonathan David 2 年之前
父节点
当前提交
10178197d5
共有 4 个文件被更改,包括 63 次插入5 次删除
  1. 13 0
      django/db/models/fields/json.py
  2. 4 0
      docs/releases/4.2.txt
  3. 27 0
      docs/topics/db/queries.txt
  4. 19 5
      tests/model_fields/test_jsonfield.py

+ 13 - 0
django/db/models/fields/json.py

@@ -4,6 +4,7 @@ from django import forms
 from django.core import checks, exceptions
 from django.db import NotSupportedError, connections, router
 from django.db.models import lookups
+from django.db.models.constants import LOOKUP_SEP
 from django.db.models.fields import TextField
 from django.db.models.lookups import PostgresOperatorLookup, Transform
 from django.utils.translation import gettext_lazy as _
@@ -379,6 +380,18 @@ class KeyTextTransform(KeyTransform):
             json_path = compile_json_path(key_transforms)
             return "(%s ->> %%s)" % lhs, tuple(params) + (json_path,)
 
+    @classmethod
+    def from_lookup(cls, lookup):
+        transform, *keys = lookup.split(LOOKUP_SEP)
+        if not keys:
+            raise ValueError("Lookup must contain key or index transforms.")
+        for key in keys:
+            transform = cls(key, transform)
+        return transform
+
+
+KT = KeyTextTransform.from_lookup
+
 
 class KeyTransformTextLookupMixin:
     """

+ 4 - 0
docs/releases/4.2.txt

@@ -216,6 +216,10 @@ Models
   allows performing actions that can fail after a database transaction is
   successfully committed.
 
+* The new :class:`KT() <django.db.models.fields.json.KT>` expression represents
+  the text value of a key, index, or path transform of
+  :class:`~django.db.models.JSONField`.
+
 Requests and Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 

+ 27 - 0
docs/topics/db/queries.txt

@@ -1059,6 +1059,33 @@ To query for missing keys, use the ``isnull`` lookup::
     :lookup:`istartswith`, :lookup:`lt`, :lookup:`lte`, :lookup:`gt`, and
     :lookup:`gte`, as well as with :ref:`containment-and-key-lookups`.
 
+``KT()`` expressions
+~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 4.2
+
+.. module:: django.db.models.fields.json
+
+.. class:: KT(lookup)
+
+    Represents the text value of a key, index, or path transform of
+    :class:`~django.db.models.JSONField`. You can use the double underscore
+    notation in ``lookup`` to chain dictionary key and index transforms.
+
+    For example::
+
+        >>> from django.db.models.fields.json import KT
+        >>> Dog.objects.create(name="Shep", data={
+        ...     "owner": {"name": "Bob"},
+        ...     "breed": ["collie", "lhasa apso"],
+        ... })
+        <Dog: Shep>
+        >>> Dogs.objects.annotate(
+        ...     first_breed=KT("data__breed__1"),
+        ...     owner_name=KT("data__owner__name")
+        ... ).filter(first_breed__startswith="lhasa", owner_name="Bob")
+        <QuerySet [<Dog: Shep>]>
+
 .. note::
 
     Due to the way in which key-path queries work,

+ 19 - 5
tests/model_fields/test_jsonfield.py

@@ -27,6 +27,7 @@ from django.db.models import (
 )
 from django.db.models.expressions import RawSQL
 from django.db.models.fields.json import (
+    KT,
     KeyTextTransform,
     KeyTransform,
     KeyTransformFactory,
@@ -374,11 +375,7 @@ class TestQuerying(TestCase):
         qs = NullableJSONModel.objects.filter(value__isnull=False)
         self.assertQuerysetEqual(
             qs.filter(value__isnull=False)
-            .annotate(
-                key=KeyTextTransform(
-                    "f", KeyTransform("1", KeyTransform("d", "value"))
-                ),
-            )
+            .annotate(key=KT("value__d__1__f"))
             .values("key")
             .annotate(count=Count("key"))
             .order_by("count"),
@@ -1078,3 +1075,20 @@ class TestQuerying(TestCase):
             ).filter(chain=F("related_key__0")),
             [related_obj],
         )
+
+    def test_key_text_transform_from_lookup(self):
+        qs = NullableJSONModel.objects.annotate(b=KT("value__bax__foo")).filter(
+            b__contains="ar",
+        )
+        self.assertSequenceEqual(qs, [self.objs[7]])
+        qs = NullableJSONModel.objects.annotate(c=KT("value__o")).filter(
+            c__contains="uot",
+        )
+        self.assertSequenceEqual(qs, [self.objs[4]])
+
+    def test_key_text_transform_from_lookup_invalid(self):
+        msg = "Lookup must contain key or index transforms."
+        with self.assertRaisesMessage(ValueError, msg):
+            KT("value")
+        with self.assertRaisesMessage(ValueError, msg):
+            KT("")