Amélioration de la recherche

This commit is contained in:
Jean-Marie Favreau 2023-11-11 12:03:47 +01:00
parent 5dd1125a68
commit 7097aa17d3
5 changed files with 123 additions and 55 deletions

View File

@ -298,9 +298,9 @@ article#filters {
} }
} }
.helptext { .helptext, .location-search {
font-size: 80%; font-size: 80%;
opacity: 0.7; margin-top: -0.7em;
} }
.django-ckeditor-widget { .django-ckeditor-widget {
@ -316,4 +316,16 @@ article#filters {
.slide-buttons { .slide-buttons {
float: right; float: right;
}
.highlight {
color: var(--primary);
font-weight: bold;
font-style: italic;
}
.search .description {
margin-left: 1em;
font-size: 90%;
margin-top: -0.5em;
} }

View File

@ -1,57 +1,64 @@
{% extends "agenda_culturel/page.html" %} {% extends "agenda_culturel/page.html" %}
{% block title %}Rechercher un évnement{% endblock %} {% block title %}Rechercher un événement{% endblock %}
{% load utils_extra %}
{% block content %} {% block content %}
<article class="search">
<article>
<header> <header>
<h1>Rechercher un événement</h1> <h1>Rechercher un événement</h1>
</header> </header>
<form method="get" class="form django-form"> <form method="get" class="form django-form" action="#results">
{% for field in filter.form %} {{ filter.form }}
<div> <button type="submit">Rechercher</button>
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% if forloop.first %}
<details>
<summary>Rechercher avancée</summary>
{% endif %}
{% if forloop.last %}
</details>
{% endif %}
{% endfor %}
<button type="submit">Rechercher</button> {% if full %}
<a href="{% url 'event_search' %}">Recherche simplifiée {% picto_from_name "chevron-right" %}</a>
{% else %}
<a href="{% url 'event_search_full' %}">Recherche avancée {% picto_from_name "chevron-right" %}</a>
{% endif %}
</form> </form>
<ul> {% if has_results %}
{% for obj in paginator_filter %} <div id="results">
<li><a href="{{ obj.get_absolute_url }}">{{ obj }}</a></li> <p>{{ paginator_filter.paginator.count }} événement{{paginator_filter.object_list.count | pluralize }} correspond{{paginator_filter.object_list.count | pluralize:"ent" }} à la recherche.</p>
{% endfor %} <ul>
</ul> {% for obj in paginator_filter %}
<footer> <li><p>{% include "agenda_culturel/date-times-inc.html" with event=obj %}
<span> &nbsp;: <a href="{{ obj.get_absolute_url }}">
{% if paginator_filter.has_previous %} {% if obj.title_hl %}{{ obj.title_hl | safe }}{% else %}{{ obj.title }}{% endif %}</a></p>
<a href="?page=1" role="button">&laquo; premier</a> <p class="location-search">{% picto_from_name "map-pin" %}
<a href="?page={{ paginator_filter.previous_page_number }}" role="button">précédent</a> {% if obj.location_hl %}{{ obj.location_hl | safe }}{% else %}{{ obj.location }}{% endif %}</p>
{% endif %} <div class="description">
{% if obj.description_hl %}{{ obj.description_hl | safe }}{% else %}{{ obj.description }}{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% if paginator_filter.paginator.num_pages != 1 %}
<footer>
<span>
{% if paginator_filter.has_previous %}
<a href="?page=1" role="button">&laquo; premier</a>
<a href="?page={{ paginator_filter.previous_page_number }}" role="button">précédent</a>
{% endif %}
<span> <span>
Page {{ paginator_filter.number }} sur {{ paginator_filter.paginator.num_pages }} Page {{ paginator_filter.number }} sur {{ paginator_filter.paginator.num_pages }}
</span> </span>
{% if paginator_filter.has_next %} {% if paginator_filter.has_next %}
<a href="?page={{ paginator_filter.next_page_number }}" role="button">suivant</a> <a href="?page={{ paginator_filter.next_page_number }}" role="button">suivant</a>
<a href="?page={{ paginator_filter.paginator.num_pages }}" role="button">dernier &raquo;</a> <a href="?page={{ paginator_filter.paginator.num_pages }}" role="button">dernier &raquo;</a>
{% endif %}
</span>
</footer>
{% endif %} {% endif %}
</span> </div>
</footer> {% endif %}
</article> </article>
{% endblock %} {% endblock %}

View File

@ -59,6 +59,10 @@ def url_day(d):
@register.simple_tag @register.simple_tag
def picto_from_name(name, datatooltip=""): def picto_from_name(name, datatooltip=""):
return mark_safe('<span data-tooltip="' + datatooltip + '"><svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + \ result = '<svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + \
'<use href="' + static("images/feather-sprite.svg") + '#' + name + '" />' + \ '<use href="' + static("images/feather-sprite.svg") + '#' + name + '" />' + \
'</svg></span>') '</svg>'
if datatooltip != "":
result = '<span data-tooltip="' + datatooltip + '">' + result + '</span>'
return mark_safe(result)

View File

@ -28,7 +28,8 @@ urlpatterns = [
path("test_app/", include("test_app.urls")), path("test_app/", include("test_app.urls")),
path("static-content/create", StaticContentCreateView.as_view(), name="create_static_content"), path("static-content/create", StaticContentCreateView.as_view(), name="create_static_content"),
path("static-content/<int:pk>/edit", StaticContentUpdateView.as_view(), name="edit_static_content"), path("static-content/<int:pk>/edit", StaticContentUpdateView.as_view(), name="edit_static_content"),
path('rechercher/', event_search, name='event_search') path('rechercher/', event_search, name='event_search'),
path('rechercher/complet/', event_search_full, name='event_search_full')
] ]
if settings.DEBUG: if settings.DEBUG:

View File

@ -4,6 +4,8 @@ from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import QueryDict from django.http import QueryDict
from django import forms from django import forms
from django.contrib.postgres.search import SearchQuery, SearchHeadline
from .forms import EventSubmissionModelForm from .forms import EventSubmissionModelForm
from .celery import create_event_from_submission from .celery import create_event_from_submission
@ -403,16 +405,34 @@ def event_list(request):
return render(request, 'agenda_culturel/list.html', {'filter': filter, 'paginator_filter': response}) return render(request, 'agenda_culturel/list.html', {'filter': filter, 'paginator_filter': response})
class SimpleSearchEventFilter(django_filters.FilterSet):
q = django_filters.CharFilter(method='custom_filter', label=_("Search"))
def custom_filter(self, queryset, name, value):
search_query = SearchQuery(value, config='french')
qs = queryset.filter(
Q(title__contains=value) | Q(location__contains=value) | Q(description__contains=value))
for f in ["title", "location", "description"]:
params = { f + "_hl": SearchHeadline(f,
search_query,
start_sel="<span class=\"highlight\">",
stop_sel="</span>",
config='french')}
qs = qs.annotate(**params)
return qs
class Meta:
model = Event
fields = ['q']
class SearchEventFilter(django_filters.FilterSet): class SearchEventFilter(django_filters.FilterSet):
tags = django_filters.CharFilter(lookup_expr='icontains') tags = django_filters.CharFilter(lookup_expr='icontains')
title = django_filters.CharFilter(lookup_expr='contains') title = django_filters.CharFilter(method="hl_filter_contains")
location = django_filters.CharFilter(lookup_expr='contains') location = django_filters.CharFilter(method="hl_filter_contains")
description = django_filters.CharFilter(lookup_expr='contains') description = django_filters.CharFilter(method="hl_filter_contains")
start_day = django_filters.DateFromToRangeFilter(widget=django_filters.widgets.RangeWidget(attrs={'type': 'date'})) start_day = django_filters.DateFromToRangeFilter(widget=django_filters.widgets.RangeWidget(attrs={'type': 'date'}))
q = django_filters.CharFilter(method='custom_filter', label=_("Search"))
o = django_filters.OrderingFilter( o = django_filters.OrderingFilter(
# tuple-mapping retains order # tuple-mapping retains order
fields=( fields=(
@ -422,18 +442,36 @@ class SearchEventFilter(django_filters.FilterSet):
), ),
) )
def custom_filter(self, queryset, name, value): def hl_filter_contains(self, queryset, name, value):
return queryset.filter(
Q(title__contains=value) | Q(location__contains=value) | Q(description__contains=value)) # first check if it contains
filter_contains = { name + "__contains": value }
queryset = queryset.filter(**filter_contains)
# then hightlight the result
search_query = SearchQuery(value, config='french')
params = { name + "_hl": SearchHeadline(name,
search_query,
start_sel="<span class=\"highlight\">",
stop_sel="</span>",
config='french')}
return queryset.annotate(**params)
class Meta: class Meta:
model = Event model = Event
fields = ['q', 'title', 'location', 'description', 'category', 'tags', 'start_day'] fields = ['title', 'location', 'description', 'category', 'tags', 'start_day']
def event_search(request, full=False):
if full:
filter = SearchEventFilter(request.GET, queryset=get_event_qs(request).order_by("-start_day"))
else:
filter = SimpleSearchEventFilter(request.GET, queryset=get_event_qs(request).order_by("-start_day"))
def event_search(request):
filter = SearchEventFilter(request.GET, queryset=get_event_qs(request).order_by("-start_day"))
paginator = Paginator(filter.qs, 10) paginator = Paginator(filter.qs, 10)
page = request.GET.get('page') page = request.GET.get('page')
@ -444,4 +482,10 @@ def event_search(request):
except EmptyPage: except EmptyPage:
response = paginator.page(paginator.num_pages) response = paginator.page(paginator.num_pages)
return render(request, 'agenda_culturel/search.html', {'filter': filter, 'paginator_filter': response}) return render(request, 'agenda_culturel/search.html', {'filter': filter,
'has_results': len(request.GET) != 0 or (len(request.GET) > 1 and "page" in request.GET),
'paginator_filter': response,
'full': full})
def event_search_full(request):
return event_search(request, True)