Order note research results: match first aliases then normalized names

This commit is contained in:
Yohann D'ANELLO 2020-08-30 22:33:59 +02:00
parent ca7f4791ed
commit a9258c332a
4 changed files with 99 additions and 27 deletions

View File

@ -4,14 +4,15 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import routers, serializers from rest_framework import routers, serializers
from rest_framework.filters import SearchFilter
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ReadOnlyModelViewSet
from activity.api.urls import register_activity_urls from activity.api.urls import register_activity_urls
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
from member.api.urls import register_members_urls from member.api.urls import register_members_urls
from note.api.urls import register_note_urls from note.api.urls import register_note_urls
from note.models import Alias
from treasury.api.urls import register_treasury_urls from treasury.api.urls import register_treasury_urls
from logs.api.urls import register_logs_urls from logs.api.urls import register_logs_urls
from permission.api.urls import register_permission_urls from permission.api.urls import register_permission_urls
@ -52,9 +53,47 @@ class UserViewSet(ReadProtectedModelViewSet):
""" """
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
filter_backends = [DjangoFilterBackend, SearchFilter] filter_backends = [DjangoFilterBackend]
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ] filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
search_fields = ['$username', '$first_name', '$last_name', '$note__alias__name', '$note__alias__normalized_name', ]
def get_queryset(self):
queryset = super().get_queryset().order_by("username")
if "search" in self.request.GET:
pattern = self.request.GET["search"]
# We match first a user by its username, then if an alias is matched without normalization
# And finally if the normalized pattern matches a normalized alias.
queryset = queryset.filter(
username__iregex="^" + pattern
).union(
queryset.filter(
Q(note__alias__name__iregex="^" + pattern)
& ~Q(username__iregex="^" + pattern)
), all=True).union(
queryset.filter(
Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
& ~Q(note__alias__name__iregex="^" + pattern)
& ~Q(username__iregex="^" + pattern)
),
all=True).union(
queryset.filter(
Q(note__alias__normalized_name__iregex="^" + pattern.lower())
& ~Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
& ~Q(note__alias__name__iregex="^" + pattern)
& ~Q(username__iregex="^" + pattern)
),
all=True).union(
queryset.filter(
(Q(last_name__iregex="^" + pattern) | Q(first_name__iregex="^" + pattern))
& ~Q(note__alias__normalized_name__iregex="^" + pattern.lower())
& ~Q(note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
& ~Q(note__alias__name__iregex="^" + pattern)
& ~Q(username__iregex="^" + pattern)
),
all=True)
return queryset
# This ViewSet is the only one that is accessible from all authenticated users! # This ViewSet is the only one that is accessible from all authenticated users!

View File

@ -180,9 +180,9 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
Filter the user list with the given pattern. Filter the user list with the given pattern.
""" """
qs = super().get_queryset().distinct("pk").annotate(alias=F("note__alias__name"))\ qs = super().get_queryset().distinct("username").annotate(alias=F("note__alias__name"))\
.annotate(normalized_alias=F("note__alias__normalized_name"))\ .annotate(normalized_alias=F("note__alias__normalized_name"))\
.filter(profile__registration_valid=True) .filter(profile__registration_valid=True).order_by("username")
if "search" in self.request.GET: if "search" in self.request.GET:
pattern = self.request.GET["search"] pattern = self.request.GET["search"]
@ -190,13 +190,16 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
return qs.none() return qs.none()
qs = qs.filter( qs = qs.filter(
Q(first_name__iregex=pattern) username__iregex="^" + pattern
| Q(last_name__iregex=pattern) ).union(
| Q(profile__section__iregex=pattern) qs.filter(
| Q(username__iregex=pattern) (Q(alias__iregex="^" + pattern)
| Q(alias__iregex=pattern) | Q(normalized_alias__iregex="^" + Alias.normalize(pattern))
| Q(normalized_alias__iregex=Alias.normalize(pattern)) | Q(last_name__iregex="^" + pattern)
) | Q(first_name__iregex="^" + pattern)
| Q(email__istartswith=pattern))
& ~Q(username__iregex="^" + pattern)
), all=True)
else: else:
qs = qs.none() qs = qs.none()

View File

@ -40,9 +40,19 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
alias = self.request.query_params.get("alias", ".*") alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter( queryset = queryset.filter(
Q(alias__name__regex="^" + alias) name__iregex="^" + alias
| Q(alias__normalized_name__regex="^" + Alias.normalize(alias)) ).union(
| Q(alias__normalized_name__regex="^" + alias.lower())) queryset.filter(
Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True).union(
queryset.filter(
Q(normalized_name__iregex="^" + alias.lower())
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True)
return queryset.distinct() return queryset.distinct()
@ -85,9 +95,19 @@ class AliasViewSet(ReadProtectedModelViewSet):
alias = self.request.query_params.get("alias", ".*") alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter( queryset = queryset.filter(
Q(name__regex="^" + alias) name__iregex="^" + alias
| Q(normalized_name__regex="^" + Alias.normalize(alias)) ).union(
| Q(normalized_name__regex="^" + alias.lower())) queryset.filter(
Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True).union(
queryset.filter(
Q(normalized_name__iregex="^" + alias.lower())
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True)
return queryset return queryset
@ -108,13 +128,25 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
queryset = super().get_queryset() queryset = super().get_queryset()
alias = self.request.query_params.get("alias", ".*") alias = self.request.query_params.get("alias", ".*")
queryset = queryset.order_by('name').prefetch_related('note')
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias.
queryset = queryset.filter( queryset = queryset.filter(
Q(name__regex="^" + alias) name__iregex="^" + alias
| Q(normalized_name__regex="^" + Alias.normalize(alias)) ).union(
| Q(normalized_name__regex="^" + alias.lower()))\ queryset.filter(
.order_by('name').prefetch_related('note') Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True).union(
queryset.filter(
Q(normalized_name__iregex="^" + alias.lower())
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True)
return queryset return queryset.distinct()
class TemplateCategoryViewSet(ReadProtectedModelViewSet): class TemplateCategoryViewSet(ReadProtectedModelViewSet):

View File

@ -79,7 +79,7 @@ function refreshBalance () {
* @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called.
*/ */
function getMatchedNotes (pattern, fun) { function getMatchedNotes (pattern, fun) {
$.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club&ordering=normalized_name", fun); $.getJSON("/api/note/alias/?format=json&alias=" + pattern + "&search=user|club", fun);
} }
/** /**
@ -261,9 +261,7 @@ function autoCompleteNote (field_id, note_list_id, notes, notes_display, alias_p
return; return;
} }
$.getJSON("/api/note/consumer/?format=json&alias=" $.getJSON("/api/note/consumer/?format=json&alias=" + pattern + "&search=user|club",
+ pattern
+ "&search=user|club&ordering=normalized_name",
function (consumers) { function (consumers) {
// The response arrived too late, we stop the request // The response arrived too late, we stop the request
if (pattern !== $("#" + field_id).val()) if (pattern !== $("#" + field_id).val())