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.
This commit is contained in:
Ian Foote 2018-11-12 19:20:35 +00:00 committed by Mariusz Felisiak
parent 343afa7880
commit d9881a025c
2 changed files with 94 additions and 2 deletions

View File

@ -5,7 +5,7 @@ from copy import copy
from django.core.exceptions import EmptyResultSet from django.core.exceptions import EmptyResultSet
from django.db.models.expressions import Case, Exists, Func, Value, When from django.db.models.expressions import Case, Exists, Func, Value, When
from django.db.models.fields import ( 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.db.models.query_utils import RegisterLookupMixin
from django.utils.datastructures import OrderedSet from django.utils.datastructures import OrderedSet
@ -548,3 +548,53 @@ class YearLt(YearLookup, LessThan):
class YearLte(YearLookup, LessThanOrEqual): class YearLte(YearLookup, LessThanOrEqual):
def get_bound_params(self, start, finish): def get_bound_params(self, start, finish):
return (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

View File

@ -4,7 +4,7 @@ import uuid
from django.core import exceptions, serializers from django.core import exceptions, serializers
from django.db import IntegrityError, connection, models from django.db import IntegrityError, connection, models
from django.db.models import CharField, F, Value 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 ( from django.test import (
SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature, SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature,
) )
@ -121,6 +121,12 @@ class TestQuerying(TestCase):
), ),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(
field__iexact='550E8400-E29B-41D4-A716-446655440000'
),
[self.objs[1]],
)
def test_isnull(self): def test_isnull(self):
self.assertSequenceEqual( self.assertSequenceEqual(
@ -133,36 +139,60 @@ class TestQuerying(TestCase):
NullableUUIDModel.objects.filter(field__contains='8400e29b'), NullableUUIDModel.objects.filter(field__contains='8400e29b'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__contains='8400-e29b'),
[self.objs[1]],
)
def test_icontains(self): def test_icontains(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
NullableUUIDModel.objects.filter(field__icontains='8400E29B'), NullableUUIDModel.objects.filter(field__icontains='8400E29B'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__icontains='8400-E29B'),
[self.objs[1]],
)
def test_startswith(self): def test_startswith(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
NullableUUIDModel.objects.filter(field__startswith='550e8400e29b4'), NullableUUIDModel.objects.filter(field__startswith='550e8400e29b4'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__startswith='550e8400-e29b-4'),
[self.objs[1]],
)
def test_istartswith(self): def test_istartswith(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
NullableUUIDModel.objects.filter(field__istartswith='550E8400E29B4'), NullableUUIDModel.objects.filter(field__istartswith='550E8400E29B4'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__istartswith='550E8400-E29B-4'),
[self.objs[1]],
)
def test_endswith(self): def test_endswith(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
NullableUUIDModel.objects.filter(field__endswith='a716446655440000'), NullableUUIDModel.objects.filter(field__endswith='a716446655440000'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__endswith='a716-446655440000'),
[self.objs[1]],
)
def test_iendswith(self): def test_iendswith(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
NullableUUIDModel.objects.filter(field__iendswith='A716446655440000'), NullableUUIDModel.objects.filter(field__iendswith='A716446655440000'),
[self.objs[1]], [self.objs[1]],
) )
self.assertSequenceEqual(
NullableUUIDModel.objects.filter(field__iendswith='A716-446655440000'),
[self.objs[1]],
)
def test_filter_with_expr(self): def test_filter_with_expr(self):
self.assertSequenceEqualWithoutHyphens( self.assertSequenceEqualWithoutHyphens(
@ -171,6 +201,18 @@ class TestQuerying(TestCase):
).filter(field__contains=F('value')), ).filter(field__contains=F('value')),
[self.objs[1]], [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): class TestSerialization(SimpleTestCase):