Fixed #33966 -- Added support for using KeyTextTransform from lookup.
This commit is contained in:
parent
3ba7f2e906
commit
10178197d5
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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("")
|
||||||
|
|
Loading…
Reference in New Issue