From d9881a025c15d87b2a7883ee50771117450ea90d Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Mon, 12 Nov 2018 19:20:35 +0000 Subject: [PATCH] Fixed #29915 -- Added support for values with hyphens to pattern lookups for UUIDField on backends without UUID datatype. Support hyphens in iexact, contains, icontains, startswith, istartswith, endswith and iendswith UUIDField filters on backends without UUID datatype. --- django/db/models/lookups.py | 52 ++++++++++++++++++++++++++++++++- tests/model_fields/test_uuid.py | 44 +++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index 2683d8971a..9344979c56 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -5,7 +5,7 @@ from copy import copy from django.core.exceptions import EmptyResultSet from django.db.models.expressions import Case, Exists, Func, Value, When from django.db.models.fields import ( - BooleanField, DateTimeField, Field, IntegerField, + BooleanField, CharField, DateTimeField, Field, IntegerField, UUIDField, ) from django.db.models.query_utils import RegisterLookupMixin from django.utils.datastructures import OrderedSet @@ -548,3 +548,53 @@ class YearLt(YearLookup, LessThan): class YearLte(YearLookup, LessThanOrEqual): def get_bound_params(self, start, finish): return (finish,) + + +class UUIDTextMixin: + """ + Strip hyphens from a value when filtering a UUIDField on backends without + a native datatype for UUID. + """ + def process_rhs(self, qn, connection): + if not connection.features.has_native_uuid_field: + from django.db.models.functions import Replace + if self.rhs_is_direct_value(): + self.rhs = Value(self.rhs) + self.rhs = Replace(self.rhs, Value('-'), Value(''), output_field=CharField()) + rhs, params = super().process_rhs(qn, connection) + return rhs, params + + +@UUIDField.register_lookup +class UUIDIExact(UUIDTextMixin, IExact): + pass + + +@UUIDField.register_lookup +class UUIDContains(UUIDTextMixin, Contains): + pass + + +@UUIDField.register_lookup +class UUIDIContains(UUIDTextMixin, IContains): + pass + + +@UUIDField.register_lookup +class UUIDStartsWith(UUIDTextMixin, StartsWith): + pass + + +@UUIDField.register_lookup +class UUIDIStartsWith(UUIDTextMixin, IStartsWith): + pass + + +@UUIDField.register_lookup +class UUIDEndsWith(UUIDTextMixin, EndsWith): + pass + + +@UUIDField.register_lookup +class UUIDIEndsWith(UUIDTextMixin, IEndsWith): + pass diff --git a/tests/model_fields/test_uuid.py b/tests/model_fields/test_uuid.py index 875dac5c84..116e40065c 100644 --- a/tests/model_fields/test_uuid.py +++ b/tests/model_fields/test_uuid.py @@ -4,7 +4,7 @@ import uuid from django.core import exceptions, serializers from django.db import IntegrityError, connection, models from django.db.models import CharField, F, Value -from django.db.models.functions import Concat +from django.db.models.functions import Concat, Repeat from django.test import ( SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature, ) @@ -121,6 +121,12 @@ class TestQuerying(TestCase): ), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter( + field__iexact='550E8400-E29B-41D4-A716-446655440000' + ), + [self.objs[1]], + ) def test_isnull(self): self.assertSequenceEqual( @@ -133,36 +139,60 @@ class TestQuerying(TestCase): NullableUUIDModel.objects.filter(field__contains='8400e29b'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__contains='8400-e29b'), + [self.objs[1]], + ) def test_icontains(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__icontains='8400E29B'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__icontains='8400-E29B'), + [self.objs[1]], + ) def test_startswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__startswith='550e8400e29b4'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__startswith='550e8400-e29b-4'), + [self.objs[1]], + ) def test_istartswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__istartswith='550E8400E29B4'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__istartswith='550E8400-E29B-4'), + [self.objs[1]], + ) def test_endswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__endswith='a716446655440000'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__endswith='a716-446655440000'), + [self.objs[1]], + ) def test_iendswith(self): self.assertSequenceEqualWithoutHyphens( NullableUUIDModel.objects.filter(field__iendswith='A716446655440000'), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.filter(field__iendswith='A716-446655440000'), + [self.objs[1]], + ) def test_filter_with_expr(self): self.assertSequenceEqualWithoutHyphens( @@ -171,6 +201,18 @@ class TestQuerying(TestCase): ).filter(field__contains=F('value')), [self.objs[1]], ) + self.assertSequenceEqual( + NullableUUIDModel.objects.annotate( + value=Concat(Value('8400'), Value('-'), Value('e29b'), output_field=CharField()), + ).filter(field__contains=F('value')), + [self.objs[1]], + ) + self.assertSequenceEqual( + NullableUUIDModel.objects.annotate( + value=Repeat(Value('0'), 4, output_field=CharField()), + ).filter(field__contains=F('value')), + [self.objs[1]], + ) class TestSerialization(SimpleTestCase):