forked from ScoDoc/ScoDoc
Merge branch 'modif' of https://scodoc.org/git/iziram/ScoDoc into iziram-rev
This commit is contained in:
commit
efa8f617bb
@ -502,12 +502,10 @@ def clear_scodoc_cache():
|
|||||||
|
|
||||||
|
|
||||||
# --------- Logging
|
# --------- Logging
|
||||||
def log(msg: str, silent_test=True):
|
def log(msg: str):
|
||||||
"""log a message.
|
"""log a message.
|
||||||
If Flask app, use configured logger, else stderr.
|
If Flask app, use configured logger, else stderr.
|
||||||
"""
|
"""
|
||||||
if silent_test and current_app and current_app.config["TESTING"]:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
dept = getattr(g, "scodoc_dept", "")
|
dept = getattr(g, "scodoc_dept", "")
|
||||||
msg = f" ({dept}) {msg}"
|
msg = f" ({dept}) {msg}"
|
||||||
@ -552,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}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
"""ScoDoc 9 API : Absences
|
"""ScoDoc 9 API : Absences
|
||||||
|
@ -19,39 +19,34 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
|
|
||||||
from app.models import Identite, Assiduite, FormSemestreInscription, FormSemestre
|
from app.models import Identite, Assiduite, FormSemestre, ModuleImpl
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduite/<int:assiduiteid>")
|
@bp.route("/assiduite/<int:assiduite_id>")
|
||||||
@api_web_bp.route("/assiduite/<int:assiduiteid>")
|
@api_web_bp.route("/assiduite/<int:assiduite_id>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# XEV à revoir pour les droits d'accès par département
|
def assiduite(assiduite_id: int = None):
|
||||||
|
|
||||||
|
|
||||||
def assiduite(
|
|
||||||
assiduite_id: int = None,
|
|
||||||
): # XEV xxx_id (sauf pour etudid qui est l'exception qui confirme la règle)
|
|
||||||
"""Retourne un objet assiduité à partir de son id
|
"""Retourne un objet assiduité à partir de son id
|
||||||
|
|
||||||
Exemple de résultat:
|
Exemple de résultat:
|
||||||
{
|
{
|
||||||
"assiduiteid": 1,
|
"assiduite_id": 1,
|
||||||
"etudid": 2,
|
"etudid": 2,
|
||||||
"moduleimpl_id": 3,
|
"moduleimpl_id": 3,
|
||||||
"date_debut": "2022-10-31T08:00+01:00",
|
"date_debut": "2022-10-31T08:00+01:00",
|
||||||
"date_fin": "2022-10-31T10:00+01:00",
|
"date_fin": "2022-10-31T10:00+01:00",
|
||||||
"etat": "retard"
|
"etat": "retard",
|
||||||
|
"desc": "une description",
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
# XEV je pense qu'il faut requeter ainsi pour vérifier qu'on est dans le bon département
|
|
||||||
# afin que quelqu'un avec la paermission ScoView dans son département n'ait pas
|
|
||||||
# accès aux infos des autres départements: à tester
|
|
||||||
query = Assiduite.query.filter_by(id=assiduite_id)
|
query = Assiduite.query.filter_by(id=assiduite_id)
|
||||||
if g.scodoc_dept:
|
# if g.scodoc_dept:
|
||||||
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
# query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
assiduite = query.first_or_404()
|
assiduite = query.first_or_404()
|
||||||
|
|
||||||
@ -264,21 +259,13 @@ def count_assiduites_formsemestre(
|
|||||||
return jsonify(scass.get_assiduites_stats(assiduites, metric, filter))
|
return jsonify(scass.get_assiduites_stats(assiduites, metric, filter))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False})
|
@bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
||||||
@api_web_bp.route(
|
@api_web_bp.route("/assiduite/<int:etudid>/create", methods=["POST"])
|
||||||
"/assiduite/<int:etudid>/create", methods=["POST"], defaults={"batch": False}
|
|
||||||
)
|
|
||||||
@bp.route(
|
|
||||||
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
|
|
||||||
)
|
|
||||||
@api_web_bp.route(
|
|
||||||
"/assiduite/<int:etudid>/create/batch", methods=["POST"], defaults={"batch": True}
|
|
||||||
)
|
|
||||||
@scodoc
|
@scodoc
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def create(etudid: int = None, batch: bool = False):
|
def create(etudid: int = None):
|
||||||
"""
|
"""
|
||||||
Création d'une assiduité pour l'étudiant (etudid)
|
Création d'une assiduité pour l'étudiant (etudid)
|
||||||
La requête doit avoir un content type "application/json":
|
La requête doit avoir un content type "application/json":
|
||||||
@ -298,12 +285,10 @@ def create(etudid: int = None, batch: bool = False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
|
etud: Identite = Identite.query.filter_by(id=etudid).first_or_404()
|
||||||
if batch:
|
|
||||||
errors: dict[int, str] = {}
|
errors: dict[int, str] = {}
|
||||||
success: dict[
|
success: dict[int, object] = {}
|
||||||
int,
|
for i, data in enumerate(request.get_json(force=True)):
|
||||||
] = {}
|
|
||||||
for i, data in enumerate(request.get_json(force=True).get("batch")):
|
|
||||||
code, obj = create_singular(data, etud)
|
code, obj = create_singular(data, etud)
|
||||||
if code == 404:
|
if code == 404:
|
||||||
errors[i] = obj
|
errors[i] = obj
|
||||||
@ -312,13 +297,6 @@ def create(etudid: int = None, batch: bool = False):
|
|||||||
|
|
||||||
return jsonify({"errors": errors, "success": success})
|
return jsonify({"errors": errors, "success": success})
|
||||||
|
|
||||||
else:
|
|
||||||
code, obj = create_singular(request.get_json(force=True), etud)
|
|
||||||
if code == 404:
|
|
||||||
return json_error(code, obj)
|
|
||||||
else:
|
|
||||||
return jsonify(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def create_singular(
|
def create_singular(
|
||||||
data: dict,
|
data: dict,
|
||||||
@ -355,64 +333,54 @@ def create_singular(
|
|||||||
|
|
||||||
# cas 4 : moduleimpl_id
|
# cas 4 : moduleimpl_id
|
||||||
|
|
||||||
moduleimpl_id = data.get("moduleimpl_id", None)
|
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||||
if moduleimpl_id is not None:
|
moduleimpl: ModuleImpl = None
|
||||||
try:
|
|
||||||
moduleimpl_id: int = int(moduleimpl_id)
|
if moduleimpl_id is not False:
|
||||||
if moduleimpl_id < 0:
|
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
||||||
raise Exception
|
if moduleimpl is None:
|
||||||
except:
|
|
||||||
errors.append("param 'moduleimpl_id': invalide")
|
errors.append("param 'moduleimpl_id': invalide")
|
||||||
|
|
||||||
|
# cas 5 : desc
|
||||||
|
|
||||||
|
desc:str = data.get("desc", None)
|
||||||
|
|
||||||
if errors != []:
|
if errors != []:
|
||||||
err: str = ", ".join(errors)
|
err: str = ", ".join(errors)
|
||||||
return (404, err)
|
return (404, err)
|
||||||
|
|
||||||
# TOUT EST OK
|
# TOUT EST OK
|
||||||
nouv_assiduite: Assiduite or int = Assiduite.create_assiduite(
|
|
||||||
|
try:
|
||||||
|
nouv_assiduite: Assiduite = Assiduite.create_assiduite(
|
||||||
date_debut=deb,
|
date_debut=deb,
|
||||||
date_fin=fin,
|
date_fin=fin,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
etud=etud,
|
etud=etud,
|
||||||
module=moduleimpl_id,
|
moduleimpl=moduleimpl,
|
||||||
)
|
)
|
||||||
|
|
||||||
if type(nouv_assiduite) is Assiduite:
|
|
||||||
db.session.add(nouv_assiduite)
|
db.session.add(nouv_assiduite)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return (200, {"assiduiteid": nouv_assiduite.assiduiteid})
|
return (200, {"assiduite_id": nouv_assiduite.assiduite_id})
|
||||||
|
except ScoValueError as excp:
|
||||||
return (
|
return (
|
||||||
404,
|
404,
|
||||||
{
|
excp.args[0],
|
||||||
1: "La période sélectionnée est déjà couverte par une autre assiduite",
|
|
||||||
2: "L'étudiant ne participe pas au moduleimpl sélectionné",
|
|
||||||
}.get(nouv_assiduite),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
|
@bp.route("/assiduite/delete", methods=["POST"])
|
||||||
@api_web_bp.route("/assiduite/delete", methods=["POST"], defaults={"batch": False})
|
@api_web_bp.route("/assiduite/delete", methods=["POST"])
|
||||||
@bp.route(
|
|
||||||
"/assiduite/delete/batch",
|
|
||||||
methods=["POST"],
|
|
||||||
defaults={"batch": True},
|
|
||||||
)
|
|
||||||
@api_web_bp.route(
|
|
||||||
"/assiduite/delete/batch",
|
|
||||||
methods=["POST"],
|
|
||||||
defaults={"batch": True},
|
|
||||||
)
|
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def delete(batch: bool = False):
|
def delete():
|
||||||
"""
|
"""
|
||||||
Suppression d'une assiduité à partir de son id
|
Suppression d'une assiduité à partir de son id
|
||||||
"""
|
"""
|
||||||
if batch:
|
assiduites: list[int] = request.get_json(force=True)
|
||||||
assiduites: list[int] = request.get_json(force=True).get("batch", [])
|
|
||||||
output = {"errors": {}, "success": {}}
|
output = {"errors": {}, "success": {}}
|
||||||
|
|
||||||
for i, ass in enumerate(assiduites):
|
for i, ass in enumerate(assiduites):
|
||||||
@ -424,16 +392,6 @@ def delete(batch: bool = False):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify(output)
|
return jsonify(output)
|
||||||
|
|
||||||
else:
|
|
||||||
code, msg = delete_singular(
|
|
||||||
request.get_json(force=True).get("assiduiteid", -1), db
|
|
||||||
)
|
|
||||||
if code == 404:
|
|
||||||
return json_error(code, msg)
|
|
||||||
if code == 200:
|
|
||||||
db.session.commit()
|
|
||||||
return jsonify({"OK": True})
|
|
||||||
|
|
||||||
|
|
||||||
def delete_singular(assiduite_id: int, db):
|
def delete_singular(assiduite_id: int, db):
|
||||||
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
||||||
@ -443,13 +401,13 @@ def delete_singular(assiduite_id: int, db):
|
|||||||
return (200, "OK")
|
return (200, "OK")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/assiduite/<int:assiduiteid>/edit", methods=["POST"])
|
@bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
||||||
@api_web_bp.route("/assiduite/<int:assiduiteid>/edit", methods=["POST"])
|
@api_web_bp.route("/assiduite/<int:assiduite_id>/edit", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
# @permission_required(Permission.ScoAssiduiteChange)
|
# @permission_required(Permission.ScoAssiduiteChange)
|
||||||
def edit(assiduiteid: int):
|
def edit(assiduite_id: int):
|
||||||
"""
|
"""
|
||||||
Edition d'une assiduité à partir de son id
|
Edition d'une assiduité à partir de son id
|
||||||
La requête doit avoir un content type "application/json":
|
La requête doit avoir un content type "application/json":
|
||||||
@ -458,7 +416,7 @@ def edit(assiduiteid: int):
|
|||||||
"moduleimpl_id": int
|
"moduleimpl_id": int
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduiteid).first_or_404()
|
assiduite: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first_or_404()
|
||||||
errors: List[str] = []
|
errors: List[str] = []
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
|
|
||||||
@ -474,19 +432,22 @@ def edit(assiduiteid: int):
|
|||||||
|
|
||||||
# Cas 2 : Moduleimpl_id
|
# Cas 2 : Moduleimpl_id
|
||||||
moduleimpl_id = data.get("moduleimpl_id", False)
|
moduleimpl_id = data.get("moduleimpl_id", False)
|
||||||
|
moduleimpl: ModuleImpl = None
|
||||||
|
|
||||||
if moduleimpl_id is not False:
|
if moduleimpl_id is not False:
|
||||||
try:
|
|
||||||
if moduleimpl_id is not None:
|
if moduleimpl_id is not None:
|
||||||
moduleimpl_id: int = int(moduleimpl_id)
|
moduleimpl = ModuleImpl.query.filter_by(id=int(moduleimpl_id)).first()
|
||||||
if moduleimpl_id < 0 or not Assiduite.verif_moduleimpl(
|
if moduleimpl is None:
|
||||||
moduleimpl_id, assiduite.etudid
|
errors.append("param 'moduleimpl_id': invalide")
|
||||||
|
else:
|
||||||
|
if not moduleimpl.est_inscrit(
|
||||||
|
Identite.query.filter_by(id=assiduite.etudid).first()
|
||||||
):
|
):
|
||||||
errors.append("param 'moduleimpl_id': etud non inscrit")
|
errors.append("param 'moduleimpl_id': etud non inscrit")
|
||||||
|
else:
|
||||||
|
assiduite.moduleimpl_id = moduleimpl_id
|
||||||
|
else:
|
||||||
assiduite.moduleimpl_id = moduleimpl_id
|
assiduite.moduleimpl_id = moduleimpl_id
|
||||||
except:
|
|
||||||
errors.append("param 'moduleimpl_id': invalide")
|
|
||||||
|
|
||||||
if errors != []:
|
if errors != []:
|
||||||
err: str = ", ".join(errors)
|
err: str = ", ".join(errors)
|
||||||
return json_error(404, err)
|
return json_error(404, err)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -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()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
"""ScoDoc 9 API : outils
|
"""ScoDoc 9 API : outils
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -361,7 +361,7 @@ class BulletinBUT:
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etat_inscription": etat_inscription,
|
"etat_inscription": etat_inscription,
|
||||||
"options": sco_preferences.bulletin_option_affichage(
|
"options": sco_preferences.bulletin_option_affichage(
|
||||||
formsemestre.id, self.prefs
|
formsemestre, self.prefs
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
if not published:
|
if not published:
|
||||||
@ -465,6 +465,7 @@ class BulletinBUT:
|
|||||||
"ressources": {},
|
"ressources": {},
|
||||||
"saes": {},
|
"saes": {},
|
||||||
"ues": {},
|
"ues": {},
|
||||||
|
"ues_capitalisees": {},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ Classe raccordant avec ScoDoc 7:
|
|||||||
avec la même interface.
|
avec la même interface.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import collections
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
@ -47,12 +47,14 @@ from app.models.validations import ScolarFormSemestreValidation
|
|||||||
from app.scodoc import sco_codes_parcours as sco_codes
|
from app.scodoc import sco_codes_parcours as sco_codes
|
||||||
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||||
|
|
||||||
from app.scodoc import sco_cursus_dut
|
from app.scodoc import sco_cursus_dut
|
||||||
|
|
||||||
|
|
||||||
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
||||||
|
"""Pour compat ScoDoc 7: à revoir pour le BUT"""
|
||||||
|
|
||||||
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
|
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
|
||||||
super().__init__(etud, formsemestre_id, res)
|
super().__init__(etud, formsemestre_id, res)
|
||||||
# Ajustements pour le BUT
|
# Ajustements pour le BUT
|
||||||
@ -65,3 +67,117 @@ class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
|||||||
def parcours_validated(self):
|
def parcours_validated(self):
|
||||||
"True si le parcours est validé"
|
"True si le parcours est validé"
|
||||||
return False # XXX TODO
|
return False # XXX TODO
|
||||||
|
|
||||||
|
|
||||||
|
class EtudCursusBUT:
|
||||||
|
"""L'état de l'étudiant dans son cursus BUT
|
||||||
|
Liste des niveaux validés/à valider
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, etud: Identite, formation: Formation):
|
||||||
|
"""formation indique la spécialité préparée"""
|
||||||
|
# Vérifie que l'étudiant est bien inscrit à un sem. de cette formation
|
||||||
|
if formation.id not in (
|
||||||
|
ins.formsemestre.formation.id for ins in etud.formsemestre_inscriptions
|
||||||
|
):
|
||||||
|
raise ScoValueError(
|
||||||
|
f"{etud.nomprenom} non inscrit dans {formation.titre} v{formation.version}"
|
||||||
|
)
|
||||||
|
if not formation.referentiel_competence:
|
||||||
|
raise ScoNoReferentielCompetences(formation=formation)
|
||||||
|
#
|
||||||
|
self.etud = etud
|
||||||
|
self.formation = formation
|
||||||
|
self.inscriptions = sorted(
|
||||||
|
[
|
||||||
|
ins
|
||||||
|
for ins in etud.formsemestre_inscriptions
|
||||||
|
if ins.formsemestre.formation.referentiel_competence
|
||||||
|
and (
|
||||||
|
ins.formsemestre.formation.referentiel_competence.id
|
||||||
|
== formation.referentiel_competence.id
|
||||||
|
)
|
||||||
|
],
|
||||||
|
key=lambda s: (s.formsemestre.semestre_id, s.formsemestre.date_debut),
|
||||||
|
)
|
||||||
|
"Liste des inscriptions aux sem. de la formation, triées par indice et chronologie"
|
||||||
|
self.parcour: ApcParcours = self.inscriptions[-1].parcour
|
||||||
|
"Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
|
||||||
|
self.niveaux_by_annee = {}
|
||||||
|
"{ annee : liste des niveaux à valider }"
|
||||||
|
self.niveaux: dict[int, ApcNiveau] = {}
|
||||||
|
"cache les niveaux"
|
||||||
|
for annee in (1, 2, 3):
|
||||||
|
niveaux_d = formation.referentiel_competence.get_niveaux_by_parcours(
|
||||||
|
annee, self.parcour
|
||||||
|
)[1]
|
||||||
|
# groupe les niveaux de tronc commun et ceux spécifiques au parcour
|
||||||
|
self.niveaux_by_annee[annee] = niveaux_d["TC"] + (
|
||||||
|
niveaux_d[self.parcour.id] if self.parcour else []
|
||||||
|
)
|
||||||
|
self.niveaux.update(
|
||||||
|
{niveau.id: niveau for niveau in self.niveaux_by_annee[annee]}
|
||||||
|
)
|
||||||
|
# Probablement inutile:
|
||||||
|
# # Cherche les validations de jury enregistrées pour chaque niveau
|
||||||
|
# self.validations_by_niveau = collections.defaultdict(lambda: [])
|
||||||
|
# " { niveau_id : [ ApcValidationRCUE ] }"
|
||||||
|
# for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||||
|
# self.validations_by_niveau[validation_rcue.niveau().id].append(
|
||||||
|
# validation_rcue
|
||||||
|
# )
|
||||||
|
# self.validation_by_niveau = {
|
||||||
|
# niveau_id: sorted(
|
||||||
|
# validations, key=lambda v: sco_codes.BUT_CODES_ORDERED[v.code]
|
||||||
|
# )[0]
|
||||||
|
# for niveau_id, validations in self.validations_by_niveau.items()
|
||||||
|
# }
|
||||||
|
# "{ niveau_id : meilleure validation pour ce niveau }"
|
||||||
|
|
||||||
|
self.validation_par_competence_et_annee = {}
|
||||||
|
"{ competence_id : { 'BUT1' : validation_rcue, ... } }"
|
||||||
|
for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||||
|
niveau = validation_rcue.niveau()
|
||||||
|
if not niveau.competence.id in self.validation_par_competence_et_annee:
|
||||||
|
self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||||
|
previous_validation = self.validation_par_competence_et_annee.get(
|
||||||
|
niveau.competence.id
|
||||||
|
).get(validation_rcue.annee())
|
||||||
|
# prend la "meilleure" validation
|
||||||
|
if (not previous_validation) or (
|
||||||
|
sco_codes.BUT_CODES_ORDERED[validation_rcue.code]
|
||||||
|
> sco_codes.BUT_CODES_ORDERED[previous_validation.code]
|
||||||
|
):
|
||||||
|
self.validation_par_competence_et_annee[niveau.competence.id][
|
||||||
|
niveau.annee
|
||||||
|
] = validation_rcue
|
||||||
|
|
||||||
|
self.competences = {
|
||||||
|
competence.id: competence
|
||||||
|
for competence in (
|
||||||
|
self.parcour.query_competences()
|
||||||
|
if self.parcour
|
||||||
|
else self.formation.referentiel_competence.get_competences_tronc_commun()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"cache { competence_id : competence }"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
competence_id : {
|
||||||
|
annee : meilleure_validation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
competence.id: {
|
||||||
|
annee: {
|
||||||
|
self.validation_par_competence_et_annee.get(competence.id, {}).get(
|
||||||
|
annee
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for annee in ("BUT1", "BUT2", "BUT3")
|
||||||
|
}
|
||||||
|
for competence in self.competences.values()
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ import re
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from flask import g, url_for
|
from flask import flash, g, url_for
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
@ -91,9 +91,15 @@ from app.models.ues import UniteEns
|
|||||||
from app.models.validations import ScolarFormSemestreValidation
|
from app.models.validations import ScolarFormSemestreValidation
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_codes_parcours as sco_codes
|
from app.scodoc import sco_codes_parcours as sco_codes
|
||||||
from app.scodoc.sco_codes_parcours import RED, UE_STANDARD
|
from app.scodoc.sco_codes_parcours import (
|
||||||
|
BUT_CODES_ORDERED,
|
||||||
|
CODES_RCUE_VALIDES,
|
||||||
|
CODES_UE_VALIDES,
|
||||||
|
RED,
|
||||||
|
UE_STANDARD,
|
||||||
|
)
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class NoRCUEError(ScoValueError):
|
class NoRCUEError(ScoValueError):
|
||||||
@ -170,7 +176,7 @@ class DecisionsProposees:
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"""<{self.__class__.__name__} valid={self.code_valide
|
return f"""<{self.__class__.__name__} valid={self.code_valide
|
||||||
} codes={self.codes} explanation={self.explanation}"""
|
} codes={self.codes} explanation={self.explanation}>"""
|
||||||
|
|
||||||
|
|
||||||
class DecisionsProposeesAnnee(DecisionsProposees):
|
class DecisionsProposeesAnnee(DecisionsProposees):
|
||||||
@ -204,7 +210,12 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
etud: Identite,
|
etud: Identite,
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
):
|
):
|
||||||
|
assert formsemestre.formation.is_apc()
|
||||||
|
if formsemestre.formation.referentiel_competence is None:
|
||||||
|
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
||||||
super().__init__(etud=etud)
|
super().__init__(etud=etud)
|
||||||
|
self.formsemestre = formsemestre
|
||||||
|
"le formsemestre utilisé pour construire ce deca"
|
||||||
self.formsemestre_id = formsemestre.id
|
self.formsemestre_id = formsemestre.id
|
||||||
"l'id du formsemestre utilisé pour construire ce deca"
|
"l'id du formsemestre utilisé pour construire ce deca"
|
||||||
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
|
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
|
||||||
@ -219,23 +230,34 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# Si les années scolaires sont distinctes, on est "à cheval"
|
||||||
|
self.a_cheval = (
|
||||||
|
formsemestre_impair
|
||||||
|
and formsemestre_pair
|
||||||
|
and formsemestre_impair.annee_scolaire()
|
||||||
|
!= formsemestre_pair.annee_scolaire()
|
||||||
|
)
|
||||||
|
"vrai si on groupe deux semestres d'années scolaires différentes"
|
||||||
# Si on part d'un semestre IMPAIR, il n'y aura pas de décision année proposée
|
# Si on part d'un semestre IMPAIR, il n'y aura pas de décision année proposée
|
||||||
# (mais on pourra évidemment valider des UE et même des RCUE)
|
# (mais on pourra évidemment valider des UE et même des RCUE)
|
||||||
self.jury_annuel: bool = formsemestre.semestre_id in (2, 4, 6)
|
self.jury_annuel: bool = formsemestre.semestre_id in (2, 4, 6)
|
||||||
"vrai si jury de fin d'année scolaire (propose code annuel)"
|
"vrai si jury de fin d'année scolaire (propose code annuel)"
|
||||||
|
|
||||||
self.formsemestre_impair = formsemestre_impair
|
self.formsemestre_impair = formsemestre_impair
|
||||||
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
"le 1er semestre du groupement (S1, S3, S5)"
|
||||||
self.formsemestre_pair = formsemestre_pair
|
self.formsemestre_pair = formsemestre_pair
|
||||||
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
"le second formsemestre (S2, S4, S6), de la même année scolaire ou d'une précédente"
|
||||||
formsemestre_last = formsemestre_pair or formsemestre_impair
|
formsemestre_last = formsemestre_pair or formsemestre_impair
|
||||||
"le formsemestre le plus avancé dans cette année"
|
"le formsemestre le plus avancé (en indice de semestre) dans le groupement"
|
||||||
|
|
||||||
self.annee_but = (formsemestre_last.semestre_id + 1) // 2
|
self.annee_but = (formsemestre_last.semestre_id + 1) // 2
|
||||||
"le rang de l'année dans le BUT: 1, 2, 3"
|
"le rang de l'année dans le BUT: 1, 2, 3"
|
||||||
assert self.annee_but in (1, 2, 3)
|
assert self.annee_but in (1, 2, 3)
|
||||||
self.rcues_annee = []
|
self.rcues_annee = []
|
||||||
"RCUEs de l'année"
|
"""RCUEs de l'année
|
||||||
|
(peuvent concerner l'année scolaire antérieur pour les redoublants
|
||||||
|
avec UE capitalisées)
|
||||||
|
"""
|
||||||
self.inscription_etat = etud.inscription_etat(formsemestre_last.id)
|
self.inscription_etat = etud.inscription_etat(formsemestre_last.id)
|
||||||
"état de l'inscription dans le semestre le plus avancé (pair si année complète)"
|
"état de l'inscription dans le semestre le plus avancé (pair si année complète)"
|
||||||
self.inscription_etat_pair = (
|
self.inscription_etat_pair = (
|
||||||
@ -334,8 +356,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
"vrai si l'année est réussie, tous niveaux validables ou validés par le jury"
|
"vrai si l'année est réussie, tous niveaux validables ou validés par le jury"
|
||||||
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
|
||||||
"Peut passer si plus de la moitié validables et tous > 8"
|
"Vrai si plus de la moitié des RCUE validables"
|
||||||
self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
|
self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
|
||||||
|
"Vrai si peut passer dans l'année BUT suivante: plus de la moitié validables et tous > 8"
|
||||||
# XXX TODO ajouter condition pour passage en S5
|
# XXX TODO ajouter condition pour passage en S5
|
||||||
|
|
||||||
# Enfin calcule les codes des UE:
|
# Enfin calcule les codes des UE:
|
||||||
@ -343,12 +366,11 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
dec_ue.compute_codes()
|
dec_ue.compute_codes()
|
||||||
|
|
||||||
# Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR
|
# Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR
|
||||||
expl_rcues = (
|
plural = self.nb_validables > 1
|
||||||
f"{self.nb_validables} niveau validable(s) sur {self.nb_competences}"
|
expl_rcues = f"""{self.nb_validables} niveau{"x" if plural else ""} validable{
|
||||||
)
|
"s" if plural else ""} sur {self.nb_competences}"""
|
||||||
if self.admis:
|
if self.admis:
|
||||||
self.codes = [sco_codes.ADM] + self.codes
|
self.codes = [sco_codes.ADM] + self.codes
|
||||||
self.explanation = expl_rcues
|
|
||||||
# elif not self.jury_annuel:
|
# elif not self.jury_annuel:
|
||||||
# self.codes = [] # pas de décision annuelle sur semestres impairs
|
# self.codes = [] # pas de décision annuelle sur semestres impairs
|
||||||
elif self.inscription_etat != scu.INSCRIT:
|
elif self.inscription_etat != scu.INSCRIT:
|
||||||
@ -364,9 +386,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
sco_codes.ABL,
|
sco_codes.ABL,
|
||||||
sco_codes.EXCLU,
|
sco_codes.EXCLU,
|
||||||
]
|
]
|
||||||
|
expl_rcues = ""
|
||||||
elif self.passage_de_droit:
|
elif self.passage_de_droit:
|
||||||
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
||||||
self.explanation = expl_rcues
|
|
||||||
elif self.valide_moitie_rcue: # mais au moins 1 rcue insuffisante
|
elif self.valide_moitie_rcue: # mais au moins 1 rcue insuffisante
|
||||||
self.codes = [
|
self.codes = [
|
||||||
sco_codes.RED,
|
sco_codes.RED,
|
||||||
@ -374,7 +396,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
sco_codes.PAS1NCI,
|
sco_codes.PAS1NCI,
|
||||||
sco_codes.ADJ,
|
sco_codes.ADJ,
|
||||||
] + self.codes
|
] + self.codes
|
||||||
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8"
|
expl_rcues += f" et {self.nb_rcues_under_8} < 8"
|
||||||
else:
|
else:
|
||||||
self.codes = [
|
self.codes = [
|
||||||
sco_codes.RED,
|
sco_codes.RED,
|
||||||
@ -383,17 +405,21 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
sco_codes.ADJ,
|
sco_codes.ADJ,
|
||||||
sco_codes.PASD, # voir #488 (discutable, conventions locales)
|
sco_codes.PASD, # voir #488 (discutable, conventions locales)
|
||||||
] + self.codes
|
] + self.codes
|
||||||
self.explanation = (
|
expl_rcues += f""" et {self.nb_rcues_under_8} niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
|
||||||
expl_rcues
|
|
||||||
+ f""" et {self.nb_rcues_under_8}
|
|
||||||
niveau{'x' if self.nb_rcues_under_8 > 1 else ''} < 8"""
|
|
||||||
)
|
|
||||||
# Si l'un des semestres est extérieur, propose ADM
|
# Si l'un des semestres est extérieur, propose ADM
|
||||||
if (
|
if (
|
||||||
self.formsemestre_impair and self.formsemestre_impair.modalite == "EXT"
|
self.formsemestre_impair and self.formsemestre_impair.modalite == "EXT"
|
||||||
) or (self.formsemestre_pair and self.formsemestre_pair.modalite == "EXT"):
|
) or (self.formsemestre_pair and self.formsemestre_pair.modalite == "EXT"):
|
||||||
self.codes.insert(0, sco_codes.ADM)
|
self.codes.insert(0, sco_codes.ADM)
|
||||||
|
self.explanation = f"<div>{expl_rcues}</div>"
|
||||||
|
messages = self.descr_pb_coherence()
|
||||||
|
if messages:
|
||||||
|
self.explanation += (
|
||||||
|
'<div class="warning">'
|
||||||
|
+ '</div><div class="warning">'.join(messages)
|
||||||
|
+ "</div>"
|
||||||
|
)
|
||||||
#
|
#
|
||||||
|
|
||||||
def infos(self) -> str:
|
def infos(self) -> str:
|
||||||
@ -526,9 +552,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
|
|
||||||
def compute_rcues_annee(self) -> list[RegroupementCoherentUE]:
|
def compute_rcues_annee(self) -> list[RegroupementCoherentUE]:
|
||||||
"""Liste des regroupements d'UE à considérer cette année.
|
"""Liste des regroupements d'UE à considérer cette année.
|
||||||
Pour le moment on ne considère pas de RCUE à cheval sur plusieurs années (redoublants).
|
On peut avoir un RCUE à cheval sur plusieurs années (redoublants avec UE capitalisées).
|
||||||
Si on n'a pas les deux semestres, aucun RCUE.
|
Si on n'a pas les deux semestres, aucun RCUE.
|
||||||
Raises ScoValueError s'il y a des UE sans RCUE.
|
|
||||||
"""
|
"""
|
||||||
if self.formsemestre_pair is None or self.formsemestre_impair is None:
|
if self.formsemestre_pair is None or self.formsemestre_impair is None:
|
||||||
return []
|
return []
|
||||||
@ -537,6 +562,20 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
for ue_pair in self.ues_pair:
|
for ue_pair in self.ues_pair:
|
||||||
rcue = None
|
rcue = None
|
||||||
for ue_impair in self.ues_impair:
|
for ue_impair in self.ues_impair:
|
||||||
|
if self.a_cheval:
|
||||||
|
# l'UE paire DOIT être capitalisée pour être utilisée
|
||||||
|
if (
|
||||||
|
self.decisions_ues[ue_pair.id].code_valide
|
||||||
|
not in CODES_UE_VALIDES
|
||||||
|
):
|
||||||
|
continue # ignore cette UE antérieure non capitalisée
|
||||||
|
# et l'UE impaire doit être actuellement meilleure que
|
||||||
|
# celle éventuellement capitalisée
|
||||||
|
if (
|
||||||
|
self.decisions_ues[ue_impair.id].ue_status
|
||||||
|
and self.decisions_ues[ue_impair.id].ue_status["is_capitalized"]
|
||||||
|
):
|
||||||
|
continue # ignore cette UE car capitalisée et actuelle moins bonne
|
||||||
if ue_pair.niveau_competence_id == ue_impair.niveau_competence_id:
|
if ue_pair.niveau_competence_id == ue_impair.niveau_competence_id:
|
||||||
rcue = RegroupementCoherentUE(
|
rcue = RegroupementCoherentUE(
|
||||||
self.etud,
|
self.etud,
|
||||||
@ -548,19 +587,22 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
ues_impair_sans_rcue.discard(ue_impair.id)
|
ues_impair_sans_rcue.discard(ue_impair.id)
|
||||||
break
|
break
|
||||||
if rcue is None:
|
# if rcue is None and not self.a_cheval:
|
||||||
raise NoRCUEError(deca=self, ue=ue_pair)
|
# raise NoRCUEError(deca=self, ue=ue_pair)
|
||||||
|
if rcue is not None:
|
||||||
rcues_annee.append(rcue)
|
rcues_annee.append(rcue)
|
||||||
if len(ues_impair_sans_rcue) > 0:
|
# Si jury annuel (pas à cheval), on doit avoir tous les RCUEs:
|
||||||
ue = UniteEns.query.get(ues_impair_sans_rcue.pop())
|
# if len(ues_impair_sans_rcue) > 0 and not self.a_cheval:
|
||||||
raise NoRCUEError(deca=self, ue=ue)
|
# ue = UniteEns.query.get(ues_impair_sans_rcue.pop())
|
||||||
|
# raise NoRCUEError(deca=self, ue=ue)
|
||||||
return rcues_annee
|
return rcues_annee
|
||||||
|
|
||||||
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
|
def compute_decisions_niveaux(self) -> dict[int, "DecisionsProposeesRCUE"]:
|
||||||
"""Pour chaque niveau de compétence de cette année, construit
|
"""Pour chaque niveau de compétence de cette année, construit
|
||||||
le DecisionsProposeesRCUE,
|
le DecisionsProposeesRCUE, ou None s'il n'y en a pas
|
||||||
ou None s'il n'y en a pas
|
|
||||||
(ne devrait pas arriver car compute_rcues_annee vérifie déjà cela).
|
(ne devrait pas arriver car compute_rcues_annee vérifie déjà cela).
|
||||||
|
|
||||||
|
Appelé à la construction du deca, donc avant décisions manuelles.
|
||||||
Return: { niveau_id : DecisionsProposeesRCUE }
|
Return: { niveau_id : DecisionsProposeesRCUE }
|
||||||
"""
|
"""
|
||||||
# Retrouve le RCUE associé à chaque niveau
|
# Retrouve le RCUE associé à chaque niveau
|
||||||
@ -591,22 +633,42 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
d[dec_rcue.rcue.ue_2.id] = dec_rcue
|
d[dec_rcue.rcue.ue_2.id] = dec_rcue
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def next_annee_semestre_id(self, code: str) -> int:
|
def next_semestre_ids(self, code: str) -> set[int]:
|
||||||
"""L'indice du semestre dans lequel l'étudiant est autorisé à
|
"""Les indices des semestres dans lequels l'étudiant est autorisé
|
||||||
poursuivre l'année suivante. None si aucun."""
|
à poursuivre après le semestre courant.
|
||||||
if self.formsemestre_pair is None:
|
"""
|
||||||
return None # seulement sur année
|
ids = set()
|
||||||
if code == RED:
|
# La poursuite d'études dans un semestre pair d’une même année
|
||||||
return self.formsemestre_pair.semestre_id - 1
|
# est de droit pour tout étudiant:
|
||||||
elif (
|
if (self.formsemestre.semestre_id % 2) and sco_codes.ParcoursBUT.NB_SEM:
|
||||||
code in sco_codes.BUT_CODES_PASSAGE
|
ids.add(self.formsemestre.semestre_id + 1)
|
||||||
|
|
||||||
|
# La poursuite d’études dans un semestre impair est possible si
|
||||||
|
# et seulement si l’étudiant a obtenu :
|
||||||
|
# - la moyenne à plus de la moitié des regroupements cohérents d’UE ;
|
||||||
|
# - et une moyenne égale ou supérieure à 8 sur 20 à chaque RCUE.
|
||||||
|
#
|
||||||
|
# La condition a paru trop stricte à de nombreux collègues.
|
||||||
|
# ScoDoc ne contraint donc pas à la respecter strictement.
|
||||||
|
# Si le code est dans BUT_CODES_PASSAGE (ADM, ADJ, PASD, PAS1NCI, ATJ),
|
||||||
|
# autorise à passer dans le semestre suivant
|
||||||
|
if (
|
||||||
|
self.jury_annuel
|
||||||
|
and code in sco_codes.BUT_CODES_PASSAGE
|
||||||
and self.formsemestre_pair.semestre_id < sco_codes.ParcoursBUT.NB_SEM
|
and self.formsemestre_pair.semestre_id < sco_codes.ParcoursBUT.NB_SEM
|
||||||
):
|
):
|
||||||
return self.formsemestre_pair.semestre_id + 1
|
ids.add(self.formsemestre.semestre_id + 1)
|
||||||
return None
|
|
||||||
|
if code == RED:
|
||||||
|
ids.add(
|
||||||
|
self.formsemestre.semestre_id - (self.formsemestre.semestre_id + 1) % 2
|
||||||
|
)
|
||||||
|
|
||||||
|
return ids
|
||||||
|
|
||||||
def record_form(self, form: dict):
|
def record_form(self, form: dict):
|
||||||
"""Enregistre les codes de jury en base
|
"""Enregistre les codes de jury en base
|
||||||
|
à partir d'un dict représentant le formulaire jury BUT:
|
||||||
form dict:
|
form dict:
|
||||||
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896
|
- 'code_ue_1896' : 'AJ' code pour l'UE id 1896
|
||||||
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
|
- 'code_rcue_6" : 'ADM' code pour le RCUE du niveau 6
|
||||||
@ -616,7 +678,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
et qu'il n'y en a pas déjà, enregistre ceux par défaut.
|
||||||
"""
|
"""
|
||||||
log("jury_but.DecisionsProposeesAnnee.record_form")
|
log("jury_but.DecisionsProposeesAnnee.record_form")
|
||||||
with sco_cache.DeferredSemCacheManager():
|
code_annee = None
|
||||||
|
codes_rcues = [] # [ (dec_rcue, code), ... ]
|
||||||
|
codes_ues = [] # [ (dec_ue, code), ... ]
|
||||||
for key in form:
|
for key in form:
|
||||||
code = form[key]
|
code = form[key]
|
||||||
# Codes d'UE
|
# Codes d'UE
|
||||||
@ -626,7 +690,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
dec_ue = self.decisions_ues.get(ue_id)
|
dec_ue = self.decisions_ues.get(ue_id)
|
||||||
if not dec_ue:
|
if not dec_ue:
|
||||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||||
dec_ue.record(code)
|
codes_ues.append((dec_ue, code))
|
||||||
else:
|
else:
|
||||||
# Codes de RCUE
|
# Codes de RCUE
|
||||||
m = re.match(r"^code_rcue_(\d+)$", key)
|
m = re.match(r"^code_rcue_(\d+)$", key)
|
||||||
@ -635,31 +699,39 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
dec_rcue = self.decisions_rcue_by_niveau.get(niveau_id)
|
||||||
if not dec_rcue:
|
if not dec_rcue:
|
||||||
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
raise ScoValueError(f"RCUE invalide niveau_id={niveau_id}")
|
||||||
dec_rcue.record(code)
|
codes_rcues.append((dec_rcue, code))
|
||||||
elif key == "code_annee":
|
elif key == "code_annee":
|
||||||
# Code annuel
|
# Code annuel
|
||||||
self.record(code)
|
code_annee = code
|
||||||
|
|
||||||
|
with sco_cache.DeferredSemCacheManager():
|
||||||
|
# Enregistre les codes, dans l'ordre UE, RCUE, Année
|
||||||
|
for dec_ue, code in codes_ues:
|
||||||
|
dec_ue.record(code)
|
||||||
|
for dec_rcue, code in codes_rcues:
|
||||||
|
dec_rcue.record(code)
|
||||||
|
self.record(code_annee)
|
||||||
self.record_all()
|
self.record_all()
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def record(self, code: str, no_overwrite=False) -> 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):
|
|
||||||
self.recorded = True
|
if code != self.code_valide and (self.code_valide is None or not no_overwrite):
|
||||||
return # no change
|
# Enregistrement du code annuel BUT
|
||||||
if self.validation:
|
if self.validation:
|
||||||
db.session.delete(self.validation)
|
db.session.delete(self.validation)
|
||||||
db.session.flush()
|
db.session.commit()
|
||||||
if code is None:
|
if code is None:
|
||||||
self.validation = None
|
self.validation = None
|
||||||
else:
|
else:
|
||||||
@ -670,32 +742,32 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
annee_scolaire=self.annee_scolaire(),
|
annee_scolaire=self.annee_scolaire(),
|
||||||
code=code,
|
code=code,
|
||||||
)
|
)
|
||||||
|
db.session.add(self.validation)
|
||||||
|
db.session.commit()
|
||||||
|
log(f"Recording {self}: {code}")
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="jury_but",
|
method="jury_but",
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
msg=f"Validation année BUT{self.annee_but}: {code}",
|
msg=f"Validation année BUT{self.annee_but}: {code}",
|
||||||
)
|
)
|
||||||
db.session.add(self.validation)
|
|
||||||
# --- Autorisation d'inscription dans semestre suivant ?
|
# --- Autorisation d'inscription dans semestre suivant ?
|
||||||
if self.formsemestre_pair is not None:
|
|
||||||
if code is None:
|
|
||||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
ScolarAutorisationInscription.delete_autorisation_etud(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
origin_formsemestre_id=self.formsemestre_pair.id,
|
origin_formsemestre_id=self.formsemestre.id,
|
||||||
)
|
)
|
||||||
else:
|
for next_semestre_id in self.next_semestre_ids(code):
|
||||||
next_semestre_id = self.next_annee_semestre_id(code)
|
|
||||||
if next_semestre_id is not None:
|
|
||||||
ScolarAutorisationInscription.autorise_etud(
|
ScolarAutorisationInscription.autorise_etud(
|
||||||
self.etud.id,
|
self.etud.id,
|
||||||
self.formsemestre_pair.formation.formation_code,
|
self.formsemestre.formation.formation_code,
|
||||||
self.formsemestre_pair.id,
|
self.formsemestre.id,
|
||||||
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"
|
||||||
@ -706,29 +778,71 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
if self.formsemestre_pair is not None:
|
if self.formsemestre_pair is not None:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
||||||
|
|
||||||
def record_all(self):
|
def record_all(
|
||||||
|
self, no_overwrite: bool = True, 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.
|
||||||
|
- 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é.
|
||||||
"""
|
"""
|
||||||
decisions = (
|
modif = False
|
||||||
list(self.decisions_ues.values())
|
# Toujours valider dans l'ordre UE, RCUE, Année
|
||||||
+ list(self.decisions_rcue_by_niveau.values())
|
annee_scolaire = self.formsemestre.annee_scolaire()
|
||||||
+ [self]
|
# UEs
|
||||||
)
|
for dec_ue in self.decisions_ues.values():
|
||||||
for dec in decisions:
|
if (
|
||||||
if not dec.recorded:
|
not dec_ue.recorded
|
||||||
|
) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
|
||||||
# rappel: le code par défaut est en tête
|
# rappel: le code par défaut est en tête
|
||||||
code = dec.codes[0] if dec.codes else None
|
code = dec_ue.codes[0] if dec_ue.codes else None
|
||||||
|
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
|
||||||
dec.record(code, no_overwrite=True)
|
# (no_overwrite=True) sauf en mode test yaml
|
||||||
|
modif |= dec_ue.record(code, no_overwrite=no_overwrite)
|
||||||
|
# RCUE :
|
||||||
|
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 ( # enregistre seulement si pas déjà validé "mieux"
|
||||||
|
(not dec_rcue.validation)
|
||||||
|
or BUT_CODES_ORDERED.get(dec_rcue.validation.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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
modif |= 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
|
||||||
|
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
|
||||||
pour cette année: décisions d'UE, de RCUE, d'année,
|
pour cette année: décisions d'UE, de RCUE, d'année,
|
||||||
et autorisations d'inscription émises.
|
et autorisations d'inscription émises.
|
||||||
Efface même si étudiant DEM ou DEF.
|
Efface même si étudiant DEM ou DEF.
|
||||||
|
Si à cheval, n'efface que pour le semestre d'origine du deca.
|
||||||
"""
|
"""
|
||||||
if only_one_sem:
|
if only_one_sem or self.a_cheval:
|
||||||
# N'efface que les autorisations venant de ce semestre,
|
# N'efface que les autorisations venant de ce semestre,
|
||||||
# et les validations de ses UEs
|
# et les validations de ses UEs
|
||||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
ScolarAutorisationInscription.delete_autorisation_etud(
|
||||||
@ -757,22 +871,37 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
)
|
)
|
||||||
for validation in validations:
|
for validation in validations:
|
||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
Scolog.logdb(
|
||||||
|
"jury_but",
|
||||||
|
etudid=self.etud.id,
|
||||||
|
msg=f"Validation année BUT{self.annee_but}: effacée",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Efface éventuelles validations de semestre
|
||||||
|
# (en principe inutilisées en BUT)
|
||||||
|
# et autres UEs (en cas de changement d'architecture de formation depuis le jury ?)
|
||||||
|
#
|
||||||
|
for validation in ScolarFormSemestreValidation.query.filter_by(
|
||||||
|
etudid=self.etud.id, formsemestre_id=self.formsemestre_id
|
||||||
|
):
|
||||||
|
db.session.delete(validation)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
self.invalidate_formsemestre_cache()
|
self.invalidate_formsemestre_cache()
|
||||||
|
|
||||||
def get_autorisations_passage(self) -> list[int]:
|
def get_autorisations_passage(self) -> list[int]:
|
||||||
"""Les liste des indices de semestres auxquels on est autorisé à
|
"""Liste des indices de semestres auxquels on est autorisé à
|
||||||
s'inscrire depuis cette année"""
|
s'inscrire depuis le semestre courant.
|
||||||
formsemestre = self.formsemestre_pair or self.formsemestre_impair
|
"""
|
||||||
if not formsemestre:
|
return sorted(
|
||||||
return []
|
[
|
||||||
return [
|
|
||||||
a.semestre_id
|
a.semestre_id
|
||||||
for a in ScolarAutorisationInscription.query.filter_by(
|
for a in ScolarAutorisationInscription.query.filter_by(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
origin_formsemestre_id=formsemestre.id,
|
origin_formsemestre_id=self.formsemestre.id,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def descr_niveaux_validation(self, line_sep: str = "\n") -> str:
|
def descr_niveaux_validation(self, line_sep: str = "\n") -> str:
|
||||||
"""Description textuelle des niveaux validés (enregistrés)
|
"""Description textuelle des niveaux validés (enregistrés)
|
||||||
@ -800,12 +929,33 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
validations.append(", ".join(v for v in valids if v))
|
validations.append(", ".join(v for v in valids if v))
|
||||||
return line_sep.join(validations)
|
return line_sep.join(validations)
|
||||||
|
|
||||||
|
def descr_pb_coherence(self) -> list[str]:
|
||||||
|
"""Description d'éventuels problèmes de cohérence entre
|
||||||
|
les décisions *enregistrées* d'UE et de RCUE.
|
||||||
|
Note: en principe, la cohérence RCUE/UE est assurée au moment de
|
||||||
|
l'enregistrement (record).
|
||||||
|
Mais la base peut avoir été modifiée par d'autres voies.
|
||||||
|
"""
|
||||||
|
messages = []
|
||||||
|
for dec_rcue in self.decisions_rcue_by_niveau.values():
|
||||||
|
if dec_rcue.code_valide in CODES_RCUE_VALIDES:
|
||||||
|
for ue in (dec_rcue.rcue.ue_1, dec_rcue.rcue.ue_2):
|
||||||
|
dec_ue = self.decisions_ues.get(ue.id)
|
||||||
|
if dec_ue:
|
||||||
|
if dec_ue.code_valide not in CODES_UE_VALIDES:
|
||||||
|
messages.append(
|
||||||
|
f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est !"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.append(f"L'UE {ue.acronyme} n'a pas décision (???)")
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
def list_ue_parcour_etud(
|
def list_ue_parcour_etud(
|
||||||
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
|
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
|
||||||
) -> tuple[ApcParcours, list[UniteEns]]:
|
) -> tuple[ApcParcours, list[UniteEns]]:
|
||||||
"""Parcour dans lequel l'étudiant est inscrit,
|
"""Parcour dans lequel l'étudiant est inscrit,
|
||||||
et liste des UEs à valider pour ce semestre
|
et liste des UEs à valider pour ce semestre (sans les UE "dispensées")
|
||||||
"""
|
"""
|
||||||
if res.etuds_parcour_id[etud.id] is None:
|
if res.etuds_parcour_id[etud.id] is None:
|
||||||
parcour = None
|
parcour = None
|
||||||
@ -820,6 +970,7 @@ def list_ue_parcour_etud(
|
|||||||
.order_by(UniteEns.numero)
|
.order_by(UniteEns.numero)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
ues = [ue for ue in ues if (etud.id, ue.id) not in res.dispense_ues]
|
||||||
return parcour, ues
|
return parcour, ues
|
||||||
|
|
||||||
|
|
||||||
@ -845,6 +996,7 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
inscription_etat: str = scu.INSCRIT,
|
inscription_etat: str = scu.INSCRIT,
|
||||||
):
|
):
|
||||||
super().__init__(etud=dec_prop_annee.etud)
|
super().__init__(etud=dec_prop_annee.etud)
|
||||||
|
self.deca = dec_prop_annee
|
||||||
self.rcue = rcue
|
self.rcue = rcue
|
||||||
if rcue is None: # RCUE non dispo, eg un seul semestre
|
if rcue is None: # RCUE non dispo, eg un seul semestre
|
||||||
self.codes = []
|
self.codes = []
|
||||||
@ -876,30 +1028,48 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
or dec_prop_annee.formsemestre_pair.modalite == "EXT"
|
or dec_prop_annee.formsemestre_pair.modalite == "EXT"
|
||||||
):
|
):
|
||||||
self.codes.insert(0, sco_codes.ADM)
|
self.codes.insert(0, sco_codes.ADM)
|
||||||
|
# S'il y a une décision enregistrée: si elle est plus favorable que celle que l'on
|
||||||
|
# proposerait, la place en tête.
|
||||||
|
# Sinon, la place en seconde place
|
||||||
|
if self.code_valide and self.code_valide != self.codes[0]:
|
||||||
|
code_default = self.codes[0]
|
||||||
|
if self.code_valide in self.codes:
|
||||||
|
self.codes.remove(self.code_valide)
|
||||||
|
if sco_codes.BUT_CODES_ORDERED.get(
|
||||||
|
self.code_valide, 0
|
||||||
|
) > sco_codes.BUT_CODES_ORDERED.get(code_default, 0):
|
||||||
|
self.codes.insert(0, self.code_valide)
|
||||||
|
else:
|
||||||
|
self.codes.insert(1, self.code_valide)
|
||||||
|
|
||||||
def record(self, code: str, no_overwrite=False):
|
def __repr__(self) -> str:
|
||||||
"""Enregistre le code"""
|
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) -> bool:
|
||||||
|
"""Enregistre le code RCUE.
|
||||||
|
Note:
|
||||||
|
- si le RCUE est ADJ, les UE non validées sont passées à ADJ
|
||||||
|
XXX on pourra imposer ici d'autres règles de cohérence
|
||||||
|
"""
|
||||||
if self.rcue is None:
|
if self.rcue is None:
|
||||||
return # pas de RCUE a enregistrer
|
return 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)
|
||||||
db.session.flush()
|
db.session.commit()
|
||||||
if code is None:
|
if code is None:
|
||||||
self.validation = None
|
self.validation = None
|
||||||
else:
|
else:
|
||||||
# log(
|
|
||||||
# f"RCUE.record(etudid={self.etud.id}, ue1_id={self.rcue.ue_1.id}, ue2_id={self.rcue.ue_2.id}, code={code} )"
|
|
||||||
# )
|
|
||||||
self.validation = ApcValidationRCUE(
|
self.validation = ApcValidationRCUE(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
formsemestre_id=self.rcue.formsemestre_2.id,
|
formsemestre_id=self.rcue.formsemestre_2.id,
|
||||||
@ -908,12 +1078,31 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
parcours_id=parcours_id,
|
parcours_id=parcours_id,
|
||||||
code=code,
|
code=code,
|
||||||
)
|
)
|
||||||
|
db.session.add(self.validation)
|
||||||
|
db.session.commit()
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="jury_but",
|
method="jury_but",
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
msg=f"Validation RCUE {repr(self.rcue)}",
|
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 ADJR sur {dec_ue}")
|
||||||
|
flash(
|
||||||
|
f"""UEs du RCUE "{dec_ue.ue.niveau_competence.competence.titre}" passées en ADJR"""
|
||||||
|
)
|
||||||
|
dec_ue.record(sco_codes.ADJR)
|
||||||
|
|
||||||
|
# Valide les niveaux inférieurs de la compétence (code ADSUP)
|
||||||
|
# TODO
|
||||||
|
|
||||||
if self.rcue.formsemestre_1 is not None:
|
if self.rcue.formsemestre_1 is not None:
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.rcue.formsemestre_1.id
|
formsemestre_id=self.rcue.formsemestre_1.id
|
||||||
@ -922,13 +1111,16 @@ class DecisionsProposeesRCUE(DecisionsProposees):
|
|||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.rcue.formsemestre_2.id
|
formsemestre_id=self.rcue.formsemestre_2.id
|
||||||
)
|
)
|
||||||
|
self.code_valide = code # mise à jour état
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
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"""
|
||||||
# par prudence, on requete toutes les validations, en cas de doublons
|
# par prudence, on requete toutes les validations, en cas de doublons
|
||||||
validations = self.rcue.query_validations()
|
validations = self.rcue.query_validations()
|
||||||
for validation in validations:
|
for validation in validations:
|
||||||
|
log(f"DecisionsProposeesRCUE: deleting {validation}")
|
||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
@ -960,7 +1152,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
sinon si compensation dans RCUE: CMP
|
sinon si compensation dans RCUE: CMP
|
||||||
sinon: ADJ, AJ
|
sinon: ADJ, AJ
|
||||||
|
|
||||||
et proposer toujours: RAT, DEF, ABAN, DEM, UEBSL (codes_communs)
|
et proposer toujours: RAT, DEF, ABAN, ADJR, DEM, UEBSL (codes_communs)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Codes toujours proposés sauf si include_communs est faux:
|
# Codes toujours proposés sauf si include_communs est faux:
|
||||||
@ -968,6 +1160,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
sco_codes.RAT,
|
sco_codes.RAT,
|
||||||
sco_codes.DEF,
|
sco_codes.DEF,
|
||||||
sco_codes.ABAN,
|
sco_codes.ABAN,
|
||||||
|
sco_codes.ADJR,
|
||||||
sco_codes.ATJ,
|
sco_codes.ATJ,
|
||||||
sco_codes.DEM,
|
sco_codes.DEM,
|
||||||
sco_codes.UEBSL,
|
sco_codes.UEBSL,
|
||||||
@ -982,14 +1175,14 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
):
|
):
|
||||||
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
||||||
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
||||||
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
validation = ScolarFormSemestreValidation.query.filter_by(
|
||||||
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
etudid=etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
||||||
).first()
|
).first()
|
||||||
super().__init__(
|
super().__init__(
|
||||||
etud=etud,
|
etud=etud,
|
||||||
code_valide=self.validation.code if self.validation is not None else None,
|
code_valide=validation.code if validation is not None else None,
|
||||||
)
|
)
|
||||||
# log(f"built {self}")
|
self.validation = validation
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.ue: UniteEns = ue
|
self.ue: UniteEns = ue
|
||||||
self.rcue: RegroupementCoherentUE = None
|
self.rcue: RegroupementCoherentUE = None
|
||||||
@ -1026,9 +1219,13 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
self.moy_ue_with_cap = ue_status["moy"]
|
self.moy_ue_with_cap = ue_status["moy"]
|
||||||
self.ue_status = ue_status
|
self.ue_status = ue_status
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
|
||||||
|
} codes={self.codes} explanation={self.explanation}>"""
|
||||||
|
|
||||||
def set_rcue(self, rcue: RegroupementCoherentUE):
|
def set_rcue(self, rcue: RegroupementCoherentUE):
|
||||||
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
"""Rattache cette UE à un RCUE. Cela peut modifier les codes
|
||||||
proposés (si compensation)"""
|
proposés par compute_codes() (si compensation)"""
|
||||||
self.rcue = rcue
|
self.rcue = rcue
|
||||||
|
|
||||||
def compute_codes(self):
|
def compute_codes(self):
|
||||||
@ -1048,9 +1245,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(
|
||||||
@ -1058,10 +1256,16 @@ 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
|
||||||
|
Scolog.logdb(
|
||||||
|
method="jury_but",
|
||||||
|
etudid=self.etud.id,
|
||||||
|
msg=f"Validation UE {self.ue.id} {self.ue.acronyme}: effacée",
|
||||||
|
commit=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.validation = ScolarFormSemestreValidation(
|
self.validation = ScolarFormSemestreValidation(
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
@ -1070,16 +1274,20 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
code=code,
|
code=code,
|
||||||
moy_ue=self.moy_ue,
|
moy_ue=self.moy_ue,
|
||||||
)
|
)
|
||||||
|
db.session.add(self.validation)
|
||||||
|
db.session.commit()
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="jury_but",
|
method="jury_but",
|
||||||
etudid=self.etud.id,
|
etudid=self.etud.id,
|
||||||
msg=f"Validation UE {self.ue.id}",
|
msg=f"Validation UE {self.ue.id} {self.ue.acronyme}({self.moy_ue}): {code}",
|
||||||
|
commit=True,
|
||||||
)
|
)
|
||||||
db.session.add(self.validation)
|
|
||||||
log(f"DecisionsProposeesUE: recording {self.validation}")
|
log(f"DecisionsProposeesUE: recording {self.validation}")
|
||||||
|
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id)
|
||||||
|
self.code_valide = code # mise à jour
|
||||||
self.recorded = True
|
self.recorded = True
|
||||||
|
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"""
|
||||||
@ -1090,7 +1298,13 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
for validation in validations:
|
for validation in validations:
|
||||||
log(f"DecisionsProposeesUE: deleting {validation}")
|
log(f"DecisionsProposeesUE: deleting {validation}")
|
||||||
db.session.delete(validation)
|
db.session.delete(validation)
|
||||||
db.session.flush()
|
Scolog.logdb(
|
||||||
|
method="jury_but",
|
||||||
|
etudid=self.etud.id,
|
||||||
|
msg=f"Validation UE {validation.ue.id} {validation.ue.acronyme}: effacée",
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
def descr_validation(self) -> str:
|
def descr_validation(self) -> str:
|
||||||
"""Description validation niveau enregistrée, pour PV jury.
|
"""Description validation niveau enregistrée, pour PV jury.
|
||||||
@ -1106,7 +1320,7 @@ class BUTCursusEtud: # WIP TODO
|
|||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre, etud: Identite):
|
def __init__(self, formsemestre: FormSemestre, etud: Identite):
|
||||||
if formsemestre.formation.referentiel_competence is None:
|
if formsemestre.formation.referentiel_competence is None:
|
||||||
raise ScoException("BUTCursusEtud: pas de référentiel de compétences")
|
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
||||||
assert len(etud.formsemestre_inscriptions) > 0
|
assert len(etud.formsemestre_inscriptions) > 0
|
||||||
self.formsemestre = formsemestre
|
self.formsemestre = formsemestre
|
||||||
self.etud = etud
|
self.etud = etud
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Jury BUT: table recap annuelle et liens saisie
|
"""Jury BUT: table recap annuelle et liens saisie
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
@ -31,7 +32,7 @@ from app.scodoc.sco_codes_parcours import (
|
|||||||
from app.scodoc import sco_formsemestre_status
|
from app.scodoc import sco_formsemestre_status
|
||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pvjury
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_saisie_jury_but(
|
def formsemestre_saisie_jury_but(
|
||||||
@ -62,16 +63,9 @@ def formsemestre_saisie_jury_but(
|
|||||||
# raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
|
# raise ScoValueError("Cette page ne fonctionne que sur les semestres pairs")
|
||||||
|
|
||||||
if formsemestre2.formation.referentiel_competence is None:
|
if formsemestre2.formation.referentiel_competence is None:
|
||||||
raise ScoValueError(
|
raise ScoNoReferentielCompetences(formation=formsemestre2.formation)
|
||||||
"""
|
|
||||||
<p>Pas de référentiel de compétences associé à la formation !</p>
|
|
||||||
<p>Pour associer un référentiel, passer par le menu <b>Semestre /
|
|
||||||
Voir la formation... </b> et suivre le lien <em>"associer à un référentiel
|
|
||||||
de compétences"</em>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
rows, titles, column_ids = get_jury_but_table(
|
rows, titles, column_ids, jury_stats = get_jury_but_table(
|
||||||
formsemestre2, read_only=read_only, mode=mode
|
formsemestre2, read_only=read_only, mode=mode
|
||||||
)
|
)
|
||||||
if not rows:
|
if not rows:
|
||||||
@ -153,6 +147,28 @@ def formsemestre_saisie_jury_but(
|
|||||||
f"""
|
f"""
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="jury_stats">
|
||||||
|
<div>Nb d'étudiants avec décision annuelle:
|
||||||
|
{sum(jury_stats["codes_annuels"].values())} / {jury_stats["nb_etuds"]}
|
||||||
|
</div>
|
||||||
|
<div><b>Codes annuels octroyés:</b></div>
|
||||||
|
<table class="jury_stats_codes">
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for code in sorted(jury_stats["codes_annuels"].keys()):
|
||||||
|
H.append(
|
||||||
|
f"""<tr>
|
||||||
|
<td>{code}</td>
|
||||||
|
<td style="text-align:right">{jury_stats["codes_annuels"][code]}</td>
|
||||||
|
<td style="text-align:right">{
|
||||||
|
(100*jury_stats["codes_annuels"][code] / jury_stats["nb_etuds"]):2.1f}%
|
||||||
|
</td>
|
||||||
|
</tr>"""
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{html_sco_header.sco_footer()}
|
{html_sco_header.sco_footer()}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -268,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(
|
||||||
@ -352,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:
|
||||||
@ -377,10 +393,17 @@ class RowCollector:
|
|||||||
|
|
||||||
def get_jury_but_table(
|
def get_jury_but_table(
|
||||||
formsemestre2: FormSemestre, read_only: bool = False, mode="jury", with_links=True
|
formsemestre2: FormSemestre, read_only: bool = False, mode="jury", with_links=True
|
||||||
) -> tuple[list[dict], list[str], list[str]]:
|
) -> tuple[list[dict], list[str], list[str], dict]:
|
||||||
"""Construit la table des résultats annuels pour le jury BUT"""
|
"""Construit la table des résultats annuels pour le jury BUT
|
||||||
|
=> rows_dict, titles, column_ids, jury_stats
|
||||||
|
où jury_stats est un dict donnant des comptages sur le jury.
|
||||||
|
"""
|
||||||
res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
|
res2: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre2)
|
||||||
titles = {} # column_id : title
|
titles = {} # column_id : title
|
||||||
|
jury_stats = {
|
||||||
|
"nb_etuds": len(formsemestre2.etuds_inscriptions),
|
||||||
|
"codes_annuels": collections.Counter(),
|
||||||
|
}
|
||||||
column_classes = {}
|
column_classes = {}
|
||||||
rows = []
|
rows = []
|
||||||
for etudid in formsemestre2.etuds_inscriptions:
|
for etudid in formsemestre2.etuds_inscriptions:
|
||||||
@ -417,6 +440,8 @@ def get_jury_but_table(
|
|||||||
f"""{deca.code_valide or ''}""",
|
f"""{deca.code_valide or ''}""",
|
||||||
"col_code_annee",
|
"col_code_annee",
|
||||||
)
|
)
|
||||||
|
if deca.code_valide:
|
||||||
|
jury_stats["codes_annuels"][deca.code_valide] += 1
|
||||||
# --- Le lien de saisie
|
# --- Le lien de saisie
|
||||||
if mode != "recap" and with_links:
|
if mode != "recap" and with_links:
|
||||||
row.add_cell(
|
row.add_cell(
|
||||||
@ -439,11 +464,14 @@ def get_jury_but_table(
|
|||||||
rows.append(row)
|
rows.append(row)
|
||||||
rows_dict = [row.get_row_dict() for row in rows]
|
rows_dict = [row.get_row_dict() for row in rows]
|
||||||
if len(rows_dict) > 0:
|
if len(rows_dict) > 0:
|
||||||
res2.recap_add_partitions(rows_dict, titles, col_idx=row.last_etud_cell_idx + 1)
|
col_idx = res2.recap_add_partitions(
|
||||||
|
rows_dict, titles, col_idx=row.last_etud_cell_idx + 1
|
||||||
|
)
|
||||||
|
res2.recap_add_cursus(rows_dict, titles, col_idx=col_idx + 1)
|
||||||
column_ids = [title for title in titles if not title.startswith("_")]
|
column_ids = [title for title in titles if not title.startswith("_")]
|
||||||
column_ids.sort(key=lambda col_id: titles.get("_" + col_id + "_col_order", 1000))
|
column_ids.sort(key=lambda col_id: titles.get("_" + col_id + "_col_order", 1000))
|
||||||
rows_dict.sort(key=lambda row: row["_nom_disp_order"])
|
rows_dict.sort(key=lambda row: row["_nom_disp_order"])
|
||||||
return rows_dict, titles, column_ids
|
return rows_dict, titles, column_ids, jury_stats
|
||||||
|
|
||||||
|
|
||||||
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -15,25 +15,32 @@ from app.scodoc import sco_cache
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_validation_auto_but(formsemestre: FormSemestre, only_adm=True) -> int:
|
def formsemestre_validation_auto_but(
|
||||||
"""Calcul automatique des décisions de jury sur une année BUT.
|
formsemestre: FormSemestre, only_adm: bool = True, no_overwrite: bool = True
|
||||||
Normalement, only_adm est True et on n'enregistre que les décisions ADM (de droit).
|
) -> int:
|
||||||
Si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
"""Calcul automatique des décisions de jury sur une "année" BUT.
|
||||||
(mode à n'utiliser que pour les tests)
|
|
||||||
|
|
||||||
Returns: nombre d'étudiants "admis"
|
- N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||||
|
si on a des RCUE "à cheval".
|
||||||
|
- Ne modifie jamais de décisions déjà enregistrées (sauf si no_overwrite est faux,
|
||||||
|
ce qui est utilisé pour certains tests unitaires).
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
Returns: nombre d'étudiants pour lesquels on a enregistré au moins un code.
|
||||||
"""
|
"""
|
||||||
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()
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return nb_admis
|
return nb_etud_modif
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -8,25 +8,34 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import flash, url_for
|
from flask import flash, render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.but import jury_but
|
from app.but import jury_but
|
||||||
from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE
|
from app.but.jury_but import (
|
||||||
|
DecisionsProposeesAnnee,
|
||||||
|
DecisionsProposeesRCUE,
|
||||||
|
DecisionsProposeesUE,
|
||||||
|
)
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
ApcNiveau,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreInscription,
|
FormSemestreInscription,
|
||||||
Identite,
|
Identite,
|
||||||
UniteEns,
|
UniteEns,
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
)
|
)
|
||||||
|
from app.models.config import ScoDocSiteConfig
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -35,53 +44,50 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
Si pas read_only, menus sélection codes jury.
|
Si pas read_only, menus sélection codes jury.
|
||||||
"""
|
"""
|
||||||
H = []
|
H = []
|
||||||
if deca.code_valide and not read_only:
|
|
||||||
erase_span = f"""<a href="{
|
|
||||||
url_for("notes.formsemestre_jury_but_erase",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
|
|
||||||
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
|
|
||||||
else:
|
|
||||||
erase_span = ""
|
|
||||||
|
|
||||||
H.append("""<div class="but_section_annee">""")
|
|
||||||
if deca.jury_annuel:
|
if deca.jury_annuel:
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
|
<div class="but_section_annee">
|
||||||
<div>
|
<div>
|
||||||
<b>Décision de jury pour l'année :</b> {
|
<b>Décision de jury pour l'année :</b> {
|
||||||
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
_gen_but_select("code_annee", deca.codes, deca.code_valide,
|
||||||
disabled=True, klass="manual")
|
disabled=True, klass="manual")
|
||||||
}
|
}
|
||||||
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
|
<span>({deca.code_valide or 'non'} enregistrée)</span>
|
||||||
<span>{erase_span}</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="but_explanation">{deca.explanation}</div>
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
H.append("""<div><em>Pas de décision annuelle (sem. impair)</em></div>""")
|
|
||||||
H.append("""</div>""")
|
|
||||||
|
|
||||||
if deca.formsemestre_pair is not None:
|
formsemestre_1 = deca.formsemestre_impair
|
||||||
annee_sco_pair = deca.formsemestre_pair.annee_scolaire()
|
formsemestre_2 = deca.formsemestre_pair
|
||||||
avertissement_redoublement = (
|
# Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval):
|
||||||
f"année {annee_sco_pair}-{annee_sco_pair+1}"
|
reverse_semestre = (
|
||||||
if annee_sco_pair != deca.annee_scolaire()
|
deca.formsemestre_pair
|
||||||
else ""
|
and deca.formsemestre_impair
|
||||||
|
and deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut
|
||||||
)
|
)
|
||||||
else:
|
if reverse_semestre:
|
||||||
avertissement_redoublement = ""
|
formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<div class="titre_niveaux"><b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b></div>
|
<div class="titre_niveaux">
|
||||||
|
<b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
|
||||||
|
</div>
|
||||||
|
<div class="but_explanation">{deca.explanation}</div>
|
||||||
<div class="but_annee">
|
<div class="but_annee">
|
||||||
<div class="titre"></div>
|
<div class="titre"></div>
|
||||||
<div class="titre">S{deca.formsemestre_impair.semestre_id
|
<div class="titre">{"S" +str(formsemestre_1.semestre_id)
|
||||||
if deca.formsemestre_impair else "-"}</div>
|
if formsemestre_1 else "-"}
|
||||||
<div class="titre">S{deca.formsemestre_pair.semestre_id
|
<span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str()
|
||||||
if deca.formsemestre_pair else "-"}
|
if formsemestre_1 else ""}</span>
|
||||||
<span class="avertissement_redoublement">{avertissement_redoublement}</span></div>
|
</div>
|
||||||
|
<div class="titre">{"S"+str(formsemestre_2.semestre_id)
|
||||||
|
if formsemestre_2 else "-"}
|
||||||
|
<span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str()
|
||||||
|
if formsemestre_2 else ""}</span>
|
||||||
|
</div>
|
||||||
<div class="titre">RCUE</div>
|
<div class="titre">RCUE</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -91,43 +97,52 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||||||
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
||||||
</div>"""
|
</div>"""
|
||||||
)
|
)
|
||||||
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id)
|
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id) # peut être None
|
||||||
if dec_rcue is None:
|
ues = [
|
||||||
H.append(
|
ue
|
||||||
"""<div class="niveau_vide"></div><div class="niveau_vide"></div><div class="niveau_vide"></div>"""
|
for ue in deca.ues_impair
|
||||||
)
|
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
||||||
continue
|
]
|
||||||
# Semestre impair
|
ue_impair = ues[0] if ues else None
|
||||||
|
ues = [
|
||||||
|
ue
|
||||||
|
for ue in deca.ues_pair
|
||||||
|
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
||||||
|
]
|
||||||
|
ue_pair = ues[0] if ues else None
|
||||||
|
# Les UEs à afficher,
|
||||||
|
# qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
|
||||||
|
ues_ro = [
|
||||||
|
(
|
||||||
|
ue_impair,
|
||||||
|
(deca.a_cheval and deca.formsemestre_id != deca.formsemestre_impair.id),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ue_pair,
|
||||||
|
deca.a_cheval and deca.formsemestre_id != deca.formsemestre_pair.id,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
# Ordonne selon les dates des 2 semestres considérés:
|
||||||
|
if reverse_semestre:
|
||||||
|
ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0]
|
||||||
|
# Colonnes d'UE:
|
||||||
|
for ue, ue_read_only in ues_ro:
|
||||||
|
if ue:
|
||||||
H.append(
|
H.append(
|
||||||
_gen_but_niveau_ue(
|
_gen_but_niveau_ue(
|
||||||
dec_rcue.rcue.ue_1,
|
ue,
|
||||||
deca.decisions_ues[dec_rcue.rcue.ue_1.id],
|
deca.decisions_ues[ue.id],
|
||||||
disabled=read_only,
|
disabled=read_only or ue_read_only,
|
||||||
|
annee_prec=ue_read_only,
|
||||||
|
niveau_id=ue.niveau_competence.id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Semestre pair
|
else:
|
||||||
H.append(
|
H.append("""<div class="niveau_vide"></div>""")
|
||||||
_gen_but_niveau_ue(
|
|
||||||
dec_rcue.rcue.ue_2,
|
# Colonne RCUE
|
||||||
deca.decisions_ues[dec_rcue.rcue.ue_2.id],
|
H.append(_gen_but_rcue(dec_rcue, niveau))
|
||||||
disabled=read_only,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# RCUE
|
|
||||||
H.append(
|
|
||||||
f"""<div class="but_niveau_rcue
|
|
||||||
{'recorded' if dec_rcue.code_valide is not None else ''}
|
|
||||||
">
|
|
||||||
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
|
||||||
<div class="but_code">{
|
|
||||||
_gen_but_select("code_rcue_"+str(niveau.id),
|
|
||||||
dec_rcue.codes,
|
|
||||||
dec_rcue.code_valide,
|
|
||||||
disabled=True, klass="manual"
|
|
||||||
)
|
|
||||||
}</div>
|
|
||||||
</div>"""
|
|
||||||
)
|
|
||||||
H.append("</div>") # but_annee
|
H.append("</div>") # but_annee
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
@ -138,9 +153,12 @@ def _gen_but_select(
|
|||||||
code_valide: str,
|
code_valide: str,
|
||||||
disabled: bool = False,
|
disabled: bool = False,
|
||||||
klass: str = "",
|
klass: str = "",
|
||||||
|
data: dict = {},
|
||||||
) -> str:
|
) -> str:
|
||||||
"Le menu html select avec les codes"
|
"Le menu html select avec les codes"
|
||||||
h = "\n".join(
|
# if disabled: # mauvaise idée car le disabled est traité en JS
|
||||||
|
# return f"""<div class="but_code {klass}">{code_valide}</div>"""
|
||||||
|
options_htm = "\n".join(
|
||||||
[
|
[
|
||||||
f"""<option value="{code}"
|
f"""<option value="{code}"
|
||||||
{'selected' if code == code_valide else ''}
|
{'selected' if code == code_valide else ''}
|
||||||
@ -151,33 +169,54 @@ def _gen_but_select(
|
|||||||
)
|
)
|
||||||
return f"""<select required name="{name}"
|
return f"""<select required name="{name}"
|
||||||
class="but_code {klass}"
|
class="but_code {klass}"
|
||||||
|
data-orig_code="{code_valide or (codes[0] if codes else '')}"
|
||||||
|
data-orig_recorded="{code_valide or ''}"
|
||||||
onchange="change_menu_code(this);"
|
onchange="change_menu_code(this);"
|
||||||
{"disabled" if disabled else ""}
|
{"disabled" if disabled else ""}
|
||||||
>{h}</select>
|
{" ".join( f'data-{k}="{v}"' for (k,v) in data.items() )}
|
||||||
|
>{options_htm}</select>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _gen_but_niveau_ue(ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled=False):
|
def _gen_but_niveau_ue(
|
||||||
|
ue: UniteEns,
|
||||||
|
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"]:
|
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
||||||
moy_ue_str = f"""<span class="ue_cap">{
|
moy_ue_str = f"""<span class="ue_cap">{
|
||||||
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
|
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
|
||||||
scoplement = f"""<div class="scoplement">
|
scoplement = f"""<div class="scoplement">
|
||||||
<div>
|
<div>
|
||||||
<b>UE {ue.acronyme} capitalisée le
|
<b>UE {ue.acronyme} capitalisée </b>
|
||||||
{dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
|
<span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
|
||||||
</b>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>UE en cours avec moyenne
|
<div>UE en cours
|
||||||
{scu.fmt_note(dec_ue.moy_ue)}
|
{ "sans notes" if np.isnan(dec_ue.moy_ue)
|
||||||
|
else
|
||||||
|
("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
moy_ue_str = f"""<span>{scu.fmt_note(dec_ue.moy_ue)}</span>"""
|
||||||
|
if dec_ue.code_valide:
|
||||||
|
scoplement = f"""<div class="scoplement">
|
||||||
|
<div>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>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
scoplement = ""
|
scoplement = ""
|
||||||
|
|
||||||
return f"""<div class="but_niveau_ue {
|
return f"""<div class="but_niveau_ue {
|
||||||
'recorded' if dec_ue.code_valide is not None else ''}
|
'recorded' if dec_ue.code_valide is not None else ''}
|
||||||
|
{'annee_prec' if annee_prec else ''}
|
||||||
">
|
">
|
||||||
<div title="{ue.titre}">{ue.acronyme}</div>
|
<div title="{ue.titre}">{ue.acronyme}</div>
|
||||||
<div class="but_note with_scoplement">
|
<div class="but_note with_scoplement">
|
||||||
@ -187,37 +226,82 @@ def _gen_but_niveau_ue(ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled=Fals
|
|||||||
<div class="but_code">{
|
<div class="but_code">{
|
||||||
_gen_but_select("code_ue_"+str(ue.id),
|
_gen_but_select("code_ue_"+str(ue.id),
|
||||||
dec_ue.codes,
|
dec_ue.codes,
|
||||||
dec_ue.code_valide, disabled=disabled
|
dec_ue.code_valide,
|
||||||
|
disabled=disabled,
|
||||||
|
klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else ""
|
||||||
)
|
)
|
||||||
}</div>
|
}</div>
|
||||||
|
|
||||||
</div>"""
|
</div>"""
|
||||||
|
|
||||||
|
|
||||||
|
def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
||||||
|
if dec_rcue is None:
|
||||||
|
return """
|
||||||
|
<div class="but_niveau_rcue niveau_vide with_scoplement">
|
||||||
|
<div></div>
|
||||||
|
<div class="scoplement">Pas de RCUE (UE non capitalisée ?)</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
scoplement = (
|
||||||
|
f"""<div class="scoplement">{
|
||||||
|
dec_rcue.validation.to_html()
|
||||||
|
}</div>"""
|
||||||
|
if dec_rcue.validation
|
||||||
|
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 {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">
|
||||||
|
{_gen_but_select("code_rcue_"+str(niveau.id),
|
||||||
|
dec_rcue.codes,
|
||||||
|
dec_rcue.code_valide,
|
||||||
|
disabled=True,
|
||||||
|
klass="manual code_rcue",
|
||||||
|
data = { "niveau_id" : str(niveau.id)}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def jury_but_semestriel(
|
def jury_but_semestriel(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
navigation_div: str = "",
|
navigation_div: str = "",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)"""
|
"""Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)."""
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
|
parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
|
||||||
inscription_etat = etud.inscription_etat(formsemestre.id)
|
inscription_etat = etud.inscription_etat(formsemestre.id)
|
||||||
semestre_terminal = (
|
semestre_terminal = (
|
||||||
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
|
formsemestre.semestre_id >= formsemestre.formation.get_parcours().NB_SEM
|
||||||
)
|
)
|
||||||
|
autorisations_passage = ScolarAutorisationInscription.query.filter_by(
|
||||||
|
etudid=etud.id,
|
||||||
|
origin_formsemestre_id=formsemestre.id,
|
||||||
|
).all()
|
||||||
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
||||||
# ou si décision déjà enregistrée:
|
# ou si décision déjà enregistrée:
|
||||||
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
||||||
formsemestre.semestre_id + 1
|
formsemestre.semestre_id + 1
|
||||||
) in (
|
) in (a.semestre_id for a in autorisations_passage)
|
||||||
a.semestre_id
|
|
||||||
for a in ScolarAutorisationInscription.query.filter_by(
|
|
||||||
etudid=etud.id,
|
|
||||||
origin_formsemestre_id=formsemestre.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
decisions_ues = {
|
decisions_ues = {
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
||||||
for ue in ues
|
for ue in ues
|
||||||
@ -230,9 +314,9 @@ def jury_but_semestriel(
|
|||||||
for key in request.form:
|
for key in request.form:
|
||||||
code = request.form[key]
|
code = request.form[key]
|
||||||
# Codes d'UE
|
# Codes d'UE
|
||||||
m = re.match(r"^code_ue_(\d+)$", key)
|
code_match = re.match(r"^code_ue_(\d+)$", key)
|
||||||
if m:
|
if code_match:
|
||||||
ue_id = int(m.group(1))
|
ue_id = int(code_match.group(1))
|
||||||
dec_ue = decisions_ues.get(ue_id)
|
dec_ue = decisions_ues.get(ue_id)
|
||||||
if not dec_ue:
|
if not dec_ue:
|
||||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
||||||
@ -241,7 +325,9 @@ def jury_but_semestriel(
|
|||||||
flash("codes enregistrés")
|
flash("codes enregistrés")
|
||||||
if not semestre_terminal:
|
if not semestre_terminal:
|
||||||
if request.form.get("autorisation_passage"):
|
if request.form.get("autorisation_passage"):
|
||||||
if not est_autorise_a_passer:
|
if not formsemestre.semestre_id + 1 in (
|
||||||
|
a.semestre_id for a in autorisations_passage
|
||||||
|
):
|
||||||
ScolarAutorisationInscription.autorise_etud(
|
ScolarAutorisationInscription.autorise_etud(
|
||||||
etud.id,
|
etud.id,
|
||||||
formsemestre.formation.formation_code,
|
formsemestre.formation.formation_code,
|
||||||
@ -250,7 +336,8 @@ def jury_but_semestriel(
|
|||||||
)
|
)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(
|
flash(
|
||||||
f"autorisation de passage en S{formsemestre.semestre_id + 1} enregistrée"
|
f"""autorisation de passage en S{formsemestre.semestre_id + 1
|
||||||
|
} enregistrée"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if est_autorise_a_passer:
|
if est_autorise_a_passer:
|
||||||
@ -279,7 +366,7 @@ def jury_but_semestriel(
|
|||||||
warning = ""
|
warning = ""
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Validation BUT",
|
page_title=f"Validation BUT S{formsemestre.semestre_id}",
|
||||||
formsemestre_id=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
cssstyles=("css/jury_but.css",),
|
cssstyles=("css/jury_but.css",),
|
||||||
@ -300,25 +387,35 @@ def jury_but_semestriel(
|
|||||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>Jury sur un semestre BUT isolé</h3>
|
<h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
|
||||||
{warning}
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="post" id="jury_but">
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]):
|
|
||||||
|
erase_span = ""
|
||||||
|
if not read_only:
|
||||||
|
# Requête toutes les validations (pas seulement celles du deca courant),
|
||||||
|
# au cas où: changement d'architecture, saisie en mode classique, ...
|
||||||
|
validations = ScolarFormSemestreValidation.query.filter_by(
|
||||||
|
etudid=etud.id, formsemestre_id=formsemestre.id
|
||||||
|
).all()
|
||||||
|
if validations:
|
||||||
erase_span = f"""<a href="{
|
erase_span = f"""<a href="{
|
||||||
url_for("notes.formsemestre_jury_but_erase",
|
url_for("notes.formsemestre_jury_but_erase",
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
||||||
etudid=etud.id, only_one_sem=1)}" class="stdlink">effacer décisions</a>"""
|
etudid=etud.id, only_one_sem=1)
|
||||||
|
}" class="stdlink">effacer les décisions enregistrées</a>"""
|
||||||
else:
|
else:
|
||||||
erase_span = "aucune décision enregistrée pour ce semestre"
|
erase_span = (
|
||||||
|
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
|
||||||
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<div class="but_section_annee">
|
<div class="but_section_annee">
|
||||||
<span>{erase_span}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
|
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
|
||||||
"""
|
"""
|
||||||
@ -354,11 +451,27 @@ def jury_but_semestriel(
|
|||||||
)
|
)
|
||||||
H.append("</div>") # but_annee
|
H.append("</div>") # but_annee
|
||||||
|
|
||||||
|
div_autorisations_passage = (
|
||||||
|
f"""
|
||||||
|
<div class="but_autorisations_passage">
|
||||||
|
<span>Autorisé à passer en :</span>
|
||||||
|
{ ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
if autorisations_passage
|
||||||
|
else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
|
||||||
|
)
|
||||||
|
H.append(div_autorisations_passage)
|
||||||
|
|
||||||
if read_only:
|
if read_only:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="but_explanation">
|
f"""<div class="but_explanation">
|
||||||
Vous n'avez pas la permission de modifier ces décisions.
|
{"Vous n'avez pas la permission de modifier ces décisions."
|
||||||
Les champs entourés en vert sont enregistrés.</div>"""
|
if formsemestre.etat
|
||||||
|
else "Semestre verrouillé."}
|
||||||
|
Les champs entourés en vert sont enregistrés.
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if formsemestre.semestre_id < formsemestre.formation.get_parcours().NB_SEM:
|
if formsemestre.semestre_id < formsemestre.formation.get_parcours().NB_SEM:
|
||||||
@ -375,13 +488,26 @@ def jury_but_semestriel(
|
|||||||
else:
|
else:
|
||||||
H.append("""<div class="help">dernier semestre de la formation.</div>""")
|
H.append("""<div class="help">dernier semestre de la formation.</div>""")
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
f"""
|
||||||
<div class="but_buttons">
|
<div class="but_buttons">
|
||||||
<input type="submit" value="Enregistrer ces décisions">
|
<span><input type="submit" value="Enregistrer ces décisions"></span>
|
||||||
|
<span>{erase_span}</span>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(navigation_div)
|
H.append(navigation_div)
|
||||||
|
H.append("</div>")
|
||||||
|
H.append(
|
||||||
|
render_template(
|
||||||
|
"but/documentation_codes_jury.html",
|
||||||
|
nom_univ=f"""Export {sco_preferences.get_preference("InstituteName")
|
||||||
|
or sco_preferences.get_preference("UnivName")
|
||||||
|
or "Apogée"}""",
|
||||||
|
codes=ScoDocSiteConfig.get_codes_apo_dict(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
@ -407,7 +533,6 @@ def infos_fiche_etud_html(etudid: int) -> str:
|
|||||||
# temporaire quick & dirty: affiche le dernier
|
# temporaire quick & dirty: affiche le dernier
|
||||||
try:
|
try:
|
||||||
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
|
deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1])
|
||||||
if True: # len(deca.rcues_annee) > 0:
|
|
||||||
return f"""<div class="infos_but">
|
return f"""<div class="infos_but">
|
||||||
{show_etud(deca, read_only=True)}
|
{show_etud(deca, read_only=True)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -430,6 +430,22 @@ class BonusAmiens(BonusSportAdditif):
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
class BonusBesanconVesoul(BonusSportAdditif):
|
||||||
|
"""Bonus IUT Besançon - Vesoul pour les UE libres
|
||||||
|
|
||||||
|
<p>Toute note non nulle, peu importe sa valeur, entraine un bonus de 0,2 point
|
||||||
|
sur toutes les moyennes d'UE.
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_besancon_vesoul"
|
||||||
|
displayed_name = "IUT de Besançon - Vesoul"
|
||||||
|
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||||
|
seuil_moy_gen = 0.0 # tous les points sont comptés
|
||||||
|
proportion_point = 1e10 # infini
|
||||||
|
bonus_max = 0.2
|
||||||
|
|
||||||
|
|
||||||
class BonusBethune(BonusSportMultiplicatif):
|
class BonusBethune(BonusSportMultiplicatif):
|
||||||
"""
|
"""
|
||||||
Calcul bonus modules optionnels (sport, culture), règle IUT de Béthune.
|
Calcul bonus modules optionnels (sport, culture), règle IUT de Béthune.
|
||||||
@ -647,7 +663,10 @@ class BonusCalais(BonusSportAdditif):
|
|||||||
dans la limite de 10 points. 6% de ces points cumulés s'ajoutent :
|
dans la limite de 10 points. 6% de ces points cumulés s'ajoutent :
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
<li><b>en BUT et LP</b> à la moyenne des UE dont l'acronyme fini par <b>BS</b> (ex : UE2.1BS, UE32BS)
|
</li>
|
||||||
|
<li><b>en BUT et LP</b> à la moyenne des UE dont l'acronyme fini par <b>BS</b>
|
||||||
|
(ex : UE2.1BS, UE32BS)
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -122,6 +122,10 @@ def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame
|
|||||||
event_date :
|
event_date :
|
||||||
} ]
|
} ]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Note: pour récupérer aussi les UE validées en CMp ou ADJ, changer une ligne
|
||||||
|
# and ( SFV.code = 'ADM' or SFV.code = 'ADJ' or SFV.code = 'CMP' )
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT DISTINCT SFV.*, ue.ue_code
|
SELECT DISTINCT SFV.*, ue.ue_code
|
||||||
FROM
|
FROM
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -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
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -33,10 +33,7 @@ import pandas as pd
|
|||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import (
|
from app.models import (
|
||||||
DispenseUE,
|
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreInscription,
|
|
||||||
Identite,
|
|
||||||
Module,
|
Module,
|
||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
ModuleUECoef,
|
ModuleUECoef,
|
||||||
@ -218,31 +215,6 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_dispense_ues(
|
|
||||||
formsemestre: FormSemestre, etudids: pd.Index, ues: list[UniteEns]
|
|
||||||
) -> set[tuple[int, int]]:
|
|
||||||
"""Construit l'ensemble des
|
|
||||||
etudids = modimpl_inscr_df.index, # les etudids
|
|
||||||
ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport
|
|
||||||
|
|
||||||
Résultat: set de (etudid, ue_id).
|
|
||||||
"""
|
|
||||||
dispense_ues = set()
|
|
||||||
ue_sem_by_code = {ue.ue_code: ue for ue in ues}
|
|
||||||
# Prend toutes les dispenses obtenues par des étudiants de ce formsemestre,
|
|
||||||
# puis filtre sur inscrits et code d'UE UE
|
|
||||||
for dispense_ue in DispenseUE.query.join(
|
|
||||||
Identite, FormSemestreInscription
|
|
||||||
).filter_by(formsemestre_id=formsemestre.id):
|
|
||||||
if dispense_ue.etudid in etudids:
|
|
||||||
# UE dans le semestre avec même code ?
|
|
||||||
ue = ue_sem_by_code.get(dispense_ue.ue.ue_code)
|
|
||||||
if ue is not None:
|
|
||||||
dispense_ues.add((dispense_ue.etudid, ue.id))
|
|
||||||
|
|
||||||
return dispense_ues
|
|
||||||
|
|
||||||
|
|
||||||
def compute_ue_moys_apc(
|
def compute_ue_moys_apc(
|
||||||
sem_cube: np.array,
|
sem_cube: np.array,
|
||||||
etuds: list,
|
etuds: list,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ from app.comp.res_compat import NotesTableCompat
|
|||||||
from app.comp.bonus_spo import BonusSport
|
from app.comp.bonus_spo import BonusSport
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import DispenseUE, UniteEns
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
modimpl.module.ue.type != UE_SPORT
|
modimpl.module.ue.type != UE_SPORT
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
]
|
]
|
||||||
self.dispense_ues = moy_ue.load_dispense_ues(
|
self.dispense_ues = DispenseUE.load_formsemestre_dispense_ues_set(
|
||||||
self.formsemestre, self.modimpl_inscr_df.index, self.ues
|
self.formsemestre, self.modimpl_inscr_df.index, self.ues
|
||||||
)
|
)
|
||||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -494,7 +494,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
classes: str = "",
|
classes: str = "",
|
||||||
idx: int = 100,
|
idx: int = 100,
|
||||||
):
|
):
|
||||||
"Add a row to our table. classes is a list of css class names"
|
"Add a cell to our table. classes is a list of css class names"
|
||||||
row[col_id] = content
|
row[col_id] = content
|
||||||
if classes:
|
if classes:
|
||||||
row[f"_{col_id}_class"] = classes + f" c{idx}"
|
row[f"_{col_id}_class"] = classes + f" c{idx}"
|
||||||
@ -519,6 +519,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
row, "code_nip", "code_nip", etud.code_nip or "", "codes", idx
|
row, "code_nip", "code_nip", etud.code_nip or "", "codes", idx
|
||||||
)
|
)
|
||||||
# --- Rang
|
# --- Rang
|
||||||
|
if not self.formsemestre.block_moyenne_generale:
|
||||||
idx = add_cell(
|
idx = add_cell(
|
||||||
row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang", idx
|
row, "rang", "Rg", self.etud_moy_gen_ranks[etudid], "rang", idx
|
||||||
)
|
)
|
||||||
@ -542,12 +543,17 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
formsemestre_id=self.formsemestre.id,
|
formsemestre_id=self.formsemestre.id,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
)
|
)
|
||||||
|
row["_nom_short_data"] = {
|
||||||
|
"etudid": etud.id,
|
||||||
|
"nomprenom": etud.nomprenom,
|
||||||
|
}
|
||||||
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
|
row["_nom_short_target_attrs"] = f'class="etudinfo" id="{etudid}"'
|
||||||
row["_nom_disp_target"] = row["_nom_short_target"]
|
row["_nom_disp_target"] = row["_nom_short_target"]
|
||||||
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
|
row["_nom_disp_target_attrs"] = row["_nom_short_target_attrs"]
|
||||||
|
|
||||||
idx = 30 # début des colonnes de notes
|
idx = 30 # début des colonnes de notes
|
||||||
# --- Moyenne générale
|
# --- Moyenne générale
|
||||||
|
if not self.formsemestre.block_moyenne_generale:
|
||||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||||
note_class = ""
|
note_class = ""
|
||||||
if moy_gen is False:
|
if moy_gen is False:
|
||||||
@ -567,7 +573,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
)
|
)
|
||||||
# --- Moyenne d'UE
|
# --- Moyenne d'UE
|
||||||
nb_ues_validables, nb_ues_warning = 0, 0
|
nb_ues_validables, nb_ues_warning = 0, 0
|
||||||
for ue in ues_sans_bonus:
|
idx_ue_start = idx
|
||||||
|
for idx_ue, ue in enumerate(ues_sans_bonus):
|
||||||
ue_status = self.get_etud_ue_status(etudid, ue.id)
|
ue_status = self.get_etud_ue_status(etudid, ue.id)
|
||||||
if ue_status is not None:
|
if ue_status is not None:
|
||||||
col_id = f"moy_ue_{ue.id}"
|
col_id = f"moy_ue_{ue.id}"
|
||||||
@ -588,7 +595,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
ue.acronyme,
|
ue.acronyme,
|
||||||
fmt_note(val),
|
fmt_note(val),
|
||||||
"col_ue" + note_class,
|
"col_ue" + note_class,
|
||||||
idx,
|
idx_ue * 10000 + idx_ue_start,
|
||||||
)
|
)
|
||||||
titles_bot[
|
titles_bot[
|
||||||
f"_{col_id}_target_attrs"
|
f"_{col_id}_target_attrs"
|
||||||
@ -609,7 +616,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
f"Bonus {ue.acronyme}",
|
f"Bonus {ue.acronyme}",
|
||||||
val_fmt_html if allow_html else val_fmt,
|
val_fmt_html if allow_html else val_fmt,
|
||||||
"col_ue_bonus",
|
"col_ue_bonus",
|
||||||
idx,
|
idx_ue * 10000 + idx_ue_start + 1,
|
||||||
)
|
)
|
||||||
row[f"_bonus_ue_{ue.id}_xls"] = val_fmt
|
row[f"_bonus_ue_{ue.id}_xls"] = val_fmt
|
||||||
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
|
# Les moyennes des modules (ou ressources et SAÉs) dans cette UE
|
||||||
@ -654,7 +661,11 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
val_fmt_html,
|
val_fmt_html,
|
||||||
# class col_res mod_ue_123
|
# class col_res mod_ue_123
|
||||||
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
|
f"col_{modimpl.module.type_abbrv()} mod_ue_{ue.id}",
|
||||||
idx,
|
idx_ue * 10000
|
||||||
|
+ idx_ue_start
|
||||||
|
+ 1
|
||||||
|
+ (modimpl.module.module_type or 0) * 1000
|
||||||
|
+ (modimpl.module.numero or 0),
|
||||||
)
|
)
|
||||||
row[f"_{col_id}_xls"] = val_fmt
|
row[f"_{col_id}_xls"] = val_fmt
|
||||||
if modimpl.module.module_type == scu.ModuleType.MALUS:
|
if modimpl.module.module_type == scu.ModuleType.MALUS:
|
||||||
@ -704,7 +715,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
else:
|
else:
|
||||||
jury_code_sem = ""
|
jury_code_sem = ""
|
||||||
else:
|
else:
|
||||||
# formations classiqes: code semestre
|
# formations classiques: code semestre
|
||||||
dec_sem = self.validations.decisions_jury.get(etudid)
|
dec_sem = self.validations.decisions_jury.get(etudid)
|
||||||
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
jury_code_sem = dec_sem["code"] if dec_sem else ""
|
||||||
idx = add_cell(
|
idx = add_cell(
|
||||||
@ -722,17 +733,22 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
f"""<a href="{url_for('notes.formsemestre_validation_etud_form',
|
f"""<a href="{url_for('notes.formsemestre_validation_etud_form',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=self.formsemestre.id, etudid=etudid
|
scodoc_dept=g.scodoc_dept, formsemestre_id=self.formsemestre.id, etudid=etudid
|
||||||
)
|
)
|
||||||
}">{"saisir" if not jury_code_sem else "modifier"} décision</a>""",
|
}">{("saisir" if not jury_code_sem else "modifier")
|
||||||
|
if self.formsemestre.etat else "voir"} décisions</a>""",
|
||||||
"col_jury_link",
|
"col_jury_link",
|
||||||
idx,
|
idx,
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
|
||||||
self.recap_add_partitions(rows, titles)
|
col_idx = self.recap_add_partitions(rows, titles)
|
||||||
|
self.recap_add_cursus(rows, titles, col_idx=col_idx + 1)
|
||||||
self._recap_add_admissions(rows, titles)
|
self._recap_add_admissions(rows, titles)
|
||||||
|
|
||||||
# tri par rang croissant
|
# tri par rang croissant
|
||||||
|
if not self.formsemestre.block_moyenne_generale:
|
||||||
rows.sort(key=lambda e: e["_rang_order"])
|
rows.sort(key=lambda e: e["_rang_order"])
|
||||||
|
else:
|
||||||
|
rows.sort(key=lambda e: e["_ues_validables_order"], reverse=True)
|
||||||
|
|
||||||
# INFOS POUR FOOTER
|
# INFOS POUR FOOTER
|
||||||
bottom_infos = self._recap_bottom_infos(ues_sans_bonus, modimpl_ids, fmt_note)
|
bottom_infos = self._recap_bottom_infos(ues_sans_bonus, modimpl_ids, fmt_note)
|
||||||
@ -749,6 +765,20 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
for row in bottom_infos.values():
|
for row in bottom_infos.values():
|
||||||
row[c_class] = row.get(c_class, "") + " col_empty"
|
row[c_class] = row.get(c_class, "") + " col_empty"
|
||||||
|
|
||||||
|
# Ligne avec la classe de chaque colonne
|
||||||
|
# récupère le type à partir des classes css (hack...)
|
||||||
|
row_class = {}
|
||||||
|
for col_id in titles:
|
||||||
|
klass = titles.get(f"_{col_id}_class")
|
||||||
|
if klass:
|
||||||
|
row_class[col_id] = " ".join(
|
||||||
|
cls[4:] for cls in klass.split() if cls.startswith("col_")
|
||||||
|
)
|
||||||
|
# cette case (nb d'UE validables) a deux classes col_xxx, on en garde une seule:
|
||||||
|
if "ues_validables" in row_class[col_id]:
|
||||||
|
row_class[col_id] = "ues_validables"
|
||||||
|
bottom_infos["type_col"] = row_class
|
||||||
|
|
||||||
# --- TABLE FOOTER: ECTS, moyennes, min, max...
|
# --- TABLE FOOTER: ECTS, moyennes, min, max...
|
||||||
footer_rows = []
|
footer_rows = []
|
||||||
for (bottom_line, row) in bottom_infos.items():
|
for (bottom_line, row) in bottom_infos.items():
|
||||||
@ -772,7 +802,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
return (rows, footer_rows, titles, column_ids)
|
return (rows, footer_rows, titles, column_ids)
|
||||||
|
|
||||||
def _recap_bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
|
def _recap_bottom_infos(self, ues, modimpl_ids: set, fmt_note) -> dict:
|
||||||
"""Les informations à mettre en bas de la table: min, max, moy, ECTS"""
|
"""Les informations à mettre en bas de la table: min, max, moy, ECTS, Apo"""
|
||||||
row_min, row_max, row_moy, row_coef, row_ects, row_apo = (
|
row_min, row_max, row_moy, row_coef, row_ects, row_apo = (
|
||||||
{"_tr_class": "bottom_info", "_title": "Min."},
|
{"_tr_class": "bottom_info", "_title": "Min."},
|
||||||
{"_tr_class": "bottom_info"},
|
{"_tr_class": "bottom_info"},
|
||||||
@ -832,7 +862,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
row_moy[f"_{colid}_class"] = "col_empty"
|
row_moy[f"_{colid}_class"] = "col_empty"
|
||||||
row_apo[colid] = modimpl.module.code_apogee or ""
|
row_apo[colid] = modimpl.module.code_apogee or ""
|
||||||
|
|
||||||
return { # { key : row } avec key = min, max, moy, coef
|
return { # { key : row } avec key = min, max, moy, coef, ...
|
||||||
"min": row_min,
|
"min": row_min,
|
||||||
"max": row_max,
|
"max": row_max,
|
||||||
"moy": row_moy,
|
"moy": row_moy,
|
||||||
@ -880,7 +910,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
}
|
}
|
||||||
first = True
|
first = True
|
||||||
for i, cid in enumerate(fields):
|
for i, cid in enumerate(fields):
|
||||||
titles[f"_{cid}_col_order"] = 10000 + i # tout à droite
|
titles[f"_{cid}_col_order"] = 100000 + i # tout à droite
|
||||||
if first:
|
if first:
|
||||||
titles[f"_{cid}_class"] = "admission admission_first"
|
titles[f"_{cid}_class"] = "admission admission_first"
|
||||||
first = False
|
first = False
|
||||||
@ -899,10 +929,29 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
else:
|
else:
|
||||||
row[f"_{cid}_class"] = "admission"
|
row[f"_{cid}_class"] = "admission"
|
||||||
|
|
||||||
def recap_add_partitions(self, rows: list[dict], titles: dict, col_idx: int = None):
|
def recap_add_cursus(self, rows: list[dict], titles: dict, col_idx: int = None):
|
||||||
|
"""Ajoute colonne avec code cursus, eg 'S1 S2 S1'"""
|
||||||
|
cid = "code_cursus"
|
||||||
|
titles[cid] = "Cursus"
|
||||||
|
titles[f"_{cid}_col_order"] = col_idx
|
||||||
|
formation_code = self.formsemestre.formation.formation_code
|
||||||
|
for row in rows:
|
||||||
|
etud = Identite.query.get(row["etudid"])
|
||||||
|
row[cid] = " ".join(
|
||||||
|
[
|
||||||
|
f"S{ins.formsemestre.semestre_id}"
|
||||||
|
for ins in reversed(etud.inscriptions())
|
||||||
|
if ins.formsemestre.formation.formation_code == formation_code
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def recap_add_partitions(
|
||||||
|
self, rows: list[dict], titles: dict, col_idx: int = None
|
||||||
|
) -> int:
|
||||||
"""Ajoute les colonnes indiquant les groupes
|
"""Ajoute les colonnes indiquant les groupes
|
||||||
rows est une liste de dict avec une clé "etudid"
|
rows est une liste de dict avec une clé "etudid"
|
||||||
Les colonnes ont la classe css "partition"
|
Les colonnes ont la classe css "partition"
|
||||||
|
Renvoie l'indice de la dernière colonne utilisée
|
||||||
"""
|
"""
|
||||||
partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
|
partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
|
||||||
self.formsemestre.id
|
self.formsemestre.id
|
||||||
@ -951,6 +1000,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
row[rg_cid] = rang.get(row["etudid"], "")
|
row[rg_cid] = rang.get(row["etudid"], "")
|
||||||
|
|
||||||
first_partition = False
|
first_partition = False
|
||||||
|
return col_order
|
||||||
|
|
||||||
def _recap_add_evaluations(
|
def _recap_add_evaluations(
|
||||||
self, rows: list[dict], titles: dict, bottom_infos: dict
|
self, rows: list[dict], titles: dict, bottom_infos: dict
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import flask_login
|
|||||||
import app
|
import app
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class ZUser(object):
|
class ZUser(object):
|
||||||
@ -180,18 +181,23 @@ def scodoc7func(func):
|
|||||||
else:
|
else:
|
||||||
arg_names = argspec.args
|
arg_names = argspec.args
|
||||||
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
for arg_name in arg_names: # pour chaque arg de la fonction vue
|
||||||
if arg_name == "REQUEST": # ne devrait plus arriver !
|
|
||||||
# debug check, TODO remove after tests
|
|
||||||
raise ValueError("invalid REQUEST parameter !")
|
|
||||||
else:
|
|
||||||
# peut produire une KeyError s'il manque un argument attendu:
|
# peut produire une KeyError s'il manque un argument attendu:
|
||||||
v = req_args[arg_name]
|
v = req_args[arg_name]
|
||||||
# try to convert all arguments to INTEGERS
|
# try to convert all arguments to INTEGERS
|
||||||
# necessary for db ids and boolean values
|
# necessary for db ids and boolean values
|
||||||
try:
|
try:
|
||||||
v = int(v)
|
v = int(v) if v else v
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError) as exc:
|
||||||
pass
|
if arg_name in {
|
||||||
|
"etudid",
|
||||||
|
"formation_id",
|
||||||
|
"formsemestre_id",
|
||||||
|
"module_id",
|
||||||
|
"moduleimpl_id",
|
||||||
|
"partition_id",
|
||||||
|
"ue_id",
|
||||||
|
}:
|
||||||
|
raise ScoValueError("page introuvable (id invalide)") from exc
|
||||||
pos_arg_values.append(v)
|
pos_arg_values.append(v)
|
||||||
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
||||||
# current_app.logger.info("req_args=%s" % req_args)
|
# current_app.logger.info("req_args=%s" % req_args)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -63,6 +63,7 @@ class CodesDecisionsForm(FlaskForm):
|
|||||||
ABL = _build_code_field("ABL")
|
ABL = _build_code_field("ABL")
|
||||||
ADC = _build_code_field("ADC")
|
ADC = _build_code_field("ADC")
|
||||||
ADJ = _build_code_field("ADJ")
|
ADJ = _build_code_field("ADJ")
|
||||||
|
ADJR = _build_code_field("ADJR")
|
||||||
ADM = _build_code_field("ADM")
|
ADM = _build_code_field("ADM")
|
||||||
AJ = _build_code_field("AJ")
|
AJ = _build_code_field("AJ")
|
||||||
ATB = _build_code_field("ATB")
|
ATB = _build_code_field("ATB")
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import ModuleImpl, ModuleImplInscription
|
from app.models import ModuleImpl, ModuleImplInscription
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.scodoc.sco_utils import EtatAssiduite, localize_datetime, verif_interval
|
from app.scodoc.sco_utils import EtatAssiduite, localize_datetime, is_period_overlapping
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ class Assiduite(db.Model):
|
|||||||
Représente une assiduité:
|
Représente une assiduité:
|
||||||
- une plage horaire lié à un état et un étudiant
|
- une plage horaire lié à un état et un étudiant
|
||||||
- un module si spécifiée
|
- un module si spécifiée
|
||||||
|
- une description si spécifiée
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "assiduites"
|
__tablename__ = "assiduites"
|
||||||
@ -40,14 +41,17 @@ class Assiduite(db.Model):
|
|||||||
)
|
)
|
||||||
etat = db.Column(db.Integer, nullable=False)
|
etat = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
|
desc = db.Column(db.Text)
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
data = {
|
data = {
|
||||||
"assiduiteid": self.assiduiteid,
|
"assiduite_id": self.assiduite_id,
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
"moduleimpl_id": self.moduleimpl_id,
|
"moduleimpl_id": self.moduleimpl_id,
|
||||||
"date_debut": self.date_debut,
|
"date_debut": self.date_debut,
|
||||||
"date_fin": self.date_fin,
|
"date_fin": self.date_fin,
|
||||||
"etat": self.etat,
|
"etat": self.etat,
|
||||||
|
"desc": self.desc,
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -58,15 +62,10 @@ class Assiduite(db.Model):
|
|||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
etat: EtatAssiduite,
|
etat: EtatAssiduite,
|
||||||
module: int
|
moduleimpl: ModuleImpl = None,
|
||||||
or None = None, # XEV est-ce un id (alors module_id ou modimpl_id), ou un objet (ModuleImpl ??) => cela simplifiera le check d'erreur
|
description: str = None,
|
||||||
) -> object or int:
|
) -> object or int:
|
||||||
"""Créer une nouvelle assiduité pour l'étudiant
|
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||||||
Documentation des codes d'erreurs renvoyés:
|
|
||||||
1: Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)
|
|
||||||
2: l'ID du module_impl n'existe pas.
|
|
||||||
#XEV => utiliser plutôt des exceptions.
|
|
||||||
"""
|
|
||||||
# Vérification de non duplication des périodes
|
# Vérification de non duplication des périodes
|
||||||
assiduites: list[Assiduite] = etud.assiduites.all()
|
assiduites: list[Assiduite] = etud.assiduites.all()
|
||||||
|
|
||||||
@ -75,67 +74,40 @@ class Assiduite(db.Model):
|
|||||||
assiduites = [
|
assiduites = [
|
||||||
ass
|
ass
|
||||||
for ass in assiduites
|
for ass in assiduites
|
||||||
if verif_interval( # XEV
|
if is_period_overlapping(
|
||||||
(date_debut, date_fin),
|
(date_debut, date_fin),
|
||||||
(ass.date_debut, ass.date_fin),
|
(ass.date_debut, ass.date_fin),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if len(assiduites) != 0:
|
if len(assiduites) != 0:
|
||||||
return 1 # XEV raise une exception
|
raise ScoValueError(
|
||||||
|
"Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
|
||||||
|
)
|
||||||
|
|
||||||
if module is not None:
|
if moduleimpl is not None:
|
||||||
# Vérification de l'existence du module pour l'étudiant
|
# Vérification de l'existence du module pour l'étudiant
|
||||||
if cls.verif_moduleimpl(module, etud):
|
if moduleimpl.est_inscrit(etud):
|
||||||
nouv_assiduite = Assiduite(
|
nouv_assiduite = Assiduite(
|
||||||
date_debut=date_debut,
|
date_debut=date_debut,
|
||||||
date_fin=date_fin,
|
date_fin=date_fin,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
etudiant=etud,
|
etudiant=etud,
|
||||||
moduleimpl_id=module,
|
moduleimpl_id=moduleimpl.id,
|
||||||
|
desc=description,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return 2
|
raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
|
||||||
else:
|
else:
|
||||||
nouv_assiduite = Assiduite(
|
nouv_assiduite = Assiduite(
|
||||||
date_debut=date_debut,
|
date_debut=date_debut,
|
||||||
date_fin=date_fin,
|
date_fin=date_fin,
|
||||||
etat=etat,
|
etat=etat,
|
||||||
etudiant=etud,
|
etudiant=etud,
|
||||||
|
desc=description,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nouv_assiduite
|
return nouv_assiduite
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def verif_moduleimpl(moduleimpl_id: int, etud: Identite or int) -> bool:
|
|
||||||
"""
|
|
||||||
Vérifie si l'étudiant est bien inscrit au moduleimpl
|
|
||||||
|
|
||||||
Retourne Vrai si c'est le cas, faux sinon
|
|
||||||
"""
|
|
||||||
# XEV: cette méthode n'a pas de raison d'être dans la classe Assiduite
|
|
||||||
# et pourrait etre ModuleImpl.est_inscrit(etud)
|
|
||||||
# + éviter les "Identite or int" : cela complique les tests, mieux vaut avoir un type unique bien défini.
|
|
||||||
output = True
|
|
||||||
|
|
||||||
# XEV: "module" est un "modimpl": changer nom sinon on pense que c'est un Module
|
|
||||||
module: ModuleImpl = ModuleImpl.query.filter_by(
|
|
||||||
moduleimpl_id=moduleimpl_id
|
|
||||||
).first()
|
|
||||||
if module is None:
|
|
||||||
output = False
|
|
||||||
|
|
||||||
if output:
|
|
||||||
search_etudid: int = etud.id if type(etud) == Identite else etud
|
|
||||||
|
|
||||||
# XEV: is_xxx indique un booléen, or ici is_module est un comptage
|
|
||||||
is_module: int = ModuleImplInscription.query.filter_by(
|
|
||||||
etudid=search_etudid, moduleimpl_id=moduleimpl_id
|
|
||||||
).count()
|
|
||||||
|
|
||||||
output = is_module > 0
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(db.Model):
|
class Justificatif(db.Model):
|
||||||
"""
|
"""
|
||||||
@ -147,7 +119,7 @@ class Justificatif(db.Model):
|
|||||||
|
|
||||||
__tablename__ = "justificatifs"
|
__tablename__ = "justificatifs"
|
||||||
|
|
||||||
justifid = db.Column(db.Integer, primary_key=True)
|
justif_id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
date_debut = db.Column(
|
date_debut = db.Column(
|
||||||
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
||||||
@ -167,12 +139,18 @@ class Justificatif(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
raison = db.Column(db.Text())
|
raison = db.Column(db.Text())
|
||||||
fichier = db.Column(db.Integer()) # XEV qu'est-ce que cet entier ?
|
|
||||||
# XEV pour les fichiers stockés, on va utiliser sco_archives.py
|
"""
|
||||||
|
Les justificatifs sont enregistrés dans
|
||||||
|
<archivedir>/justificatifs/<dept_id>/<etudid>/<nom_fichier.extension>
|
||||||
|
|
||||||
|
d'après sco_archives.py#JustificatifArchiver
|
||||||
|
"""
|
||||||
|
fichier = db.Column(db.Text())
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
data = {
|
data = {
|
||||||
"justifid": self.assiduiteid,
|
"justif_id": self.assiduite_id,
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
"date_debut": self.date_debut,
|
"date_debut": self.date_debut,
|
||||||
"date_fin": self.date_fin,
|
"date_fin": self.date_fin,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
||||||
@ -14,7 +14,7 @@ import sqlalchemy
|
|||||||
from app import db
|
from app import db
|
||||||
|
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences
|
||||||
|
|
||||||
|
|
||||||
# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
|
# from https://stackoverflow.com/questions/2537471/method-of-iterating-over-sqlalchemy-models-defined-columns
|
||||||
@ -54,13 +54,15 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
"Référentiel de compétence d'une spécialité"
|
"Référentiel de compétence d'une spécialité"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||||
annexe = db.Column(db.Text())
|
annexe = db.Column(db.Text()) # '1', '22', ...
|
||||||
specialite = db.Column(db.Text())
|
specialite = db.Column(db.Text()) # 'CJ', 'RT', 'INFO', ...
|
||||||
specialite_long = db.Column(db.Text())
|
specialite_long = db.Column(
|
||||||
type_titre = db.Column(db.Text())
|
db.Text()
|
||||||
type_structure = db.Column(db.Text())
|
) # 'Carrière Juridique', 'Réseaux et télécommunications', ...
|
||||||
|
type_titre = db.Column(db.Text()) # 'B.U.T.'
|
||||||
|
type_structure = db.Column(db.Text()) # 'type1', 'type2', ...
|
||||||
type_departement = db.Column(db.Text()) # "secondaire", "tertiaire"
|
type_departement = db.Column(db.Text()) # "secondaire", "tertiaire"
|
||||||
version_orebut = db.Column(db.Text())
|
version_orebut = db.Column(db.Text()) # '2021-12-11 00:00:00'
|
||||||
_xml_attribs = { # Orébut xml attrib : attribute
|
_xml_attribs = { # Orébut xml attrib : attribute
|
||||||
"type": "type_titre",
|
"type": "type_titre",
|
||||||
"version": "version_orebut",
|
"version": "version_orebut",
|
||||||
@ -92,9 +94,10 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
return ""
|
return ""
|
||||||
return self.version_orebut.split()[0]
|
return self.version_orebut.split()[0]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, parcours: list["ApcParcours"] = None, with_app_critiques=True):
|
||||||
"""Représentation complète du ref. de comp.
|
"""Représentation complète du ref. de comp.
|
||||||
comme un dict.
|
comme un dict.
|
||||||
|
Si parcours est une liste de parcours, restreint l'export aux parcours listés.
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"dept_id": self.dept_id,
|
"dept_id": self.dept_id,
|
||||||
@ -109,8 +112,14 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
if self.scodoc_date_loaded
|
if self.scodoc_date_loaded
|
||||||
else "",
|
else "",
|
||||||
"scodoc_orig_filename": self.scodoc_orig_filename,
|
"scodoc_orig_filename": self.scodoc_orig_filename,
|
||||||
"competences": {x.titre: x.to_dict() for x in self.competences},
|
"competences": {
|
||||||
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
x.titre: x.to_dict(with_app_critiques=with_app_critiques)
|
||||||
|
for x in self.competences
|
||||||
|
},
|
||||||
|
"parcours": {
|
||||||
|
x.code: x.to_dict()
|
||||||
|
for x in (self.parcours if parcours is None else parcours)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_niveaux_by_parcours(
|
def get_niveaux_by_parcours(
|
||||||
@ -172,6 +181,27 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
||||||
return parcours, niveaux_by_parcours_no_tc
|
return parcours, niveaux_by_parcours_no_tc
|
||||||
|
|
||||||
|
def get_competences_tronc_commun(self) -> list["ApcCompetence"]:
|
||||||
|
"""Liste des compétences communes à tous les parcours du référentiel."""
|
||||||
|
parcours = self.parcours.all()
|
||||||
|
if not parcours:
|
||||||
|
return []
|
||||||
|
|
||||||
|
ids = set.intersection(
|
||||||
|
*[
|
||||||
|
{competence.id for competence in parcour.query_competences()}
|
||||||
|
for parcour in parcours
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return sorted(
|
||||||
|
[
|
||||||
|
competence
|
||||||
|
for competence in parcours[0].query_competences()
|
||||||
|
if competence.id in ids
|
||||||
|
],
|
||||||
|
key=lambda c: c.numero or 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ApcCompetence(db.Model, XMLModel):
|
class ApcCompetence(db.Model, XMLModel):
|
||||||
"Compétence"
|
"Compétence"
|
||||||
@ -213,7 +243,7 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<ApcCompetence {self.id} {self.titre!r}>"
|
return f"<ApcCompetence {self.id} {self.titre!r}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, with_app_critiques=True):
|
||||||
"repr dict recursive sur situations, composantes, niveaux"
|
"repr dict recursive sur situations, composantes, niveaux"
|
||||||
return {
|
return {
|
||||||
"id_orebut": self.id_orebut,
|
"id_orebut": self.id_orebut,
|
||||||
@ -225,7 +255,10 @@ class ApcCompetence(db.Model, XMLModel):
|
|||||||
"composantes_essentielles": [
|
"composantes_essentielles": [
|
||||||
x.to_dict() for x in self.composantes_essentielles
|
x.to_dict() for x in self.composantes_essentielles
|
||||||
],
|
],
|
||||||
"niveaux": {x.annee: x.to_dict() for x in self.niveaux},
|
"niveaux": {
|
||||||
|
x.annee: x.to_dict(with_app_critiques=with_app_critiques)
|
||||||
|
for x in self.niveaux
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_dict_bul(self) -> dict:
|
def to_dict_bul(self) -> dict:
|
||||||
@ -291,13 +324,15 @@ class ApcNiveau(db.Model, XMLModel):
|
|||||||
return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={
|
return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={
|
||||||
self.annee!r} {self.competence!r}>"""
|
self.annee!r} {self.competence!r}>"""
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, with_app_critiques=True):
|
||||||
"as a dict, recursif sur les AC"
|
"as a dict, recursif (ou non) sur les AC"
|
||||||
return {
|
return {
|
||||||
"libelle": self.libelle,
|
"libelle": self.libelle,
|
||||||
"annee": self.annee,
|
"annee": self.annee,
|
||||||
"ordre": self.ordre,
|
"ordre": self.ordre,
|
||||||
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
|
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques}
|
||||||
|
if with_app_critiques
|
||||||
|
else {},
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_dict_bul(self):
|
def to_dict_bul(self):
|
||||||
@ -322,9 +357,8 @@ class ApcNiveau(db.Model, XMLModel):
|
|||||||
if annee not in {1, 2, 3}:
|
if annee not in {1, 2, 3}:
|
||||||
raise ValueError("annee invalide pour un parcours BUT")
|
raise ValueError("annee invalide pour un parcours BUT")
|
||||||
if referentiel_competence is None:
|
if referentiel_competence is None:
|
||||||
raise ScoValueError(
|
raise ScoNoReferentielCompetences()
|
||||||
"Pas de référentiel de compétences associé à la formation !"
|
|
||||||
)
|
|
||||||
annee_formation = f"BUT{annee}"
|
annee_formation = f"BUT{annee}"
|
||||||
if parcour is None:
|
if parcour is None:
|
||||||
return ApcNiveau.query.filter(
|
return ApcNiveau.query.filter(
|
||||||
@ -470,6 +504,14 @@ class ApcParcours(db.Model, XMLModel):
|
|||||||
d["annees"] = {x.ordre: x.to_dict() for x in self.annees}
|
d["annees"] = {x.ordre: x.to_dict() for x in self.annees}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def query_competences(self) -> flask_sqlalchemy.BaseQuery:
|
||||||
|
"Les compétences associées à ce parcours"
|
||||||
|
return (
|
||||||
|
ApcCompetence.query.join(ApcParcoursNiveauCompetence, ApcAnneeParcours)
|
||||||
|
.filter_by(parcours_id=self.id)
|
||||||
|
.order_by(ApcCompetence.numero)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ApcAnneeParcours(db.Model, XMLModel):
|
class ApcAnneeParcours(db.Model, XMLModel):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -2,19 +2,17 @@
|
|||||||
|
|
||||||
"""Décisions de jury (validations) des RCUE et années du BUT
|
"""Décisions de jury (validations) des RCUE et années du BUT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import flask_sqlalchemy
|
|
||||||
from sqlalchemy.sql import text
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from app import db
|
import flask_sqlalchemy
|
||||||
|
|
||||||
|
from app import db
|
||||||
from app.models import CODE_STR_LEN
|
from app.models import CODE_STR_LEN
|
||||||
from app.models.but_refcomp import ApcNiveau
|
from app.models.but_refcomp import ApcNiveau
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.ues import UniteEns
|
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import sco_codes_parcours as sco_codes
|
from app.scodoc import sco_codes_parcours as sco_codes
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -63,13 +61,32 @@ class ApcValidationRCUE(db.Model):
|
|||||||
self.ue1}/{self.ue2}:{self.code!r}>"""
|
self.ue1}/{self.ue2}:{self.code!r}>"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"""décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {self.code}"""
|
return f"""Décision sur RCUE {self.ue1.acronyme}/{self.ue2.acronyme}: {
|
||||||
|
self.code} enregistrée le {self.date.strftime("%d/%m/%Y")}"""
|
||||||
|
|
||||||
|
def to_html(self) -> str:
|
||||||
|
"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")}
|
||||||
|
à {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
|
||||||
return self.ue2.niveau_competence
|
return self.ue2.niveau_competence
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"as a dict"
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
d.pop("_sa_instance_state", None)
|
||||||
|
return d
|
||||||
|
|
||||||
def to_dict_bul(self) -> dict:
|
def to_dict_bul(self) -> dict:
|
||||||
"Export dict pour bulletins: le code et le niveau de compétence"
|
"Export dict pour bulletins: le code et le niveau de compétence"
|
||||||
niveau = self.niveau()
|
niveau = self.niveau()
|
||||||
@ -96,10 +113,6 @@ class RegroupementCoherentUE:
|
|||||||
dec_ue_2: "DecisionsProposeesUE",
|
dec_ue_2: "DecisionsProposeesUE",
|
||||||
inscription_etat: str,
|
inscription_etat: str,
|
||||||
):
|
):
|
||||||
from app.comp import res_sem
|
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
|
||||||
|
|
||||||
# from app.but.jury_but import DecisionsProposeesUE
|
|
||||||
ue_1 = dec_ue_1.ue
|
ue_1 = dec_ue_1.ue
|
||||||
ue_2 = dec_ue_2.ue
|
ue_2 = dec_ue_2.ue
|
||||||
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
|
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
|
||||||
@ -144,6 +157,11 @@ class RegroupementCoherentUE:
|
|||||||
self.ue_1.acronyme}({self.moy_ue_1}) {
|
self.ue_1.acronyme}({self.moy_ue_1}) {
|
||||||
self.ue_2.acronyme}({self.moy_ue_2})>"""
|
self.ue_2.acronyme}({self.moy_ue_2})>"""
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"""RCUE {
|
||||||
|
self.ue_1.acronyme}({self.moy_ue_1}) + {
|
||||||
|
self.ue_2.acronyme}({self.moy_ue_2})"""
|
||||||
|
|
||||||
def query_validations(
|
def query_validations(
|
||||||
self,
|
self,
|
||||||
) -> flask_sqlalchemy.BaseQuery: # list[ApcValidationRCUE]
|
) -> flask_sqlalchemy.BaseQuery: # list[ApcValidationRCUE]
|
||||||
@ -174,8 +192,9 @@ class RegroupementCoherentUE:
|
|||||||
return self.query_validations().count() > 0
|
return self.query_validations().count() > 0
|
||||||
|
|
||||||
def est_compensable(self):
|
def est_compensable(self):
|
||||||
"""Vrai si ce RCUE est validable par compensation
|
"""Vrai si ce RCUE est validable (uniquement) par compensation
|
||||||
c'est à dire que sa moyenne est > 10 avec une UE < 10
|
c'est à dire que sa moyenne est > 10 avec une UE < 10.
|
||||||
|
Note: si ADM, est_compensable est faux.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
(self.moy_rcue is not None)
|
(self.moy_rcue is not None)
|
||||||
@ -296,7 +315,8 @@ class ApcValidationAnnee(db.Model):
|
|||||||
formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees")
|
formsemestre = db.relationship("FormSemestre", backref="apc_validations_annees")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.id} {self.etud} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"
|
return f"""<{self.__class__.__name__} {self.id} {self.etud
|
||||||
|
} BUT{self.ordre}/{self.annee_scolaire}:{self.code!r}>"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}"""
|
return f"""décision sur année BUT{self.ordre} {self.annee_scolaire} : {self.code}"""
|
||||||
@ -333,7 +353,8 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
|||||||
titres_rcues.append(f"""pas de compétence: code {dec_rcue["code"]}""")
|
titres_rcues.append(f"""pas de compétence: code {dec_rcue["code"]}""")
|
||||||
else:
|
else:
|
||||||
titres_rcues.append(
|
titres_rcues.append(
|
||||||
f"""{niveau["competence"]["titre"]} {niveau["ordre"]}: {dec_rcue["code"]}"""
|
f"""{niveau["competence"]["titre"]} {niveau["ordre"]}: {
|
||||||
|
dec_rcue["code"]}"""
|
||||||
)
|
)
|
||||||
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
|
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
|
||||||
decisions["descr_decisions_niveaux"] = (
|
decisions["descr_decisions_niveaux"] = (
|
||||||
|
@ -13,6 +13,7 @@ from app.scodoc.sco_codes_parcours import (
|
|||||||
ABL,
|
ABL,
|
||||||
ADC,
|
ADC,
|
||||||
ADJ,
|
ADJ,
|
||||||
|
ADJR,
|
||||||
ADM,
|
ADM,
|
||||||
AJ,
|
AJ,
|
||||||
ATB,
|
ATB,
|
||||||
@ -34,6 +35,7 @@ CODES_SCODOC_TO_APO = {
|
|||||||
ABL: "ABL",
|
ABL: "ABL",
|
||||||
ADC: "ADMC",
|
ADC: "ADMC",
|
||||||
ADJ: "ADM",
|
ADJ: "ADM",
|
||||||
|
ADJR: "ADM",
|
||||||
ADM: "ADM",
|
ADM: "ADM",
|
||||||
AJ: "AJ",
|
AJ: "AJ",
|
||||||
ATB: "AJAC",
|
ATB: "AJAC",
|
||||||
|
@ -55,7 +55,8 @@ class Formation(db.Model):
|
|||||||
modules = db.relationship("Module", lazy="dynamic", backref="formation")
|
modules = db.relationship("Module", lazy="dynamic", backref="formation")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme!r}')>"
|
return f"""<{self.__class__.__name__}(id={self.id}, dept_id={
|
||||||
|
self.dept_id}, acronyme={self.acronyme!r}, version={self.version})>"""
|
||||||
|
|
||||||
def to_html(self) -> str:
|
def to_html(self) -> str:
|
||||||
"titre complet pour affichage"
|
"titre complet pour affichage"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -56,55 +56,58 @@ class FormSemestre(db.Model):
|
|||||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
||||||
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
formation_id = db.Column(db.Integer, db.ForeignKey("notes_formations.id"))
|
||||||
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
semestre_id = db.Column(db.Integer, nullable=False, default=1, server_default="1")
|
||||||
titre = db.Column(db.Text())
|
titre = db.Column(db.Text(), nullable=False)
|
||||||
date_debut = db.Column(db.Date())
|
date_debut = db.Column(db.Date(), nullable=False)
|
||||||
date_fin = db.Column(db.Date())
|
date_fin = db.Column(db.Date(), nullable=False)
|
||||||
etat = db.Column(
|
etat = db.Column(db.Boolean(), nullable=False, default=True, server_default="true")
|
||||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
"False si verrouillé"
|
||||||
) # False si verrouillé
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
# semestres decales (pour gestion jurys):
|
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
||||||
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), default="white", server_default="white"
|
db.String(SHORT_STR_LEN),
|
||||||
|
default="white",
|
||||||
|
server_default="white",
|
||||||
|
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(
|
||||||
@ -114,6 +117,7 @@ class FormSemestre(db.Model):
|
|||||||
"ModuleImpl",
|
"ModuleImpl",
|
||||||
backref="formsemestre",
|
backref="formsemestre",
|
||||||
lazy="dynamic",
|
lazy="dynamic",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
etuds = db.relationship(
|
etuds = db.relationship(
|
||||||
"Identite",
|
"Identite",
|
||||||
@ -153,6 +157,11 @@ class FormSemestre(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>"
|
return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>"
|
||||||
|
|
||||||
|
def sort_key(self) -> tuple:
|
||||||
|
"""clé pour tris par ordre alphabétique
|
||||||
|
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
||||||
|
return (self.date_debut, self.semestre_id)
|
||||||
|
|
||||||
def to_dict(self, convert_objects=False) -> dict:
|
def to_dict(self, convert_objects=False) -> dict:
|
||||||
"""dict (compatible ScoDoc7).
|
"""dict (compatible ScoDoc7).
|
||||||
If convert_objects, convert all attributes to native types
|
If convert_objects, convert all attributes to native types
|
||||||
@ -321,7 +330,7 @@ class FormSemestre(db.Model):
|
|||||||
if self.formation.is_apc():
|
if self.formation.is_apc():
|
||||||
modimpls.sort(
|
modimpls.sort(
|
||||||
key=lambda m: (
|
key=lambda m: (
|
||||||
m.module.module_type or 0,
|
m.module.module_type or 0, # ressources (2) avant SAEs (3)
|
||||||
m.module.numero or 0,
|
m.module.numero or 0,
|
||||||
m.module.code or 0,
|
m.module.code or 0,
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
@ -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)
|
||||||
|
|
||||||
|
@ -20,14 +20,12 @@ class ModuleImpl(db.Model):
|
|||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
moduleimpl_id = db.synonym("id")
|
moduleimpl_id = db.synonym("id")
|
||||||
module_id = db.Column(
|
module_id = db.Column(db.Integer, db.ForeignKey("notes_modules.id"), nullable=False)
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_modules.id"),
|
|
||||||
)
|
|
||||||
formsemestre_id = db.Column(
|
formsemestre_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
index=True,
|
index=True,
|
||||||
|
nullable=False,
|
||||||
)
|
)
|
||||||
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
|
responsable_id = db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"))
|
||||||
# formule de calcul moyenne:
|
# formule de calcul moyenne:
|
||||||
@ -101,6 +99,22 @@ class ModuleImpl(db.Model):
|
|||||||
d.pop("module", None)
|
d.pop("module", None)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def est_inscrit(self, etud: Identite) -> bool:
|
||||||
|
"""
|
||||||
|
Vérifie si l'étudiant est bien inscrit au moduleimpl
|
||||||
|
|
||||||
|
Retourne Vrai si c'est le cas, faux sinon
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_module: int = (
|
||||||
|
ModuleImplInscription.query.filter_by(
|
||||||
|
etudid=etud.id, moduleimpl_id=self.id
|
||||||
|
).count()
|
||||||
|
> 0
|
||||||
|
)
|
||||||
|
|
||||||
|
return is_module
|
||||||
|
|
||||||
|
|
||||||
# Enseignants (chargés de TD ou TP) d'un moduleimpl
|
# Enseignants (chargés de TD ou TP) d'un moduleimpl
|
||||||
notes_modules_enseignants = db.Table(
|
notes_modules_enseignants = db.Table(
|
||||||
|
@ -37,7 +37,9 @@ class Module(db.Model):
|
|||||||
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
|
# Type: ModuleType.STANDARD, MALUS, RESSOURCE, SAE (enum)
|
||||||
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||||
# Relations:
|
# Relations:
|
||||||
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
modimpls = db.relationship(
|
||||||
|
"ModuleImpl", backref="module", lazy="dynamic", cascade="all, delete-orphan"
|
||||||
|
)
|
||||||
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
|
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
|
||||||
tags = db.relationship(
|
tags = db.relationship(
|
||||||
"NotesTag",
|
"NotesTag",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""ScoDoc 9 models : Unités d'Enseignement (UE)
|
"""ScoDoc 9 models : Unités d'Enseignement (UE)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
@ -109,6 +111,7 @@ class UniteEns(db.Model):
|
|||||||
e["ects"] = e["ects"]
|
e["ects"] = e["ects"]
|
||||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||||
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
||||||
|
e["parcour"] = self.parcour.to_dict() if self.parcour else None
|
||||||
if with_module_ue_coefs:
|
if with_module_ue_coefs:
|
||||||
if convert_objects:
|
if convert_objects:
|
||||||
e["module_ue_coefs"] = [
|
e["module_ue_coefs"] = [
|
||||||
@ -217,6 +220,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):
|
||||||
@ -244,17 +249,30 @@ 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} )")
|
||||||
|
|
||||||
|
|
||||||
class DispenseUE(db.Model):
|
class DispenseUE(db.Model):
|
||||||
"""Dispense d'UE
|
"""Dispense d'UE
|
||||||
Utilisé en PCC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
|
Utilisé en APC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
|
||||||
qu'ils ne refont pas.
|
qu'ils ne refont pas.
|
||||||
|
La dispense d'UE n'est PAS une validation:
|
||||||
|
- elle n'est pas affectée par les décisions de jury (pas effacée)
|
||||||
|
- elle est associée à un formsemestre
|
||||||
|
- elle ne permet pas la délivrance d'ECTS ou du diplôme.
|
||||||
|
|
||||||
|
On utilise cette dispense et non une "inscription" par souci d'efficacité:
|
||||||
|
en général, la grande majorité des étudiants suivront toutes les UEs de leur parcours,
|
||||||
|
la dispense étant une exception.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__table_args__ = (db.UniqueConstraint("ue_id", "etudid"),)
|
__table_args__ = (db.UniqueConstraint("formsemestre_id", "ue_id", "etudid"),)
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
formsemestre_id = formsemestre_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey("notes_formsemestre.id"), index=True, nullable=True
|
||||||
|
)
|
||||||
ue_id = db.Column(
|
ue_id = db.Column(
|
||||||
db.Integer,
|
db.Integer,
|
||||||
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
||||||
@ -273,3 +291,25 @@ class DispenseUE(db.Model):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"""<{self.__class__.__name__} {self.id} etud={
|
return f"""<{self.__class__.__name__} {self.id} etud={
|
||||||
repr(self.etud)} ue={repr(self.ue)}>"""
|
repr(self.etud)} ue={repr(self.ue)}>"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_formsemestre_dispense_ues_set(
|
||||||
|
cls, formsemestre: "FormSemestre", etudids: pd.Index, ues: list[UniteEns]
|
||||||
|
) -> set[tuple[int, int]]:
|
||||||
|
"""Construit l'ensemble des
|
||||||
|
etudids = modimpl_inscr_df.index, # les etudids
|
||||||
|
ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport
|
||||||
|
|
||||||
|
Résultat: set de (etudid, ue_id).
|
||||||
|
"""
|
||||||
|
# Prend toutes les dispenses obtenues par des étudiants de ce formsemestre,
|
||||||
|
# puis filtre sur inscrits et ues
|
||||||
|
ue_ids = {ue.id for ue in ues}
|
||||||
|
dispense_ues = {
|
||||||
|
(dispense_ue.etudid, dispense_ue.ue_id)
|
||||||
|
for dispense_ue in DispenseUE.query.filter_by(
|
||||||
|
formsemestre_id=formsemestre.id
|
||||||
|
)
|
||||||
|
if dispense_ue.etudid in etudids and dispense_ue.ue_id in ue_ids
|
||||||
|
}
|
||||||
|
return dispense_ues
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
|
from app import log
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
from app.models import CODE_STR_LEN
|
from app.models import CODE_STR_LEN
|
||||||
from app.models.events import Scolog
|
from app.models.events import Scolog
|
||||||
@ -58,7 +59,7 @@ class ScolarFormSemestreValidation(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"
|
return f"{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.ue_id:
|
if self.ue_id:
|
||||||
@ -93,6 +94,10 @@ class ScolarAutorisationInscription(db.Model):
|
|||||||
db.ForeignKey("notes_formsemestre.id"),
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"""{self.__class__.__name__}(id={self.id}, etudid={
|
||||||
|
self.etudid}, semestre_id={self.semestre_id})"""
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"as a dict"
|
"as a dict"
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
@ -116,7 +121,10 @@ class ScolarAutorisationInscription(db.Model):
|
|||||||
semestre_id=semestre_id,
|
semestre_id=semestre_id,
|
||||||
)
|
)
|
||||||
db.session.add(autorisation)
|
db.session.add(autorisation)
|
||||||
Scolog.logdb("autorise_etud", etudid=etudid, msg=f"passage vers S{semestre_id}")
|
Scolog.logdb(
|
||||||
|
"autorise_etud", etudid=etudid, msg=f"Passage vers S{semestre_id}: autorisé"
|
||||||
|
)
|
||||||
|
log(f"ScolarAutorisationInscription: recording {autorisation}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_autorisation_etud(
|
def delete_autorisation_etud(
|
||||||
@ -130,10 +138,11 @@ class ScolarAutorisationInscription(db.Model):
|
|||||||
)
|
)
|
||||||
for autorisation in autorisations:
|
for autorisation in autorisations:
|
||||||
db.session.delete(autorisation)
|
db.session.delete(autorisation)
|
||||||
|
log(f"ScolarAutorisationInscription: deleting {autorisation}")
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
"autorise_etud",
|
"autorise_etud",
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
msg=f"annule passage vers S{autorisation.semestre_id}",
|
msg=f"Passage vers S{autorisation.semestre_id}: effacé",
|
||||||
)
|
)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -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:
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -105,13 +105,13 @@ class BaseArchiver(object):
|
|||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
log("creating directory %s" % path)
|
log(f"creating directory {path}")
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
|
||||||
def get_obj_dir(self, oid):
|
def get_obj_dir(self, oid: int):
|
||||||
"""
|
"""
|
||||||
:return: path to directory of archives for this object (eg formsemestre_id or etudid).
|
:return: path to directory of archives for this object (eg formsemestre_id or etudid).
|
||||||
If directory does not yet exist, create it.
|
If directory does not yet exist, create it.
|
||||||
@ -142,7 +142,7 @@ class BaseArchiver(object):
|
|||||||
dirs = glob.glob(base + "*")
|
dirs = glob.glob(base + "*")
|
||||||
return [os.path.split(x)[1] for x in dirs]
|
return [os.path.split(x)[1] for x in dirs]
|
||||||
|
|
||||||
def list_obj_archives(self, oid):
|
def list_obj_archives(self, oid: int):
|
||||||
"""Returns
|
"""Returns
|
||||||
:return: list of archive identifiers for this object (paths to non empty dirs)
|
:return: list of archive identifiers for this object (paths to non empty dirs)
|
||||||
"""
|
"""
|
||||||
@ -157,7 +157,7 @@ class BaseArchiver(object):
|
|||||||
dirs.sort()
|
dirs.sort()
|
||||||
return dirs
|
return dirs
|
||||||
|
|
||||||
def delete_archive(self, archive_id):
|
def delete_archive(self, archive_id: str):
|
||||||
"""Delete (forever) this archive"""
|
"""Delete (forever) this archive"""
|
||||||
self.initialize()
|
self.initialize()
|
||||||
try:
|
try:
|
||||||
@ -166,7 +166,7 @@ class BaseArchiver(object):
|
|||||||
finally:
|
finally:
|
||||||
scu.GSL.release()
|
scu.GSL.release()
|
||||||
|
|
||||||
def get_archive_date(self, archive_id):
|
def get_archive_date(self, archive_id: str):
|
||||||
"""Returns date (as a DateTime object) of an archive"""
|
"""Returns date (as a DateTime object) of an archive"""
|
||||||
return datetime.datetime(
|
return datetime.datetime(
|
||||||
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
|
*[int(x) for x in os.path.split(archive_id)[1].split("-")]
|
||||||
@ -183,17 +183,17 @@ class BaseArchiver(object):
|
|||||||
files.sort()
|
files.sort()
|
||||||
return [f for f in files if f and f[0] != "_"]
|
return [f for f in files if f and f[0] != "_"]
|
||||||
|
|
||||||
def get_archive_name(self, archive_id):
|
def get_archive_name(self, archive_id: str):
|
||||||
"""name identifying archive, to be used in web URLs"""
|
"""name identifying archive, to be used in web URLs"""
|
||||||
return os.path.split(archive_id)[1]
|
return os.path.split(archive_id)[1]
|
||||||
|
|
||||||
def is_valid_archive_name(self, archive_name):
|
def is_valid_archive_name(self, archive_name: str):
|
||||||
"""check if name is valid."""
|
"""check if name is valid."""
|
||||||
return re.match(
|
return re.match(
|
||||||
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
|
"^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}$", archive_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_id_from_name(self, oid, archive_name):
|
def get_id_from_name(self, oid, archive_name: str):
|
||||||
"""returns archive id (check that name is valid)"""
|
"""returns archive id (check that name is valid)"""
|
||||||
self.initialize()
|
self.initialize()
|
||||||
if not self.is_valid_archive_name(archive_name):
|
if not self.is_valid_archive_name(archive_name):
|
||||||
@ -206,7 +206,7 @@ class BaseArchiver(object):
|
|||||||
raise ScoValueError(f"Archive {archive_name} introuvable")
|
raise ScoValueError(f"Archive {archive_name} introuvable")
|
||||||
return archive_id
|
return archive_id
|
||||||
|
|
||||||
def get_archive_description(self, archive_id):
|
def get_archive_description(self, archive_id: str) -> str:
|
||||||
"""Return description of archive"""
|
"""Return description of archive"""
|
||||||
self.initialize()
|
self.initialize()
|
||||||
filename = os.path.join(archive_id, "_description.txt")
|
filename = os.path.join(archive_id, "_description.txt")
|
||||||
@ -247,7 +247,7 @@ class BaseArchiver(object):
|
|||||||
data = data.encode(scu.SCO_ENCODING)
|
data = data.encode(scu.SCO_ENCODING)
|
||||||
self.initialize()
|
self.initialize()
|
||||||
filename = scu.sanitize_filename(filename)
|
filename = scu.sanitize_filename(filename)
|
||||||
log("storing %s (%d bytes) in %s" % (filename, len(data), archive_id))
|
log(f"storing {filename} ({len(data)} bytes) in {archive_id}")
|
||||||
try:
|
try:
|
||||||
scu.GSL.acquire()
|
scu.GSL.acquire()
|
||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
@ -261,16 +261,18 @@ class BaseArchiver(object):
|
|||||||
"""Retreive data"""
|
"""Retreive data"""
|
||||||
self.initialize()
|
self.initialize()
|
||||||
if not scu.is_valid_filename(filename):
|
if not scu.is_valid_filename(filename):
|
||||||
log('Archiver.get: invalid filename "%s"' % filename)
|
log(f"""Archiver.get: invalid filename '{filename}'""")
|
||||||
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
raise ScoValueError("archive introuvable (déjà supprimée ?)")
|
||||||
fname = os.path.join(archive_id, filename)
|
fname = os.path.join(archive_id, filename)
|
||||||
log("reading archive file %s" % fname)
|
log(f"reading archive file {fname}")
|
||||||
with open(fname, "rb") as f:
|
with open(fname, "rb") as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_archived_file(self, oid, archive_name, filename):
|
def get_archived_file(self, oid, archive_name, filename):
|
||||||
"""Recupere donnees du fichier indiqué et envoie au client"""
|
"""Recupère les donnees du fichier indiqué et envoie au client.
|
||||||
|
Returns: Response
|
||||||
|
"""
|
||||||
archive_id = self.get_id_from_name(oid, archive_name)
|
archive_id = self.get_id_from_name(oid, archive_name)
|
||||||
data = self.get(archive_id, filename)
|
data = self.get(archive_id, filename)
|
||||||
mime = mimetypes.guess_type(filename)[0]
|
mime = mimetypes.guess_type(filename)[0]
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -1084,7 +1084,7 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
|||||||
recipients = [recipient_addr]
|
recipients = [recipient_addr]
|
||||||
sender = sco_preferences.get_preference("email_from_addr", formsemestre_id)
|
sender = sco_preferences.get_preference("email_from_addr", formsemestre_id)
|
||||||
if copy_addr:
|
if copy_addr:
|
||||||
bcc = copy_addr.strip()
|
bcc = copy_addr.strip().split(",")
|
||||||
else:
|
else:
|
||||||
bcc = ""
|
bcc = ""
|
||||||
|
|
||||||
@ -1094,7 +1094,7 @@ def mail_bulletin(formsemestre_id, infos, pdfdata, filename, recipient_addr):
|
|||||||
subject,
|
subject,
|
||||||
sender,
|
sender,
|
||||||
recipients,
|
recipients,
|
||||||
bcc=[bcc],
|
bcc=bcc,
|
||||||
text_body=hea,
|
text_body=hea,
|
||||||
attachments=[
|
attachments=[
|
||||||
{"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
|
{"filename": filename, "mimetype": scu.PDF_MIMETYPE, "data": pdfdata}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -435,7 +435,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
plusminus = pluslink
|
plusminus = pluslink
|
||||||
try:
|
try:
|
||||||
ects_txt = str(int(ue["ects"]))
|
ects_txt = str(int(ue["ects"]))
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError, TypeError):
|
||||||
ects_txt = "-"
|
ects_txt = "-"
|
||||||
|
|
||||||
t = {
|
t = {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -122,6 +122,7 @@ ABL = "ABL"
|
|||||||
ADM = "ADM" # moyenne gen., barres UE, assiduité: sem. validé
|
ADM = "ADM" # moyenne gen., barres UE, assiduité: sem. validé
|
||||||
ADC = "ADC" # admis par compensation (eg moy(S1, S2) > 10)
|
ADC = "ADC" # admis par compensation (eg moy(S1, S2) > 10)
|
||||||
ADJ = "ADJ" # admis par le jury
|
ADJ = "ADJ" # admis par le jury
|
||||||
|
ADJR = "ADJR" # UE admise car son RCUE est ADJ
|
||||||
ATT = "ATT" #
|
ATT = "ATT" #
|
||||||
ATJ = "ATJ" # pb assiduité: décision repoussée au semestre suivant
|
ATJ = "ATJ" # pb assiduité: décision repoussée au semestre suivant
|
||||||
ATB = "ATB"
|
ATB = "ATB"
|
||||||
@ -158,6 +159,7 @@ CODES_EXPL = {
|
|||||||
ABL: "Année blanche",
|
ABL: "Année blanche",
|
||||||
ADC: "Validé par compensation",
|
ADC: "Validé par compensation",
|
||||||
ADJ: "Validé par le Jury",
|
ADJ: "Validé par le Jury",
|
||||||
|
ADJR: "UE validée car son RCUE est validé ADJ par le jury",
|
||||||
ADM: "Validé",
|
ADM: "Validé",
|
||||||
AJ: "Ajourné (ou UE/BC de BUT en attente pour problème de moyenne)",
|
AJ: "Ajourné (ou UE/BC de BUT en attente pour problème de moyenne)",
|
||||||
ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)",
|
ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)",
|
||||||
@ -185,16 +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_DE_DROIT = {ADM, CMP} # validation "de droit"
|
||||||
|
CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR}
|
||||||
|
"UE validée"
|
||||||
|
|
||||||
|
CODES_RCUE_VALIDES_DE_DROIT = {ADM, CMP}
|
||||||
|
CODES_RCUE_VALIDES = CODES_RCUE_VALIDES_DE_DROIT | {ADJ}
|
||||||
|
"Niveau RCUE validé"
|
||||||
|
|
||||||
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée
|
|
||||||
CODES_RCUE_VALIDES = CODES_UE_VALIDES # 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
|
||||||
@ -205,21 +214,36 @@ BUT_CODES_PASSAGE = {
|
|||||||
PAS1NCI,
|
PAS1NCI,
|
||||||
ATJ,
|
ATJ,
|
||||||
}
|
}
|
||||||
|
# les codes, du plus "défavorable" à l'étudiant au plus favorable:
|
||||||
|
# (valeur par défaut 0)
|
||||||
|
BUT_CODES_ORDERED = {
|
||||||
|
NAR: 0,
|
||||||
|
DEF: 0,
|
||||||
|
AJ: 10,
|
||||||
|
ATJ: 20,
|
||||||
|
CMP: 50,
|
||||||
|
ADC: 50,
|
||||||
|
PASD: 50,
|
||||||
|
PAS1NCI: 60,
|
||||||
|
ADJR: 90,
|
||||||
|
ADJ: 100,
|
||||||
|
ADM: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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 = {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# ScoDoc
|
# ScoDoc
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
# Gestion scolarite IUT
|
# Gestion scolarite IUT
|
||||||
#
|
#
|
||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user