misc: specialize user search for phone numbers (#48352)
This commit is contained in:
parent
3d1fbb6af2
commit
e51b20fbe7
|
@ -17,9 +17,11 @@
|
|||
import datetime
|
||||
import logging
|
||||
import unicodedata
|
||||
import re
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.search import TrigramDistance
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, transaction, connection
|
||||
from django.db.models import F, Value, FloatField, Subquery, OuterRef
|
||||
from django.db.models.functions import Lower, Coalesce
|
||||
|
@ -30,23 +32,52 @@ from django.contrib.auth.models import BaseUserManager
|
|||
from authentic2 import app_settings
|
||||
from authentic2.models import Attribute, AttributeValue, UserExternalId
|
||||
from authentic2.utils.lookups import Unaccent, ImmutableConcat
|
||||
from authentic2.attribute_kinds import clean_phone_number
|
||||
|
||||
|
||||
class UserQuerySet(models.QuerySet):
|
||||
|
||||
def free_text_search(self, search):
|
||||
terms = search.split()
|
||||
# clean search string
|
||||
search = search.strip()
|
||||
if not search:
|
||||
return self
|
||||
# normalize spaces
|
||||
search = re.sub(r'\s+', ' ', search)
|
||||
|
||||
# get searchable attributes
|
||||
phone_attributes = []
|
||||
other_attributes = []
|
||||
for attribute in Attribute.objects.filter(searchable=True):
|
||||
if attribute.kind == 'phone_number':
|
||||
phone_attributes.append(attribute)
|
||||
else:
|
||||
other_attributes.append(attribute)
|
||||
|
||||
# look directly for phone number if it's the sole search term
|
||||
if phone_attributes and ' ' not in search:
|
||||
try:
|
||||
phone_number = clean_phone_number(search)
|
||||
except ValidationError:
|
||||
pass
|
||||
else:
|
||||
# look only for the phone number if it starts with any local prefix
|
||||
if phone_number.startswith(
|
||||
tuple(app_settings.A2_LOCAL_PHONE_PREFIXES)):
|
||||
return self.filter(
|
||||
attribute_values__content=phone_number,
|
||||
attribute_values__attribute__in=phone_attributes)
|
||||
|
||||
terms = search.split()
|
||||
if not terms:
|
||||
return self
|
||||
|
||||
searchable_attributes = Attribute.objects.filter(searchable=True)
|
||||
queries = []
|
||||
for term in terms:
|
||||
q = None
|
||||
|
||||
specific_queries = []
|
||||
for a in searchable_attributes:
|
||||
for a in other_attributes:
|
||||
kind = a.get_kind()
|
||||
free_text_search_function = kind.get('free_text_search')
|
||||
if free_text_search_function:
|
||||
|
@ -66,7 +97,7 @@ class UserQuerySet(models.QuerySet):
|
|||
| models.query.Q(last_name__icontains=term)
|
||||
| models.query.Q(email__icontains=term)
|
||||
)
|
||||
for a in searchable_attributes:
|
||||
for a in other_attributes:
|
||||
if a.name in ('first_name', 'last_name'):
|
||||
continue
|
||||
q = q | models.query.Q(
|
||||
|
@ -74,7 +105,7 @@ class UserQuerySet(models.QuerySet):
|
|||
queries.append(q)
|
||||
self = self.filter(six.moves.reduce(models.query.Q.__and__, queries))
|
||||
# search by attributes can match multiple times
|
||||
if searchable_attributes:
|
||||
if other_attributes:
|
||||
self = self.distinct()
|
||||
return self
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ from django.core.exceptions import ValidationError
|
|||
|
||||
from django_rbac.utils import get_permission_model, get_role_model
|
||||
|
||||
from authentic2.models import Attribute
|
||||
|
||||
Permission = get_permission_model()
|
||||
Role = get_role_model()
|
||||
User = get_user_model()
|
||||
|
@ -58,3 +60,41 @@ class CustomUserTestCase(TestCase):
|
|||
self.assertIn(r.id, [rparent1.id, rparent2.id])
|
||||
self.assertEqual(r.member, [])
|
||||
|
||||
|
||||
|
||||
def test_free_text_search_phone_number(db, django_assert_num_queries):
|
||||
Attribute.objects.create(name='phone', label='phone', kind='phone_number', searchable=True)
|
||||
Attribute.objects.create(name='mobile', label='mobile', kind='phone_number', searchable=True)
|
||||
|
||||
user1 = User.objects.create(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
email='john.doe@example.com')
|
||||
user1.attributes.phone = '0166666666'
|
||||
user1.attributes.mobile = '0666666666'
|
||||
|
||||
user2 = User.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Doe',
|
||||
email='jane.doe@example.com')
|
||||
user2.attributes.phone = '0166666666'
|
||||
user2.attributes.mobile = '0677777777'
|
||||
|
||||
user3 = User.objects.create(
|
||||
first_name='Joe',
|
||||
last_name='Doe',
|
||||
email='joe.doe@example.com')
|
||||
user3.attributes.phone = '0166666666'
|
||||
user3.attributes.mobile = '0699999999'
|
||||
|
||||
with django_assert_num_queries(2):
|
||||
assert set(User.objects.free_text_search('jo doe')) == set([user1, user3])
|
||||
|
||||
with django_assert_num_queries(2):
|
||||
assert set(User.objects.free_text_search(' +33-6-77-77-77-77 ')) == set([user2])
|
||||
|
||||
with django_assert_num_queries(2):
|
||||
assert set(User.objects.free_text_search(' 0033.6.99.99.99.99 ')) == set([user3])
|
||||
|
||||
with django_assert_num_queries(2):
|
||||
assert set(User.objects.free_text_search(' 01.66.66.66.66 ')) == set([user1, user2, user3])
|
||||
|
|
Loading…
Reference in New Issue