Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into table
This commit is contained in:
commit
83b47db3dc
@ -550,3 +550,22 @@ def scodoc_flash_status_messages():
|
|||||||
f"Mode test: mails redirigés vers {email_test_mode_address}",
|
f"Mode test: mails redirigés vers {email_test_mode_address}",
|
||||||
category="warning",
|
category="warning",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def critical_error(msg):
|
||||||
|
"""Handle a critical error: flush all caches, display message to the user"""
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
log(f"\n*** CRITICAL ERROR: {msg}")
|
||||||
|
send_scodoc_alarm(f"CRITICAL ERROR: {msg}", msg)
|
||||||
|
clear_scodoc_cache()
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""
|
||||||
|
Une erreur est survenue.
|
||||||
|
|
||||||
|
Si le problème persiste, merci de contacter le support ScoDoc via
|
||||||
|
{scu.SCO_DISCORD_ASSISTANCE}
|
||||||
|
|
||||||
|
{msg}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
@ -19,6 +19,7 @@ from app.models import FormSemestre, FormSemestreInscription, Identite
|
|||||||
from app.models import GroupDescr, Partition
|
from app.models import GroupDescr, Partition
|
||||||
from app.models.groups import group_membership
|
from app.models.groups import group_membership
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -170,24 +171,15 @@ def set_etud_group(etudid: int, group_id: int):
|
|||||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
group = query.first_or_404()
|
group = query.first_or_404()
|
||||||
|
if not group.partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
|
||||||
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
return json_error(404, "etud non inscrit au formsemestre du groupe")
|
||||||
groups = (
|
|
||||||
GroupDescr.query.filter_by(partition_id=group.partition.id)
|
sco_groups.change_etud_group_in_partition(
|
||||||
.join(group_membership)
|
etudid, group_id, group.partition.to_dict()
|
||||||
.filter_by(etudid=etudid)
|
|
||||||
)
|
)
|
||||||
ok = False
|
|
||||||
for other_group in groups:
|
|
||||||
if other_group.id == group_id:
|
|
||||||
ok = True
|
|
||||||
else:
|
|
||||||
other_group.etuds.remove(etud)
|
|
||||||
if not ok:
|
|
||||||
group.etuds.append(etud)
|
|
||||||
log(f"set_etud_group({etud}, {group})")
|
|
||||||
db.session.commit()
|
|
||||||
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
|
||||||
return jsonify({"group_id": group_id, "etudid": etudid})
|
return jsonify({"group_id": group_id, "etudid": etudid})
|
||||||
|
|
||||||
|
|
||||||
@ -207,6 +199,8 @@ def group_remove_etud(group_id: int, etudid: int):
|
|||||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
group = query.first_or_404()
|
group = query.first_or_404()
|
||||||
|
if not group.partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if etud in group.etuds:
|
if etud in group.etuds:
|
||||||
group.etuds.remove(etud)
|
group.etuds.remove(etud)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -232,6 +226,8 @@ def partition_remove_etud(partition_id: int, etudid: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
partition = query.first_or_404()
|
partition = query.first_or_404()
|
||||||
|
if not partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
groups = (
|
groups = (
|
||||||
GroupDescr.query.filter_by(partition_id=partition_id)
|
GroupDescr.query.filter_by(partition_id=partition_id)
|
||||||
.join(group_membership)
|
.join(group_membership)
|
||||||
@ -262,8 +258,10 @@ def group_create(partition_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
partition: Partition = query.first_or_404()
|
partition: Partition = query.first_or_404()
|
||||||
|
if not partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.groups_editable:
|
if not partition.groups_editable:
|
||||||
return json_error(404, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
group_name = data.get("group_name")
|
group_name = data.get("group_name")
|
||||||
if group_name is None:
|
if group_name is None:
|
||||||
@ -294,8 +292,10 @@ def group_delete(group_id: int):
|
|||||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
group: GroupDescr = query.first_or_404()
|
group: GroupDescr = query.first_or_404()
|
||||||
|
if not group.partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return json_error(404, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
formsemestre_id = group.partition.formsemestre_id
|
formsemestre_id = group.partition.formsemestre_id
|
||||||
log(f"deleting {group}")
|
log(f"deleting {group}")
|
||||||
db.session.delete(group)
|
db.session.delete(group)
|
||||||
@ -318,8 +318,10 @@ def group_edit(group_id: int):
|
|||||||
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
)
|
)
|
||||||
group: GroupDescr = query.first_or_404()
|
group: GroupDescr = query.first_or_404()
|
||||||
|
if not group.partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not group.partition.groups_editable:
|
if not group.partition.groups_editable:
|
||||||
return json_error(404, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
group_name = data.get("group_name")
|
group_name = data.get("group_name")
|
||||||
if group_name is not None:
|
if group_name is not None:
|
||||||
@ -358,6 +360,8 @@ def partition_create(formsemestre_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
|
if not formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
partition_name = data.get("partition_name")
|
partition_name = data.get("partition_name")
|
||||||
if partition_name is None:
|
if partition_name is None:
|
||||||
@ -406,6 +410,8 @@ def formsemestre_order_partitions(formsemestre_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
query = query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||||
|
if not formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
partition_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if not isinstance(partition_ids, int) and not all(
|
if not isinstance(partition_ids, int) and not all(
|
||||||
isinstance(x, int) for x in partition_ids
|
isinstance(x, int) for x in partition_ids
|
||||||
@ -443,6 +449,8 @@ def partition_order_groups(partition_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
partition: Partition = query.first_or_404()
|
partition: Partition = query.first_or_404()
|
||||||
|
if not partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
group_ids = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if not isinstance(group_ids, int) and not all(
|
if not isinstance(group_ids, int) and not all(
|
||||||
isinstance(x, int) for x in group_ids
|
isinstance(x, int) for x in group_ids
|
||||||
@ -484,6 +492,8 @@ def partition_edit(partition_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
partition: Partition = query.first_or_404()
|
partition: Partition = query.first_or_404()
|
||||||
|
if not partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
data = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
modified = False
|
modified = False
|
||||||
partition_name = data.get("partition_name")
|
partition_name = data.get("partition_name")
|
||||||
@ -542,6 +552,8 @@ def partition_delete(partition_id: int):
|
|||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
partition: Partition = query.first_or_404()
|
partition: Partition = query.first_or_404()
|
||||||
|
if not partition.formsemestre.etat:
|
||||||
|
return json_error(403, "formsemestre verrouillé")
|
||||||
if not partition.partition_name:
|
if not partition.partition_name:
|
||||||
return json_error(404, "ne peut pas supprimer la partition par défaut")
|
return json_error(404, "ne peut pas supprimer la partition par défaut")
|
||||||
is_parcours = partition.is_parcours()
|
is_parcours = partition.is_parcours()
|
||||||
|
@ -102,7 +102,7 @@ class EtudCursusBUT:
|
|||||||
)
|
)
|
||||||
"Liste des inscriptions aux sem. de la formation, triées par indice et chronologie"
|
"Liste des inscriptions aux sem. de la formation, triées par indice et chronologie"
|
||||||
self.parcour: ApcParcours = self.inscriptions[-1].parcour
|
self.parcour: ApcParcours = self.inscriptions[-1].parcour
|
||||||
"Le parcour à valider: celui du DERNIER semestre suivi (peut être None)"
|
"Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
|
||||||
self.niveaux_by_annee = {}
|
self.niveaux_by_annee = {}
|
||||||
"{ annee : liste des niveaux à valider }"
|
"{ annee : liste des niveaux à valider }"
|
||||||
self.niveaux: dict[int, ApcNiveau] = {}
|
self.niveaux: dict[int, ApcNiveau] = {}
|
||||||
@ -142,7 +142,7 @@ class EtudCursusBUT:
|
|||||||
self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||||
previous_validation = self.validation_par_competence_et_annee.get(
|
previous_validation = self.validation_par_competence_et_annee.get(
|
||||||
niveau.competence.id
|
niveau.competence.id
|
||||||
)
|
).get(validation_rcue.annee())
|
||||||
# prend la "meilleure" validation
|
# prend la "meilleure" validation
|
||||||
if (not previous_validation) or (
|
if (not previous_validation) or (
|
||||||
sco_codes.BUT_CODES_ORDERED[validation_rcue.code]
|
sco_codes.BUT_CODES_ORDERED[validation_rcue.code]
|
||||||
|
@ -693,20 +693,20 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False) -> bool:
|
||||||
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
"""Enregistre le code de l'année, et au besoin l'autorisation d'inscription.
|
||||||
Si no_overwrite, ne fait rien si un code est déjà enregistré.
|
Si no_overwrite, ne fait rien si un code est déjà enregistré.
|
||||||
Si l'étudiant est DEM ou DEF, ne fait rien.
|
Si l'étudiant est DEM ou DEF, ne fait rien.
|
||||||
"""
|
"""
|
||||||
if self.inscription_etat != scu.INSCRIT:
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
return
|
return False
|
||||||
if code and not code in self.codes:
|
if code and not code in self.codes:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}"
|
f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}"
|
||||||
)
|
)
|
||||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
return # no change
|
return False # no change
|
||||||
if self.validation:
|
if self.validation:
|
||||||
db.session.delete(self.validation)
|
db.session.delete(self.validation)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -746,9 +746,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
next_semestre_id,
|
next_semestre_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.recorded = True
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
self.recorded = True
|
||||||
self.invalidate_formsemestre_cache()
|
self.invalidate_formsemestre_cache()
|
||||||
|
return True
|
||||||
|
|
||||||
def invalidate_formsemestre_cache(self):
|
def invalidate_formsemestre_cache(self):
|
||||||
"invalide le résultats des deux formsemestres"
|
"invalide le résultats des deux formsemestres"
|
||||||
@ -759,13 +760,20 @@ 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, no_overwrite: bool = True):
|
def record_all(
|
||||||
|
self, no_overwrite: bool = True, only_validantes: bool = False
|
||||||
|
) -> bool:
|
||||||
"""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.
|
- 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.
|
- Pour les RCUE: n'enregistre que si la nouvelle décision est plus favorable que l'ancienne.
|
||||||
|
|
||||||
|
Si only_validantes, n'enregistre que des décisions "validantes" de droit: ADM ou CMP.
|
||||||
|
|
||||||
|
Return: True si au moins un code modifié et enregistré.
|
||||||
"""
|
"""
|
||||||
# Toujours valider dans l'ordre UE, RCUE, Année:
|
modif = False
|
||||||
|
# Toujours valider dans l'ordre UE, RCUE, Année
|
||||||
annee_scolaire = self.formsemestre.annee_scolaire()
|
annee_scolaire = self.formsemestre.annee_scolaire()
|
||||||
# UEs
|
# UEs
|
||||||
for dec_ue in self.decisions_ues.values():
|
for dec_ue in self.decisions_ues.values():
|
||||||
@ -774,25 +782,40 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
|
) 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_ue.codes[0] if dec_ue.codes else None
|
code = dec_ue.codes[0] if dec_ue.codes else None
|
||||||
|
if (not only_validantes) or code in sco_codes.CODES_UE_VALIDES_DE_DROIT:
|
||||||
# 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
|
||||||
# (no_overwrite=True) sauf en mode test yaml
|
# (no_overwrite=True) sauf en mode test yaml
|
||||||
dec_ue.record(code, no_overwrite=no_overwrite)
|
modif |= dec_ue.record(code, no_overwrite=no_overwrite)
|
||||||
# RCUE : enregistre seulement si pas déjà validé "mieux"
|
# RCUE :
|
||||||
for dec_rcue in self.decisions_rcue_by_niveau.values():
|
for dec_rcue in self.decisions_rcue_by_niveau.values():
|
||||||
code = dec_rcue.codes[0] if dec_rcue.codes else None
|
code = dec_rcue.codes[0] if dec_rcue.codes else None
|
||||||
if (not dec_rcue.recorded) and (
|
if (
|
||||||
|
(not dec_rcue.recorded)
|
||||||
|
and ( # enregistre seulement si pas déjà validé "mieux"
|
||||||
(not dec_rcue.validation)
|
(not dec_rcue.validation)
|
||||||
or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0)
|
or BUT_CODES_ORDERED.get(dec_rcue.validation.code, 0)
|
||||||
< BUT_CODES_ORDERED.get(code, 0)
|
< BUT_CODES_ORDERED.get(code, 0)
|
||||||
|
)
|
||||||
|
and ( # décision validante de droit ?
|
||||||
|
(
|
||||||
|
(not only_validantes)
|
||||||
|
or code in sco_codes.CODES_RCUE_VALIDES_DE_DROIT
|
||||||
|
)
|
||||||
|
)
|
||||||
):
|
):
|
||||||
dec_rcue.record(code, no_overwrite=no_overwrite)
|
modif |= dec_rcue.record(code, no_overwrite=no_overwrite)
|
||||||
# Année:
|
# Année:
|
||||||
if not self.recorded:
|
if not self.recorded:
|
||||||
# rappel: le code par défaut est en tête
|
# rappel: le code par défaut est en tête
|
||||||
code = self.codes[0] if self.codes else None
|
code = self.codes[0] if self.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
|
||||||
# (no_overwrite=True) sauf en mode test yaml
|
# (no_overwrite=True) sauf en mode test yaml
|
||||||
self.record(code, no_overwrite=no_overwrite)
|
if (
|
||||||
|
not only_validantes
|
||||||
|
) or code in sco_codes.CODES_ANNEE_BUT_VALIDES_DE_DROIT:
|
||||||
|
modif |= self.record(code, no_overwrite=no_overwrite)
|
||||||
|
|
||||||
|
return modif
|
||||||
|
|
||||||
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
|
||||||
@ -1005,23 +1028,23 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide
|
return f"""<{self.__class__.__name__} rcue={self.rcue} valid={self.code_valide
|
||||||
} codes={self.codes} explanation={self.explanation}"""
|
} codes={self.codes} explanation={self.explanation}"""
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False) -> bool:
|
||||||
"""Enregistre le code RCUE.
|
"""Enregistre le code RCUE.
|
||||||
Note:
|
Note:
|
||||||
- si le RCUE est ADJ, les UE non validées sont passées à ADJ
|
- 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
|
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 False # pas de RCUE a enregistrer
|
||||||
if self.inscription_etat != scu.INSCRIT:
|
if self.inscription_etat != scu.INSCRIT:
|
||||||
return
|
return False
|
||||||
if code and not code in self.codes:
|
if code and not code in self.codes:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}"
|
f"code UE invalide pour ue_id={self.ue.id}: {html.escape(code)}"
|
||||||
)
|
)
|
||||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
return # no change
|
return False # no change
|
||||||
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)
|
||||||
@ -1057,7 +1080,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
flash(
|
flash(
|
||||||
f"""UEs du RCUE "{dec_ue.ue.niveau_competence.competence.titre}" passées en ADJR"""
|
f"""UEs du RCUE "{dec_ue.ue.niveau_competence.competence.titre}" passées en ADJR"""
|
||||||
)
|
)
|
||||||
dec_ue.record("ADJR")
|
dec_ue.record(sco_codes.ADJR)
|
||||||
|
|
||||||
# Valide les niveaux inférieurs de la compétence (code ADSUP)
|
# Valide les niveaux inférieurs de la compétence (code ADSUP)
|
||||||
# TODO
|
# TODO
|
||||||
@ -1072,6 +1095,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
self.code_valide = code # mise à jour état
|
self.code_valide = code # mise à jour état
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
return True
|
||||||
|
|
||||||
def erase(self):
|
def erase(self):
|
||||||
"""Efface la décision de jury de cet étudiant pour cet RCUE"""
|
"""Efface la décision de jury de cet étudiant pour cet RCUE"""
|
||||||
@ -1203,9 +1227,10 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
||||||
self.explanation = "notes insuffisantes"
|
self.explanation = "notes insuffisantes"
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False) -> bool:
|
||||||
"""Enregistre le code jury pour cette UE.
|
"""Enregistre le code jury pour cette UE.
|
||||||
Si no_overwrite, n'enregistre pas s'il y a déjà un code.
|
Si no_overwrite, n'enregistre pas s'il y a déjà un code.
|
||||||
|
Return: True si code enregistré (modifié)
|
||||||
"""
|
"""
|
||||||
if code and not code in self.codes:
|
if code and not code in self.codes:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
@ -1213,7 +1238,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
if code == self.code_valide or (self.code_valide is not None and no_overwrite):
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
return # no change
|
return False # no change
|
||||||
self.erase()
|
self.erase()
|
||||||
if code is None:
|
if code is None:
|
||||||
self.validation = None
|
self.validation = None
|
||||||
@ -1244,6 +1269,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
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.code_valide = code # mise à jour
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
return True
|
||||||
|
|
||||||
def erase(self):
|
def erase(self):
|
||||||
"""Efface la décision de jury de cet étudiant pour cette UE"""
|
"""Efface la décision de jury de cet étudiant pour cette UE"""
|
||||||
|
@ -284,6 +284,10 @@ class RowCollector:
|
|||||||
self["_nom_disp_order"] = etud.sort_key
|
self["_nom_disp_order"] = etud.sort_key
|
||||||
self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail")
|
self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail")
|
||||||
self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court")
|
self.add_cell("nom_short", "Nom", etud.nom_short, "identite_court")
|
||||||
|
self["_nom_short_data"] = {
|
||||||
|
"etudid": etud.id,
|
||||||
|
"nomprenom": etud.nomprenom,
|
||||||
|
}
|
||||||
if with_links:
|
if with_links:
|
||||||
self["_nom_short_order"] = etud.sort_key
|
self["_nom_short_order"] = etud.sort_key
|
||||||
self["_nom_short_target"] = url_for(
|
self["_nom_short_target"] = url_for(
|
||||||
@ -368,10 +372,6 @@ class RowCollector:
|
|||||||
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
+ ((" " + scu.EMO_WARNING) if deca.nb_rcues_under_8 > 0 else ""),
|
||||||
"col_rcue col_rcues_validables" + klass,
|
"col_rcue col_rcues_validables" + klass,
|
||||||
)
|
)
|
||||||
self["_rcues_validables_data"] = {
|
|
||||||
"etudid": deca.etud.id,
|
|
||||||
"nomprenom": deca.etud.nomprenom,
|
|
||||||
}
|
|
||||||
if len(deca.rcues_annee) > 0:
|
if len(deca.rcues_annee) > 0:
|
||||||
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
||||||
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
||||||
|
@ -18,29 +18,29 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
def formsemestre_validation_auto_but(
|
def formsemestre_validation_auto_but(
|
||||||
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
|
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
|
||||||
) -> int:
|
) -> 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
|
|
||||||
|
- N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||||
si on a des RCUE "à cheval".
|
si on a des RCUE "à cheval".
|
||||||
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
|
- Ne modifie jamais de décisions déjà enregistrées (sauf si no_overwrite est faux,
|
||||||
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
ce qui est utilisé pour certains tests unitaires).
|
||||||
(mode à n'utiliser que pour les tests)
|
- Normalement, only_adm est True et on n'enregistre que les décisions validantes
|
||||||
|
de droit: ADM ou CMP.
|
||||||
|
En revanche, si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
||||||
|
(mode à n'utiliser que pour les tests unitaires vérifiant la saisie des jurys)
|
||||||
|
|
||||||
Si no_overwrite est vrai (défaut), ne ré-écrit jamais les codes déjà enregistrés
|
Returns: nombre d'étudiants pour lesquels on a enregistré au moins un code.
|
||||||
(utiliser faux pour certains tests)
|
|
||||||
|
|
||||||
Returns: nombre d'étudiants "admis"
|
|
||||||
"""
|
"""
|
||||||
if not formsemestre.formation.is_apc():
|
if not formsemestre.formation.is_apc():
|
||||||
raise ScoValueError("fonction réservée aux formations BUT")
|
raise ScoValueError("fonction réservée aux formations BUT")
|
||||||
nb_admis = 0
|
nb_etud_modif = 0
|
||||||
with sco_cache.DeferredSemCacheManager():
|
with sco_cache.DeferredSemCacheManager():
|
||||||
for etudid in formsemestre.etuds_inscriptions:
|
for etudid in formsemestre.etuds_inscriptions:
|
||||||
etud: Identite = Identite.query.get(etudid)
|
etud: Identite = Identite.query.get(etudid)
|
||||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
if deca.admis: # année réussie
|
nb_etud_modif += deca.record_all(
|
||||||
nb_admis += 1
|
no_overwrite=no_overwrite, only_validantes=only_adm
|
||||||
if deca.admis or not only_adm:
|
)
|
||||||
deca.record_all(no_overwrite=no_overwrite)
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return nb_admis
|
return nb_etud_modif
|
||||||
|
@ -39,6 +39,7 @@ from dataclasses import dataclass
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Evaluation, EvaluationUEPoids, ModuleImpl
|
from app.models import Evaluation, EvaluationUEPoids, ModuleImpl
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -484,7 +485,8 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
if nb_etuds == 0:
|
if nb_etuds == 0:
|
||||||
return pd.Series()
|
return pd.Series()
|
||||||
evals_coefs = self.get_evaluations_coefs(modimpl).reshape(-1)
|
evals_coefs = self.get_evaluations_coefs(modimpl).reshape(-1)
|
||||||
assert evals_coefs.shape == (nb_evals,)
|
if evals_coefs.shape != (nb_evals,):
|
||||||
|
app.critical_error("compute_module_moy: vals_coefs.shape != nb_evals")
|
||||||
evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
|
evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
|
||||||
# Les coefs des évals pour chaque étudiant: là où il a des notes
|
# Les coefs des évals pour chaque étudiant: là où il a des notes
|
||||||
# non neutralisées
|
# non neutralisées
|
||||||
|
@ -553,7 +553,11 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"Nom",
|
"Nom",
|
||||||
etud.nom_short,
|
etud.nom_short,
|
||||||
"identite_court",
|
"identite_court",
|
||||||
data={"order": etud.sort_key},
|
data={
|
||||||
|
"order": etud.sort_key,
|
||||||
|
"etudid": etud.id,
|
||||||
|
"nomprenom": etud.nomprenom,
|
||||||
|
},
|
||||||
target=url_bulletin,
|
target=url_bulletin,
|
||||||
target_attrs=f'class="etudinfo" id="{etudid}"',
|
target_attrs=f'class="etudinfo" id="{etudid}"',
|
||||||
)
|
)
|
||||||
|
@ -71,6 +71,11 @@ class ApcValidationRCUE(db.Model):
|
|||||||
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}
|
<em>enregistrée le {self.date.strftime("%d/%m/%Y")}
|
||||||
à {self.date.strftime("%Hh%M")}</em>"""
|
à {self.date.strftime("%Hh%M")}</em>"""
|
||||||
|
|
||||||
|
def annee(self) -> str:
|
||||||
|
"""l'année BUT concernée: "BUT1", "BUT2" ou "BUT3" """
|
||||||
|
niveau = self.niveau()
|
||||||
|
return niveau.annee if niveau else None
|
||||||
|
|
||||||
def niveau(self) -> ApcNiveau:
|
def niveau(self) -> ApcNiveau:
|
||||||
"""Le niveau de compétence associé à cet RCUE."""
|
"""Le niveau de compétence associé à cet RCUE."""
|
||||||
# Par convention, il est donné par la seconde UE
|
# Par convention, il est donné par la seconde UE
|
||||||
|
@ -63,51 +63,51 @@ class FormSemestre(db.Model):
|
|||||||
"False si verrouillé"
|
"False si verrouillé"
|
||||||
modalite = db.Column(
|
modalite = db.Column(
|
||||||
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite")
|
||||||
) # "FI", "FAP", "FC", ...
|
)
|
||||||
# gestion compensation sem DUT:
|
"Modalité de formation: 'FI', 'FAP', 'FC', ..."
|
||||||
gestion_compensation = db.Column(
|
gestion_compensation = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# ne publie pas le bulletin XML ou JSON:
|
"gestion compensation sem DUT (inutilisé en APC)"
|
||||||
bul_hide_xml = db.Column(
|
bul_hide_xml = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# Bloque le calcul des moyennes (générale et d'UE)
|
"ne publie pas le bulletin XML ou JSON"
|
||||||
block_moyennes = db.Column(
|
block_moyennes = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# Bloque le calcul de la moyenne générale (utile pour BUT)
|
"Bloque le calcul des moyennes (générale et d'UE)"
|
||||||
block_moyenne_generale = db.Column(
|
block_moyenne_generale = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
||||||
# semestres decales (pour gestion jurys):
|
|
||||||
gestion_semestrielle = db.Column(
|
gestion_semestrielle = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# couleur fond bulletins HTML:
|
"Semestres décalés (pour gestion jurys DUT, pas implémenté ou utile en BUT)"
|
||||||
bul_bgcolor = db.Column(
|
bul_bgcolor = db.Column(
|
||||||
db.String(SHORT_STR_LEN),
|
db.String(SHORT_STR_LEN),
|
||||||
default="white",
|
default="white",
|
||||||
server_default="white",
|
server_default="white",
|
||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
# autorise resp. a modifier semestre:
|
"couleur fond bulletins HTML"
|
||||||
resp_can_edit = db.Column(
|
resp_can_edit = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# autorise resp. a modifier slt les enseignants:
|
"autorise resp. à modifier le formsemestre"
|
||||||
resp_can_change_ens = db.Column(
|
resp_can_change_ens = db.Column(
|
||||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||||
)
|
)
|
||||||
# autorise les ens a creer des evals:
|
"autorise resp. a modifier slt les enseignants"
|
||||||
ens_can_edit_eval = db.Column(
|
ens_can_edit_eval = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="False"
|
db.Boolean(), nullable=False, default=False, server_default="False"
|
||||||
)
|
)
|
||||||
# code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'
|
"autorise les enseignants à créer des évals dans leurs modimpls"
|
||||||
elt_sem_apo = db.Column(db.Text()) # peut être fort long !
|
elt_sem_apo = db.Column(db.Text()) # peut être fort long !
|
||||||
# code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'
|
"code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'"
|
||||||
elt_annee_apo = db.Column(db.Text())
|
elt_annee_apo = db.Column(db.Text())
|
||||||
|
"code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'"
|
||||||
|
|
||||||
# Relations:
|
# Relations:
|
||||||
etapes = db.relationship(
|
etapes = db.relationship(
|
||||||
|
@ -87,6 +87,7 @@ class Partition(db.Model):
|
|||||||
def to_dict(self, with_groups=False) -> dict:
|
def to_dict(self, with_groups=False) -> dict:
|
||||||
"""as a dict, with or without groups"""
|
"""as a dict, with or without groups"""
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
|
d["partition_id"] = self.id
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
d.pop("formsemestre", None)
|
d.pop("formsemestre", None)
|
||||||
|
|
||||||
|
@ -219,6 +219,8 @@ class UniteEns(db.Model):
|
|||||||
|
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
# Invalidation du cache
|
||||||
|
self.formation.invalidate_cached_sems()
|
||||||
log(f"ue.set_niveau_competence( {self}, {niveau} )")
|
log(f"ue.set_niveau_competence( {self}, {niveau} )")
|
||||||
|
|
||||||
def set_parcour(self, parcour: ApcParcours):
|
def set_parcour(self, parcour: ApcParcours):
|
||||||
@ -246,6 +248,8 @@ class UniteEns(db.Model):
|
|||||||
self.niveau_competence = None
|
self.niveau_competence = None
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
# Invalidation du cache
|
||||||
|
self.formation.invalidate_cached_sems()
|
||||||
log(f"ue.set_parcour( {self}, {parcour} )")
|
log(f"ue.set_parcour( {self}, {parcour} )")
|
||||||
|
|
||||||
|
|
||||||
|
@ -459,8 +459,7 @@ class JuryPE(object):
|
|||||||
etud = self.get_cache_etudInfo_d_un_etudiant(etudid)
|
etud = self.get_cache_etudInfo_d_un_etudiant(etudid)
|
||||||
(_, parcours) = sco_report.get_codeparcoursetud(etud)
|
(_, parcours) = sco_report.get_codeparcoursetud(etud)
|
||||||
if (
|
if (
|
||||||
len(set(sco_codes_parcours.CODES_SEM_REO.keys()) & set(parcours.values()))
|
len(sco_codes_parcours.CODES_SEM_REO & set(parcours.values())) > 0
|
||||||
> 0
|
|
||||||
): # Eliminé car NAR apparait dans le parcours
|
): # Eliminé car NAR apparait dans le parcours
|
||||||
reponse = True
|
reponse = True
|
||||||
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
|
if pe_tools.PE_DEBUG and pe_tools.PE_DEBUG >= 2:
|
||||||
@ -563,9 +562,8 @@ class JuryPE(object):
|
|||||||
dec = nt.get_etud_decision_sem(
|
dec = nt.get_etud_decision_sem(
|
||||||
etudid
|
etudid
|
||||||
) # quelle est la décision du jury ?
|
) # quelle est la décision du jury ?
|
||||||
if dec and dec["code"] in list(
|
if dec and (dec["code"] in sco_codes_parcours.CODES_SEM_VALIDES):
|
||||||
sco_codes_parcours.CODES_SEM_VALIDES.keys()
|
# isinstance( sesMoyennes[i+1], float) and
|
||||||
): # isinstance( sesMoyennes[i+1], float) and
|
|
||||||
# mT = sesMoyennes[i+1] # substitue la moyenne si le semestre suivant est "valide"
|
# mT = sesMoyennes[i+1] # substitue la moyenne si le semestre suivant est "valide"
|
||||||
leFid = sem["formsemestre_id"]
|
leFid = sem["formsemestre_id"]
|
||||||
else:
|
else:
|
||||||
|
@ -187,20 +187,23 @@ CODES_EXPL = {
|
|||||||
|
|
||||||
# Les codes de semestres:
|
# Les codes de semestres:
|
||||||
CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT}
|
CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT}
|
||||||
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé
|
CODES_SEM_VALIDES_DE_DROIT = {ADM, ADC}
|
||||||
CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
CODES_SEM_VALIDES = CODES_SEM_VALIDES_DE_DROIT | {ADJ} # semestre validé
|
||||||
|
CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente
|
||||||
|
|
||||||
CODES_SEM_REO = {NAR: 1} # reorientation
|
CODES_SEM_REO = {NAR} # reorientation
|
||||||
|
|
||||||
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True, ADJR: True}
|
CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit"
|
||||||
|
CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR}
|
||||||
"UE validée"
|
"UE validée"
|
||||||
|
|
||||||
CODES_RCUE_VALIDES = {ADM, CMP, ADJ}
|
CODES_RCUE_VALIDES_DE_DROIT = {ADM, CMP}
|
||||||
|
CODES_RCUE_VALIDES = CODES_RCUE_VALIDES_DE_DROIT | {ADJ}
|
||||||
"Niveau RCUE validé"
|
"Niveau RCUE validé"
|
||||||
|
|
||||||
# Pour le BUT:
|
# Pour le BUT:
|
||||||
|
CODES_ANNEE_BUT_VALIDES_DE_DROIT = {ADM, PASD}
|
||||||
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
|
||||||
CODES_RCUE = {ADM, AJ, CMP}
|
|
||||||
BUT_BARRE_UE8 = 8.0 - NOTES_TOLERANCE
|
BUT_BARRE_UE8 = 8.0 - NOTES_TOLERANCE
|
||||||
BUT_BARRE_UE = BUT_BARRE_RCUE = 10.0 - NOTES_TOLERANCE
|
BUT_BARRE_UE = BUT_BARRE_RCUE = 10.0 - NOTES_TOLERANCE
|
||||||
BUT_RCUE_SUFFISANT = 8.0 - NOTES_TOLERANCE
|
BUT_RCUE_SUFFISANT = 8.0 - NOTES_TOLERANCE
|
||||||
@ -230,17 +233,17 @@ BUT_CODES_ORDERED = {
|
|||||||
|
|
||||||
def code_semestre_validant(code: str) -> bool:
|
def code_semestre_validant(code: str) -> bool:
|
||||||
"Vrai si ce CODE entraine la validation du semestre"
|
"Vrai si ce CODE entraine la validation du semestre"
|
||||||
return CODES_SEM_VALIDES.get(code, False)
|
return code in CODES_SEM_VALIDES
|
||||||
|
|
||||||
|
|
||||||
def code_semestre_attente(code: str) -> bool:
|
def code_semestre_attente(code: str) -> bool:
|
||||||
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
"Vrai si ce CODE est un code d'attente (semestre validable plus tard par jury ou compensation)"
|
||||||
return CODES_SEM_ATTENTES.get(code, False)
|
return code in CODES_SEM_ATTENTES
|
||||||
|
|
||||||
|
|
||||||
def code_ue_validant(code: str) -> bool:
|
def code_ue_validant(code: str) -> bool:
|
||||||
"Vrai si ce code d'UE est validant (ie attribue les ECTS)"
|
"Vrai si ce code d'UE est validant (ie attribue les ECTS)"
|
||||||
return CODES_UE_VALIDES.get(code, False)
|
return code in CODES_UE_VALIDES
|
||||||
|
|
||||||
|
|
||||||
DEVENIR_EXPL = {
|
DEVENIR_EXPL = {
|
||||||
|
@ -890,7 +890,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
|
|||||||
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
|
car ils ne dépendent que de la note d'UE et de la validation ou non du semestre.
|
||||||
Les UE des semestres NON ASSIDUS ne sont jamais validées (code AJ).
|
Les UE des semestres NON ASSIDUS ne sont jamais validées (code AJ).
|
||||||
"""
|
"""
|
||||||
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
|
valid_semestre = code_etat_sem in CODES_SEM_VALIDES
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
@ -1185,7 +1185,10 @@ def do_formsemestre_clone(
|
|||||||
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
"""Clone a semestre: make copy, same modules, same options, same resps, same partitions.
|
||||||
New dates, responsable_id
|
New dates, responsable_id
|
||||||
"""
|
"""
|
||||||
log("cloning %s" % orig_formsemestre_id)
|
log(f"cloning orig_formsemestre_id")
|
||||||
|
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
||||||
|
orig_formsemestre_id
|
||||||
|
)
|
||||||
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# 1- create sem
|
# 1- create sem
|
||||||
@ -1196,7 +1199,8 @@ def do_formsemestre_clone(
|
|||||||
args["date_fin"] = date_fin
|
args["date_fin"] = date_fin
|
||||||
args["etat"] = 1 # non verrouillé
|
args["etat"] = 1 # non verrouillé
|
||||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
||||||
log("created formsemestre %s" % formsemestre_id)
|
log(f"created formsemestre {formsemestre_id}")
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
# 2- create moduleimpls
|
# 2- create moduleimpls
|
||||||
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
mods_orig = sco_moduleimpl.moduleimpl_list(formsemestre_id=orig_formsemestre_id)
|
||||||
for mod_orig in mods_orig:
|
for mod_orig in mods_orig:
|
||||||
@ -1258,7 +1262,12 @@ def do_formsemestre_clone(
|
|||||||
args["formsemestre_id"] = formsemestre_id
|
args["formsemestre_id"] = formsemestre_id
|
||||||
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
_ = sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, args)
|
||||||
|
|
||||||
# 5- Copy partitions and groups
|
# 6- Copie les parcours
|
||||||
|
formsemestre.parcours = formsemestre_orig.parcours
|
||||||
|
db.session.add(formsemestre)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# 7- Copy partitions and groups
|
||||||
if clone_partitions:
|
if clone_partitions:
|
||||||
sco_groups_copy.clone_partitions_and_groups(
|
sco_groups_copy.clone_partitions_and_groups(
|
||||||
orig_formsemestre_id, formsemestre_id
|
orig_formsemestre_id, formsemestre_id
|
||||||
|
@ -643,12 +643,12 @@ def formsemestre_description_table(
|
|||||||
titles = {title: title for title in columns_ids}
|
titles = {title: title for title in columns_ids}
|
||||||
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
|
||||||
titles["ects"] = "ECTS"
|
titles["ects"] = "ECTS"
|
||||||
titles["jour"] = "Evaluation"
|
titles["jour"] = "Évaluation"
|
||||||
titles["description"] = ""
|
titles["description"] = ""
|
||||||
titles["coefficient"] = "Coef. éval."
|
titles["coefficient"] = "Coef. éval."
|
||||||
titles["evalcomplete_str"] = "Complète"
|
titles["evalcomplete_str"] = "Complète"
|
||||||
titles["parcours"] = "Parcours"
|
titles["parcours"] = "Parcours"
|
||||||
titles["publish_incomplete_str"] = "Toujours Utilisée"
|
titles["publish_incomplete_str"] = "Toujours utilisée"
|
||||||
title = f"{parcours.SESSION_NAME.capitalize()} {formsemestre.titre_mois()}"
|
title = f"{parcours.SESSION_NAME.capitalize()} {formsemestre.titre_mois()}"
|
||||||
|
|
||||||
R = []
|
R = []
|
||||||
@ -727,6 +727,8 @@ def formsemestre_description_table(
|
|||||||
evals.reverse() # ordre chronologique
|
evals.reverse() # ordre chronologique
|
||||||
# Ajoute etat:
|
# Ajoute etat:
|
||||||
for e in evals:
|
for e in evals:
|
||||||
|
e["_jour_order"] = e["jour"].isoformat()
|
||||||
|
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
|
||||||
e["UE"] = l["UE"]
|
e["UE"] = l["UE"]
|
||||||
e["_UE_td_attrs"] = l["_UE_td_attrs"]
|
e["_UE_td_attrs"] = l["_UE_td_attrs"]
|
||||||
e["Code"] = l["Code"]
|
e["Code"] = l["Code"]
|
||||||
|
@ -781,8 +781,8 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Choix code semestre:
|
# Choix code semestre:
|
||||||
codes = list(sco_codes_parcours.CODES_JURY_SEM)
|
codes = sorted(sco_codes_parcours.CODES_JURY_SEM)
|
||||||
codes.sort() # fortuitement, cet ordre convient bien !
|
# fortuitement, cet ordre convient bien !
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td>Code semestre: </td><td><select name="code_etat"><option value="" selected>Choisir...</option>'
|
'<tr><td>Code semestre: </td><td><select name="code_etat"><option value="" selected>Choisir...</option>'
|
||||||
|
@ -664,8 +664,10 @@ def set_group(etudid: int, group_id: int) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def change_etud_group_in_partition(etudid, group_id, partition=None):
|
def change_etud_group_in_partition(etudid: int, group_id: int, partition: dict = None):
|
||||||
"""Inscrit etud au groupe de cette partition, et le desinscrit d'autres groupes de cette partition."""
|
"""Inscrit etud au groupe de cette partition,
|
||||||
|
et le desinscrit d'autres groupes de cette partition.
|
||||||
|
"""
|
||||||
log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id))
|
log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id))
|
||||||
|
|
||||||
# 0- La partition
|
# 0- La partition
|
||||||
@ -706,7 +708,7 @@ def change_etud_group_in_partition(etudid, group_id, partition=None):
|
|||||||
cnx.commit()
|
cnx.commit()
|
||||||
|
|
||||||
# 5- Update parcours
|
# 5- Update parcours
|
||||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||||
formsemestre.update_inscriptions_parcours_from_groups()
|
formsemestre.update_inscriptions_parcours_from_groups()
|
||||||
|
|
||||||
# 6- invalidate cache
|
# 6- invalidate cache
|
||||||
@ -1558,11 +1560,14 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
|||||||
|
|
||||||
|
|
||||||
def do_evaluation_listeetuds_groups(
|
def do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, groups=None, getallstudents=False, include_demdef=False
|
evaluation_id: int,
|
||||||
):
|
groups=None,
|
||||||
"""Donne la liste des etudids inscrits a cette evaluation dans les
|
getallstudents: bool = False,
|
||||||
|
include_demdef: bool = False,
|
||||||
|
) -> list[tuple[int, str]]:
|
||||||
|
"""Donne la liste non triée des etudids inscrits à cette évaluation dans les
|
||||||
groupes indiqués.
|
groupes indiqués.
|
||||||
Si getallstudents==True, donne tous les etudiants inscrits a cette
|
Si getallstudents==True, donne tous les étudiants inscrits à cette
|
||||||
evaluation.
|
evaluation.
|
||||||
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
Si include_demdef, compte aussi les etudiants démissionnaires et défaillants
|
||||||
(sinon, par défaut, seulement les 'I')
|
(sinon, par défaut, seulement les 'I')
|
||||||
|
@ -36,6 +36,7 @@ from flask import url_for, g, request
|
|||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.models import FormSemestre
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
@ -175,6 +176,8 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
(la liste doit avoir été vérifiée au préalable)
|
(la liste doit avoir été vérifiée au préalable)
|
||||||
En option: inscrit aux mêmes groupes que dans le semestre origine
|
En option: inscrit aux mêmes groupes que dans le semestre origine
|
||||||
"""
|
"""
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||||
|
formsemestre.setup_parcours_groups()
|
||||||
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||||
@ -190,7 +193,6 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
# du nom de la partition: évidemment, cela ne marche pas si on a les
|
# du nom de la partition: évidemment, cela ne marche pas si on a les
|
||||||
# même noms de groupes dans des partitions différentes)
|
# même noms de groupes dans des partitions différentes)
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
log("cherche groupes de %(nom)s" % etud)
|
|
||||||
|
|
||||||
# recherche le semestre origine (il serait plus propre de l'avoir conservé!)
|
# recherche le semestre origine (il serait plus propre de l'avoir conservé!)
|
||||||
if len(etud["sems"]) < 2:
|
if len(etud["sems"]) < 2:
|
||||||
@ -201,13 +203,11 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
|
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
cursem_groups_by_name = dict(
|
cursem_groups_by_name = {
|
||||||
[
|
g["group_name"]: g
|
||||||
(g["group_name"], g)
|
|
||||||
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
||||||
if g["group_name"]
|
if g["group_name"]
|
||||||
]
|
}
|
||||||
)
|
|
||||||
|
|
||||||
# forme la liste des groupes présents dans les deux semestres:
|
# forme la liste des groupes présents dans les deux semestres:
|
||||||
partition_groups = [] # [ partition+group ] (ds nouveau sem.)
|
partition_groups = [] # [ partition+group ] (ds nouveau sem.)
|
||||||
@ -217,9 +217,8 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
|||||||
new_group = cursem_groups_by_name[prev_group_name]
|
new_group = cursem_groups_by_name[prev_group_name]
|
||||||
partition_groups.append(new_group)
|
partition_groups.append(new_group)
|
||||||
|
|
||||||
# inscrit aux groupes
|
# Inscrit aux groupes
|
||||||
for partition_group in partition_groups:
|
for partition_group in partition_groups:
|
||||||
if partition_group["groups_editable"]:
|
|
||||||
sco_groups.change_etud_group_in_partition(
|
sco_groups.change_etud_group_in_partition(
|
||||||
etudid,
|
etudid,
|
||||||
partition_group["group_id"],
|
partition_group["group_id"],
|
||||||
@ -481,11 +480,12 @@ def build_page(
|
|||||||
|
|
||||||
|
|
||||||
def formsemestre_inscr_passage_help(sem):
|
def formsemestre_inscr_passage_help(sem):
|
||||||
return (
|
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
||||||
"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
|
||||||
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
||||||
<a class="stdlink"
|
<a class="stdlink"
|
||||||
href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>,
|
href="{
|
||||||
|
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=sem["formsemestre_id"] )
|
||||||
|
}">{sem['titreannee']}</a>,
|
||||||
et d'en désincrire si besoin.
|
et d'en désincrire si besoin.
|
||||||
</p>
|
</p>
|
||||||
<p>Les étudiants sont groupés par semestres d'origines. Ceux qui sont en caractères
|
<p>Les étudiants sont groupés par semestres d'origines. Ceux qui sont en caractères
|
||||||
@ -495,10 +495,13 @@ def formsemestre_inscr_passage_help(sem):
|
|||||||
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter d'autres
|
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter d'autres
|
||||||
étudiants à inscrire dans le semestre destination.</p>
|
étudiants à inscrire dans le semestre destination.</p>
|
||||||
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.</p>
|
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.</p>
|
||||||
|
<p>Le bouton <em>inscrire aux mêmes groupes</em> ne prend en compte que les groupes qui existent
|
||||||
|
dans les deux semestres: pensez à créer les partitions et groupes que vous souhaitez conserver
|
||||||
|
<b>avant</b> d'inscrire les étudiants.
|
||||||
|
</p>
|
||||||
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !</p>
|
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !</p>
|
||||||
</div>"""
|
</div>
|
||||||
% sem
|
"""
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def etuds_select_boxes(
|
def etuds_select_boxes(
|
||||||
@ -574,13 +577,13 @@ def etuds_select_boxes(
|
|||||||
if with_checkbox:
|
if with_checkbox:
|
||||||
H.append(
|
H.append(
|
||||||
""" (Select.
|
""" (Select.
|
||||||
<a href="#" onclick="sem_select('%(id)s', true);">tous</a>
|
<a href="#" class="stdlink" onclick="sem_select('%(id)s', true);">tous</a>
|
||||||
<a href="#" onclick="sem_select('%(id)s', false );">aucun</a>""" # "
|
<a href="#" class="stdlink" onclick="sem_select('%(id)s', false );">aucun</a>""" # "
|
||||||
% infos
|
% infos
|
||||||
)
|
)
|
||||||
if sel_inscrits:
|
if sel_inscrits:
|
||||||
H.append(
|
H.append(
|
||||||
"""<a href="#" onclick="sem_select_inscrits('%(id)s');">inscrits</a>"""
|
"""<a href="#" class="stdlink" onclick="sem_select_inscrits('%(id)s');">inscrits</a>"""
|
||||||
% infos
|
% infos
|
||||||
)
|
)
|
||||||
if with_checkbox or sel_inscrits:
|
if with_checkbox or sel_inscrits:
|
||||||
|
@ -41,6 +41,7 @@ from app import log
|
|||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_edit_ue
|
from app.scodoc import sco_edit_ue
|
||||||
|
@ -459,7 +459,7 @@ def ficheEtud(etudid=None):
|
|||||||
# XXX dev
|
# XXX dev
|
||||||
info["but_cursus_mkup"] = ""
|
info["but_cursus_mkup"] = ""
|
||||||
if info["sems"]:
|
if info["sems"]:
|
||||||
last_sem = FormSemestre.query.get_or_404(info["sems"][-1]["formsemestre_id"])
|
last_sem = FormSemestre.query.get_or_404(info["sems"][0]["formsemestre_id"])
|
||||||
if last_sem.formation.is_apc():
|
if last_sem.formation.is_apc():
|
||||||
but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation)
|
but_cursus = cursus_but.EtudCursusBUT(etud, last_sem.formation)
|
||||||
info["but_cursus_mkup"] = render_template(
|
info["but_cursus_mkup"] = render_template(
|
||||||
|
@ -967,31 +967,35 @@ def has_existing_decision(M, E, etudid):
|
|||||||
# Nouveau formulaire saisie notes (2016)
|
# Nouveau formulaire saisie notes (2016)
|
||||||
|
|
||||||
|
|
||||||
def saisie_notes(evaluation_id, group_ids=[]):
|
def saisie_notes(evaluation_id: int, group_ids: list = None):
|
||||||
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
"""Formulaire saisie notes d'une évaluation pour un groupe"""
|
||||||
if not isinstance(evaluation_id, int):
|
if not isinstance(evaluation_id, int):
|
||||||
raise ScoInvalidParamError()
|
raise ScoInvalidParamError()
|
||||||
group_ids = [int(group_id) for group_id in group_ids]
|
group_ids = [int(group_id) for group_id in (group_ids or [])]
|
||||||
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
|
||||||
if not evals:
|
if not evals:
|
||||||
raise ScoValueError("évaluation inexistante")
|
raise ScoValueError("évaluation inexistante")
|
||||||
E = evals[0]
|
E = evals[0]
|
||||||
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
formsemestre_id = M["formsemestre_id"]
|
formsemestre_id = M["formsemestre_id"]
|
||||||
|
moduleimpl_status_url = url_for(
|
||||||
|
"notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
moduleimpl_id=E["moduleimpl_id"],
|
||||||
|
)
|
||||||
# Check access
|
# Check access
|
||||||
# (admin, respformation, and responsable_id)
|
# (admin, respformation, and responsable_id)
|
||||||
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
if not sco_permissions_check.can_edit_notes(current_user, E["moduleimpl_id"]):
|
||||||
return (
|
return f"""
|
||||||
html_sco_header.sco_header()
|
{html_sco_header.sco_header()}
|
||||||
+ "<h2>Modification des notes impossible pour %s</h2>"
|
<h2>Modification des notes impossible pour {current_user.user_name}</h2>
|
||||||
% current_user.user_name
|
|
||||||
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||||||
avez l'autorisation d'effectuer cette opération)</p>
|
avez l'autorisation d'effectuer cette opération)</p>
|
||||||
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
<p><a href="{ moduleimpl_status_url }">Continuer</a>
|
||||||
|
</p>
|
||||||
|
{html_sco_header.sco_footer()}
|
||||||
"""
|
"""
|
||||||
% E["moduleimpl_id"]
|
|
||||||
+ html_sco_header.sco_footer()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
@ -1049,8 +1053,14 @@ def saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
alone=True,
|
alone=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
H.append("""</td><td style="padding-left: 35px;"><button class="btn_masquer_DEM">Masquer les DEM</button></td></tr></table></div>""")
|
H.append(
|
||||||
H.append("""<style>
|
"""
|
||||||
|
</td>
|
||||||
|
<td style="padding-left: 35px;"><button class="btn_masquer_DEM">Masquer les DEM</button></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
.btn_masquer_DEM{
|
.btn_masquer_DEM{
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
@ -1061,19 +1071,14 @@ def saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
body.masquer_DEM .etud_dem{
|
body.masquer_DEM .etud_dem{
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
</style>""")
|
</style>
|
||||||
|
"""
|
||||||
# Le formulaire de saisie des notes:
|
|
||||||
destination = url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
moduleimpl_id=E["moduleimpl_id"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
form = _form_saisie_notes(E, M, groups_infos, destination=destination)
|
# Le formulaire de saisie des notes:
|
||||||
|
form = _form_saisie_notes(E, M, groups_infos, destination=moduleimpl_status_url)
|
||||||
if form is None:
|
if form is None:
|
||||||
log(f"redirecting to {destination}")
|
return flask.redirect(moduleimpl_status_url)
|
||||||
return flask.redirect(destination)
|
|
||||||
H.append(form)
|
H.append(form)
|
||||||
#
|
#
|
||||||
H.append("</div>") # /saisie_notes
|
H.append("</div>") # /saisie_notes
|
||||||
@ -1104,6 +1109,9 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
|||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
# infos identite etudiant
|
# infos identite etudiant
|
||||||
e = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
|
e = sco_etud.etudident_list(cnx, {"etudid": etudid})[0]
|
||||||
|
etud: Identite = Identite.query.get(etudid)
|
||||||
|
# TODO: refactor et eliminer etudident_list.
|
||||||
|
e["etud"] = etud # utilisé seulement pour le tri -- a refactorer
|
||||||
sco_etud.format_etud_ident(e)
|
sco_etud.format_etud_ident(e)
|
||||||
etuds.append(e)
|
etuds.append(e)
|
||||||
# infos inscription dans ce semestre
|
# infos inscription dans ce semestre
|
||||||
@ -1155,7 +1163,7 @@ def _get_sorted_etuds(eval_dict: dict, etudids: list, formsemestre_id: int):
|
|||||||
e["val"] = "DEM"
|
e["val"] = "DEM"
|
||||||
e["explanation"] = "Démission"
|
e["explanation"] = "Démission"
|
||||||
|
|
||||||
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
|
etuds.sort(key=lambda x: x["etud"].sort_key)
|
||||||
|
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
@ -26,8 +26,10 @@ function change_menu_code(elt) {
|
|||||||
let ue_selects = elt.parentElement.parentElement.parentElement.querySelectorAll(
|
let ue_selects = elt.parentElement.parentElement.parentElement.querySelectorAll(
|
||||||
"select.ue_rcue_" + elt.dataset.niveau_id);
|
"select.ue_rcue_" + elt.dataset.niveau_id);
|
||||||
ue_selects.forEach(select => {
|
ue_selects.forEach(select => {
|
||||||
|
if (select.value != "ADM") {
|
||||||
select.value = "ADJR";
|
select.value = "ADJR";
|
||||||
change_menu_code(select); // pour changer les styles
|
change_menu_code(select); // pour changer les styles
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,11 +219,11 @@ $(function () {
|
|||||||
localStorage.setItem(order_info_key, order_info);
|
localStorage.setItem(order_info_key, order_info);
|
||||||
}
|
}
|
||||||
let etudids = [];
|
let etudids = [];
|
||||||
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
document.querySelectorAll("td.identite_court").forEach(e => {
|
||||||
etudids.push(e.dataset.etudid);
|
etudids.push(e.dataset.etudid);
|
||||||
});
|
});
|
||||||
let noms = [];
|
let noms = [];
|
||||||
document.querySelectorAll("td.col_rcues_validables").forEach(e => {
|
document.querySelectorAll("td.identite_court").forEach(e => {
|
||||||
noms.push(e.dataset.nomprenom);
|
noms.push(e.dataset.nomprenom);
|
||||||
});
|
});
|
||||||
const etudids_key = JSON.stringify(["etudids", url.origin, formsemestre_id]);
|
const etudids_key = JSON.stringify(["etudids", url.origin, formsemestre_id]);
|
||||||
|
@ -8,14 +8,22 @@
|
|||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
|
|
||||||
<h2>Calcul automatique des décisions de jury annuelle BUT</h2>
|
<h2>Calcul automatique des décisions de jury du BUT</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Seuls les étudiants qui valident l'année seront affectés:
|
<li>N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||||
tous les niveaux de compétences (RCUE) validables
|
si on a des RCUE "à cheval" sur deux années.
|
||||||
(moyenne annuelle au dessus de 10);
|
</li>
|
||||||
|
<li>Ne modifie jamais de décisions déjà enregistrées.
|
||||||
|
</li>
|
||||||
|
<li>N'enregistre que les décisions <b>validantes de droit: ADM ou CMP</b>.
|
||||||
|
</li>
|
||||||
|
<li>L'assiduité n'est <b>pas</b> prise en compte.
|
||||||
</li>
|
</li>
|
||||||
<li>l'assiduité n'est <b>pas</b> prise en compte;</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
<p>
|
||||||
|
En conséquence, saisir ensuite <b>manuellement les décisions manquantes</b>,
|
||||||
|
notamment sur les UEs en dessous de 10.
|
||||||
|
</p>
|
||||||
<p class="warning">
|
<p class="warning">
|
||||||
Il est nécessaire de relire soigneusement les décisions à l'issue de cette procédure !
|
Il est nécessaire de relire soigneusement les décisions à l'issue de cette procédure !
|
||||||
</p>
|
</p>
|
||||||
|
@ -2350,7 +2350,7 @@ def formsemestre_validation_but(
|
|||||||
etud: Identite = Identite.query.filter_by(
|
etud: Identite = Identite.query.filter_by(
|
||||||
id=etudid, dept_id=g.scodoc_dept_id
|
id=etudid, dept_id=g.scodoc_dept_id
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
|
nb_etuds = formsemestre.etuds.count()
|
||||||
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
|
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
|
||||||
# provisoires avec NEXT et PREV
|
# provisoires avec NEXT et PREV
|
||||||
try:
|
try:
|
||||||
@ -2360,16 +2360,24 @@ def formsemestre_validation_but(
|
|||||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||||
|
|
||||||
# --- Navigation
|
# --- Navigation
|
||||||
prev_lnk = f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
prev_lnk = (
|
||||||
|
f"""{scu.EMO_PREV_ARROW} <a href="{url_for(
|
||||||
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id, etudid="PREV"
|
formsemestre_id=formsemestre_id, etudid="PREV"
|
||||||
)}" class="stdlink"">précédent</a>
|
)}" class="stdlink"">précédent</a>
|
||||||
"""
|
"""
|
||||||
next_lnk = f"""<a href="{url_for(
|
if nb_etuds > 1
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
next_lnk = (
|
||||||
|
f"""<a href="{url_for(
|
||||||
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id, etudid="NEXT"
|
formsemestre_id=formsemestre_id, etudid="NEXT"
|
||||||
)}" class="stdlink"">suivant</a> {scu.EMO_NEXT_ARROW}
|
)}" class="stdlink"">suivant</a> {scu.EMO_NEXT_ARROW}
|
||||||
"""
|
"""
|
||||||
|
if nb_etuds > 1
|
||||||
|
else ""
|
||||||
|
)
|
||||||
navigation_div = f"""
|
navigation_div = f"""
|
||||||
<div class="but_navigation">
|
<div class="but_navigation">
|
||||||
<div class="prev">
|
<div class="prev">
|
||||||
@ -2548,10 +2556,10 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||||||
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if not form.cancel.data:
|
if not form.cancel.data:
|
||||||
nb_admis = jury_but_validation_auto.formsemestre_validation_auto_but(
|
nb_etud_modif = jury_but_validation_auto.formsemestre_validation_auto_but(
|
||||||
formsemestre
|
formsemestre
|
||||||
)
|
)
|
||||||
flash(f"Décisions enregistrées ({nb_admis} admis)")
|
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_saisie_jury",
|
"notes.formsemestre_saisie_jury",
|
||||||
@ -2563,7 +2571,7 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||||||
"but/formsemestre_validation_auto_but.html",
|
"but/formsemestre_validation_auto_but.html",
|
||||||
form=form,
|
form=form,
|
||||||
sco=ScoData(formsemestre=formsemestre),
|
sco=ScoData(formsemestre=formsemestre),
|
||||||
title=f"Calcul automatique jury BUT",
|
title="Calcul automatique jury BUT",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2641,7 +2649,17 @@ def formsemestre_validation_auto(formsemestre_id):
|
|||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=scu.ScoURL(),
|
||||||
)
|
)
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||||
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||||
|
).first_or_404()
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"notes.formsemestre_validation_auto_but",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
return sco_formsemestre_validation.formsemestre_validation_auto(formsemestre_id)
|
return sco_formsemestre_validation.formsemestre_validation_auto(formsemestre_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -935,7 +935,7 @@ def partition_editor(formsemestre_id: int):
|
|||||||
def create_partition_parcours(formsemestre_id):
|
def create_partition_parcours(formsemestre_id):
|
||||||
"""Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
|
"""Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
|
||||||
avec un groupe par parcours."""
|
avec un groupe par parcours."""
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
|
@ -4,4 +4,5 @@ markers =
|
|||||||
but_gb
|
but_gb
|
||||||
lemans
|
lemans
|
||||||
lyon
|
lyon
|
||||||
|
test_test
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.4.29"
|
SCOVERSION = "9.4.31"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from app import models
|
|||||||
|
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
from app.entreprises.models import entreprises_reset_database
|
from app.entreprises.models import entreprises_reset_database
|
||||||
from app.models import departements
|
from app.models import Departement, departements
|
||||||
from app.models import Formation, UniteEns, Matiere, Module
|
from app.models import Formation, UniteEns, Matiere, Module
|
||||||
from app.models import FormSemestre, FormSemestreInscription
|
from app.models import FormSemestre, FormSemestreInscription
|
||||||
from app.models import GroupDescr
|
from app.models import GroupDescr
|
||||||
@ -73,6 +73,7 @@ def make_shell_context():
|
|||||||
"ctx": app.test_request_context(),
|
"ctx": app.test_request_context(),
|
||||||
"current_app": flask.current_app,
|
"current_app": flask.current_app,
|
||||||
"current_user": current_user,
|
"current_user": current_user,
|
||||||
|
"Departement": Departement,
|
||||||
"db": db,
|
"db": db,
|
||||||
"Evaluation": Evaluation,
|
"Evaluation": Evaluation,
|
||||||
"flask": flask,
|
"flask": flask,
|
||||||
|
@ -34,7 +34,11 @@ def test_list_users(api_admin_headers):
|
|||||||
|
|
||||||
# Tous les utilisateurs, vus par SuperAdmin:
|
# Tous les utilisateurs, vus par SuperAdmin:
|
||||||
users = GET("/users/query", headers=admin_h)
|
users = GET("/users/query", headers=admin_h)
|
||||||
|
assert len(users) > 2
|
||||||
|
# Les utilisateurs du dept. TAPI
|
||||||
|
users_TAPI = GET("/users/query?departement=TAPI", headers=admin_h)
|
||||||
|
nb_TAPI = len(users_TAPI)
|
||||||
|
assert nb_TAPI > 1
|
||||||
# Les utilisateurs de chaque département (+ ceux sans département)
|
# Les utilisateurs de chaque département (+ ceux sans département)
|
||||||
all_users = []
|
all_users = []
|
||||||
for acronym in [dept["acronym"] for dept in depts] + [""]:
|
for acronym in [dept["acronym"] for dept in depts] + [""]:
|
||||||
@ -59,9 +63,8 @@ def test_list_users(api_admin_headers):
|
|||||||
for i, u in enumerate(u for u in u_users if u["dept"] != "TAPI"):
|
for i, u in enumerate(u for u in u_users if u["dept"] != "TAPI"):
|
||||||
headers = get_auth_headers(u["user_name"], "test")
|
headers = get_auth_headers(u["user_name"], "test")
|
||||||
users_by_u = GET("/users/query", headers=headers)
|
users_by_u = GET("/users/query", headers=headers)
|
||||||
assert len(users_by_u) == 4 + i
|
assert len(users_by_u) == nb_TAPI + 1 + i
|
||||||
# explication: tous ont le droit de voir les 3 users de TAPI
|
# explication: tous ont le droit de voir les users de TAPI
|
||||||
# (test, other et u_TAPI)
|
|
||||||
# plus l'utilisateur de chaque département jusqu'au leur
|
# plus l'utilisateur de chaque département jusqu'au leur
|
||||||
# (u_AA voit AA, u_BB voit AA et BB, etc)
|
# (u_AA voit AA, u_BB voit AA et BB, etc)
|
||||||
|
|
||||||
@ -90,6 +93,10 @@ def test_edit_users(api_admin_headers):
|
|||||||
)
|
)
|
||||||
assert user["dept"] == "TAPI"
|
assert user["dept"] == "TAPI"
|
||||||
assert user["active"] is False
|
assert user["active"] is False
|
||||||
|
user = GET(f"/user/{user['id']}", headers=admin_h)
|
||||||
|
assert user["nom"] == "Toto"
|
||||||
|
assert user["dept"] == "TAPI"
|
||||||
|
assert user["active"] is False
|
||||||
|
|
||||||
|
|
||||||
def test_roles(api_admin_headers):
|
def test_roles(api_admin_headers):
|
||||||
@ -229,3 +236,10 @@ def test_modif_users_depts(api_admin_headers):
|
|||||||
ok = True
|
ok = True
|
||||||
assert ok
|
assert ok
|
||||||
# Nettoyage:
|
# Nettoyage:
|
||||||
|
# on ne peut pas supprimer l'utilisateur lambda, mais on
|
||||||
|
# le rend inactif et on le retire de son département
|
||||||
|
u = POST_JSON(
|
||||||
|
f"/user/{u_lambda['id']}/edit",
|
||||||
|
{"active": False, "dept": None},
|
||||||
|
headers=admin_h,
|
||||||
|
)
|
||||||
|
47
tests/api/test_test.py
Normal file
47
tests/api/test_test.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
|
||||||
|
"""Unit tests for... tests
|
||||||
|
|
||||||
|
Ensure test DB is in the expected initial state.
|
||||||
|
|
||||||
|
Usage: pytest tests/unit/test_test.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tests.api.setup_test_api import (
|
||||||
|
api_headers,
|
||||||
|
GET,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.test_test
|
||||||
|
def test_test_db(api_headers):
|
||||||
|
"""Check that we indeed have: 2 users, 1 dept, 3 formsemestres.
|
||||||
|
Juste après init, les ensembles seront ceux donnés ci-dessous.
|
||||||
|
Les autres tests peuvent ajouter des éléments, c'edt pourquoi on utilise issubset().
|
||||||
|
"""
|
||||||
|
headers = api_headers
|
||||||
|
assert {
|
||||||
|
"admin_api",
|
||||||
|
"admin",
|
||||||
|
"lecteur_api",
|
||||||
|
"other",
|
||||||
|
"test",
|
||||||
|
"u_AA",
|
||||||
|
"u_BB",
|
||||||
|
"u_CC",
|
||||||
|
"u_DD",
|
||||||
|
"u_TAPI",
|
||||||
|
}.issubset({u["user_name"] for u in GET("/users/query", headers=headers)})
|
||||||
|
assert {
|
||||||
|
"AA",
|
||||||
|
"BB",
|
||||||
|
"CC",
|
||||||
|
"DD",
|
||||||
|
"TAPI",
|
||||||
|
}.issubset({d["acronym"] for d in GET("/departements", headers=headers)})
|
||||||
|
assert 1 in (
|
||||||
|
formsemestre["semestre_id"]
|
||||||
|
for formsemestre in GET("/formsemestres/query", headers=headers)
|
||||||
|
)
|
@ -129,7 +129,7 @@ FormSemestres:
|
|||||||
|
|
||||||
|
|
||||||
Etudiants:
|
Etudiants:
|
||||||
Aaaaa:
|
Aïaaa: # avec un i trema
|
||||||
prenom: Étudiant_SEE
|
prenom: Étudiant_SEE
|
||||||
civilite: M
|
civilite: M
|
||||||
formsemestres:
|
formsemestres:
|
||||||
@ -196,7 +196,7 @@ Etudiants:
|
|||||||
|
|
||||||
S3:
|
S3:
|
||||||
parcours: SEE
|
parcours: SEE
|
||||||
Bbbbb:
|
Azbbbb: # Az devrait être trié après Aï.
|
||||||
prenom: Étudiante_BMB
|
prenom: Étudiante_BMB
|
||||||
civilite: F
|
civilite: F
|
||||||
formsemestres:
|
formsemestres:
|
||||||
|
Loading…
Reference in New Issue
Block a user