forked from ScoDoc/ScoDoc
507 lines
22 KiB
Python
507 lines
22 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
##############################################################################
|
|
# Module "Avis de poursuite d'étude"
|
|
# conçu et développé par Cléo Baras (IUT de Grenoble)
|
|
##############################################################################
|
|
|
|
"""
|
|
Created on 17/01/2024
|
|
|
|
@author: barasc
|
|
"""
|
|
|
|
import app.pe.pe_tools as pe_tools
|
|
from app.models import FormSemestre, Identite
|
|
from app.pe.pe_tools import pe_print
|
|
from app.scodoc import (
|
|
sco_etud,
|
|
codes_cursus,
|
|
sco_formsemestre,
|
|
sco_formsemestre_inscriptions,
|
|
sco_report,
|
|
)
|
|
import datetime
|
|
|
|
|
|
class EtudiantsJuryPE:
|
|
"""Classe centralisant la gestion des étudiants à prendre en compte dans un jury de PE"""
|
|
|
|
def __init__(self):
|
|
""" """
|
|
|
|
"Les identités des étudiants traités pour le jury"
|
|
self.identites = {} # ex. ETUDINFO_DICT
|
|
"Les cursus (semestres suivis, abandons, ...) des étudiants"
|
|
self.cursus = {}
|
|
|
|
"Les etudids des étudiants à considérer au jury (ceux qui seront effectivement diplômés)"
|
|
self.etudiants_jury_ids = {}
|
|
"Les etudids des étudiants dont il faut calculer les moyennes/classements (même si d'éventuels abandons)"
|
|
self.etudiants_ids = {}
|
|
|
|
"Les formsemestres dont il faut calculer les moyennes par tag"
|
|
self.formsemestres_jury_ids = {}
|
|
|
|
def find_etudiants(self, annee_diplome: int, formation_id: int):
|
|
"""Liste des étudiants à prendre en compte dans le jury PE, en les recherchant
|
|
de manière automatique par rapport à leur année de diplomation ``annee_diplome``
|
|
dans la formation ``formation_id``.
|
|
|
|
Les données obtenues sont stockées dans les attributs de EtudiantsJuryPE.
|
|
|
|
Args:
|
|
annee_diplome: L'année de diplomation
|
|
formation_id: L'identifiant de la formation (inutilisé)
|
|
|
|
*Remarque* : ex: JuryPE.get_etudiants_in_jury()
|
|
"""
|
|
"Les cosemestres donnant lieu à même année de diplome"
|
|
cosemestres = pe_tools.get_cosemestres_diplomants(annee_diplome, None)
|
|
pe_tools.pe_print(
|
|
"1) Recherche des coSemestres -> %d trouvés" % len(cosemestres)
|
|
)
|
|
|
|
"""Les étudiants inscrits dans les co-semestres (ceux du jury mais aussi d'autres ayant été réorientés ou ayant abandonnés)"""
|
|
pe_tools.pe_print("2) Liste des étudiants dans les différents co-semestres")
|
|
self.etudiants_ids = get_etudiants_dans_semestres(cosemestres)
|
|
pe_tools.pe_print(
|
|
" => %d étudiants trouvés dans les cosemestres" % len(self.etudiants_ids)
|
|
)
|
|
|
|
"""Analyse des parcours étudiants pour déterminer leur année effective de diplome
|
|
avec prise en compte des redoublements, des abandons, ...."""
|
|
pe_tools.pe_print("3) Analyse des parcours individuels des étudiants")
|
|
|
|
no_etud = 0
|
|
for no_etud, etudid in enumerate(self.etudiants_ids):
|
|
"""L'identité de l'étudiant"""
|
|
identite = Identite.get_etud(etudid)
|
|
self.identites[etudid] = identite
|
|
|
|
"""L'analyse de son cursus"""
|
|
self.analyse_etat_etudiant(etudid, cosemestres)
|
|
|
|
"""L'analyse de son parcours pour atteindre chaque semestre de la formation"""
|
|
self.analyse_parcours_etudiant_dans_semestres(etudid)
|
|
|
|
if (no_etud + 1) % 10 == 0:
|
|
pe_tools.pe_print((no_etud + 1), " ", end="")
|
|
no_etud += 1
|
|
pe_tools.pe_print()
|
|
|
|
"""Les étudiants à prendre dans le diplôme, étudiants ayant abandonnés non compris"""
|
|
self.etudiants_jury_ids = self.get_etudiants(annee_diplome)
|
|
|
|
"""Les étudiants dont il faut calculer les moyennes"""
|
|
self.etudiants_ids = {etudid for etudid in self.cursus}
|
|
|
|
"""Les formsemestres (des étudiants) dont il faut calculer les moyennes"""
|
|
self.formsemestres_jury_ids = self.get_formsemestres_jury()
|
|
|
|
# Synthèse
|
|
pe_tools.pe_print(
|
|
f" => {len(self.etudiants_jury_ids)} étudiants à diplômer en {annee_diplome}"
|
|
)
|
|
nbre_abandons = len(self.etudiants_ids) - len(self.etudiants_ids)
|
|
pe_tools.pe_print(f" => {nbre_abandons} étudiants éliminer pour abandon")
|
|
pe_tools.pe_print(
|
|
f" => quelques étudiants futurs diplômés : "
|
|
+ ", ".join([str(etudid) for etudid in list(self.etudiants_jury_ids)[:10]])
|
|
)
|
|
pe_tools.pe_print(
|
|
f" => semestres dont il faut calculer les moyennes : "
|
|
+ ", ".join([str(fid) for fid in list(self.formsemestres_jury_ids)])
|
|
)
|
|
|
|
def get_etudiants(self, annee_diplome: int) -> dict[Identite]:
|
|
"""Identités des étudiants (sous forme d'un dictionnaire `{etudid: Identite(etudid)}`
|
|
qui vont être à traiter au jury PE pour
|
|
l'année de diplômation donnée et n'ayant ni été réorienté, ni abandonné.
|
|
|
|
Args:
|
|
annee_diplome: Année de diplomation visée pour le jury
|
|
|
|
Returns:
|
|
Un dictionnaire `{etudid: Identite(etudid)}`
|
|
"""
|
|
etudids = [
|
|
etudid
|
|
for etudid in self.cursus
|
|
if self.cursus[etudid]["diplome"] == annee_diplome and self.cursus[etudid]["abandon"]
|
|
]
|
|
etudiants = {etudid: self.identites[etudid] for etudid in etudids}
|
|
return etudiants
|
|
|
|
|
|
def analyse_etat_etudiant(
|
|
self, etudid: int, cosemestres: dict[int, FormSemestre]
|
|
):
|
|
"""Analyse le cursus d'un étudiant pouvant être :
|
|
|
|
* l'un de ceux sur lesquels le jury va statuer (année de diplômation du jury considéré)
|
|
* un étudiant qui ne sera pas considéré dans le jury mais qui a participé dans sa scolarité
|
|
à un (ou plusieurs) semestres communs aux étudiants du jury (et impactera les classements)
|
|
|
|
L'analyse consiste :
|
|
|
|
* à insérer une entrée dans ``self.cursus`` pour mémoriser son identité,
|
|
avec son nom, prénom, etc...
|
|
* à analyser son parcours, pour déterminer s'il n'a (ou non) abandonné l'IUT en cours de
|
|
route (cf. clé abandon)
|
|
|
|
Args:
|
|
etudid: L'etudid d'un étudiant, à ajouter à ceux traiter par le jury
|
|
cosemestres: Dictionnaire {fid: Formsemestre(fid)} donnant accès aux cosemestres
|
|
de même année de diplomation
|
|
"""
|
|
identite = Identite.get_etud(etudid)
|
|
|
|
"""Le cursus global de l'étudiant (restreint aux semestres APC)"""
|
|
semestres_etudiant = {
|
|
frmsem.formsemestre_id: frmsem
|
|
for frmsem in identite.get_formsemestres()
|
|
if frmsem.formation.is_apc()
|
|
}
|
|
|
|
self.cursus[etudid] = {
|
|
"etudid": etudid, # les infos sur l'étudiant
|
|
"etat_civil": identite.etat_civil, # Ajout à la table jury
|
|
"diplome": annee_diplome(identite), # Le date prévisionnelle de son diplôme
|
|
"formsemestres": semestres_etudiant, # les semestres de l'étudiant
|
|
"semestres": {},
|
|
"aggregats": {},
|
|
}
|
|
|
|
""" Est-il réorienté / démissionnaire ou a-t-il arrêté volontairement sa formation ?"""
|
|
self.cursus[etudid]["abandon"] = arret_de_formation(identite, cosemestres)
|
|
|
|
|
|
def analyse_parcours_etudiant_dans_semestres(self, etudid):
|
|
"""Structure les informations sur les semestres suivis par un
|
|
étudiant, pour identifier les semestres qui seront pris en compte lors de ses calculs
|
|
de moyennes PE.
|
|
|
|
La structure s'appuie sur les numéros de semestre: pour chaque Si, stocke :
|
|
* le (ou les) formsemestres de numéro i qu'a suivi un étudiant (2 si redoublant)
|
|
* le dernier semestre de numéro i qu'il a suivi (1 ou 0 si pas encore suivi)
|
|
|
|
Elle s'appuie également sur les aggrégats: pour chaque aggrégat (par ex, 3A=S1+S2+S3),
|
|
identifie les semestres que l'étudiant a suivi pour l'amener jusqu'au semestre terminal
|
|
de l'aggrégat. Ce parcours peut être :
|
|
* S1+S2+S1+S2+S3 si redoublement de la 1ère année
|
|
* S1+S2+(année de césure)+S3 si césure, ...
|
|
"""
|
|
semestres_etudiant = self.cursus[etudid]["formsemestres"]
|
|
|
|
for nom_sem in pe_tools.TOUS_LES_SEMESTRES:
|
|
i = int(nom_sem[1]) # le n° du semestre
|
|
semestres_i = {
|
|
fid: semestres_etudiant[fid]
|
|
for fid in semestres_etudiant
|
|
if semestres_etudiant[fid].semestre_id == i
|
|
} # les semestres de n°i de l'étudiant
|
|
self.cursus[etudid]["aggregats"][nom_sem] = semestres_i
|
|
self.cursus[etudid]["semestres"][nom_sem] = get_dernier_semestre(semestres_i)
|
|
|
|
|
|
"""Tri des semestres par aggrégat et par semestre terminal"""
|
|
for aggregat in pe_tools.TOUS_LES_AGGREGATS:
|
|
self.cursus[etudid][aggregat] = {}
|
|
"""L'aggrégat considéré (par ex: 3S), son nom de son semestre terminal (par ex: S3) et son numéro (par ex: 3)"""
|
|
noms_semestre_de_aggregat = pe_tools.PARCOURS[aggregat]["aggregat"]
|
|
nom_semestre_terminal = noms_semestre_de_aggregat[-1]
|
|
numero_semestre_terminal = int(nom_semestre_terminal[-1])
|
|
|
|
"""Les semestres terminaux de l'aggrégat"""
|
|
# formsemestres_terminal = self.cursus[etudid]["aggregats"][nom_semestre_terminal]
|
|
# dernier_formsemestre_terminal = get_dernier_semestre(formsemestres_terminal) # le dernier en date
|
|
dernier_formsemestre_terminal = self.cursus[etudid]["semestres"][nom_semestre_terminal]
|
|
|
|
# for formsem_id_term in formsemestres_terminal:
|
|
if dernier_formsemestre_terminal: # ne considérant que le dernier
|
|
formsem_id_term = list(dernier_formsemestre_terminal.keys())[0]
|
|
|
|
formsemestre_terminal = self.cursus[etudid]["formsemestres"][formsem_id_term]
|
|
|
|
"""Semestres de n° inférieur (pax ex: des S1, S2, S3 pour un S3 terminal) et qui lui sont antérieurs"""
|
|
semestres_aggreges = {}
|
|
for fid in self.cursus[etudid]["formsemestres"]:
|
|
semestre = self.cursus[etudid]["formsemestres"][fid]
|
|
if (
|
|
semestre.semestre_id <= numero_semestre_terminal
|
|
and semestre.date_fin <= formsemestre_terminal.date_fin
|
|
):
|
|
semestres_aggreges[fid] = semestre
|
|
|
|
self.cursus[etudid][aggregat][formsem_id_term] = semestres_aggreges
|
|
|
|
def get_formsemestres_terminaux_aggregat(self, aggregat: str):
|
|
"""Pour un aggrégat donné, ensemble des formsemestres terminaux possibles pour l'aggrégat (pour l'aggrégat '3S'
|
|
incluant S1+S2+S3, a pour semestre terminal S3). Ces formsemestres traduisent :
|
|
|
|
* les différents parcours des étudiants liés par exemple au choix de modalité (par ex: S1 FI + S2 FI + S3 FI
|
|
ou S1 FI + S2 FI + S3 UFA), en renvoyant les formsemestre_id du S3 FI et du S3 UFA.
|
|
* les éventuelles situations de redoublement (par ex pour 1 étudiant ayant redoublé sa 2ème année :
|
|
S1 + S2 + S3 (1ère session) et S1 + S2 + S3 + S4 + S3 (2ème session), en renvoyant les formsemestre_id du
|
|
S3 (1ère session) et du S3 (2ème session)
|
|
|
|
Args:
|
|
aggregat: L'aggrégat
|
|
|
|
Returns:
|
|
Un dictionnaire {fid: FormSemestre(fid)}
|
|
"""
|
|
formsemestres_terminaux = {}
|
|
for etudid in self.cursus:
|
|
"""Les formsemestre_id des semestres terminaux"""
|
|
fids = self.cursus[etudid][aggregat].keys()
|
|
"""Pour chaque identifiant de semestre terminal, récupère le formsemestre associé"""
|
|
for fid in fids:
|
|
if fid not in formsemestres_terminaux:
|
|
formsemestres_terminaux[fid] = self.cursus[etudid][aggregat][fid][
|
|
fid
|
|
]
|
|
return formsemestres_terminaux
|
|
|
|
def get_semestres_a_aggreger(self, aggregat: str, formsemestre_id_terminal: int):
|
|
"""Pour un aggrégat donné associé à un formsemestre terminal cible, renvoie l'ensemble des semestres à
|
|
prendre en compte dans l'aggrégat sous la forme d'un dictionnaire {fid: FormSemestre(fid)}.
|
|
|
|
Fusionne les cursus individuels des étudiants, dont le cursus correspond à l'aggrégat visé.
|
|
|
|
Args:
|
|
aggregat: Un aggrégat (par ex. 1A, 2A, 3S, 6S)
|
|
formsemestre_id_terminal: L'identifiant du formsemestre terminal de l'aggrégat, devant correspondre au
|
|
dernier semestre de l'aggrégat
|
|
"""
|
|
noms_semestres_aggreges = pe_tools.PARCOURS[aggregat]["aggregat"]
|
|
|
|
formsemestres = {}
|
|
for etudid in self.cursus:
|
|
cursus_etudiant = self.cursus[etudid][aggregat]
|
|
if formsemestre_id_terminal in cursus_etudiant:
|
|
formsemestres_etudiant = cursus_etudiant[formsemestre_id_terminal]
|
|
formsemestres = formsemestres | formsemestres_etudiant
|
|
return formsemestres
|
|
|
|
def get_formsemestres_jury(self, semestres_recherches=None):
|
|
"""Ayant connaissance des étudiants dont il faut calculer les moyennes pour
|
|
le jury PE (attribut `self.etudiant_ids) et de leur cursus,
|
|
renvoie un dictionnaire `{fid: FormSemestre(fid)}`
|
|
contenant l'ensemble des formsemestres de leur cursus, dont il faudra calculer
|
|
la moyenne. Les formsemestres sont limités à ceux indiqués dans ``semestres_recherches``.
|
|
|
|
Args:
|
|
semestres_recherches: Une liste ou une chaine de caractères parmi :
|
|
|
|
* None : pour obtenir tous les formsemestres du jury
|
|
* 'Si' : pour obtenir les semestres de n° i (par ex. 'S1')
|
|
* 'iA' : pour obtenir les semestres de l'année i (par ex. '1A' donne ['S1, 'S2'])
|
|
* '3S', '4S' : pour obtenir les combinaisons de semestres définies par les aggrégats
|
|
|
|
Returns:
|
|
Un dictionnaire de la forme {fid: FormSemestre(fid)}
|
|
|
|
Remarque:
|
|
Une liste de la forme `[ 'Si', 'iA' , ... ]` (combinant les formats précédents) est possible.
|
|
"""
|
|
if semestres_recherches is None:
|
|
"""Appel récursif pour obtenir tous les semestres (validants)"""
|
|
semestres = self.get_formsemestres_jury(pe_tools.AGGREGAT_DIPLOMANT)
|
|
return semestres
|
|
elif isinstance(semestres_recherches, list):
|
|
"""Appel récursif sur tous les éléments de la liste"""
|
|
semestres = {}
|
|
for elmt in semestres_recherches:
|
|
semestres_elmt = self.get_formsemestres_jury(elmt)
|
|
semestres = semestres | semestres_elmt
|
|
return semestres
|
|
elif (
|
|
isinstance(semestres_recherches, str)
|
|
and semestres_recherches in pe_tools.TOUS_LES_AGGREGATS
|
|
):
|
|
"""Cas d'un aggrégat avec appel récursif sur toutes les entrées de l'aggrégat"""
|
|
semestres = self.get_formsemestres_jury(
|
|
pe_tools.PARCOURS[semestres_recherches]["aggregat"]
|
|
)
|
|
return semestres
|
|
elif (
|
|
isinstance(semestres_recherches, str)
|
|
and semestres_recherches in pe_tools.TOUS_LES_SEMESTRES
|
|
):
|
|
"""semestres_recherches est un nom de semestre de type S1,
|
|
pour une recherche parmi les étudiants à prendre en compte
|
|
dans le jury (diplômé et redoublants non diplômé)
|
|
"""
|
|
nom_sem = semestres_recherches
|
|
semestres = {}
|
|
for etudid in self.etudiants_ids:
|
|
semestres = semestres | self.cursus[etudid]["aggregats"][nom_sem]
|
|
return semestres
|
|
else:
|
|
raise ValueError(
|
|
"Probleme de paramètres d'appel dans get_formsemestreids_du_jury"
|
|
)
|
|
|
|
|
|
def get_etudiants_dans_semestres(semestres: dict[FormSemestre]) -> set:
|
|
"""Ensemble d'identifiants des étudiants (identifiés via leur ``etudid``)
|
|
inscrits à l'un des semestres de la liste de ``semestres``.
|
|
|
|
Remarque : Les ``cosemestres`` sont généralement obtenus avec ``sco_formsemestre.do_formsemestre_list()``
|
|
|
|
Args:
|
|
semestres: Un dictionnaire {fid: Formsemestre(fid)} donnant un
|
|
ensemble d'identifiant de semestres
|
|
|
|
Returns:
|
|
Un ensemble d``etudid``
|
|
"""
|
|
|
|
etudiants_ids = set()
|
|
for fid, sem in semestres.items(): # pour chacun des semestres de la liste
|
|
etudiants_du_sem = {ins.etudid for ins in sem.inscriptions}
|
|
|
|
pe_print(f" --> {sem} : {len(etudiants_du_sem)} etudiants")
|
|
etudiants_ids = (
|
|
etudiants_ids | etudiants_du_sem
|
|
) # incluant la suppression des doublons
|
|
|
|
return etudiants_ids
|
|
|
|
|
|
def annee_diplome(identite: Identite) -> int:
|
|
"""L'année de diplôme prévue d'un étudiant en fonction de ses semestres
|
|
d'inscription (pour un BUT).
|
|
|
|
Args:
|
|
identite: L'identité d'un étudiant
|
|
|
|
Returns:
|
|
L'année prévue de sa diplômation
|
|
|
|
NOTE: Pourrait être déplacé dans app.models.etudiants.Identite
|
|
"""
|
|
formsemestres = identite.get_formsemestres()
|
|
if formsemestres:
|
|
return max(
|
|
[
|
|
pe_tools.get_annee_diplome_semestre(sem_base)
|
|
for sem_base in formsemestres
|
|
]
|
|
)
|
|
else:
|
|
return None
|
|
|
|
|
|
|
|
def arret_de_formation(identite: Identite, cosemestres: list[FormSemestre]) -> bool:
|
|
"""Détermine si un étudiant a arrêté sa formation. Il peut s'agir :
|
|
|
|
* d'une réorientation à l'initiative du jury de semestre ou d'une démission ; dans ce cas, utilise les
|
|
décisions prises dans les jury de semestres (code NAR pour réorienté & DEM pour démissionnaire)
|
|
|
|
* d'un arrêt volontaire : l'étudiant disparait des listes d'inscrit, sans
|
|
pour autant avoir été indiqué NAR ou DEM. Dans ce cas, recherche son dernier semestre validé et
|
|
regarde s'il n'existe pas parmi les semestres existants dans Scodoc un semestre postérieur
|
|
(en terme de date de début)
|
|
de n° au moins égal à celui de son dernier semestre valide dans lequel il aurait pu
|
|
s'inscrire mais ne l'a pas fait.
|
|
|
|
Args:
|
|
identite: L'identité d'un étudiant
|
|
cosemestres: Les semestres donnant lieu à diplômation (sans redoublement) en date du jury
|
|
|
|
Returns:
|
|
Est-il réorienté, démissionnaire ou a-t-il arrêté de son propre chef sa formation ?
|
|
|
|
TODO:: A reprendre avec l'accès aux résultats des jury prévu par Scodoc9
|
|
"""
|
|
etudid = identite.etudid
|
|
reponse = False
|
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
|
(code, parcours) = sco_report.get_code_cursus_etud(etud)
|
|
|
|
# Est-il réorienté ou démissionnaire ?
|
|
if (
|
|
len(codes_cursus.CODES_SEM_REO & set(parcours.values())) > 0
|
|
): # Eliminé car NAR apparait dans le parcours
|
|
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
|
|
pe_tools.pe_print(" -> à éliminer car réorienté (NAR)")
|
|
return True
|
|
|
|
if "DEM" in list(parcours.values()): # Eliminé car DEM
|
|
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
|
|
pe_tools.pe_print(" -> à éliminer car DEM")
|
|
return True
|
|
|
|
# A-t-il arrêté volontairement sa formation ?
|
|
dernier_formsemestre = identite.get_formsemestres()[0]
|
|
|
|
# Y-a-t-il des cosemestres dans lesquels il aurait pu s'incrire ?
|
|
formsestres_superieurs_possibles = []
|
|
for fid, sem in cosemestres.items(): # Les semestres ayant des inscrits
|
|
if (
|
|
sem.formsemestre_id != dernier_formsemestre.formsemestre_id
|
|
and sem.date_debut.year >= dernier_formsemestre.date_debut.year
|
|
and sem.semestre_id > dernier_formsemestre.semestre_id
|
|
): # date de debut des semestres possibles postérieur au dernier semestre de l'étudiant et de niveau plus élevé que le dernier semestre valide de l'étudiant
|
|
formsestres_superieurs_possibles.append(fid)
|
|
|
|
if len(formsestres_superieurs_possibles) > 0:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def get_dernier_semestre(semestres: dict[int, FormSemestre]):
|
|
"""Renvoie le dernier semestre en date d'un dictionnaire
|
|
de semestres de la forme {fid: FormSemestre(fid)}.
|
|
La date prise en compte est celle marquant la **fin** des semestres.
|
|
|
|
Args:
|
|
semestres: Un dictionnaire de semestres
|
|
|
|
Return:
|
|
Un dictionnaire {fid: FormSemestre(fid)} contenant le semestre le plus récent
|
|
"""
|
|
if semestres:
|
|
fid_dernier_semestre = list(semestres.keys())[0]
|
|
dernier_semestre = {fid_dernier_semestre: semestres[fid_dernier_semestre]}
|
|
for fid in semestres:
|
|
if (
|
|
semestres[fid].date_fin
|
|
> dernier_semestre[fid_dernier_semestre].date_fin
|
|
):
|
|
dernier_semestre = {fid: semestres[fid]}
|
|
fid_dernier_semestre = fid
|
|
return dernier_semestre
|
|
else:
|
|
return {}
|