Modif gestion dates annees scolaire pour mieux gérer l'hémisphère sud.

This commit is contained in:
Emmanuel Viennet 2024-12-18 17:33:18 +01:00
parent b6344c27a8
commit bb99cd8aa6
6 changed files with 104 additions and 43 deletions

View File

@ -526,7 +526,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
return formsemestre.annee_scolaire() return formsemestre.annee_scolaire()
def annee_scolaire_str(self) -> str: def annee_scolaire_str(self) -> str:
"L'année scolaire, eg '2021 - 2022'" "L'année scolaire, eg '2021 - 2022' ou '2021' en hémisphère sud"
formsemestre = self.formsemestre_impair or self.formsemestre_pair formsemestre = self.formsemestre_impair or self.formsemestre_pair
return formsemestre.annee_scolaire_str().replace(" ", "") return formsemestre.annee_scolaire_str().replace(" ", "")

View File

@ -355,7 +355,9 @@ class ScoDocSiteConfig(models.ScoDocModel):
@classmethod @classmethod
def get_month_debut_periode2(cls) -> int: def get_month_debut_periode2(cls) -> int:
"""Mois de début de l'année scolaire.""" """Mois de début de la seconde période (semestre) de l'année.
Par défaut, 12 (décembre). Sera souvent juillet en hémisphère sud.
"""
return cls._get_int_field("month_debut_periode2", scu.MONTH_DEBUT_PERIODE2) return cls._get_int_field("month_debut_periode2", scu.MONTH_DEBUT_PERIODE2)
@classmethod @classmethod

View File

@ -699,18 +699,27 @@ class FormSemestre(models.ScoDocModel):
""" """
if self.semestre_id <= 0: if self.semestre_id <= 0:
return False # formations sans semestres return False # formations sans semestres
# août (8) en métropole, janvier (1) en hémisphère sud:
month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire()
if month_debut < 4:
# Hémisphère sud: utilise date début deuxième période
# typiquement juillet
pivot = ScoDocSiteConfig.get_month_debut_periode2()
return (
# impair commençaant en fin d'année civile
(self.semestre_id % 2 and self.date_debut.month >= pivot)
or
# pair commençant en début d'année civile
((not self.semestre_id % 2) and self.date_debut.month < pivot)
)
# Hémisphère nord
return ( return (
# impair # impair
( (self.semestre_id % 2 and self.date_debut.month < month_debut)
self.semestre_id % 2
and self.date_debut.month < scu.MONTH_DEBUT_ANNEE_SCOLAIRE
)
or or
# pair # pair
( ((not self.semestre_id % 2) and self.date_debut.month >= month_debut)
(not self.semestre_id % 2)
and self.date_debut.month >= scu.MONTH_DEBUT_ANNEE_SCOLAIRE
)
) )
@classmethod @classmethod
@ -719,8 +728,8 @@ class FormSemestre(models.ScoDocModel):
date_debut: datetime.date, date_debut: datetime.date,
year=False, year=False,
periode=None, periode=None,
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, mois_pivot_annee: int | None = None,
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, mois_pivot_periode: int | None = None,
) -> bool: ) -> bool:
"""Vrai si la date_debut est dans la période indiquée (1,2,0) """Vrai si la date_debut est dans la période indiquée (1,2,0)
du semestre `periode` de l'année scolaire indiquée du semestre `periode` de l'année scolaire indiquée
@ -734,6 +743,10 @@ class FormSemestre(models.ScoDocModel):
""" """
if not year: if not year:
year = scu.annee_scolaire() year = scu.annee_scolaire()
if mois_pivot_annee is None:
mois_pivot_annee = ScoDocSiteConfig.get_month_debut_annee_scolaire()
if mois_pivot_periode is None:
mois_pivot_periode = ScoDocSiteConfig.get_month_debut_periode2()
# n'utilise pas le jour pivot # n'utilise pas le jour pivot
jour_pivot_annee = jour_pivot_periode = 1 jour_pivot_annee = jour_pivot_periode = 1
# calcule l'année universitaire et la période # calcule l'année universitaire et la période
@ -758,10 +771,10 @@ class FormSemestre(models.ScoDocModel):
def comp_periode( def comp_periode(
cls, cls,
date_debut: datetime, date_debut: datetime,
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE, mois_pivot_annee: int | None = None,
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, mois_pivot_periode: int | None = None,
jour_pivot_annee=1, jour_pivot_annee: int = 1,
jour_pivot_periode=1, jour_pivot_periode: int = 1,
) -> tuple[int, int]: ) -> tuple[int, int]:
"""Calcule la session associée à un formsemestre commençant en date_debut """Calcule la session associée à un formsemestre commençant en date_debut
sous la forme (année, période) sous la forme (année, période)
@ -782,6 +795,10 @@ class FormSemestre(models.ScoDocModel):
pp < pa -----------------|-------------------|----------------> pp < pa -----------------|-------------------|---------------->
(A-1, P:1) pp (A-1, P:2) pa (A, P:1) (A-1, P:1) pp (A-1, P:2) pa (A, P:1)
""" """
if mois_pivot_annee is None:
mois_pivot_annee = ScoDocSiteConfig.get_month_debut_annee_scolaire()
if mois_pivot_periode is None:
mois_pivot_periode = ScoDocSiteConfig.get_month_debut_periode2()
pivot_annee = 100 * mois_pivot_annee + jour_pivot_annee pivot_annee = 100 * mois_pivot_annee + jour_pivot_annee
pivot_periode = 100 * mois_pivot_periode + jour_pivot_periode pivot_periode = 100 * mois_pivot_periode + jour_pivot_periode
pivot_sem = 100 * date_debut.month + date_debut.day pivot_sem = 100 * date_debut.month + date_debut.day
@ -930,7 +947,7 @@ class FormSemestre(models.ScoDocModel):
return scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month) return scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month)
def annee_scolaire_str(self): def annee_scolaire_str(self):
"2021 - 2022" """2021 - 2022 (ou "2021" si hémisphère sud)"""
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month) return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
def mois_debut(self) -> str: def mois_debut(self) -> str:

View File

@ -778,7 +778,7 @@ class CursusMasterLMD(TypeCursus):
TYPE_CURSUS = CodesCursus.MasterLMD TYPE_CURSUS = CodesCursus.MasterLMD
NAME = "Master LMD" NAME = "Master LMD"
NB_SEM = 4 NB_SEM = 4
COMPENSATION_UE = True # variabale inutilisée COMPENSATION_UE = True # variable inutilisée
UNUSED_CODES = set((ADC, ATT, ATB)) UNUSED_CODES = set((ADC, ATT, ATB))

View File

@ -1429,13 +1429,23 @@ def timedate_human_repr():
def annee_scolaire_repr(year, month): def annee_scolaire_repr(year, month):
"""representation de l'annee scolaire : '2009 - 2010' """Représentation de l'annee scolaire : '2009 - 2010'
à partir d'une date. à partir d'une date.
Dans l'hémisphère sud, l'année scolaire coincide avec l'année civile.
On considère que si le mois de début de l'année scolaire est au
premier trimestre (jan-fev-mar), alors l'affichage n'indique que l'année
en cours (eg "2024").
""" """
if month >= MONTH_DEBUT_ANNEE_SCOLAIRE: # apres le 1er aout from app.models.config import ScoDocSiteConfig
month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire()
if month_debut < 4:
# Hémisphère sud:
return str(year) if month >= month_debut else str(year - 1)
# Hémisphère nord
if month >= month_debut: # par ex. apres le 1er aout
return f"{year} - {year + 1}" return f"{year} - {year + 1}"
else: return f"{year - 1} - {year}"
return f"{year - 1} - {year}"
def annee_scolaire() -> int: def annee_scolaire() -> int:
@ -1450,10 +1460,12 @@ def annee_scolaire_debut(year, month) -> int:
Par défaut (hémisphère nord), l'année du mois de août Par défaut (hémisphère nord), l'année du mois de août
précédent la date indiquée. précédent la date indiquée.
""" """
if int(month) >= MONTH_DEBUT_ANNEE_SCOLAIRE: from app.models.config import ScoDocSiteConfig
month_debut = ScoDocSiteConfig.get_month_debut_annee_scolaire()
if int(month) >= month_debut:
return int(year) return int(year)
else: return int(year) - 1
return int(year) - 1
def date_debut_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime: def date_debut_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime:
@ -1461,11 +1473,15 @@ def date_debut_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime
Si annee_sco n'est pas spécifié, année courante Si annee_sco n'est pas spécifié, année courante
(par défaut, l'année scolaire en métropole commence le 1er aout) (par défaut, l'année scolaire en métropole commence le 1er aout)
""" """
from app.models.config import ScoDocSiteConfig
if annee_sco is None: if annee_sco is None:
annee_sco = annee_scolaire() annee_sco = annee_scolaire()
try: try:
return datetime.datetime( return datetime.datetime(
year=annee_sco, month=MONTH_DEBUT_ANNEE_SCOLAIRE, day=1 year=annee_sco,
month=ScoDocSiteConfig.get_month_debut_annee_scolaire(),
day=1,
) )
except ValueError as exc: except ValueError as exc:
raise ScoValueError("année scolaire invalide") from exc raise ScoValueError("année scolaire invalide") from exc
@ -1478,11 +1494,15 @@ def date_fin_annee_scolaire(annee_sco: int | None = None) -> datetime.datetime:
# on prend la date de début de l'année scolaire suivante, # on prend la date de début de l'année scolaire suivante,
# et on lui retire 1 jour. # et on lui retire 1 jour.
# On s'affranchit ainsi des problèmes de durées de mois. # On s'affranchit ainsi des problèmes de durées de mois.
from app.models.config import ScoDocSiteConfig
if annee_sco is None: if annee_sco is None:
annee_sco = annee_scolaire() annee_sco = annee_scolaire()
try: try:
return datetime.datetime( return datetime.datetime(
year=annee_sco + 1, month=MONTH_DEBUT_ANNEE_SCOLAIRE, day=1 year=annee_sco + 1,
month=ScoDocSiteConfig.get_month_debut_annee_scolaire(),
day=1,
) - datetime.timedelta(days=1) ) - datetime.timedelta(days=1)
except (TypeError, ValueError) as exc: except (TypeError, ValueError) as exc:
raise ScoValueError("année scolaire invalide") from exc raise ScoValueError("année scolaire invalide") from exc

View File

@ -3,7 +3,7 @@
"""Test Periodes """Test Periodes
Utiliser comme: Utiliser comme:
pytest tests/unit/test_periode.py pytest tests/unit/_test_periode.py
Calcule la session associée à un formsemestre sous la forme (année, période) Calcule la session associée à un formsemestre sous la forme (année, période)
année: première année de l'année scolaire année: première année de l'année scolaire
@ -32,8 +32,30 @@ import datetime
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc.sco_formsemestre import sem_in_semestre_scolaire from app.scodoc.sco_formsemestre import sem_in_semestre_scolaire
# Nota: pour accélerer (éviter de recrer l'app),
# on regroupe ces petits tests dans une seule fonction
def test_default():
def test_periode(test_client):
"Joue tous les tests de ce module"
_test_default()
_test_automne_nord()
_test_noel_nord()
_test_ete_nord()
_test_printemps_sud()
_test_automne_sud()
_test_noel_sud()
_test_ete_sud()
_test_nouvel_an_sud()
_test_nouvel_an_special_pp_before_pa()
_test_nouvel_ete_pp_before_pa()
_test_automne_special_pp_before_pa()
_test_sem_in_periode1_default()
_test_sem_in_periode2_default()
_test_sem_in_annee_default()
def _test_default():
# with default # with default
assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 1, 1)) assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 1, 1))
assert (2021, 2) == FormSemestre.comp_periode( assert (2021, 2) == FormSemestre.comp_periode(
@ -41,59 +63,59 @@ def test_default():
) )
def test_automne_nord(): def _test_automne_nord():
assert (2022, 1) == FormSemestre.comp_periode(datetime.datetime(2022, 9, 1)) assert (2022, 1) == FormSemestre.comp_periode(datetime.datetime(2022, 9, 1))
def test_noel_nord(): def _test_noel_nord():
assert (2022, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 12, 15)) assert (2022, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 12, 15))
def test_ete_nord(): def _test_ete_nord():
assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 7, 30)) assert (2021, 2) == FormSemestre.comp_periode(datetime.datetime(2022, 7, 30))
def test_printemps_sud(): def _test_printemps_sud():
assert (2022, 1) == FormSemestre.comp_periode( assert (2022, 1) == FormSemestre.comp_periode(
datetime.datetime(2022, 1, 1), 1, 1, 1, 8 datetime.datetime(2022, 1, 1), 1, 1, 1, 8
) )
def test_automne_sud(): def _test_automne_sud():
assert (2022, 2) == FormSemestre.comp_periode( assert (2022, 2) == FormSemestre.comp_periode(
datetime.datetime(2022, 8, 2), 1, 8, 1, 1 datetime.datetime(2022, 8, 2), 1, 8, 1, 1
) )
def test_noel_sud(): def _test_noel_sud():
assert (2022, 2) == FormSemestre.comp_periode( assert (2022, 2) == FormSemestre.comp_periode(
datetime.datetime(2022, 12, 30), 1, 8, 1, 1 datetime.datetime(2022, 12, 30), 1, 8, 1, 1
) )
def test_ete_sud(): def _test_ete_sud():
assert (2022, 1) == FormSemestre.comp_periode( assert (2022, 1) == FormSemestre.comp_periode(
datetime.datetime(2022, 7, 30), 1, 8, 1, 1 datetime.datetime(2022, 7, 30), 1, 8, 1, 1
) )
def test_nouvel_an_sud(): def _test_nouvel_an_sud():
assert (2021, 2) == FormSemestre.comp_periode( assert (2021, 2) == FormSemestre.comp_periode(
datetime.datetime(2022, 1, 1), 3, 8, 1, 1 datetime.datetime(2022, 1, 1), 3, 8, 1, 1
) )
def test_nouvel_an_special_pp_before_pa(): def _test_nouvel_an_special_pp_before_pa():
assert (2023, 1) == FormSemestre.comp_periode( assert (2023, 1) == FormSemestre.comp_periode(
datetime.datetime(2024, 1, 10), 8, 2, 1, 1 datetime.datetime(2024, 1, 10), 8, 2, 1, 1
) )
def test_nouvel_ete_pp_before_pa(): def _test_nouvel_ete_pp_before_pa():
assert (2023, 2) == FormSemestre.comp_periode(datetime.datetime(2024, 6, 1), 8, 2) assert (2023, 2) == FormSemestre.comp_periode(datetime.datetime(2024, 6, 1), 8, 2)
def test_automne_special_pp_before_pa(): def _test_automne_special_pp_before_pa():
assert (2024, 1) == FormSemestre.comp_periode(datetime.datetime(2024, 9, 20), 8, 2) assert (2024, 1) == FormSemestre.comp_periode(datetime.datetime(2024, 9, 20), 8, 2)
@ -105,7 +127,7 @@ sem_next_year = {"date_debut_iso": "2023-08-16"}
sem_prev_year = {"date_debut_iso": "2022-07-31"} sem_prev_year = {"date_debut_iso": "2022-07-31"}
def test_sem_in_periode1_default(): def _test_sem_in_periode1_default():
assert True is sem_in_semestre_scolaire(sem_automne, 2022, 1) assert True is sem_in_semestre_scolaire(sem_automne, 2022, 1)
assert False is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 1) assert False is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 1)
assert False is sem_in_semestre_scolaire(sem_printemps, 2022, 1) assert False is sem_in_semestre_scolaire(sem_printemps, 2022, 1)
@ -114,7 +136,7 @@ def test_sem_in_periode1_default():
assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1) assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1)
def test_sem_in_periode2_default(): def _test_sem_in_periode2_default():
assert False is sem_in_semestre_scolaire(sem_automne, 2022, 2) assert False is sem_in_semestre_scolaire(sem_automne, 2022, 2)
assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 2) assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022, 2)
assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 2) assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 2)
@ -123,7 +145,7 @@ def test_sem_in_periode2_default():
assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1) assert False is sem_in_semestre_scolaire(sem_prev_year, 2022, 1)
def test_sem_in_annee_default(): def _test_sem_in_annee_default():
assert True is sem_in_semestre_scolaire(sem_automne, 2022, 0) assert True is sem_in_semestre_scolaire(sem_automne, 2022, 0)
assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022) assert True is sem_in_semestre_scolaire(sem_nouvel_an, 2022)
assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 0) assert True is sem_in_semestre_scolaire(sem_printemps, 2022, 0)