From 00810061d3a21125eae7c6e15670a33eeccc7764 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 8 Jan 2021 12:00:46 +0100 Subject: [PATCH] custom_user: specialize free_text_search for common search terms (#49957) --- src/authentic2/custom_user/managers.py | 59 +++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/authentic2/custom_user/managers.py b/src/authentic2/custom_user/managers.py index b1b9ffd3..0b7635b1 100644 --- a/src/authentic2/custom_user/managers.py +++ b/src/authentic2/custom_user/managers.py @@ -17,24 +17,71 @@ import datetime import logging import unicodedata +import uuid 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 -from django.utils import six -from django.utils import timezone +from django.utils import timezone, six 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.utils.date import parse_date +from authentic2.attribute_kinds import clean_number class UserQuerySet(models.QuerySet): def free_text_search(self, search): + search = search.strip() + + if '@' in search: + return self.filter(email__icontains=search).order('email') + + try: + guid = uuid.UUID(search) + except ValueError: + pass + else: + return self.filter(uuid=guid.hex) + + try: + phone_number = clean_number(search) + except ValidationError: + pass + else: + attribute_values = AttributeValue.objects.filter( + content__contains=phone_number, attribute__kind='phone_number') + qs = self.filter(attribute_values__in=attribute_values).order_by('last_name', 'first_name') + if qs.exists(): + return qs + + try: + date = parse_date(search) + except ValueError: + pass + else: + attribute_values = AttributeValue.objects.filter( + content__contains=date.isoformat(), attribute__kind='birthdate') + qs = self.filter(attribute_values__in=attribute_values).order_by('last_name', 'first_name') + if qs.exists(): + return qs + + qs = self.find_duplicates(fullname=search, limit=None) + if qs.exists(): + return qs + + qs = self.filter(username__istartswith=search) + if qs.exists(): + return qs + return self.free_text_search_complex(search) + + def free_text_search_complex(self, search): terms = search.split() if not terms: @@ -44,7 +91,6 @@ class UserQuerySet(models.QuerySet): queries = [] for term in terms: q = None - specific_queries = [] for a in searchable_attributes: kind = a.get_kind() @@ -78,7 +124,9 @@ class UserQuerySet(models.QuerySet): self = self.distinct() return self - def find_duplicates(self, first_name=None, last_name=None, fullname=None, birthdate=None): + + + def find_duplicates(self, first_name=None, last_name=None, fullname=None, birthdate=None, limit=5): with connection.cursor() as cursor: cursor.execute( "SET pg_trgm.similarity_threshold = %f" % app_settings.A2_DUPLICATES_THRESHOLD @@ -96,7 +144,8 @@ class UserQuerySet(models.QuerySet): qs = qs.filter(name__trigram_similar=name) qs = qs.annotate(dist=TrigramDistance('name', name)) qs = qs.order_by('dist') - qs = qs[:5] + if limit is not None: + qs = qs[:limit] # alter distance according to additionnal parameters if birthdate: -- 2.29.2