Custom client for UTBM UV API calls

This commit is contained in:
imperosol 2025-01-21 13:57:18 +01:00
parent cddc67f097
commit 27e3781653
3 changed files with 57 additions and 26 deletions

View File

@ -10,13 +10,13 @@ from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseS
from core.auth.api_permissions import HasPerm from core.auth.api_permissions import HasPerm
from pedagogy.models import UV from pedagogy.models import UV
from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema
from pedagogy.utbm_api import find_uv from pedagogy.utbm_api import UtbmApiClient
@api_controller("/uv") @api_controller("/uv")
class UvController(ControllerBase): class UvController(ControllerBase):
@route.get( @route.get(
"/{year}/{code}", "/{code}",
permissions=[ permissions=[
# this route will almost always be called in the context # this route will almost always be called in the context
# of a UV creation/edition # of a UV creation/edition
@ -26,10 +26,14 @@ class UvController(ControllerBase):
response=UvSchema, response=UvSchema,
) )
def fetch_from_utbm_api( def fetch_from_utbm_api(
self, year: Annotated[int, Ge(2010)], code: str, lang: Query[str] = "fr" self,
code: str,
lang: Query[str] = "fr",
year: Query[Annotated[int, Ge(2010)] | None] = None,
): ):
"""Fetch UV data from the UTBM API and returns it after some parsing.""" """Fetch UV data from the UTBM API and returns it after some parsing."""
res = find_uv(lang, year, code) with UtbmApiClient() as client:
res = client.find_uv(lang, code, year)
if res is None: if res is None:
raise NotFound raise NotFound
return res return res

View File

@ -46,12 +46,7 @@
const codeInput = document.querySelector('input[name="code"]') const codeInput = document.querySelector('input[name="code"]')
autofillBtn.addEventListener('click', () => { autofillBtn.addEventListener('click', () => {
const today = new Date() const url = `/api/uv/${codeInput.value}`;
let year = today.getFullYear()
if (today.getMonth() < 7) { // student year starts in september
year--
}
const url = `/api/uv/${year}/${codeInput.value}`;
deleteQuickNotifs() deleteQuickNotifs()
$.ajax({ $.ajax({

View File

@ -2,24 +2,56 @@
import requests import requests
from django.conf import settings from django.conf import settings
from django.utils.functional import cached_property
from pedagogy.schemas import ShortUvList, UtbmFullUvSchema, UtbmShortUvSchema, UvSchema from pedagogy.schemas import ShortUvList, UtbmFullUvSchema, UtbmShortUvSchema, UvSchema
def find_uv(lang, year, code) -> UvSchema | None: class UtbmApiClient(requests.Session):
"""A wrapper around `requests.Session` to perform requests to the UTBM UV API."""
BASE_URL = settings.SITH_PEDAGOGY_UTBM_API
_cache = {}
@cached_property
def current_year(self) -> int:
"""Fetch from the API the latest existing year"""
url = f"{self.BASE_URL}/guides/fr"
response = self.get(url)
return response.json()[-1]["annee"]
def fetch_short_uvs(
self, lang: str = "fr", year: int | None = None
) -> list[UtbmShortUvSchema]:
"""Get the list of UVs in their short format from the UTBM API"""
if year is None:
year = self.current_year
if "short_uvs" not in self._cache:
self._cache["short_uvs"] = {}
if lang not in self._cache["short_uvs"]:
self._cache["short_uvs"][lang] = {}
if year not in self._cache["short_uvs"][lang]:
url = f"{self.BASE_URL}/uvs/{lang}/{year}"
response = self.get(url)
uvs = ShortUvList.validate_json(response.content)
self._cache["short_uvs"][lang][year] = uvs
return self._cache["short_uvs"][lang][year]
def find_uv(self, lang: str, code: str, year: int | None = None) -> UvSchema | None:
"""Find an UV from the UTBM API.""" """Find an UV from the UTBM API."""
# query the UV list # query the UV list
base_url = settings.SITH_PEDAGOGY_UTBM_API if not year:
uvs_url = f"{base_url}/uvs/{lang}/{year}" year = self.current_year
response = requests.get(uvs_url) # the UTBM API has no way to fetch a single short uv,
uvs: list[UtbmShortUvSchema] = ShortUvList.validate_json(response.content) # and short uvs contain infos that we need and are not
# in the full uv schema, so we must fetch everything.
short_uv = next((uv for uv in uvs if uv.code == code), None) short_uvs = self.fetch_short_uvs(lang, year)
short_uv = next((uv for uv in short_uvs if uv.code == code), None)
if short_uv is None: if short_uv is None:
return None return None
# get detailed information about the UV # get detailed information about the UV
uv_url = f"{base_url}/uv/{lang}/{year}/{code}/{short_uv.code_formation}" uv_url = f"{self.BASE_URL}/uv/{lang}/{year}/{code}/{short_uv.code_formation}"
response = requests.get(uv_url) response = requests.get(uv_url)
full_uv = UtbmFullUvSchema.model_validate_json(response.content) full_uv = UtbmFullUvSchema.model_validate_json(response.content)
return make_clean_uv(short_uv, full_uv) return make_clean_uv(short_uv, full_uv)