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

This commit is contained in:
Allen Jonathan David 2022-08-30 22:56:18 +05:30 committed by Mariusz Felisiak
parent 3ba7f2e906
commit 10178197d5
4 changed files with 63 additions and 5 deletions

View File

@ -4,6 +4,7 @@ from django import forms
from django.core import checks, exceptions from django.core import checks, exceptions
from django.db import NotSupportedError, connections, router from django.db import NotSupportedError, connections, router
from django.db.models import lookups 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.fields import TextField
from django.db.models.lookups import PostgresOperatorLookup, Transform from django.db.models.lookups import PostgresOperatorLookup, Transform
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -379,6 +380,18 @@ class KeyTextTransform(KeyTransform):
json_path = compile_json_path(key_transforms) json_path = compile_json_path(key_transforms)
return "(%s ->> %%s)" % lhs, tuple(params) + (json_path,) 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: class KeyTransformTextLookupMixin:
""" """

View File

@ -216,6 +216,10 @@ Models
allows performing actions that can fail after a database transaction is allows performing actions that can fail after a database transaction is
successfully committed. 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 Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1059,6 +1059,33 @@ To query for missing keys, use the ``isnull`` lookup::
:lookup:`istartswith`, :lookup:`lt`, :lookup:`lte`, :lookup:`gt`, and :lookup:`istartswith`, :lookup:`lt`, :lookup:`lte`, :lookup:`gt`, and
:lookup:`gte`, as well as with :ref:`containment-and-key-lookups`. :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:: .. note::
Due to the way in which key-path queries work, Due to the way in which key-path queries work,

View File

@ -27,6 +27,7 @@ from django.db.models import (
) )
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.db.models.fields.json import ( from django.db.models.fields.json import (
KT,
KeyTextTransform, KeyTextTransform,
KeyTransform, KeyTransform,
KeyTransformFactory, KeyTransformFactory,
@ -374,11 +375,7 @@ class TestQuerying(TestCase):
qs = NullableJSONModel.objects.filter(value__isnull=False) qs = NullableJSONModel.objects.filter(value__isnull=False)
self.assertQuerysetEqual( self.assertQuerysetEqual(
qs.filter(value__isnull=False) qs.filter(value__isnull=False)
.annotate( .annotate(key=KT("value__d__1__f"))
key=KeyTextTransform(
"f", KeyTransform("1", KeyTransform("d", "value"))
),
)
.values("key") .values("key")
.annotate(count=Count("key")) .annotate(count=Count("key"))
.order_by("count"), .order_by("count"),
@ -1078,3 +1075,20 @@ class TestQuerying(TestCase):
).filter(chain=F("related_key__0")), ).filter(chain=F("related_key__0")),
[related_obj], [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("")