mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #33966 -- Added support for using KeyTextTransform from lookup.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							3ba7f2e906
						
					
				
				
					commit
					10178197d5
				
			| @@ -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: | ||||
|     """ | ||||
|   | ||||
| @@ -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 | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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("") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user