Projet

Général

Profil

0001-misc-specialize-user-search-for-phone-numbers-48352.patch

Benjamin Dauvergne, 14 novembre 2020 10:29

Télécharger (5,9 ko)

Voir les différences:

Subject: [PATCH 1/2] misc: specialize user search for phone numbers (#48352)

 src/authentic2/custom_user/managers.py | 41 ++++++++++++++++++++++----
 tests/test_custom_user.py              | 40 +++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)
src/authentic2/custom_user/managers.py
17 17
import datetime
18 18
import logging
19 19
import unicodedata
20
import re
20 21

  
21 22
from django.contrib.contenttypes.models import ContentType
22 23
from django.contrib.postgres.search import TrigramDistance
24
from django.core.exceptions import ValidationError
23 25
from django.db import models, transaction, connection
24 26
from django.db.models import F, Value, FloatField, Subquery, OuterRef
25 27
from django.db.models.functions import Lower, Coalesce
......
30 32
from authentic2 import app_settings
31 33
from authentic2.models import Attribute, AttributeValue, UserExternalId
32 34
from authentic2.utils.lookups import Unaccent, ImmutableConcat
35
from authentic2.attribute_kinds import clean_phone_number
33 36

  
34 37

  
35 38
class UserQuerySet(models.QuerySet):
36 39

  
37 40
    def free_text_search(self, search):
38
        terms = search.split()
41
        # clean search string
42
        search = search.strip()
43
        if not search:
44
            return self
45
        # normalize spaces
46
        search = re.sub(r'\s+', ' ', search)
47

  
48
        # get searchable attributes
49
        phone_attributes = []
50
        other_attributes = []
51
        for attribute in Attribute.objects.filter(searchable=True):
52
            if attribute.kind == 'phone_number':
53
                phone_attributes.append(attribute)
54
            else:
55
                other_attributes.append(attribute)
56

  
57
        # look directly for phone number if it's the sole search term
58
        if phone_attributes and ' ' not in search:
59
            try:
60
                phone_number = clean_phone_number(search)
61
            except ValidationError:
62
                pass
63
            else:
64
                # look only for the phone number if it starts with any local prefix
65
                if phone_number.startswith(
66
                        tuple(app_settings.A2_LOCAL_PHONE_PREFIXES)):
67
                    return self.filter(
68
                        attribute_values__content=phone_number,
69
                        attribute_values__attribute__in=phone_attributes)
39 70

  
71
        terms = search.split()
40 72
        if not terms:
41 73
            return self
42 74

  
43
        searchable_attributes = Attribute.objects.filter(searchable=True)
44 75
        queries = []
45 76
        for term in terms:
46 77
            q = None
47 78

  
48 79
            specific_queries = []
49
            for a in searchable_attributes:
80
            for a in other_attributes:
50 81
                kind = a.get_kind()
51 82
                free_text_search_function = kind.get('free_text_search')
52 83
                if free_text_search_function:
......
66 97
                | models.query.Q(last_name__icontains=term)
67 98
                | models.query.Q(email__icontains=term)
68 99
            )
69
            for a in searchable_attributes:
100
            for a in other_attributes:
70 101
                if a.name in ('first_name', 'last_name'):
71 102
                    continue
72 103
                q = q | models.query.Q(
......
74 105
            queries.append(q)
75 106
        self = self.filter(six.moves.reduce(models.query.Q.__and__, queries))
76 107
        # search by attributes can match multiple times
77
        if searchable_attributes:
108
        if other_attributes:
78 109
            self = self.distinct()
79 110
        return self
80 111

  
tests/test_custom_user.py
21 21

  
22 22
from django_rbac.utils import get_permission_model, get_role_model
23 23

  
24
from authentic2.models import Attribute
25

  
24 26
Permission = get_permission_model()
25 27
Role = get_role_model()
26 28
User = get_user_model()
......
58 60
                self.assertIn(r.id, [rparent1.id, rparent2.id])
59 61
                self.assertEqual(r.member, [])
60 62

  
63

  
64

  
65
def test_free_text_search_phone_number(db, django_assert_num_queries):
66
    Attribute.objects.create(name='phone', label='phone', kind='phone_number', searchable=True)
67
    Attribute.objects.create(name='mobile', label='mobile', kind='phone_number', searchable=True)
68

  
69
    user1 = User.objects.create(
70
        first_name='John',
71
        last_name='Doe',
72
        email='john.doe@example.com')
73
    user1.attributes.phone = '0166666666'
74
    user1.attributes.mobile = '0666666666'
75

  
76
    user2 = User.objects.create(
77
        first_name='Jane',
78
        last_name='Doe',
79
        email='jane.doe@example.com')
80
    user2.attributes.phone = '0166666666'
81
    user2.attributes.mobile = '0677777777'
82

  
83
    user3 = User.objects.create(
84
        first_name='Joe',
85
        last_name='Doe',
86
        email='joe.doe@example.com')
87
    user3.attributes.phone = '0166666666'
88
    user3.attributes.mobile = '0699999999'
89

  
90
    with django_assert_num_queries(2):
91
        assert set(User.objects.free_text_search('jo doe')) == set([user1, user3])
92

  
93
    with django_assert_num_queries(2):
94
        assert set(User.objects.free_text_search('  +33-6-77-77-77-77  ')) == set([user2])
95

  
96
    with django_assert_num_queries(2):
97
        assert set(User.objects.free_text_search('  0033.6.99.99.99.99  ')) == set([user3])
98

  
99
    with django_assert_num_queries(2):
100
        assert set(User.objects.free_text_search('  01.66.66.66.66  ')) == set([user1, user2, user3])
61
-