mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 14:13:21 +00:00
Merge pull request #746 from ae-utbm/pedagogy
Use full text search in pedagogy uv search api
This commit is contained in:
commit
26c70aa071
@ -1,6 +1,8 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils import html
|
||||||
|
from haystack.query import SearchQuerySet
|
||||||
from ninja import FilterSchema, ModelSchema, Schema
|
from ninja import FilterSchema, ModelSchema, Schema
|
||||||
from pydantic import AliasPath, ConfigDict, Field, TypeAdapter
|
from pydantic import AliasPath, ConfigDict, Field, TypeAdapter
|
||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
@ -120,6 +122,27 @@ class UvFilterSchema(FilterSchema):
|
|||||||
language: str = "FR"
|
language: str = "FR"
|
||||||
department: set[str] | None = Field(None, q="department__in")
|
department: set[str] | None = Field(None, q="department__in")
|
||||||
|
|
||||||
|
def filter_search(self, value: str | None) -> Q:
|
||||||
|
"""Special filter for the search text.
|
||||||
|
|
||||||
|
It does a full text search if available.
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return Q()
|
||||||
|
|
||||||
|
if len(value) < 3 or (len(value) < 5 and any(c.isdigit() for c in value)):
|
||||||
|
# Likely to be an UV code
|
||||||
|
return Q(code__istartswith=value)
|
||||||
|
|
||||||
|
qs = list(
|
||||||
|
SearchQuerySet()
|
||||||
|
.models(UV)
|
||||||
|
.autocomplete(auto=html.escape(value))
|
||||||
|
.values_list("pk", flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Q(id__in=qs)
|
||||||
|
|
||||||
def filter_semester(self, value: set[str] | None) -> Q:
|
def filter_semester(self, value: set[str] | None) -> Q:
|
||||||
"""Special filter for the semester.
|
"""Special filter for the semester.
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import json
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.testcases import call_command
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
@ -21,16 +22,31 @@ class TestUVSearch(TestCase):
|
|||||||
uv_recipe = Recipe(UV, author=cls.root)
|
uv_recipe = Recipe(UV, author=cls.root)
|
||||||
uvs = [
|
uvs = [
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="AP4A", credit_type="CS", semester="AUTUMN", department="GI"
|
code="AP4A",
|
||||||
|
credit_type="CS",
|
||||||
|
semester="AUTUMN",
|
||||||
|
department="GI",
|
||||||
|
manager="francky",
|
||||||
|
title="Programmation Orientée Objet: Concepts fondamentaux et mise en pratique avec le langage C++",
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="MT01", credit_type="CS", semester="AUTUMN", department="TC"
|
code="MT01",
|
||||||
|
credit_type="CS",
|
||||||
|
semester="AUTUMN",
|
||||||
|
department="TC",
|
||||||
|
manager="ben",
|
||||||
|
title="Intégration1. Algèbre linéaire - Fonctions de deux variables",
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="PHYS11", credit_type="CS", semester="AUTUMN", department="TC"
|
code="PHYS11", credit_type="CS", semester="AUTUMN", department="TC"
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="TNEV", credit_type="TM", semester="SPRING", department="TC"
|
code="TNEV",
|
||||||
|
credit_type="TM",
|
||||||
|
semester="SPRING",
|
||||||
|
department="TC",
|
||||||
|
manager="moss",
|
||||||
|
title="tnetennba",
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="MT10", credit_type="TM", semester="AUTUMN", department="IMSI"
|
code="MT10", credit_type="TM", semester="AUTUMN", department="IMSI"
|
||||||
@ -40,9 +56,11 @@ class TestUVSearch(TestCase):
|
|||||||
credit_type="TM",
|
credit_type="TM",
|
||||||
semester="AUTUMN_AND_SPRING",
|
semester="AUTUMN_AND_SPRING",
|
||||||
department="GI",
|
department="GI",
|
||||||
|
manager="francky",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
UV.objects.bulk_create(uvs)
|
UV.objects.bulk_create(uvs)
|
||||||
|
call_command("update_index")
|
||||||
|
|
||||||
def test_permissions(self):
|
def test_permissions(self):
|
||||||
# Test with anonymous user
|
# Test with anonymous user
|
||||||
@ -92,14 +110,22 @@ class TestUVSearch(TestCase):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_search_by_code(self):
|
def test_search_by_text(self):
|
||||||
self.client.force_login(self.root)
|
self.client.force_login(self.root)
|
||||||
res = self.client.get(self.url + "?search=MT")
|
for query, expected in (
|
||||||
|
# UV code search case insensitive
|
||||||
|
("m", {"MT01", "MT10"}),
|
||||||
|
("M", {"MT01", "MT10"}),
|
||||||
|
("mt", {"MT01", "MT10"}),
|
||||||
|
("MT", {"MT01", "MT10"}),
|
||||||
|
("algèbre", {"MT01"}), # Title search case insensitive
|
||||||
|
# Manager search
|
||||||
|
("moss", {"TNEV"}),
|
||||||
|
("francky", {"DA50", "AP4A"}),
|
||||||
|
):
|
||||||
|
res = self.client.get(self.url + f"?search={query}")
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
assert {uv["code"] for uv in json.loads(res.content)["results"]} == {
|
assert {uv["code"] for uv in json.loads(res.content)["results"]} == expected
|
||||||
"MT01",
|
|
||||||
"MT10",
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_search_by_credit_type(self):
|
def test_search_by_credit_type(self):
|
||||||
self.client.force_login(self.root)
|
self.client.force_login(self.root)
|
||||||
|
Loading…
Reference in New Issue
Block a user