Merge pull request #746 from ae-utbm/pedagogy

Use full text search in pedagogy uv search api
This commit is contained in:
thomas girod 2024-07-29 18:12:43 +02:00 committed by GitHub
commit 26c70aa071
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 10 deletions

View File

@ -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.

View File

@ -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)