diff --git a/itou/www/autocomplete/views.py b/itou/www/autocomplete/views.py index 4442e84425c..d4b3a5e0bda 100644 --- a/itou/www/autocomplete/views.py +++ b/itou/www/autocomplete/views.py @@ -1,8 +1,10 @@ from datetime import datetime +from functools import reduce +from operator import add from django.contrib.auth.decorators import login_not_required -from django.db.models import Exists, F, OuterRef, Q, Value -from django.db.models.functions import Least, Lower, NullIf, StrIndex +from django.db.models import Exists, F, IntegerField, OuterRef, Q, Value +from django.db.models.functions import Cast, Least, Lower, NullIf, StrIndex from django.http import JsonResponse from unidecode import unidecode @@ -13,7 +15,6 @@ from itou.users.enums import UserKind from itou.users.models import User from itou.utils.auth import check_user -from itou.utils.db import or_queries from itou.www.gps.views import is_allowed_to_use_gps_advanced_features @@ -148,30 +149,34 @@ def gps_users_autocomplete(request): # Fallback to unaccent / icontains for now search_terms = term.split(" ") - name_q = [] - for term in search_terms: - name_q.append(Q(first_name__unaccent__istartswith=term)) - name_q.append(Q(last_name__unaccent__istartswith=term)) - users_qs = ( - User.objects.filter(or_queries(name_q)) - .filter(kind=UserKind.JOB_SEEKER) - .exclude( - Exists( - FollowUpGroup.objects.filter( - beneficiary_id=OuterRef("pk"), - memberships__member=current_user, - memberships__is_active=True, - ) + + users_qs = User.objects.filter(kind=UserKind.JOB_SEEKER).exclude( + Exists( + FollowUpGroup.objects.filter( + beneficiary_id=OuterRef("pk"), + memberships__member=current_user, + memberships__is_active=True, ) ) - )[:10] + ) + + def match_term(term): + return Cast( + Exists( + User.objects.filter(pk=OuterRef("pk")).filter( + Q(first_name__unaccent__icontains=term) | Q(last_name__unaccent__icontains=term) + ), + ), + IntegerField(), + ) + users_qs = users_qs.annotate(rank=reduce(add, [match_term(term) for term in search_terms])).order_by("-rank") users = [ { - "text": user.get_full_name(), + "text": f"{user.title}. {user.get_full_name()} ({user.jobseeker_profile.birthdate})", "id": user.pk, } - for user in users_qs + for user in users_qs[:10] ] return JsonResponse({"results": users}) diff --git a/tests/gps/test_views.py b/tests/gps/test_views.py index 2c4b54c3fad..9222f7eb1f1 100644 --- a/tests/gps/test_views.py +++ b/tests/gps/test_views.py @@ -46,23 +46,23 @@ def test_user_autocomplete(client): FollowUpGroupFactory(beneficiary=third_beneficiary, memberships=3, memberships__member=prescriber) FollowUpGroupFactory(beneficiary=second_beneficiary, memberships=2) - def get_autocomplete_results(user): + def get_autocomplete_results(user, term="gps"): client.force_login(user) - response = client.get(reverse("autocomplete:gps_users") + "?term=gps") - return set(r["id"] for r in response.json()["results"]) + response = client.get(reverse("autocomplete:gps_users") + f"?term={term}") + return [r["id"] for r in response.json()["results"]] # Employers should get the 3 job seekers. results = get_autocomplete_results(EmployerFactory(with_company=True)) - assert results == {first_beneficiary.pk, second_beneficiary.pk, third_beneficiary.pk} + assert set(results) == {first_beneficiary.pk, second_beneficiary.pk, third_beneficiary.pk} # Authorized prescribers should get the 3 job seekers. org = PrescriberOrganizationWithMembershipFactory(authorized=True) results = get_autocomplete_results(org.members.get()) - assert results == {first_beneficiary.pk, second_beneficiary.pk, third_beneficiary.pk} + assert set(results) == {first_beneficiary.pk, second_beneficiary.pk, third_beneficiary.pk} # We should not get ourself nor the first and third user user because we are a member of their group results = get_autocomplete_results(prescriber) - assert results == {second_beneficiary.pk} + assert set(results) == {second_beneficiary.pk} # Now, if we remove the first user from our group by setting the membership to is_active False # The autocomplete should return it again @@ -73,7 +73,11 @@ def get_autocomplete_results(user): # We should not get ourself but we should get the first beneficiary (we are is_active=False) # and the second one (we are not part of his group) results = get_autocomplete_results(prescriber) - assert results == {first_beneficiary.pk, second_beneficiary.pk} + assert set(results) == {first_beneficiary.pk, second_beneficiary.pk} + + # with "martin gps" Martin is the first result + results = get_autocomplete_results(prescriber, term="martin gps") + assert results == [second_beneficiary.pk, first_beneficiary.pk] @pytest.mark.parametrize(