experiENS/avisstage/views_search.py

306 lines
9.2 KiB
Python
Raw Permalink Normal View History

2021-02-07 23:24:33 +01:00
import json
import logging
from datetime import date
2017-06-16 00:17:25 +02:00
from django import forms
from django.conf import settings
2021-02-07 23:24:33 +01:00
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
2021-02-08 02:27:37 +01:00
from django.core.paginator import InvalidPage, Paginator
2021-02-07 23:24:33 +01:00
from django.db.models import Case, Q, When
from django.http import HttpResponseBadRequest, JsonResponse
2021-02-08 02:27:37 +01:00
from django.shortcuts import render
2017-06-16 00:17:25 +02:00
USE_ELASTICSEARCH = getattr(settings, "USE_ELASTICSEARCH", True)
if USE_ELASTICSEARCH:
from .documents import StageDocument
2018-12-28 23:46:24 +01:00
from .decorators import en_scolarite_required
2017-06-17 00:34:11 +02:00
from .models import Stage
2021-02-07 23:24:33 +01:00
from .statics import NIVEAU_SCOL_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS
2017-06-16 00:17:25 +02:00
2018-12-26 20:23:20 +01:00
logger = logging.getLogger("recherche")
2021-02-08 02:27:37 +01:00
2017-06-16 00:17:25 +02:00
# Recherche
class SearchForm(forms.Form):
2017-06-17 00:34:11 +02:00
generique = forms.CharField(required=False)
2021-02-07 23:50:15 +01:00
sujet = forms.CharField(label="À propos de", required=False)
2021-02-07 23:15:47 +01:00
contexte = forms.CharField(
2021-06-29 00:11:18 +02:00
label="Contexte (lieu, encadrant·e·s, structure)", required=False
2021-02-07 23:15:47 +01:00
)
2021-02-07 23:50:15 +01:00
apres_annee = forms.IntegerField(label="Après cette année", required=False)
avant_annee = forms.IntegerField(label="Avant cette année", required=False)
2021-02-07 23:15:47 +01:00
type_stage = forms.ChoiceField(
label="Type de stage",
2021-02-07 23:50:15 +01:00
choices=([("", "")] + list(TYPE_STAGE_OPTIONS)),
2021-02-07 23:15:47 +01:00
required=False,
)
niveau_scol = forms.ChoiceField(
label="Année d'étude",
2021-02-07 23:50:15 +01:00
choices=([("", "")] + list(NIVEAU_SCOL_OPTIONS)),
2021-02-07 23:15:47 +01:00
required=False,
)
type_lieu = forms.ChoiceField(
2021-02-07 23:50:15 +01:00
label="Type de lieu d'accueil",
choices=([("", "")] + list(TYPE_LIEU_OPTIONS)),
2021-02-07 23:15:47 +01:00
required=False,
)
tri = forms.ChoiceField(
2021-02-07 23:50:15 +01:00
label="Tri par",
choices=[("pertinence", "Pertinence"), ("-date_maj", "Dernière mise à jour")],
2021-02-07 23:15:47 +01:00
required=False,
initial="pertinence",
)
2017-06-17 00:34:11 +02:00
2017-06-16 00:17:25 +02:00
def cherche(**kwargs):
2017-06-17 00:34:11 +02:00
filtres = Q(public=True)
use_dsl = False
def field_relevant(field, test_string=True):
2021-02-07 23:15:47 +01:00
return (
field in kwargs
and kwargs[field] is not None
and ((not test_string) or kwargs[field].strip() != "")
)
2017-06-17 00:34:11 +02:00
if USE_ELASTICSEARCH:
dsl = StageDocument.search()
#
# Recherche libre AVEC ELASTICSEARCH
#
# Champ générique : recherche dans tous les champs
if field_relevant("generique"):
2021-02-07 23:15:47 +01:00
# print("Filtre generique", kwargs['generique'])
dsl = dsl.query(
2021-06-28 23:00:32 +02:00
"multi_match",
2021-02-07 23:15:47 +01:00
query=kwargs["generique"],
fuzziness="auto",
fields=[
"sujet^3",
"encadrants",
"type_stage",
"niveau_scol",
"structure",
2021-06-28 23:00:32 +02:00
"lieux.*^2",
"auteur.nom^2",
"thematiques^2",
2021-02-07 23:15:47 +01:00
"matieres",
],
)
use_dsl = True
# Sujet -> Recherche dan les noms de sujets et les thématiques
if field_relevant("sujet"):
2021-02-07 23:15:47 +01:00
dsl = dsl.query(
"multi_match",
query=kwargs["sujet"],
fields=["sujet^2", "thematiques", "matieres"],
fuzziness="auto",
)
use_dsl = True
# Contexte -> Encadrants, structure, lieu
if field_relevant("contexte"):
2021-02-07 23:15:47 +01:00
dsl = dsl.query(
"multi_match",
query=kwargs["contexte"],
fields=[
"encadrants",
"structure^2",
"lieux.nom",
"lieux.pays",
"lieux.ville",
],
fuzziness="auto",
)
use_dsl = True
else:
# Sans ElasticSearch, on active quand même une approximation de
# recherche en base de données
if field_relevant("generique"):
generique = kwargs["generique"]
2021-02-07 23:15:47 +01:00
filtres = (
Q(sujet__icontains=generique)
| Q(thematiques__name__icontains=generique)
| Q(matieres__nom__icontains=generique)
| Q(lieux__nom__icontains=generique)
)
2021-02-07 23:15:47 +01:00
# Autres champs -> non fonctionnels
if field_relevant("sujet") or field_relevant("contexte"):
raise NotImplementedError(
2021-02-07 23:15:47 +01:00
"ElasticSearch doit être activé pour ce type de recherche"
)
2017-06-17 00:34:11 +02:00
#
# Filtres directs db
#
2021-02-07 23:15:47 +01:00
2017-06-17 00:34:11 +02:00
# Dates
2021-02-07 23:15:47 +01:00
if field_relevant("avant_annee", False):
2021-08-29 14:00:27 +02:00
dte = date(min(2100, kwargs["avant_annee"]) + 1, 1, 1)
2017-06-17 00:34:11 +02:00
filtres &= Q(date_fin__lt=dte)
2021-02-07 23:15:47 +01:00
if field_relevant("apres_annee", False):
2021-08-29 14:00:27 +02:00
dte = date(max(2000, kwargs["apres_annee"]), 1, 1)
2017-06-17 00:34:11 +02:00
filtres &= Q(date_debut__gte=dte)
# Type de stage
2021-02-07 23:15:47 +01:00
if field_relevant("type_stage"):
2017-06-17 00:34:11 +02:00
filtres &= Q(type_stage=kwargs["type_stage"])
2021-02-07 23:15:47 +01:00
if field_relevant("niveau_scol"):
2017-06-20 00:24:48 +02:00
filtres &= Q(niveau_scol=kwargs["niveau_scol"])
2017-06-17 00:34:11 +02:00
# Type de lieu
2021-02-07 23:15:47 +01:00
if field_relevant("type_lieu"):
2017-06-17 00:34:11 +02:00
filtres &= Q(lieux__type_lieu=kwargs["type_lieu"])
2021-06-28 23:25:16 +02:00
# Tri
tri = "pertinence"
2017-06-17 00:34:11 +02:00
2021-02-07 23:15:47 +01:00
if field_relevant("tri") and kwargs["tri"] in ["-date_maj"]:
tri = kwargs["tri"]
2021-06-28 23:25:16 +02:00
if not use_dsl:
tri = "-date_maj"
# Application
resultat = Stage.objects.filter(filtres).distinct()
if USE_ELASTICSEARCH and use_dsl:
2021-06-28 23:29:58 +02:00
dsl_res = [s.meta.id for s in dsl.scan()]
2021-06-28 23:25:16 +02:00
resultat = resultat.filter(id__in=dsl_res)
if tri == "pertinence":
resultat = resultat.order_by(
2021-02-07 23:15:47 +01:00
Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(dsl_res)])
2021-06-28 23:25:16 +02:00
)
else:
resultat = resultat.order_by(tri)
2021-08-29 14:00:27 +02:00
else:
resultat = resultat.order_by(tri)
2017-06-17 17:41:52 +02:00
return resultat, tri
2021-02-07 23:15:47 +01:00
2017-06-16 00:17:25 +02:00
@login_required
2018-12-28 23:46:24 +01:00
@en_scolarite_required
2017-06-16 00:17:25 +02:00
def recherche(request):
form = SearchForm()
2021-02-07 23:15:47 +01:00
return render(request, "avisstage/recherche/recherche.html", {"form": form})
@login_required
2018-12-28 23:46:24 +01:00
@en_scolarite_required
def recherche_resultats(request):
2017-06-16 00:17:25 +02:00
stages = []
2021-02-07 23:15:47 +01:00
tri = ""
vue = "vue-liste"
lieux = []
stageids = []
2017-06-16 00:17:25 +02:00
if request.method == "GET":
form = SearchForm(request.GET)
if form.is_valid():
page = request.GET.get("page", 1)
search_args = form.cleaned_data
# Gestion du cache
2021-02-07 23:15:47 +01:00
lsearch_args = {
key: val
for key, val in search_args.items()
if val != "" and val is not None
}
2018-12-26 20:23:20 +01:00
cache_key = json.dumps(lsearch_args, sort_keys=True)
cached = cache.get(cache_key)
if cached is None:
# Requête effective
stages, tri = cherche(**search_args)
2021-02-07 23:15:47 +01:00
stageids = list(stages.values_list("id", flat=True))
lieux = [
[stageid, lieuid]
for (stageid, lieuid) in stages.values_list("id", "lieux")
if lieuid is not None
]
# Sauvegarde dans le cache
to_cache = {"stages": stageids, "lieux": lieux, "tri": tri}
cache.set(cache_key, to_cache, 600)
2018-12-26 20:23:20 +01:00
logger.info(cache_key)
else:
# Lecture du cache
stageids = cached["stages"]
lieux = cached["lieux"]
tri = cached["tri"]
2018-12-26 20:23:20 +01:00
logger.info("recherche en cache")
# Pagination
paginator = Paginator(stageids, 25)
try:
stageids = paginator.page(page)
except InvalidPage:
stageids = []
if cached is None:
2021-02-07 23:15:47 +01:00
stages = stages[
max(0, stageids.start_index() - 1) : stageids.end_index()
]
else:
2021-02-07 23:15:47 +01:00
orderer = Case(
*[When(pk=pk, then=pos) for pos, pk in enumerate(stageids)]
)
stages = Stage.objects.filter(id__in=stageids).order_by(orderer)
2021-02-07 23:15:47 +01:00
stages = stages.prefetch_related(
"lieux", "auteur", "matieres", "thematiques"
)
2017-06-16 00:17:25 +02:00
else:
form = SearchForm()
2017-06-17 17:41:52 +02:00
if stages:
2021-02-07 23:15:47 +01:00
vue = "vue-hybride"
# Version JSON pour recherche dynamique
if request.GET.get("format") == "json":
2021-02-07 23:15:47 +01:00
return JsonResponse(
{"stages": stages, "page": page, "num_pages": paginator.num_pages}
)
template_name = "avisstage/recherche/resultats.html"
if request.GET.get("format") == "raw":
2021-02-07 23:15:47 +01:00
template_name = "avisstage/recherche/stage_items.html"
return render(
request,
template_name,
{
"form": form,
"stages": stages,
"paginator": stageids,
"tri": tri,
"vue": vue,
"lieux": lieux,
"MAPBOX_API_KEY": settings.MAPBOX_API_KEY,
},
)
@login_required
2018-12-28 23:46:24 +01:00
@en_scolarite_required
def stage_items(request):
try:
2021-02-07 23:15:47 +01:00
stageids = [int(a) for a in request.GET.get("ids", "").split(";")]
except ValueError:
return HttpResponseBadRequest("Paramètre incorrect")
2021-02-07 23:15:47 +01:00
stages = Stage.objects.filter(id__in=stageids).prefetch_related(
"lieux", "auteur", "matieres", "thematiques"
)
return render(request, "avisstage/recherche/stage_items.html", {"stages": stages})