diff --git a/src/agenda_culturel/filters.py b/src/agenda_culturel/filters.py index 0c0131d..8cab6da 100644 --- a/src/agenda_culturel/filters.py +++ b/src/agenda_culturel/filters.py @@ -5,8 +5,15 @@ import re import django_filters from django import forms from django.contrib.gis.measure import D -from django.contrib.postgres.search import SearchHeadline, SearchQuery -from django.db.models import F, Q +from django.contrib.postgres.search import ( + SearchVector, + SearchQuery, + SearchRank, + SearchHeadline, + TrigramSimilarity, +) +from django.db.models import Q, F, Func +from django.db.models.functions import Greatest, Lower from django.http import QueryDict from django.utils.translation import gettext_lazy as _ @@ -18,6 +25,7 @@ from .models import ( RecurrentImport, ReferenceLocation, Tag, + remove_accents, ) @@ -531,18 +539,27 @@ class SimpleSearchEventFilter(django_filters.FilterSet): return qs def custom_filter(self, queryset, name, value): + value = remove_accents(value) search_query = SearchQuery(value, config="french") - qs = queryset.filter( - Q(title__icontains=value) - | Q(category__name__icontains=value) - | Q(tags__icontains=[value]) - | Q(exact_location__name__icontains=value) - | Q(description__icontains=value) - ) + + search_vector = SearchVector( + "title", weight="A", config="french" + ) + SearchVector("description", weight="B", config="french") + + # Full-text rank + qs = queryset.annotate( + rank=SearchRank(search_vector, search_query), + similarity=Greatest( + TrigramSimilarity(Func(Lower("title"), function="unaccent"), value), + TrigramSimilarity( + Func(Lower("description"), function="unaccent"), value + ), + ), + relevance=F("rank") + F("similarity"), + ).filter(Q(rank__gte=0.5) | Q(similarity__gte=0.3)) + for f in [ "title", - "category__name", - "exact_location__name", "description", ]: params = { @@ -556,7 +573,7 @@ class SimpleSearchEventFilter(django_filters.FilterSet): ) } qs = qs.annotate(**params) - return qs + return qs.order_by("-relevance") class Meta: model = Event diff --git a/src/agenda_culturel/templates/agenda_culturel/paginator_filter.html b/src/agenda_culturel/templates/agenda_culturel/paginator_filter.html index 1b5da17..7c65cbe 100644 --- a/src/agenda_culturel/templates/agenda_culturel/paginator_filter.html +++ b/src/agenda_culturel/templates/agenda_culturel/paginator_filter.html @@ -1,15 +1,17 @@ -{% if paginator_filter.paginator.num_pages != 1 %} +{% if paginator_filter.paginator.num_pages != 1 and paginator_filter|length > 0 %} {% if paginator_filter.has_previous %} « premier précédent {% endif %} - Page {{ paginator_filter.number }} sur {{ paginator_filter.paginator.num_pages }} + Page {{ paginator_filter.number }} + {% if not nomax %}sur {{ paginator_filter.paginator.num_pages }}{% endif %} + {% if paginator_filter.has_next %} suivant - dernier » + {% if paginator_filter.count < 9999999998 %} + dernier » + {% endif %} {% endif %} -{% else %} - Page 1 sur 1 {% endif %} diff --git a/src/agenda_culturel/templates/agenda_culturel/search.html b/src/agenda_culturel/templates/agenda_culturel/search.html index ab29da8..cd58b05 100644 --- a/src/agenda_culturel/templates/agenda_culturel/search.html +++ b/src/agenda_culturel/templates/agenda_culturel/search.html @@ -26,7 +26,7 @@ Recherche avancée {% picto_from_name "chevron-right" %} {% endif %} - {% if has_results or categories %} + {% if has_results or categories or tags or places or organisations or rimports %}
{% if categories %}
@@ -87,16 +87,13 @@ {% endfor %}
{% endif %} -

- {{ paginator_filter.paginator.count }} événement{{ paginator_filter.object_list.count | pluralize }} correspond{{ paginator_filter.object_list.count | pluralize:"ent" }} à la recherche. -

{% for obj in paginator_filter %} {% include "agenda_culturel/single-event/event-in-flat-list-inc.html" with event=obj %} {% endfor %}
{% endif %} diff --git a/src/agenda_culturel/views/search_views.py b/src/agenda_culturel/views/search_views.py index 8bd38d8..3e12708 100644 --- a/src/agenda_culturel/views/search_views.py +++ b/src/agenda_culturel/views/search_views.py @@ -2,7 +2,6 @@ import emoji from django.core.paginator import PageNotAnInteger, EmptyPage from django.shortcuts import render -from . import PaginatorFilter from ..filters import SearchEventFilter, SimpleSearchEventFilter from ..models import ( Category, @@ -17,6 +16,8 @@ from django.db.models import Q, F, Func def event_search(request, full=False): + from .utils import PaginatorFilterCountless + categories = None tags = None places = None @@ -79,7 +80,7 @@ def event_search(request, full=False): name__icontains=request.GET["q"] ) - paginator = PaginatorFilter(filter, 10, request) + paginator = PaginatorFilterCountless(filter, 10, request) page = request.GET.get("page") try: diff --git a/src/agenda_culturel/views/utils.py b/src/agenda_culturel/views/utils.py index 90a2210..66207a1 100644 --- a/src/agenda_culturel/views/utils.py +++ b/src/agenda_culturel/views/utils.py @@ -1,5 +1,6 @@ from django.core.paginator import EmptyPage, Paginator from django.utils.translation import gettext_lazy as _ +from django.utils.functional import cached_property from django.db.models import Aggregate, FloatField from ..models import Event @@ -75,3 +76,10 @@ class PaginatorFilter(Paginator): page.url_next_page = self.request.get_full_path() return page + + +class PaginatorFilterCountless(PaginatorFilter): + + @cached_property + def count(self): + return 9999999999