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
def log(msg: str, silent_test=True):
def log(msg: str):
"""log a message.
If Flask app, use configured logger, else stderr.
"""
if silent_test and current_app and current_app.config["TESTING"]:
return
try:
dept = getattr(g, "scodoc_dept", "")
msg = f" ({dept}) {msg}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
@ -91,9 +91,15 @@ from app.models.ues import UniteEns
from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import sco_cache
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.sco_exceptions import ScoException, ScoValueError
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
class NoRCUEError(ScoValueError):
@ -170,7 +176,7 @@ class DecisionsProposees:
def __repr__(self) -> str:
return f"""<{self.__class__.__name__} valid={self.code_valide
} codes={self.codes} explanation={self.explanation}"""
} codes={self.codes} explanation={self.explanation}>"""
class DecisionsProposeesAnnee(DecisionsProposees):
@ -205,6 +211,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
formsemestre: FormSemestre,
):
assert formsemestre.formation.is_apc()
if formsemestre.formation.referentiel_competence is None:
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
super().__init__(etud=etud)
self.formsemestre = formsemestre
"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"
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)
"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
# Enfin calcule les codes des UE:
@ -362,7 +371,6 @@ class DecisionsProposeesAnnee(DecisionsProposees):
"s" if plural else ""} sur {self.nb_competences}"""
if self.admis:
self.codes = [sco_codes.ADM] + self.codes
self.explanation = expl_rcues
# elif not self.jury_annuel:
# self.codes = [] # pas de décision annuelle sur semestres impairs
elif self.inscription_etat != scu.INSCRIT:
@ -378,9 +386,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.ABL,
sco_codes.EXCLU,
]
expl_rcues = ""
elif self.passage_de_droit:
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
self.codes = [
sco_codes.RED,
@ -388,7 +396,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.PAS1NCI,
sco_codes.ADJ,
] + 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:
self.codes = [
sco_codes.RED,
@ -397,17 +405,21 @@ class DecisionsProposeesAnnee(DecisionsProposees):
sco_codes.ADJ,
sco_codes.PASD, # voir #488 (discutable, conventions locales)
] + 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
if (
self.formsemestre_impair and self.formsemestre_impair.modalite == "EXT"
) or (self.formsemestre_pair and self.formsemestre_pair.modalite == "EXT"):
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:
@ -581,9 +593,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
"""Pour chaque niveau de compétence de cette année, construit
le DecisionsProposeesRCUE,
ou None s'il n'y en a pas
le DecisionsProposeesRCUE, ou None s'il n'y en a pas
(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 }
"""
# Retrouve le RCUE associé à chaque niveau
@ -630,6 +643,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
def record_form(self, form: dict):
"""Enregistre les codes de jury en base
à partir d'un dict représentant le formulaire jury BUT:
form dict:
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
@ -639,32 +653,42 @@ class DecisionsProposeesAnnee(DecisionsProposees):
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
"""
log("jury_but.DecisionsProposeesAnnee.record_form")
with sco_cache.DeferredSemCacheManager():
for key in form:
code = form[key]
# Codes d'UE
m = re.match(r"^code_ue_(\d+)$", key)
code_annee = None
codes_rcues = [] # [ (dec_rcue, code), ... ]
codes_ues = [] # [ (dec_ue, code), ... ]
for key in form:
code = form[key]
# Codes d'UE
m = re.match(r"^code_ue_(\d+)$", key)
if m:
ue_id = int(m.group(1))
dec_ue = self.decisions_ues.get(ue_id)
if not dec_ue:
raise ScoValueError(f"UE invalide ue_id={ue_id}")
codes_ues.append((dec_ue, code))
else:
# Codes de RCUE
m = re.match(r"^code_rcue_(\d+)$", key)
if m:
ue_id = int(m.group(1))
dec_ue = self.decisions_ues.get(ue_id)
if not dec_ue:
raise ScoValueError(f"UE invalide ue_id={ue_id}")
dec_ue.record(code)
else:
# Codes de RCUE
m = re.match(r"^code_rcue_(\d+)$", key)
if m:
niveau_id = int(m.group(1))
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
if not dec_rcue:
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
dec_rcue.record(code)
elif key == "code_annee":
# Code annuel
self.record(code)
niveau_id = int(m.group(1))
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
if not dec_rcue:
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
codes_rcues.append((dec_rcue, code))
elif key == "code_annee":
# Code annuel
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()
db.session.commit()
db.session.commit()
def record(self, code: str, no_overwrite=False):
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
@ -682,7 +706,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
return # no change
if self.validation:
db.session.delete(self.validation)
db.session.flush()
db.session.commit()
if code is None:
self.validation = None
else:
@ -693,12 +717,15 @@ class DecisionsProposeesAnnee(DecisionsProposees):
annee_scolaire=self.annee_scolaire(),
code=code,
)
db.session.add(self.validation)
db.session.commit()
log(f"Recording {self}: {code}")
Scolog.logdb(
method="jury_but",
etudid=self.etud.id,
msg=f"Validation année BUT{self.annee_but}: {code}",
)
db.session.add(self.validation)
# --- Autorisation d'inscription dans semestre suivant ?
if self.formsemestre_pair is not None:
if code is None:
@ -729,29 +756,49 @@ class DecisionsProposeesAnnee(DecisionsProposees):
if self.formsemestre_pair is not None:
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,
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 = (
list(self.decisions_ues.values())
+ list(self.decisions_rcue_by_niveau.values())
+ [self]
)
for dec in decisions:
if not dec.recorded:
# Toujours valider dans l'ordre UE, RCUE, Année:
annee_scolaire = self.formsemestre.annee_scolaire()
# UEs
for dec_ue in self.decisions_ues.values():
if (
not dec_ue.recorded
) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
# 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
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):
"""Efface les décisions de jury de cet étudiant
pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises.
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,
# et les validations de ses UEs
ScolarAutorisationInscription.delete_autorisation_etud(
@ -779,13 +826,23 @@ class DecisionsProposeesAnnee(DecisionsProposees):
ordre=self.annee_but,
)
for validation in validations:
db.session.delete(validation)
Scolog.logdb(
"jury_but",
etudid=self.etud.id,
msg=f"Validation année BUT{self.annee_but}: effacée",
)
db.session.delete(validation)
db.session.flush()
# 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.commit()
self.invalidate_formsemestre_cache()
def get_autorisations_passage(self) -> list[int]:
@ -828,6 +885,27 @@ class DecisionsProposeesAnnee(DecisionsProposees):
validations.append(", ".join(v for v in valids if v))
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(
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
@ -873,6 +951,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
inscription_etat: str = scu.INSCRIT,
):
super().__init__(etud=dec_prop_annee.etud)
self.deca = dec_prop_annee
self.rcue = rcue
if rcue is None: # RCUE non dispo, eg un seul semestre
self.codes = []
@ -904,9 +983,30 @@ class DecisionsProposeesRCUE(DecisionsProposees):
or dec_prop_annee.formsemestre_pair.modalite == "EXT"
):
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):
"""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:
return # pas de RCUE a enregistrer
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
if self.validation:
db.session.delete(self.validation)
db.session.flush()
db.session.commit()
if code is None:
self.validation = None
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(
etudid=self.etud.id,
formsemestre_id=self.rcue.formsemestre_2.id,
@ -936,12 +1033,25 @@ class DecisionsProposeesRCUE(DecisionsProposees):
parcours_id=parcours_id,
code=code,
)
db.session.add(self.validation)
db.session.commit()
Scolog.logdb(
method="jury_but",
etudid=self.etud.id,
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:
sco_cache.invalidate_formsemestre(
formsemestre_id=self.rcue.formsemestre_1.id
@ -950,6 +1060,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
sco_cache.invalidate_formsemestre(
formsemestre_id=self.rcue.formsemestre_2.id
)
self.code_valide = code # mise à jour état
self.recorded = True
def erase(self):
@ -957,6 +1068,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
# par prudence, on requete toutes les validations, en cas de doublons
validations = self.rcue.query_validations()
for validation in validations:
log(f"DecisionsProposeesRCUE: deleting {validation}")
db.session.delete(validation)
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)
# 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
).first()
super().__init__(
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.ue: UniteEns = ue
self.rcue: RegroupementCoherentUE = None
@ -1056,11 +1168,11 @@ class DecisionsProposeesUE(DecisionsProposees):
def __repr__(self) -> str:
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):
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
proposés (si compensation)"""
proposés par compute_codes() (si compensation)"""
self.rcue = rcue
def compute_codes(self):
@ -1098,6 +1210,7 @@ class DecisionsProposeesUE(DecisionsProposees):
method="jury_but",
etudid=self.etud.id,
msg=f"Validation UE {self.ue.id} {self.ue.acronyme}: effacée",
commit=True,
)
else:
self.validation = ScolarFormSemestreValidation(
@ -1107,15 +1220,18 @@ class DecisionsProposeesUE(DecisionsProposees):
code=code,
moy_ue=self.moy_ue,
)
db.session.add(self.validation)
db.session.commit()
Scolog.logdb(
method="jury_but",
etudid=self.etud.id,
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}")
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
self.code_valide = code # mise à jour
self.recorded = True
def erase(self):
@ -1126,13 +1242,14 @@ class DecisionsProposeesUE(DecisionsProposees):
)
for validation in validations:
log(f"DecisionsProposeesUE: deleting {validation}")
db.session.delete(validation)
Scolog.logdb(
method="jury_but",
etudid=self.etud.id,
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:
"""Description validation niveau enregistrée, pour PV jury.
@ -1148,7 +1265,7 @@ class BUTCursusEtud: # WIP TODO
def __init__(self, formsemestre: FormSemestre, etud: Identite):
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
self.formsemestre = formsemestre
self.etud = etud

View File

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

View File

@ -1,6 +1,6 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# 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_pvjury
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(
@ -63,14 +63,7 @@ def formsemestre_saisie_jury_but(
# raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
if formsemestre2.formation.referentiel_competence is None:
raise ScoValueError(
"""
<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>
"""
)
raise ScoNoReferentielCompetences(formation=formsemestre2.formation)
rows, titles, column_ids, jury_stats = get_jury_but_table(
formsemestre2, read_only=read_only, mode=mode

View File

@ -1,6 +1,6 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
@ -15,12 +15,19 @@ from app.scodoc import sco_cache
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.
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).
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
(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"
"""
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
nb_admis += 1
if deca.admis or not only_adm:
deca.record_all()
deca.record_all(no_overwrite=no_overwrite)
db.session.commit()
return nb_admis

View File

@ -1,6 +1,6 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
@ -30,6 +30,7 @@ from app.models import (
Identite,
UniteEns,
ScolarAutorisationInscription,
ScolarFormSemestreValidation,
)
from app.scodoc import html_sco_header
from app.scodoc.sco_exceptions import ScoValueError
@ -41,32 +42,21 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
Si pas read_only, menus sélection codes jury.
"""
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">""")
if deca.jury_annuel:
H.append(
f"""
<div>
<b>Décision de jury pour l'année :</b> {
_gen_but_select("code_annee", deca.codes, deca.code_valide,
disabled=True, klass="manual")
}
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
<span>{erase_span}</span>
</div>
H.append(
f"""
<div>
<b>Décision de jury pour l'année :</b> {
_gen_but_select("code_annee", deca.codes, deca.code_valide,
disabled=True, klass="manual")
}
<span>({deca.code_valide or 'non'} enregistrée)</span>
</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 = ""
)
div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>"""
H.append("""</div>""")
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
]
ue_pair = ues[0] if ues else None
# Les UEs à afficher, toujours en readonly
# sur le formsemestre de l'année précédente du redoublant
# Les UEs à afficher,
# qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
ues_ro = [
(
ue_impair,
@ -143,6 +133,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
deca.decisions_ues[ue.id],
disabled=read_only or ue_read_only,
annee_prec=ue_read_only,
niveau_id=ue.niveau_competence.id,
)
)
else:
@ -161,6 +152,7 @@ def _gen_but_select(
code_valide: str,
disabled: bool = False,
klass: str = "",
data: dict = {},
) -> str:
"Le menu html select avec les codes"
# 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}"
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);"
{"disabled" if disabled else ""}
{" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
>{options_htm}</select>
"""
@ -187,6 +182,7 @@ def _gen_but_niveau_ue(
dec_ue: DecisionsProposeesUE,
disabled: bool = False,
annee_prec: bool = False,
niveau_id: int = None,
) -> str:
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
moy_ue_str = f"""<span class="ue_cap">{
@ -207,7 +203,14 @@ def _gen_but_niveau_ue(
"""
else:
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
scoplement = ""
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 = ""
return f"""<div class="but_niveau_ue {
'recorded' if dec_ue.code_valide is not None else ''}
@ -221,7 +224,9 @@ def _gen_but_niveau_ue(
<div class="but_code">{
_gen_but_select("code_ue_"+str(ue.id),
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>
@ -245,21 +250,29 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
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"""
<div class="but_niveau_rcue
{'recorded' if dec_rcue.code_valide is not None else ''}
<div class="but_niveau_rcue {niveau_rcue_class}
">
<div class="but_note with_scoplement">
<div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
{scoplement}
</div>
<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.code_valide,
disabled=True, klass="manual"
disabled=True,
klass="manual code_rcue",
data = { "niveau_id" : str(niveau.id)}
)}
</div>
</div>
</div>
"""
@ -278,17 +291,15 @@ def jury_but_semestriel(
semestre_terminal = (
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,
# ou si décision déjà enregistrée:
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
formsemestre.semestre_id + 1
) in (
a.semestre_id
for a in ScolarAutorisationInscription.query.filter_by(
etudid=etud.id,
origin_formsemestre_id=formsemestre.id,
)
)
) in (a.semestre_id for a in autorisations_passage)
decisions_ues = {
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
for ue in ues
@ -312,7 +323,9 @@ def jury_but_semestriel(
flash("codes enregistrés")
if not semestre_terminal:
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(
etud.id,
formsemestre.formation.formation_code,
@ -351,7 +364,7 @@ def jury_but_semestriel(
warning = ""
H = [
html_sco_header.sco_header(
page_title="Validation BUT",
page_title=f"Validation BUT S{formsemestre.semestre_id}",
formsemestre_id=formsemestre.id,
etudid=etud.id,
cssstyles=("css/jury_but.css",),
@ -372,25 +385,35 @@ def jury_but_semestriel(
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
</div>
</div>
<h3>Jury sur un semestre BUT isolé</h3>
<h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
{warning}
</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 = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
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>"""
else:
erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre."
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="{
url_for("notes.formsemestre_jury_but_erase",
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>"""
else:
erase_span = (
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
)
H.append(
f"""
<div class="but_section_annee">
<span>{erase_span}</span>
</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" {
"checked" if est_autorise_a_passer else ""}>
<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>
</div>
"""
@ -447,9 +473,10 @@ def jury_but_semestriel(
else:
H.append("""<div class="help">dernier semestre de la formation.</div>""")
H.append(
"""
f"""
<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>
"""
)

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
@ -14,7 +14,7 @@ import sqlalchemy
from app import db
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
@ -54,13 +54,15 @@ class ApcReferentielCompetences(db.Model, XMLModel):
"Référentiel de compétence d'une spécialité"
id = db.Column(db.Integer, primary_key=True)
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
annexe = db.Column(db.Text())
specialite = db.Column(db.Text())
specialite_long = db.Column(db.Text())
type_titre = db.Column(db.Text())
type_structure = db.Column(db.Text())
annexe = db.Column(db.Text()) # '1', '22', ...
specialite = db.Column(db.Text()) # 'CJ', 'RT', 'INFO', ...
specialite_long = 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"
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
"type": "type_titre",
"version": "version_orebut",
@ -322,9 +324,8 @@ class ApcNiveau(db.Model, XMLModel):
if annee not in {1, 2, 3}:
raise ValueError("annee invalide pour un parcours BUT")
if referentiel_competence is None:
raise ScoValueError(
"Pas de référentiel de compétences associé à la formation !"
)
raise ScoNoReferentielCompetences()
annee_formation = f"BUT{annee}"
if parcour is None:
return ApcNiveau.query.filter(

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,9 @@ class Module(db.Model):
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
# 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)
tags = db.relationship(
"NotesTag",

View File

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

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by
@ -105,13 +105,13 @@ class BaseArchiver(object):
try:
scu.GSL.acquire()
if not os.path.isdir(path):
log("creating directory %s" % path)
log(f"creating directory {path}")
os.mkdir(path)
finally:
scu.GSL.release()
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).
If directory does not yet exist, create it.
@ -142,7 +142,7 @@ class BaseArchiver(object):
dirs = glob.glob(base + "*")
return [os.path.split(x)[1] for x in dirs]
def list_obj_archives(self, oid):
def list_obj_archives(self, oid: int):
"""Returns
:return: list of archive identifiers for this object (paths to non empty dirs)
"""
@ -157,7 +157,7 @@ class BaseArchiver(object):
dirs.sort()
return dirs
def delete_archive(self, archive_id):
def delete_archive(self, archive_id: str):
"""Delete (forever) this archive"""
self.initialize()
try:
@ -166,7 +166,7 @@ class BaseArchiver(object):
finally:
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"""
return datetime.datetime(
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
@ -183,17 +183,17 @@ class BaseArchiver(object):
files.sort()
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"""
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."""
return re.match(
"^[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)"""
self.initialize()
if not self.is_valid_archive_name(archive_name):
@ -206,7 +206,7 @@ class BaseArchiver(object):
raise ScoValueError(f"Archive {archive_name} introuvable")
return archive_id
def get_archive_description(self, archive_id):
def get_archive_description(self, archive_id: str) -> str:
"""Return description of archive"""
self.initialize()
filename = os.path.join(archive_id, "_description.txt")
@ -247,7 +247,7 @@ class BaseArchiver(object):
data = data.encode(scu.SCO_ENCODING)
self.initialize()
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:
scu.GSL.acquire()
fname = os.path.join(archive_id, filename)
@ -261,16 +261,18 @@ class BaseArchiver(object):
"""Retreive data"""
self.initialize()
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 ?)")
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:
data = f.read()
return data
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)
data = self.get(archive_id, filename)
mime = mimetypes.guess_type(filename)[0]

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# 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
try:
ects_txt = str(int(ue["ects"]))
except (ValueError, KeyError):
except (ValueError, KeyError, TypeError):
ects_txt = "-"
t = {

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by
@ -205,6 +205,20 @@ BUT_CODES_PASSAGE = {
PAS1NCI,
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:

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -4,7 +4,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -2,7 +2,7 @@
#
# 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
# it under the terms of the GNU General Public License as published by

View File

@ -5,7 +5,7 @@
#
# 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
# 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