This commit is contained in:
Emmanuel Viennet 2023-01-12 09:55:15 -03:00
commit e2141b6505
196 changed files with 2275 additions and 716 deletions

View File

@ -502,12 +502,10 @@ def clear_scodoc_cache():
# --------- Logging # --------- Logging
def log(msg: str, silent_test=True): def log(msg: str):
"""log a message. """log a message.
If Flask app, use configured logger, else stderr. If Flask app, use configured logger, else stderr.
""" """
if silent_test and current_app and current_app.config["TESTING"]:
return
try: try:
dept = getattr(g, "scodoc_dept", "") dept = getattr(g, "scodoc_dept", "")
msg = f" ({dept}) {msg}" msg = f" ({dept}) {msg}"

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
"""ScoDoc 9 API : Absences """ScoDoc 9 API : Absences

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
"""ScoDoc 9 API : outils """ScoDoc 9 API : outils

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -361,7 +361,7 @@ class BulletinBUT:
"formsemestre_id": formsemestre.id, "formsemestre_id": formsemestre.id,
"etat_inscription": etat_inscription, "etat_inscription": etat_inscription,
"options": sco_preferences.bulletin_option_affichage( "options": sco_preferences.bulletin_option_affichage(
formsemestre.id, self.prefs formsemestre, self.prefs
), ),
} }
if not published: if not published:
@ -465,6 +465,7 @@ class BulletinBUT:
"ressources": {}, "ressources": {},
"saes": {}, "saes": {},
"ues": {}, "ues": {},
"ues_capitalisees": {},
} }
) )

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
from xml.etree import ElementTree from xml.etree import ElementTree

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -91,9 +91,15 @@ from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours as sco_codes from app.scodoc import sco_codes_parcours as sco_codes
from app.scodoc.sco_codes_parcours import CODES_UE_VALIDES, RED, UE_STANDARD from app.scodoc.sco_codes_parcours import (
BUT_CODES_ORDERED,
CODES_RCUE_VALIDES,
CODES_UE_VALIDES,
RED,
UE_STANDARD,
)
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoException, ScoValueError from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
class NoRCUEError(ScoValueError): class NoRCUEError(ScoValueError):
@ -170,7 +176,7 @@ class DecisionsProposees:
def __repr__(self) -> str: def __repr__(self) -> str:
return f"""<{self.__class__.__name__} valid={self.code_valide return f"""<{self.__class__.__name__} valid={self.code_valide
} codes={self.codes} explanation={self.explanation}""" } codes={self.codes} explanation={self.explanation}>"""
class DecisionsProposeesAnnee(DecisionsProposees): class DecisionsProposeesAnnee(DecisionsProposees):
@ -205,6 +211,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
formsemestre: FormSemestre, formsemestre: FormSemestre,
): ):
assert formsemestre.formation.is_apc() assert formsemestre.formation.is_apc()
if formsemestre.formation.referentiel_competence is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
super().__init__(etud=etud) super().__init__(etud=etud)
self.formsemestre = formsemestre self.formsemestre = formsemestre
"le formsemestre utilisé pour construire ce deca" "le formsemestre utilisé pour construire ce deca"
@ -348,8 +356,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
) )
"vrai si l'année est réussie, tous niveaux validables ou validés par le jury" "vrai si l'année est réussie, tous niveaux validables ou validés par le jury"
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2) self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
"Peut passer si plus de la moitié validables et tous > 8" "Vrai si plus de la moitié des RCUE validables"
self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0) self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
"Vrai si peut passer dans l'année BUT suivante: plus de la moitié validables et tous > 8"
# XXX TODO ajouter condition pour passage en S5 # XXX TODO ajouter condition pour passage en S5
# Enfin calcule les codes des UE: # Enfin calcule les codes des UE:
@ -362,7 +371,6 @@ class DecisionsProposeesAnnee(DecisionsProposees):
"s" if plural else ""} sur {self.nb_competences}""" "s" if plural else ""} sur {self.nb_competences}"""
if self.admis: if self.admis:
self.codes = [sco_codes.ADM] + self.codes self.codes = [sco_codes.ADM] + self.codes
self.explanation = expl_rcues
# elif not self.jury_annuel: # elif not self.jury_annuel:
# self.codes = [] # pas de décision annuelle sur semestres impairs # self.codes = [] # pas de décision annuelle sur semestres impairs
elif self.inscription_etat != scu.INSCRIT: elif self.inscription_etat != scu.INSCRIT:
@ -378,9 +386,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.ABL, sco_codes.ABL,
sco_codes.EXCLU, sco_codes.EXCLU,
] ]
expl_rcues = ""
elif self.passage_de_droit: elif self.passage_de_droit:
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
self.explanation = expl_rcues
elif self.valide_moitie_rcue: # mais au moins 1 rcue insuffisante elif self.valide_moitie_rcue: # mais au moins 1 rcue insuffisante
self.codes = [ self.codes = [
sco_codes.RED, sco_codes.RED,
@ -388,7 +396,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.PAS1NCI, sco_codes.PAS1NCI,
sco_codes.ADJ, sco_codes.ADJ,
] + self.codes ] + self.codes
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8" expl_rcues += f" et {self.nb_rcues_under_8} < 8"
else: else:
self.codes = [ self.codes = [
sco_codes.RED, sco_codes.RED,
@ -397,17 +405,21 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.ADJ, sco_codes.ADJ,
sco_codes.PASD, # voir #488 (discutable, conventions locales) sco_codes.PASD, # voir #488 (discutable, conventions locales)
] + self.codes ] + self.codes
self.explanation = ( expl_rcues += f""" et {self.nb_rcues_under_8} niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
expl_rcues
+ f""" et {self.nb_rcues_under_8}
niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
)
# Si l'un des semestres est extérieur, propose ADM # Si l'un des semestres est extérieur, propose ADM
if ( if (
self.formsemestre_impair and self.formsemestre_impair.modalite == "EXT" self.formsemestre_impair and self.formsemestre_impair.modalite == "EXT"
) or (self.formsemestre_pair and self.formsemestre_pair.modalite == "EXT"): ) or (self.formsemestre_pair and self.formsemestre_pair.modalite == "EXT"):
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
self.explanation = f"<div>{expl_rcues}</div>"
messages = self.descr_pb_coherence()
if messages:
self.explanation += (
'<div class="warning">'
+ '</div><div class="warning">'.join(messages)
+ "</div>"
)
# #
def infos(self) -> str: def infos(self) -> str:
@ -581,9 +593,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]: def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
"""Pour chaque niveau de compétence de cette année, construit """Pour chaque niveau de compétence de cette année, construit
le DecisionsProposeesRCUE, le DecisionsProposeesRCUE, ou None s'il n'y en a pas
ou None s'il n'y en a pas
(ne devrait pas arriver car compute_rcues_annee vérifie déjà cela). (ne devrait pas arriver car compute_rcues_annee vérifie déjà cela).
Appelé à la construction du deca, donc avant décisions manuelles.
Return: { niveau_id : DecisionsProposeesRCUE } Return: { niveau_id : DecisionsProposeesRCUE }
""" """
# Retrouve le RCUE associé à chaque niveau # Retrouve le RCUE associé à chaque niveau
@ -630,6 +643,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def record_form(self, form: dict): def record_form(self, form: dict):
"""Enregistre les codes de jury en base """Enregistre les codes de jury en base
à partir d'un dict représentant le formulaire jury BUT:
form dict: form dict:
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896 - 'code_ue_1896' : 'AJ' code pour l'UE id 1896
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6 - 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
@ -639,7 +653,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
et qu'il n'y en a pas déjà, enregistre ceux par défaut. et qu'il n'y en a pas déjà, enregistre ceux par défaut.
""" """
log("jury_but.DecisionsProposeesAnnee.record_form") log("jury_but.DecisionsProposeesAnnee.record_form")
with sco_cache.DeferredSemCacheManager(): code_annee = None
codes_rcues = [] # [ (dec_rcue, code), ... ]
codes_ues = [] # [ (dec_ue, code), ... ]
for key in form: for key in form:
code = form[key] code = form[key]
# Codes d'UE # Codes d'UE
@ -649,7 +665,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
dec_ue = self.decisions_ues.get(ue_id) dec_ue = self.decisions_ues.get(ue_id)
if not dec_ue: if not dec_ue:
raise ScoValueError(f"UE invalide ue_id={ue_id}") raise ScoValueError(f"UE invalide ue_id={ue_id}")
dec_ue.record(code) codes_ues.append((dec_ue, code))
else: else:
# Codes de RCUE # Codes de RCUE
m = re.match(r"^code_rcue_(\d+)$", key) m = re.match(r"^code_rcue_(\d+)$", key)
@ -658,12 +674,20 @@ class DecisionsProposeesAnnee(DecisionsProposees):
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id) dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
if not dec_rcue: if not dec_rcue:
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}") raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
dec_rcue.record(code) codes_rcues.append((dec_rcue, code))
elif key == "code_annee": elif key == "code_annee":
# Code annuel # Code annuel
self.record(code) code_annee = code
with sco_cache.DeferredSemCacheManager():
# Enregistre les codes, dans l'ordre UE, RCUE, Année
for dec_ue, code in codes_ues:
dec_ue.record(code)
for dec_rcue, code in codes_rcues:
dec_rcue.record(code)
self.record(code_annee)
self.record_all() self.record_all()
db.session.commit() db.session.commit()
def record(self, code: str, no_overwrite=False): def record(self, code: str, no_overwrite=False):
@ -682,7 +706,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
return # no change return # no change
if self.validation: if self.validation:
db.session.delete(self.validation) db.session.delete(self.validation)
db.session.flush() db.session.commit()
if code is None: if code is None:
self.validation = None self.validation = None
else: else:
@ -693,12 +717,15 @@ class DecisionsProposeesAnnee(DecisionsProposees):
annee_scolaire=self.annee_scolaire(), annee_scolaire=self.annee_scolaire(),
code=code, code=code,
) )
db.session.add(self.validation)
db.session.commit()
log(f"Recording {self}: {code}")
Scolog.logdb( Scolog.logdb(
method="jury_but", method="jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation année BUT{self.annee_but}: {code}", msg=f"Validation année BUT{self.annee_but}: {code}",
) )
db.session.add(self.validation)
# --- Autorisation d'inscription dans semestre suivant ? # --- Autorisation d'inscription dans semestre suivant ?
if self.formsemestre_pair is not None: if self.formsemestre_pair is not None:
if code is None: if code is None:
@ -729,29 +756,49 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if self.formsemestre_pair is not None: if self.formsemestre_pair is not None:
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id) sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
def record_all(self): def record_all(self, no_overwrite: bool = True):
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire, """Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
et sont donc en mode "automatique" et sont donc en mode "automatique".
- Si "à cheval", ne modifie pas les codes UE de l'année scolaire précédente.
- Pour les RCUE: n'enregistre que si la nouvelle décision est plus favorable que l'ancienne.
""" """
decisions = ( # Toujours valider dans l'ordre UE, RCUE, Année:
list(self.decisions_ues.values()) annee_scolaire = self.formsemestre.annee_scolaire()
+ list(self.decisions_rcue_by_niveau.values()) # UEs
+ [self] for dec_ue in self.decisions_ues.values():
) if (
for dec in decisions: not dec_ue.recorded
if not dec.recorded: ) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
# rappel: le code par défaut est en tête # rappel: le code par défaut est en tête
code = dec.codes[0] if dec.codes else None code = dec_ue.codes[0] if dec_ue.codes else None
# enregistre le code jury seulement s'il n'y a pas déjà de code # enregistre le code jury seulement s'il n'y a pas déjà de code
dec.record(code, no_overwrite=True) # (no_overwrite=True) sauf en mode test yaml
dec_ue.record(code, no_overwrite=no_overwrite)
# RCUE : enregistre seulement si pas déjà validé "mieux"
for dec_rcue in self.decisions_rcue_by_niveau.values():
code = dec_rcue.codes[0] if dec_rcue.codes else None
if (not dec_rcue.recorded) and (
(not dec_rcue.validation)
or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0)
< BUT_CODES_ORDERED.get(code, 0)
):
dec_rcue.record(code, no_overwrite=no_overwrite)
# Année:
if not self.recorded:
# rappel: le code par défaut est en tête
code = self.codes[0] if self.codes else None
# enregistre le code jury seulement s'il n'y a pas déjà de code
# (no_overwrite=True) sauf en mode test yaml
self.record(code, no_overwrite=no_overwrite)
def erase(self, only_one_sem=False): def erase(self, only_one_sem=False):
"""Efface les décisions de jury de cet étudiant """Efface les décisions de jury de cet étudiant
pour cette année: décisions d'UE, de RCUE, d'année, pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises. et autorisations d'inscription émises.
Efface même si étudiant DEM ou DEF. Efface même si étudiant DEM ou DEF.
Si à cheval, n'efface que pour le semestre d'origine du deca.
""" """
if only_one_sem: if only_one_sem or self.a_cheval:
# N'efface que les autorisations venant de ce semestre, # N'efface que les autorisations venant de ce semestre,
# et les validations de ses UEs # et les validations de ses UEs
ScolarAutorisationInscription.delete_autorisation_etud( ScolarAutorisationInscription.delete_autorisation_etud(
@ -779,13 +826,23 @@ class DecisionsProposeesAnnee(DecisionsProposees):
ordre=self.annee_but, ordre=self.annee_but,
) )
for validation in validations: for validation in validations:
db.session.delete(validation)
Scolog.logdb( Scolog.logdb(
"jury_but", "jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation année BUT{self.annee_but}: effacée", msg=f"Validation année BUT{self.annee_but}: effacée",
) )
# Efface éventuelles validations de semestre
# (en principe inutilisées en BUT)
# et autres UEs (en cas de changement d'architecture de formation depuis le jury ?)
#
for validation in ScolarFormSemestreValidation.query.filter_by(
etudid=self.etud.id, formsemestre_id=self.formsemestre_id
):
db.session.delete(validation) db.session.delete(validation)
db.session.flush()
db.session.commit()
self.invalidate_formsemestre_cache() self.invalidate_formsemestre_cache()
def get_autorisations_passage(self) -> list[int]: def get_autorisations_passage(self) -> list[int]:
@ -828,6 +885,27 @@ class DecisionsProposeesAnnee(DecisionsProposees):
validations.append(", ".join(v for v in valids if v)) validations.append(", ".join(v for v in valids if v))
return line_sep.join(validations) return line_sep.join(validations)
def descr_pb_coherence(self) -> list[str]:
"""Description d'éventuels problèmes de cohérence entre
les décisions *enregistrées* d'UE et de RCUE.
Note: en principe, la cohérence RCUE/UE est assurée au moment de
l'enregistrement (record).
Mais la base peut avoir été modifiée par d'autres voies.
"""
messages = []
for dec_rcue in self.decisions_rcue_by_niveau.values():
if dec_rcue.code_valide in CODES_RCUE_VALIDES:
for ue in (dec_rcue.rcue.ue_1, dec_rcue.rcue.ue_2):
dec_ue = self.decisions_ues.get(ue.id)
if dec_ue:
if dec_ue.code_valide not in CODES_UE_VALIDES:
messages.append(
f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est !"
)
else:
messages.append(f"L'UE {ue.acronyme} n'a pas décision (???)")
return messages
def list_ue_parcour_etud( def list_ue_parcour_etud(
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
@ -873,6 +951,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
inscription_etat: str = scu.INSCRIT, inscription_etat: str = scu.INSCRIT,
): ):
super().__init__(etud=dec_prop_annee.etud) super().__init__(etud=dec_prop_annee.etud)
self.deca = dec_prop_annee
self.rcue = rcue self.rcue = rcue
if rcue is None: # RCUE non dispo, eg un seul semestre if rcue is None: # RCUE non dispo, eg un seul semestre
self.codes = [] self.codes = []
@ -904,9 +983,30 @@ class DecisionsProposeesRCUE(DecisionsProposees):
or dec_prop_annee.formsemestre_pair.modalite == "EXT" or dec_prop_annee.formsemestre_pair.modalite == "EXT"
): ):
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
# S'il y a une décision enregistrée: si elle est plus favorable que celle que l'on
# proposerait, la place en tête.
# Sinon, la place en seconde place
if self.code_valide and self.code_valide != self.codes[0]:
code_default = self.codes[0]
if self.code_valide in self.codes:
self.codes.remove(self.code_valide)
if sco_codes.BUT_CODES_ORDERED.get(
self.code_valide, 0
) > sco_codes.BUT_CODES_ORDERED.get(code_default, 0):
self.codes.insert(0, self.code_valide)
else:
self.codes.insert(1, self.code_valide)
def __repr__(self) -> str:
return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide
} codes={self.codes} explanation={self.explanation}"""
def record(self, code: str, no_overwrite=False): def record(self, code: str, no_overwrite=False):
"""Enregistre le code""" """Enregistre le code RCUE.
Note:
- si le RCUE est ADJ, les UE non validées sont passées à ADJ
XXX on pourra imposer ici d'autres règles de cohérence
"""
if self.rcue is None: if self.rcue is None:
return # pas de RCUE a enregistrer return # pas de RCUE a enregistrer
if self.inscription_etat != scu.INSCRIT: if self.inscription_etat != scu.INSCRIT:
@ -921,13 +1021,10 @@ class DecisionsProposeesRCUE(DecisionsProposees):
parcours_id = self.parcour.id if self.parcour is not None else None parcours_id = self.parcour.id if self.parcour is not None else None
if self.validation: if self.validation:
db.session.delete(self.validation) db.session.delete(self.validation)
db.session.flush() db.session.commit()
if code is None: if code is None:
self.validation = None self.validation = None
else: else:
# log(
# f"RCUE.record(etudid={self.etud.id}, ue1_id={self.rcue.ue_1.id}, ue2_id={self.rcue.ue_2.id}, code={code} )"
# )
self.validation = ApcValidationRCUE( self.validation = ApcValidationRCUE(
etudid=self.etud.id, etudid=self.etud.id,
formsemestre_id=self.rcue.formsemestre_2.id, formsemestre_id=self.rcue.formsemestre_2.id,
@ -936,12 +1033,25 @@ class DecisionsProposeesRCUE(DecisionsProposees):
parcours_id=parcours_id, parcours_id=parcours_id,
code=code, code=code,
) )
db.session.add(self.validation)
db.session.commit()
Scolog.logdb( Scolog.logdb(
method="jury_but", method="jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation {self.rcue}: {code}", msg=f"Validation {self.rcue}: {code}",
commit=True,
) )
db.session.add(self.validation) log(f"rcue.record {self}: {code}")
# Modifie au besoin les codes d'UE
if code == "ADJ":
deca = self.deca
for ue_id in (self.rcue.ue_1.id, self.rcue.ue_2.id):
dec_ue = deca.decisions_ues.get(ue_id)
if dec_ue and dec_ue.code_valide not in CODES_UE_VALIDES:
log(f"rcue.record: force ADJ sur {dec_ue}")
dec_ue.record("ADJ")
if self.rcue.formsemestre_1 is not None: if self.rcue.formsemestre_1 is not None:
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
formsemestre_id=self.rcue.formsemestre_1.id formsemestre_id=self.rcue.formsemestre_1.id
@ -950,6 +1060,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
formsemestre_id=self.rcue.formsemestre_2.id formsemestre_id=self.rcue.formsemestre_2.id
) )
self.code_valide = code # mise à jour état
self.recorded = True self.recorded = True
def erase(self): def erase(self):
@ -957,6 +1068,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
# par prudence, on requete toutes les validations, en cas de doublons # par prudence, on requete toutes les validations, en cas de doublons
validations = self.rcue.query_validations() validations = self.rcue.query_validations()
for validation in validations: for validation in validations:
log(f"DecisionsProposeesRCUE: deleting {validation}")
db.session.delete(validation) db.session.delete(validation)
db.session.flush() db.session.flush()
@ -1010,14 +1122,14 @@ class DecisionsProposeesUE(DecisionsProposees):
): ):
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non) # Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
# mais ici on a restreint au formsemestre donc une seule (prend la première) # mais ici on a restreint au formsemestre donc une seule (prend la première)
self.validation = ScolarFormSemestreValidation.query.filter_by( validation = ScolarFormSemestreValidation.query.filter_by(
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
).first() ).first()
super().__init__( super().__init__(
etud=etud, etud=etud,
code_valide=self.validation.code if self.validation is not None else None, code_valide=validation.code if validation is not None else None,
) )
# log(f"built {self}") self.validation = validation
self.formsemestre = formsemestre self.formsemestre = formsemestre
self.ue: UniteEns = ue self.ue: UniteEns = ue
self.rcue: RegroupementCoherentUE = None self.rcue: RegroupementCoherentUE = None
@ -1056,11 +1168,11 @@ class DecisionsProposeesUE(DecisionsProposees):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
} codes={self.codes} explanation={self.explanation}""" } codes={self.codes} explanation={self.explanation}>"""
def set_rcue(self, rcue: RegroupementCoherentUE): def set_rcue(self, rcue: RegroupementCoherentUE):
"""Rattache cette UE à un RCUE. Cela peut modifier les codes """Rattache cette UE à un RCUE. Cela peut modifier les codes
proposés (si compensation)""" proposés par compute_codes() (si compensation)"""
self.rcue = rcue self.rcue = rcue
def compute_codes(self): def compute_codes(self):
@ -1098,6 +1210,7 @@ class DecisionsProposeesUE(DecisionsProposees):
method="jury_but", method="jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation UE {self.ue.id} {self.ue.acronyme}: effacée", msg=f"Validation UE {self.ue.id} {self.ue.acronyme}: effacée",
commit=True,
) )
else: else:
self.validation = ScolarFormSemestreValidation( self.validation = ScolarFormSemestreValidation(
@ -1107,15 +1220,18 @@ class DecisionsProposeesUE(DecisionsProposees):
code=code, code=code,
moy_ue=self.moy_ue, moy_ue=self.moy_ue,
) )
db.session.add(self.validation)
db.session.commit()
Scolog.logdb( Scolog.logdb(
method="jury_but", method="jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation UE {self.ue.id} {self.ue.acronyme}({self.moy_ue}): {code}", msg=f"Validation UE {self.ue.id} {self.ue.acronyme}({self.moy_ue}): {code}",
commit=True,
) )
db.session.add(self.validation)
log(f"DecisionsProposeesUE: recording {self.validation}") log(f"DecisionsProposeesUE: recording {self.validation}")
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id) sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
self.code_valide = code # mise à jour
self.recorded = True self.recorded = True
def erase(self): def erase(self):
@ -1126,13 +1242,14 @@ class DecisionsProposeesUE(DecisionsProposees):
) )
for validation in validations: for validation in validations:
log(f"DecisionsProposeesUE: deleting {validation}") log(f"DecisionsProposeesUE: deleting {validation}")
db.session.delete(validation)
Scolog.logdb( Scolog.logdb(
method="jury_but", method="jury_but",
etudid=self.etud.id, etudid=self.etud.id,
msg=f"Validation UE {validation.ue.id} {validation.ue.acronyme}: effacée", msg=f"Validation UE {validation.ue.id} {validation.ue.acronyme}: effacée",
) )
db.session.delete(validation)
db.session.flush() db.session.commit()
def descr_validation(self) -> str: def descr_validation(self) -> str:
"""Description validation niveau enregistrée, pour PV jury. """Description validation niveau enregistrée, pour PV jury.
@ -1148,7 +1265,7 @@ class BUTCursusEtud: # WIP TODO
def __init__(self, formsemestre: FormSemestre, etud: Identite): def __init__(self, formsemestre: FormSemestre, etud: Identite):
if formsemestre.formation.referentiel_competence is None: if formsemestre.formation.referentiel_competence is None:
raise ScoException("BUTCursusEtud: pas de référentiel de compétences") raise ScoNoReferentielCompetences(formation=formsemestre.formation)
assert len(etud.formsemestre_inscriptions) > 0 assert len(etud.formsemestre_inscriptions) > 0
self.formsemestre = formsemestre self.formsemestre = formsemestre
self.etud = etud self.etud = etud

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -32,7 +32,7 @@ from app.scodoc.sco_codes_parcours import (
from app.scodoc import sco_formsemestre_status from app.scodoc import sco_formsemestre_status
from app.scodoc import sco_pvjury from app.scodoc import sco_pvjury
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
def formsemestre_saisie_jury_but( def formsemestre_saisie_jury_but(
@ -63,14 +63,7 @@ def formsemestre_saisie_jury_but(
# raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs") # raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
if formsemestre2.formation.referentiel_competence is None: if formsemestre2.formation.referentiel_competence is None:
raise ScoValueError( raise ScoNoReferentielCompetences(formation=formsemestre2.formation)
"""
<p>Pas de référentiel de compétences associé à la formation !</p>
<p>Pour associer un référentiel, passer par le menu <b>Semestre /
Voir la formation... </b> et suivre le lien <em>"associer à un référentiel
de compétences"</em>
"""
)
rows, titles, column_ids, jury_stats = get_jury_but_table( rows, titles, column_ids, jury_stats = get_jury_but_table(
formsemestre2, read_only=read_only, mode=mode formsemestre2, read_only=read_only, mode=mode

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -15,12 +15,19 @@ from app.scodoc import sco_cache
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
def formsemestre_validation_auto_but(formsemestre: FormSemestre, only_adm=True) -> int: def formsemestre_validation_auto_but(
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
) -> int:
"""Calcul automatique des décisions de jury sur une année BUT. """Calcul automatique des décisions de jury sur une année BUT.
Ne modifie jamais de décisions de l'année scolaire précédente, même
si on a des RCUE "à cheval".
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit). Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
(mode à n'utiliser que pour les tests) (mode à n'utiliser que pour les tests)
Si no_overwrite est vrai (défaut), ne -écrit jamais les codes déjà enregistrés
(utiliser faux pour certains tests)
Returns: nombre d'étudiants "admis" Returns: nombre d'étudiants "admis"
""" """
if not formsemestre.formation.is_apc(): if not formsemestre.formation.is_apc():
@ -33,7 +40,7 @@ def formsemestre_validation_auto_but(formsemestre: FormSemestre, only_adm=True)
if deca.admis: # année réussie if deca.admis: # année réussie
nb_admis += 1 nb_admis += 1
if deca.admis or not only_adm: if deca.admis or not only_adm:
deca.record_all() deca.record_all(no_overwrite=no_overwrite)
db.session.commit() db.session.commit()
return nb_admis return nb_admis

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -30,6 +30,7 @@ from app.models import (
Identite, Identite,
UniteEns, UniteEns,
ScolarAutorisationInscription, ScolarAutorisationInscription,
ScolarFormSemestreValidation,
) )
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
@ -41,16 +42,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
Si pas read_only, menus sélection codes jury. Si pas read_only, menus sélection codes jury.
""" """
H = [] H = []
if deca.code_valide and not read_only:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
else:
erase_span = ""
H.append("""<div class="but_section_annee">""") H.append("""<div class="but_section_annee">""")
if deca.jury_annuel:
H.append( H.append(
f""" f"""
<div> <div>
@ -58,15 +51,12 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
_gen_but_select("code_annee", deca.codes, deca.code_valide, _gen_but_select("code_annee", deca.codes, deca.code_valide,
disabled=True, klass="manual") disabled=True, klass="manual")
} }
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span> <span>({deca.code_valide or 'non'} enregistrée)</span>
<span>{erase_span}</span>
</div> </div>
""" """
) )
div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>""" div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>"""
else:
H.append("""<div><em>Pas de décision annuelle (sem. impair).</em></div>""")
div_explanation = ""
H.append("""</div>""") H.append("""</div>""")
formsemestre_1 = deca.formsemestre_impair formsemestre_1 = deca.formsemestre_impair
@ -119,8 +109,8 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
if ue.niveau_competence and ue.niveau_competence.id == niveau.id if ue.niveau_competence and ue.niveau_competence.id == niveau.id
] ]
ue_pair = ues[0] if ues else None ue_pair = ues[0] if ues else None
# Les UEs à afficher, toujours en readonly # Les UEs à afficher,
# sur le formsemestre de l'année précédente du redoublant # qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
ues_ro = [ ues_ro = [
( (
ue_impair, ue_impair,
@ -143,6 +133,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
deca.decisions_ues[ue.id], deca.decisions_ues[ue.id],
disabled=read_only or ue_read_only, disabled=read_only or ue_read_only,
annee_prec=ue_read_only, annee_prec=ue_read_only,
niveau_id=ue.niveau_competence.id,
) )
) )
else: else:
@ -161,6 +152,7 @@ def _gen_but_select(
code_valide: str, code_valide: str,
disabled: bool = False, disabled: bool = False,
klass: str = "", klass: str = "",
data: dict = {},
) -> str: ) -> str:
"Le menu html select avec les codes" "Le menu html select avec les codes"
# if disabled: # mauvaise idée car le disabled est traité en JS # if disabled: # mauvaise idée car le disabled est traité en JS
@ -176,8 +168,11 @@ def _gen_but_select(
) )
return f"""<select required name="{name}" return f"""<select required name="{name}"
class="but_code {klass}" class="but_code {klass}"
data-orig_code="{code_valide or (codes[0] if codes else '')}"
data-orig_recorded="{code_valide or ''}"
onchange="change_menu_code(this);" onchange="change_menu_code(this);"
{"disabled" if disabled else ""} {"disabled" if disabled else ""}
{" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
>{options_htm}</select> >{options_htm}</select>
""" """
@ -187,6 +182,7 @@ def _gen_but_niveau_ue(
dec_ue: DecisionsProposeesUE, dec_ue: DecisionsProposeesUE,
disabled: bool = False, disabled: bool = False,
annee_prec: bool = False, annee_prec: bool = False,
niveau_id: int = None,
) -> str: ) -> str:
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
moy_ue_str = f"""<span class="ue_cap">{ moy_ue_str = f"""<span class="ue_cap">{
@ -207,6 +203,13 @@ def _gen_but_niveau_ue(
""" """
else: else:
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>""" moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
if dec_ue.code_valide:
scoplement = f"""<div class="scoplement">
Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
à {dec_ue.validation.event_date.strftime("%Hh%M")}
</div>
"""
else:
scoplement = "" scoplement = ""
return f"""<div class="but_niveau_ue { return f"""<div class="but_niveau_ue {
@ -221,7 +224,9 @@ def _gen_but_niveau_ue(
<div class="but_code">{ <div class="but_code">{
_gen_but_select("code_ue_"+str(ue.id), _gen_but_select("code_ue_"+str(ue.id),
dec_ue.codes, dec_ue.codes,
dec_ue.code_valide, disabled=disabled dec_ue.code_valide,
disabled=disabled,
klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else ""
) )
}</div> }</div>
@ -245,23 +250,31 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
else "" else ""
) )
# Déjà enregistré ?
niveau_rcue_class = ""
if dec_rcue.code_valide is not None and dec_rcue.codes:
if dec_rcue.code_valide == dec_rcue.codes[0]:
niveau_rcue_class = "recorded"
else:
niveau_rcue_class = "recorded_different"
return f""" return f"""
<div class="but_niveau_rcue <div class="but_niveau_rcue {niveau_rcue_class}
{'recorded' if dec_rcue.code_valide is not None else ''}
"> ">
<div class="but_note with_scoplement"> <div class="but_note with_scoplement">
<div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div> <div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
{scoplement} {scoplement}
</div> </div>
<div class="but_code"> <div class="but_code">
<div>{_gen_but_select("code_rcue_"+str(niveau.id), {_gen_but_select("code_rcue_"+str(niveau.id),
dec_rcue.codes, dec_rcue.codes,
dec_rcue.code_valide, dec_rcue.code_valide,
disabled=True, klass="manual" disabled=True,
klass="manual code_rcue",
data = { "niveau_id" : str(niveau.id)}
)} )}
</div> </div>
</div> </div>
</div>
""" """
@ -278,17 +291,15 @@ def jury_but_semestriel(
semestre_terminal = ( semestre_terminal = (
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
) )
autorisations_passage = ScolarAutorisationInscription.query.filter_by(
etudid=etud.id,
origin_formsemestre_id=formsemestre.id,
).all()
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair, # Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
# ou si décision déjà enregistrée: # ou si décision déjà enregistrée:
est_autorise_a_passer = (formsemestre.semestre_id % 2) or ( est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
formsemestre.semestre_id + 1 formsemestre.semestre_id + 1
) in ( ) in (a.semestre_id for a in autorisations_passage)
a.semestre_id
for a in ScolarAutorisationInscription.query.filter_by(
etudid=etud.id,
origin_formsemestre_id=formsemestre.id,
)
)
decisions_ues = { decisions_ues = {
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat) ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
for ue in ues for ue in ues
@ -312,7 +323,9 @@ def jury_but_semestriel(
flash("codes enregistrés") flash("codes enregistrés")
if not semestre_terminal: if not semestre_terminal:
if request.form.get("autorisation_passage"): if request.form.get("autorisation_passage"):
if not est_autorise_a_passer: if not formsemestre.semestre_id + 1 in (
a.semestre_id for a in autorisations_passage
):
ScolarAutorisationInscription.autorise_etud( ScolarAutorisationInscription.autorise_etud(
etud.id, etud.id,
formsemestre.formation.formation_code, formsemestre.formation.formation_code,
@ -351,7 +364,7 @@ def jury_but_semestriel(
warning = "" warning = ""
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title="Validation BUT", page_title=f"Validation BUT S{formsemestre.semestre_id}",
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
etudid=etud.id, etudid=etud.id,
cssstyles=("css/jury_but.css",), cssstyles=("css/jury_but.css",),
@ -372,25 +385,35 @@ def jury_but_semestriel(
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a> }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
</div> </div>
</div> </div>
<h3>Jury sur un semestre BUT isolé</h3> <h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
{warning} {warning}
</div> </div>
<form method="POST"> <form method="post" id="jury_but">
""", """,
] ]
if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]):
erase_span = ""
if not read_only:
# Requête toutes les validations (pas seulement celles du deca courant),
# au cas où: changement d'architecture, saisie en mode classique, ...
validations = ScolarFormSemestreValidation.query.filter_by(
etudid=etud.id, formsemestre_id=formsemestre.id
).all()
if validations:
erase_span = f"""<a href="{ erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase", url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
etudid=etud.id, only_one_sem=1)}" class="stdlink">effacer les décisions enregistrées</a>""" etudid=etud.id, only_one_sem=1)
}" class="stdlink">effacer les décisions enregistrées</a>"""
else: else:
erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre." erase_span = (
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
)
H.append( H.append(
f""" f"""
<div class="but_section_annee"> <div class="but_section_annee">
<span>{erase_span}</span>
</div> </div>
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div> <div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
""" """
@ -440,6 +463,9 @@ def jury_but_semestriel(
<input type="checkbox" name="autorisation_passage" value="1" { <input type="checkbox" name="autorisation_passage" value="1" {
"checked" if est_autorise_a_passer else ""}> "checked" if est_autorise_a_passer else ""}>
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em> <em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
{("(autorisations enregistrées: " + ' '.join(
'S' + str(a.semestre_id or '') for a in autorisations_passage) + ")"
) if autorisations_passage else ""}
</input> </input>
</div> </div>
""" """
@ -447,9 +473,10 @@ def jury_but_semestriel(
else: else:
H.append("""<div class="help">dernier semestre de la formation.</div>""") H.append("""<div class="help">dernier semestre de la formation.</div>""")
H.append( H.append(
""" f"""
<div class="but_buttons"> <div class="but_buttons">
<input type="submit" value="Enregistrer ces décisions"> <span><input type="submit" value="Enregistrer ces décisions"></span>
<span>{erase_span}</span>
</div> </div>
""" """
) )

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -1,7 +1,7 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
"""ScoDoc 9 models : Référentiel Compétence BUT 2021 """ScoDoc 9 models : Référentiel Compétence BUT 2021
@ -14,7 +14,7 @@ import sqlalchemy
from app import db from app import db
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns # from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
@ -54,13 +54,15 @@ class ApcReferentielCompetences(db.Model, XMLModel):
"Référentiel de compétence d'une spécialité" "Référentiel de compétence d'une spécialité"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
annexe = db.Column(db.Text()) annexe = db.Column(db.Text()) # '1', '22', ...
specialite = db.Column(db.Text()) specialite = db.Column(db.Text()) # 'CJ', 'RT', 'INFO', ...
specialite_long = db.Column(db.Text()) specialite_long = db.Column(
type_titre = db.Column(db.Text()) db.Text()
type_structure = db.Column(db.Text()) ) # 'Carrière Juridique', 'Réseaux et télécommunications', ...
type_titre = db.Column(db.Text()) # 'B.U.T.'
type_structure = db.Column(db.Text()) # 'type1', 'type2', ...
type_departement = db.Column(db.Text()) # "secondaire", "tertiaire" type_departement = db.Column(db.Text()) # "secondaire", "tertiaire"
version_orebut = db.Column(db.Text()) version_orebut = db.Column(db.Text()) # '2021-12-11 00:00:00'
_xml_attribs = { # Orébut xml attrib : attribute _xml_attribs = { # Orébut xml attrib : attribute
"type": "type_titre", "type": "type_titre",
"version": "version_orebut", "version": "version_orebut",
@ -322,9 +324,8 @@ class ApcNiveau(db.Model, XMLModel):
if annee not in {1, 2, 3}: if annee not in {1, 2, 3}:
raise ValueError("annee invalide pour un parcours BUT") raise ValueError("annee invalide pour un parcours BUT")
if referentiel_competence is None: if referentiel_competence is None:
raise ScoValueError( raise ScoNoReferentielCompetences()
"Pas de référentiel de compétences associé à la formation !"
)
annee_formation = f"BUT{annee}" annee_formation = f"BUT{annee}"
if parcour is None: if parcour is None:
return ApcNiveau.query.filter( return ApcNiveau.query.filter(

View File

@ -68,7 +68,8 @@ class ApcValidationRCUE(db.Model):
"description en HTML" "description en HTML"
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}:
<b>{self.code}</b> <b>{self.code}</b>
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}</em>""" <em>enregistrée le {self.date.strftime("%d/%m/%Y")}
à {self.date.strftime("%Hh%M")}</em>"""
def niveau(self) -> ApcNiveau: def niveau(self) -> ApcNiveau:
"""Le niveau de compétence associé à cet RCUE.""" """Le niveau de compétence associé à cet RCUE."""
@ -180,8 +181,9 @@ class RegroupementCoherentUE:
return self.query_validations().count() > 0 return self.query_validations().count() > 0
def est_compensable(self): def est_compensable(self):
"""Vrai si ce RCUE est validable par compensation """Vrai si ce RCUE est validable (uniquement) par compensation
c'est à dire que sa moyenne est > 10 avec une UE < 10 c'est à dire que sa moyenne est > 10 avec une UE < 10.
Note: si ADM, est_compensable est faux.
""" """
return ( return (
(self.moy_rcue is not None) (self.moy_rcue is not None)

View File

@ -1,7 +1,7 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################
@ -56,9 +56,9 @@ class FormSemestre(db.Model):
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id")) formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1") semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
titre = db.Column(db.Text()) titre = db.Column(db.Text(), nullable=False)
date_debut = db.Column(db.Date()) date_debut = db.Column(db.Date(), nullable=False)
date_fin = db.Column(db.Date()) date_fin = db.Column(db.Date(), nullable=False)
etat = db.Column( etat = db.Column(
db.Boolean(), nullable=False, default=True, server_default="true" db.Boolean(), nullable=False, default=True, server_default="true"
) # False si verrouillé ) # False si verrouillé
@ -87,7 +87,10 @@ class FormSemestre(db.Model):
) )
# couleur fond bulletins HTML: # couleur fond bulletins HTML:
bul_bgcolor = db.Column( bul_bgcolor = db.Column(
db.String(SHORT_STR_LEN), default="white", server_default="white" db.String(SHORT_STR_LEN),
default="white",
server_default="white",
nullable=False,
) )
# autorise resp. a modifier semestre: # autorise resp. a modifier semestre:
resp_can_edit = db.Column( resp_can_edit = db.Column(
@ -114,6 +117,7 @@ class FormSemestre(db.Model):
"ModuleImpl", "ModuleImpl",
backref="formsemestre", backref="formsemestre",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan",
) )
etuds = db.relationship( etuds = db.relationship(
"Identite", "Identite",

View File

@ -1,7 +1,7 @@
# -*- coding: UTF-8 -* # -*- coding: UTF-8 -*
############################################################################## ##############################################################################
# ScoDoc # ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE # See LICENSE
############################################################################## ##############################################################################

View File

@ -20,14 +20,12 @@ class ModuleImpl(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
moduleimpl_id = db.synonym("id") moduleimpl_id = db.synonym("id")
module_id = db.Column( module_id = db.Column(db.Integer, db.ForeignKey("notes_modules.id"), nullable=False)
db.Integer,
db.ForeignKey("notes_modules.id"),
)
formsemestre_id = db.Column( formsemestre_id = db.Column(
db.Integer, db.Integer,
db.ForeignKey("notes_formsemestre.id"), db.ForeignKey("notes_formsemestre.id"),
index=True, index=True,
nullable=False,
) )
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id")) responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
# formule de calcul moyenne: # formule de calcul moyenne:

View File

@ -37,7 +37,9 @@ class Module(db.Model):
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum) # Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0") module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
# Relations: # Relations:
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic") modimpls = db.relationship(
"ModuleImpl", backref="module", lazy="dynamic", cascade="all, delete-orphan"
)
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True) ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
tags = db.relationship( tags = db.relationship(
"NotesTag", "NotesTag",

View File

@ -4,6 +4,7 @@
""" """
from app import db from app import db
from app import log
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
from app.models.events import Scolog from app.models.events import Scolog
@ -93,6 +94,10 @@ class ScolarAutorisationInscription(db.Model):
db.ForeignKey("notes_formsemestre.id"), db.ForeignKey("notes_formsemestre.id"),
) )
def __repr__(self) -> str:
return f"""{self.__class__.__name__}(id={self.id}, etudid={
self.etudid}, semestre_id={self.semestre_id})"""
def to_dict(self) -> dict: def to_dict(self) -> dict:
"as a dict" "as a dict"
d = dict(self.__dict__) d = dict(self.__dict__)
@ -119,6 +124,7 @@ class ScolarAutorisationInscription(db.Model):
Scolog.logdb( Scolog.logdb(
"autorise_etud", etudid=etudid, msg=f"Passage vers S{semestre_id}: autorisé" "autorise_etud", etudid=etudid, msg=f"Passage vers S{semestre_id}: autorisé"
) )
log(f"ScolarAutorisationInscription: recording {autorisation}")
@classmethod @classmethod
def delete_autorisation_etud( def delete_autorisation_etud(
@ -132,6 +138,7 @@ class ScolarAutorisationInscription(db.Model):
) )
for autorisation in autorisations: for autorisation in autorisations:
db.session.delete(autorisation) db.session.delete(autorisation)
log(f"ScolarAutorisationInscription: deleting {autorisation}")
Scolog.logdb( Scolog.logdb(
"autorise_etud", "autorise_etud",
etudid=etudid, etudid=etudid,

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -105,13 +105,13 @@ class BaseArchiver(object):
try: try:
scu.GSL.acquire() scu.GSL.acquire()
if not os.path.isdir(path): if not os.path.isdir(path):
log("creating directory %s" % path) log(f"creating directory {path}")
os.mkdir(path) os.mkdir(path)
finally: finally:
scu.GSL.release() scu.GSL.release()
self.initialized = True self.initialized = True
def get_obj_dir(self, oid): def get_obj_dir(self, oid: int):
""" """
:return: path to directory of archives for this object (eg formsemestre_id or etudid). :return: path to directory of archives for this object (eg formsemestre_id or etudid).
If directory does not yet exist, create it. If directory does not yet exist, create it.
@ -142,7 +142,7 @@ class BaseArchiver(object):
dirs = glob.glob(base + "*") dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs] return [os.path.split(x)[1] for x in dirs]
def list_obj_archives(self, oid): def list_obj_archives(self, oid: int):
"""Returns """Returns
:return: list of archive identifiers for this object (paths to non empty dirs) :return: list of archive identifiers for this object (paths to non empty dirs)
""" """
@ -157,7 +157,7 @@ class BaseArchiver(object):
dirs.sort() dirs.sort()
return dirs return dirs
def delete_archive(self, archive_id): def delete_archive(self, archive_id: str):
"""Delete (forever) this archive""" """Delete (forever) this archive"""
self.initialize() self.initialize()
try: try:
@ -166,7 +166,7 @@ class BaseArchiver(object):
finally: finally:
scu.GSL.release() scu.GSL.release()
def get_archive_date(self, archive_id): def get_archive_date(self, archive_id: str):
"""Returns date (as a DateTime object) of an archive""" """Returns date (as a DateTime object) of an archive"""
return datetime.datetime( return datetime.datetime(
*[int(x) for x in os.path.split(archive_id)[1].split("-")] *[int(x) for x in os.path.split(archive_id)[1].split("-")]
@ -183,17 +183,17 @@ class BaseArchiver(object):
files.sort() files.sort()
return [f for f in files if f and f[0] != "_"] return [f for f in files if f and f[0] != "_"]
def get_archive_name(self, archive_id): def get_archive_name(self, archive_id: str):
"""name identifying archive, to be used in web URLs""" """name identifying archive, to be used in web URLs"""
return os.path.split(archive_id)[1] return os.path.split(archive_id)[1]
def is_valid_archive_name(self, archive_name): def is_valid_archive_name(self, archive_name: str):
"""check if name is valid.""" """check if name is valid."""
return re.match( return re.match(
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name "^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
) )
def get_id_from_name(self, oid, archive_name): def get_id_from_name(self, oid, archive_name: str):
"""returns archive id (check that name is valid)""" """returns archive id (check that name is valid)"""
self.initialize() self.initialize()
if not self.is_valid_archive_name(archive_name): if not self.is_valid_archive_name(archive_name):
@ -206,7 +206,7 @@ class BaseArchiver(object):
raise ScoValueError(f"Archive {archive_name} introuvable") raise ScoValueError(f"Archive {archive_name} introuvable")
return archive_id return archive_id
def get_archive_description(self, archive_id): def get_archive_description(self, archive_id: str) -> str:
"""Return description of archive""" """Return description of archive"""
self.initialize() self.initialize()
filename = os.path.join(archive_id, "_description.txt") filename = os.path.join(archive_id, "_description.txt")
@ -247,7 +247,7 @@ class BaseArchiver(object):
data = data.encode(scu.SCO_ENCODING) data = data.encode(scu.SCO_ENCODING)
self.initialize() self.initialize()
filename = scu.sanitize_filename(filename) filename = scu.sanitize_filename(filename)
log("storing %s (%d bytes) in %s" % (filename, len(data), archive_id)) log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
try: try:
scu.GSL.acquire() scu.GSL.acquire()
fname = os.path.join(archive_id, filename) fname = os.path.join(archive_id, filename)
@ -261,16 +261,18 @@ class BaseArchiver(object):
"""Retreive data""" """Retreive data"""
self.initialize() self.initialize()
if not scu.is_valid_filename(filename): if not scu.is_valid_filename(filename):
log('Archiver.get: invalid filename "%s"' % filename) log(f"""Archiver.get: invalid filename '{filename}'""")
raise ScoValueError("archive introuvable (déjà supprimée ?)") raise ScoValueError("archive introuvable (déjà supprimée ?)")
fname = os.path.join(archive_id, filename) fname = os.path.join(archive_id, filename)
log("reading archive file %s" % fname) log(f"reading archive file {fname}")
with open(fname, "rb") as f: with open(fname, "rb") as f:
data = f.read() data = f.read()
return data return data
def get_archived_file(self, oid, archive_name, filename): def get_archived_file(self, oid, archive_name, filename):
"""Recupere donnees du fichier indiqué et envoie au client""" """Recupère les donnees du fichier indiqué et envoie au client.
Returns: Response
"""
archive_id = self.get_id_from_name(oid, archive_name) archive_id = self.get_id_from_name(oid, archive_name)
data = self.get(archive_id, filename) data = self.get(archive_id, filename)
mime = mimetypes.guess_type(filename)[0] mime = mimetypes.guess_type(filename)[0]

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -435,7 +435,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
plusminus = pluslink plusminus = pluslink
try: try:
ects_txt = str(int(ue["ects"])) ects_txt = str(int(ue["ects"]))
except (ValueError, KeyError): except (ValueError, KeyError, TypeError):
ects_txt = "-" ects_txt = "-"
t = { t = {

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -205,6 +205,20 @@ BUT_CODES_PASSAGE = {
PAS1NCI, PAS1NCI,
ATJ, ATJ,
} }
# les codes, du plus "défavorable" à l'étudiant au plus favorable:
# (valeur par défaut 0)
BUT_CODES_ORDERED = {
"NAR": 0,
"DEF": 0,
"AJ": 10,
"ATJ": 20,
"CMP": 50,
"ADC": 50,
"PASD": 50,
"PAS1NCI": 60,
"ADJ": 100,
"ADM": 100,
}
def code_semestre_validant(code: str) -> bool: def code_semestre_validant(code: str) -> bool:

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -4,7 +4,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -2,7 +2,7 @@
# #
# ScoDoc # ScoDoc
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
# #
# Gestion scolarite IUT # Gestion scolarite IUT
# #
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by

Some files were not shown because too many files have changed in this diff Show More