misc: specialize user search for phone numbers (#48352)

This commit is contained in:
Benjamin Dauvergne 2020-11-14 09:21:28 +01:00
parent 3d1fbb6af2
commit e51b20fbe7
2 changed files with 76 additions and 5 deletions

View File

@ -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

View File

@ -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])