diff --git a/pedagogy/api.py b/pedagogy/api.py index e8d34351..ce1fdde0 100644 --- a/pedagogy/api.py +++ b/pedagogy/api.py @@ -10,13 +10,13 @@ from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseS from core.auth.api_permissions import HasPerm from pedagogy.models import UV from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema -from pedagogy.utbm_api import find_uv +from pedagogy.utbm_api import UtbmApiClient @api_controller("/uv") class UvController(ControllerBase): @route.get( - "/{year}/{code}", + "/{code}", permissions=[ # this route will almost always be called in the context # of a UV creation/edition @@ -26,10 +26,14 @@ class UvController(ControllerBase): response=UvSchema, ) 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.""" - res = find_uv(lang, year, code) + with UtbmApiClient() as client: + res = client.find_uv(lang, code, year) if res is None: raise NotFound return res diff --git a/pedagogy/templates/pedagogy/uv_edit.jinja b/pedagogy/templates/pedagogy/uv_edit.jinja index 7ab54105..b4869e14 100644 --- a/pedagogy/templates/pedagogy/uv_edit.jinja +++ b/pedagogy/templates/pedagogy/uv_edit.jinja @@ -46,12 +46,7 @@ const codeInput = document.querySelector('input[name="code"]') autofillBtn.addEventListener('click', () => { - const today = new Date() - let year = today.getFullYear() - if (today.getMonth() < 7) { // student year starts in september - year-- - } - const url = `/api/uv/${year}/${codeInput.value}`; + const url = `/api/uv/${codeInput.value}`; deleteQuickNotifs() $.ajax({ @@ -70,7 +65,7 @@ .filter(([elem, _]) => !!elem) // skip non-existing DOM elements .forEach(([elem, val]) => { // write the value in the form field if (elem.tagName === 'TEXTAREA') { - // MD editor text input + // MD editor text input elem.parentNode.querySelector('.CodeMirror').CodeMirror.setValue(val); } else { elem.value = val; diff --git a/pedagogy/utbm_api.py b/pedagogy/utbm_api.py index 700c22f3..f0d1377d 100644 --- a/pedagogy/utbm_api.py +++ b/pedagogy/utbm_api.py @@ -2,27 +2,59 @@ import requests from django.conf import settings +from django.utils.functional import cached_property from pedagogy.schemas import ShortUvList, UtbmFullUvSchema, UtbmShortUvSchema, UvSchema -def find_uv(lang, year, code) -> UvSchema | None: - """Find an UV from the UTBM API.""" - # query the UV list - base_url = settings.SITH_PEDAGOGY_UTBM_API - uvs_url = f"{base_url}/uvs/{lang}/{year}" - response = requests.get(uvs_url) - uvs: list[UtbmShortUvSchema] = ShortUvList.validate_json(response.content) +class UtbmApiClient(requests.Session): + """A wrapper around `requests.Session` to perform requests to the UTBM UV API.""" - short_uv = next((uv for uv in uvs if uv.code == code), None) - if short_uv is None: - return None + BASE_URL = settings.SITH_PEDAGOGY_UTBM_API + _cache = {} - # get detailed information about the UV - uv_url = f"{base_url}/uv/{lang}/{year}/{code}/{short_uv.code_formation}" - response = requests.get(uv_url) - full_uv = UtbmFullUvSchema.model_validate_json(response.content) - return make_clean_uv(short_uv, full_uv) + @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.""" + # query the UV list + if not year: + year = self.current_year + # the UTBM API has no way to fetch a single short uv, + # and short uvs contain infos that we need and are not + # in the full uv schema, so we must fetch everything. + 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: + return None + + # get detailed information about the UV + uv_url = f"{self.BASE_URL}/uv/{lang}/{year}/{code}/{short_uv.code_formation}" + response = requests.get(uv_url) + full_uv = UtbmFullUvSchema.model_validate_json(response.content) + return make_clean_uv(short_uv, full_uv) def make_clean_uv(short_uv: UtbmShortUvSchema, full_uv: UtbmFullUvSchema) -> UvSchema: