api_views: order users as free_text_search() when `q` is used (#50536)

This commit is contained in:
Benjamin Dauvergne 2021-01-26 09:08:33 +01:00
parent 4c029ae062
commit 51becec7cf
2 changed files with 18 additions and 8 deletions

View File

@ -66,6 +66,7 @@ from . import (utils, decorators, attribute_kinds, app_settings, hooks,
from .models import Attribute, PasswordReset, Service
from .a2_rbac.utils import get_default_ou
from .journal_event_types import UserLogin, UserRegistration
from .utils.lookups import Unaccent
# Retro-compatibility with older Django versions
@ -384,6 +385,7 @@ class BaseUserSerializer(serializers.ModelSerializer):
required=False, default=get_default_ou)
date_joined = serializers.DateTimeField(read_only=True)
last_login = serializers.DateTimeField(read_only=True)
dist = serializers.FloatField(read_only=True)
send_registration_email = serializers.BooleanField(write_only=True, required=False,
default=False)
send_registration_email_next_url = serializers.URLField(write_only=True, required=False)
@ -747,9 +749,14 @@ class UsersAPI(api_mixins.GetOrCreateMixinView, HookMixin, ExceptionHandlerMixin
# https://django-filter.readthedocs.io/en/master/guide/migration.html
filter_class = UsersFilter
filterset_class = UsersFilter
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS + [FreeTextSearchFilter]
filter_backends = [FreeTextSearchFilter] + api_settings.DEFAULT_FILTER_BACKENDS
pagination_class = pagination.CursorPagination
ordering = ['last_name', 'first_name', 'email', 'username']
@property
def ordering(self):
if 'q' in self.request.GET:
return ['dist', Unaccent('last_name'), Unaccent('first_name')]
return User._meta.ordering
def get_queryset(self):
User = get_user_model()

View File

@ -40,20 +40,23 @@ class UserQuerySet(models.QuerySet):
def free_text_search(self, search):
search = search.strip()
def wrap_qs(qs):
return qs.annotate(dist=Value(0, output_field=FloatField()))
if len(search) == 0:
return self.none()
return wrap_qs(self.none())
if '@' in search and len(search.split()) == 1:
qs = self.filter(email__iexact=search).order_by('last_name', 'first_name')
if qs.exists():
return qs
return wrap_qs(qs)
try:
guid = uuid.UUID(search)
except ValueError:
pass
else:
return self.filter(uuid=guid.hex)
return wrap_qs(self.filter(uuid=guid.hex))
try:
phone_number = clean_number(search)
@ -64,7 +67,7 @@ class UserQuerySet(models.QuerySet):
search_vector=SearchQuery(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
return wrap_qs(qs)
try:
date = parse_date(search)
@ -75,7 +78,7 @@ class UserQuerySet(models.QuerySet):
search_vector=SearchQuery(date.isoformat()), attribute__kind='birthdate')
qs = self.filter(attribute_values__in=attribute_values).order_by('last_name', 'first_name')
if qs.exists():
return qs
return wrap_qs(qs)
qs = self.find_duplicates(fullname=search, limit=None, threshold=0.2)
extra_user_ids = set()
@ -89,7 +92,7 @@ class UserQuerySet(models.QuerySet):
).values_list('id', flat=True))
if extra_user_ids:
qs = qs | self.filter(id__in=extra_user_ids)
qs = qs.order_by('dist', 'last_name', 'first_name')
qs = qs.order_by('dist', Unaccent('last_name'), Unaccent('first_name'))
return qs
def find_duplicates(self, first_name=None, last_name=None, fullname=None, birthdate=None, limit=5, threshold=None):