forked from ScoDoc/ScoDoc
Compare commits
126 Commits
ae1a3fb676
...
7ff32762ed
Author | SHA1 | Date | |
---|---|---|---|
7ff32762ed | |||
351cbbb5f6 | |||
569c09d66d | |||
8d50cd2a8e | |||
1d137875d3 | |||
|
9b89ca436e | ||
|
de47277e7c | ||
39873183a8 | |||
a8703edfc5 | |||
|
06727f1b9b | ||
|
c37a92aa5c | ||
c41307c637 | |||
3163e00ff5 | |||
070c9ea36f | |||
6224f37e4a | |||
e877e04cc6 | |||
af557f9c93 | |||
8a49d99292 | |||
07c2f00277 | |||
c94fea9f9e | |||
96e445cb49 | |||
8f7320e044 | |||
d6eeb5116d | |||
b57c7980e6 | |||
8163c6814c | |||
|
c8a042cc09 | ||
|
70605edad7 | ||
|
faa6f552d4 | ||
ba506d7f8e | |||
1808e7f5a4 | |||
|
379d636259 | ||
4ae484061e | |||
320cfbebc8 | |||
e0208d0650 | |||
f18fbe284a | |||
|
6acf72c0c9 | ||
b675a57678 | |||
ecd753fae7 | |||
|
c55e02497e | ||
|
7c7697631e | ||
0fe60aaa64 | |||
|
09f697941d | ||
|
cefef8a89a | ||
b7cbfbae67 | |||
|
0fee2e612b | ||
1167c13787 | |||
963c09976b | |||
|
dbb5ac8946 | ||
|
93bb9d598e | ||
|
9801cf7936 | ||
|
db44e8e5f4 | ||
|
ffdaf2a19a | ||
|
ba77b155c5 | ||
fed84559fc | |||
02a5b00ecf | |||
dcdf6a8012 | |||
912a213dcd | |||
3575e89dc0 | |||
|
675eccd6b6 | ||
|
07a8658672 | ||
|
80bd02114e | ||
|
65a4b31fbd | ||
|
7bdae70d38 | ||
21c0625147 | |||
e18c1d8fd0 | |||
5867d0f430 | |||
9897ccc659 | |||
|
7575959bd4 | ||
|
2aafbad9e2 | ||
50f2cd7a0f | |||
fd8fbb9e02 | |||
|
ebcef76950 | ||
|
13349776af | ||
|
f275286b71 | ||
|
f4f6c13d79 | ||
e7f23efe65 | |||
e44d3fd5dc | |||
fac36fa11c | |||
9289535359 | |||
|
d73b925006 | ||
6749ca70d6 | |||
|
dea403b03d | ||
|
ab9543c310 | ||
|
f94998f66b | ||
|
eb88a8ca83 | ||
7042650fd9 | |||
2745ffd687 | |||
9a882ea41d | |||
ea6003e812 | |||
5c6935337e | |||
60998d2e20 | |||
29b877d9ed | |||
|
6834c19015 | ||
|
f47fc4ba46 | ||
5894c6f952 | |||
af1d1884c7 | |||
|
881bf82000 | ||
|
2ed4516a97 | ||
|
75ce1ccd31 | ||
|
f8d5f6ea11 | ||
|
70995fbd7e | ||
dc095765f2 | |||
|
1cec3fa703 | ||
|
032454aefd | ||
|
e3344cf424 | ||
|
d7acff9d35 | ||
|
decdf59e20 | ||
|
42fc08a3a3 | ||
|
f3770fb5c7 | ||
63b28a3277 | |||
bb23cdcea7 | |||
3ca5636454 | |||
42882154d5 | |||
489acb26d2 | |||
8ee373db7d | |||
8e56dc2418 | |||
b3331bd886 | |||
89afb672af | |||
8f25284038 | |||
f29002a57d | |||
69780b3f24 | |||
fbff151be0 | |||
3b436fa0f3 | |||
8847a1f008 | |||
ac882e9ccd | |||
000e016985 |
@ -637,14 +637,12 @@ def critical_error(msg):
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
log(f"\n*** CRITICAL ERROR: {msg}")
|
log(f"\n*** CRITICAL ERROR: {msg}")
|
||||||
send_scodoc_alarm(f"CRITICAL ERROR: {msg}", msg)
|
subject = f"CRITICAL ERROR: {msg}".strip()[:68]
|
||||||
|
send_scodoc_alarm(subject, msg)
|
||||||
clear_scodoc_cache()
|
clear_scodoc_cache()
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"""
|
f"""
|
||||||
Une erreur est survenue.
|
Une erreur est survenue, veuillez ré-essayer.
|
||||||
|
|
||||||
Si le problème persiste, merci de contacter le support ScoDoc via
|
|
||||||
{scu.SCO_DISCORD_ASSISTANCE}
|
|
||||||
|
|
||||||
{msg}
|
{msg}
|
||||||
"""
|
"""
|
||||||
|
@ -161,8 +161,17 @@ def count_assiduites(
|
|||||||
query?est_just=f
|
query?est_just=f
|
||||||
query?est_just=t
|
query?est_just=t
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
moduleimpl_id:<int:moduleimpl_id>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
metric:<array[string]:metric>
|
||||||
|
split:<bool:split>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -253,6 +262,15 @@ def assiduites(etudid: int = None, nip=None, ine=None, with_query: bool = False)
|
|||||||
query?est_just=f
|
query?est_just=f
|
||||||
query?est_just=t
|
query?est_just=t
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
moduleimpl_id:<int:moduleimpl_id>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -329,6 +347,16 @@ def assiduites_group(with_query: bool = False):
|
|||||||
query?est_just=f
|
query?est_just=f
|
||||||
query?est_just=t
|
query?est_just=t
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
moduleimpl_id:<int:moduleimpl_id>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
etudids:<array[int]:etudids
|
||||||
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -388,7 +416,16 @@ def assiduites_group(with_query: bool = False):
|
|||||||
@as_json
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||||
"""Retourne toutes les assiduités du formsemestre"""
|
"""Retourne toutes les assiduités du formsemestre
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
moduleimpl_id:<int:moduleimpl_id>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre à partir du formsemestre_id
|
# Récupération du formsemestre à partir du formsemestre_id
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
@ -438,7 +475,20 @@ def assiduites_formsemestre(formsemestre_id: int, with_query: bool = False):
|
|||||||
def count_assiduites_formsemestre(
|
def count_assiduites_formsemestre(
|
||||||
formsemestre_id: int = None, with_query: bool = False
|
formsemestre_id: int = None, with_query: bool = False
|
||||||
):
|
):
|
||||||
"""Comptage des assiduités du formsemestre"""
|
"""Comptage des assiduités du formsemestre
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
moduleimpl_id:<int:moduleimpl_id>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
formsemestre_id:<int:formsemestre_id>
|
||||||
|
metric:<array[string]:metric>
|
||||||
|
split:<bool:split>
|
||||||
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre à partir du formsemestre_id
|
# Récupération du formsemestre à partir du formsemestre_id
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
|
@ -15,12 +15,14 @@ from flask_login import login_required
|
|||||||
import app
|
import app
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.decorators import scodoc, permission_required
|
from app.decorators import scodoc, permission_required
|
||||||
from app.models import (
|
from app.models import (
|
||||||
ApcNiveau,
|
ApcNiveau,
|
||||||
ApcParcours,
|
ApcParcours,
|
||||||
Formation,
|
Formation,
|
||||||
|
Module,
|
||||||
UniteEns,
|
UniteEns,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
@ -336,3 +338,166 @@ def desassoc_ue_niveau(ue_id: int):
|
|||||||
# "usage web"
|
# "usage web"
|
||||||
flash(f"UE {ue.acronyme} dé-associée")
|
flash(f"UE {ue.acronyme} dé-associée")
|
||||||
return {"status": 0}
|
return {"status": 0}
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ue/<int:ue_id>", methods=["GET"])
|
||||||
|
@api_web_bp.route("/ue/<int:ue_id>", methods=["GET"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def get_ue(ue_id: int):
|
||||||
|
"""Renvoie l'UE"""
|
||||||
|
query = UniteEns.query.filter_by(id=ue_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
ue: UniteEns = query.first_or_404()
|
||||||
|
return ue.to_dict(convert_objects=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/module/<int:module_id>", methods=["GET"])
|
||||||
|
@api_web_bp.route("/module/<int:module_id>", methods=["GET"])
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def get_module(module_id: int):
|
||||||
|
"""Renvoie le module"""
|
||||||
|
query = Module.query.filter_by(id=module_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
module: Module = query.first_or_404()
|
||||||
|
return module.to_dict(convert_objects=True)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"])
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
|
||||||
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"]
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee", defaults={"code_apogee": ""}, methods=["POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.EditFormation)
|
||||||
|
def ue_set_code_apogee(ue_id: int, code_apogee: str = ""):
|
||||||
|
"""Change le code Apogée de l'UE.
|
||||||
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||||
|
par des virgules.
|
||||||
|
(Ce changement peut être fait sur formation verrouillée)
|
||||||
|
|
||||||
|
Si code_apogee n'est pas spécifié ou vide,
|
||||||
|
utilise l'argument value du POST (utilisé par jinplace.js)
|
||||||
|
|
||||||
|
Le retour est une chaîne (le code enregistré), pas json.
|
||||||
|
"""
|
||||||
|
if not code_apogee:
|
||||||
|
code_apogee = request.form.get("value", "")
|
||||||
|
query = UniteEns.query.filter_by(id=ue_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
ue: UniteEns = query.first_or_404()
|
||||||
|
|
||||||
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
||||||
|
|
||||||
|
log(f"API ue_set_code_apogee: ue_id={ue.id} code_apogee={code_apogee}")
|
||||||
|
|
||||||
|
ue.code_apogee = code_apogee
|
||||||
|
db.session.add(ue)
|
||||||
|
db.session.commit()
|
||||||
|
return code_apogee or ""
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>", methods=["POST"])
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee_rcue/<string:code_apogee>", methods=["POST"]
|
||||||
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee_rcue",
|
||||||
|
defaults={"code_apogee": ""},
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/ue/<int:ue_id>/set_code_apogee_rcue",
|
||||||
|
defaults={"code_apogee": ""},
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.EditFormation)
|
||||||
|
def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""):
|
||||||
|
"""Change le code Apogée du RCUE de l'UE.
|
||||||
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||||
|
par des virgules.
|
||||||
|
(Ce changement peut être fait sur formation verrouillée)
|
||||||
|
|
||||||
|
Si code_apogee n'est pas spécifié ou vide,
|
||||||
|
utilise l'argument value du POST (utilisé par jinplace.js)
|
||||||
|
|
||||||
|
Le retour est une chaîne (le code enregistré), pas json.
|
||||||
|
"""
|
||||||
|
if not code_apogee:
|
||||||
|
code_apogee = request.form.get("value", "")
|
||||||
|
query = UniteEns.query.filter_by(id=ue_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
ue: UniteEns = query.first_or_404()
|
||||||
|
|
||||||
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
||||||
|
|
||||||
|
log(f"API ue_set_code_apogee_rcue: ue_id={ue.id} code_apogee={code_apogee}")
|
||||||
|
|
||||||
|
ue.code_apogee_rcue = code_apogee
|
||||||
|
db.session.add(ue)
|
||||||
|
db.session.commit()
|
||||||
|
return code_apogee or ""
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route(
|
||||||
|
"/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@bp.route(
|
||||||
|
"/module/<int:module_id>/set_code_apogee",
|
||||||
|
defaults={"code_apogee": ""},
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@api_web_bp.route(
|
||||||
|
"/module/<int:module_id>/set_code_apogee",
|
||||||
|
defaults={"code_apogee": ""},
|
||||||
|
methods=["POST"],
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.EditFormation)
|
||||||
|
def module_set_code_apogee(module_id: int, code_apogee: str = ""):
|
||||||
|
"""Change le code Apogée du module.
|
||||||
|
Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
|
||||||
|
par des virgules.
|
||||||
|
(Ce changement peut être fait sur formation verrouillée)
|
||||||
|
|
||||||
|
Si code_apogee n'est pas spécifié ou vide,
|
||||||
|
utilise l'argument value du POST (utilisé par jinplace.js)
|
||||||
|
|
||||||
|
Le retour est une chaîne (le code enregistré), pas json.
|
||||||
|
"""
|
||||||
|
if not code_apogee:
|
||||||
|
code_apogee = request.form.get("value", "")
|
||||||
|
query = Module.query.filter_by(id=module_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
module: Module = query.first_or_404()
|
||||||
|
|
||||||
|
code_apogee = code_apogee.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
||||||
|
|
||||||
|
log(f"API module_set_code_apogee: module_id={module.id} code_apogee={code_apogee}")
|
||||||
|
|
||||||
|
module.code_apogee = code_apogee
|
||||||
|
db.session.add(module)
|
||||||
|
db.session.commit()
|
||||||
|
return code_apogee or ""
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
"""ScoDoc 9 API : Justificatifs
|
"""ScoDoc 9 API : Justificatifs"""
|
||||||
"""
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
@ -113,6 +113,16 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
|||||||
user_id (l'id de l'auteur du justificatif)
|
user_id (l'id de l'auteur du justificatif)
|
||||||
query?user_id=[int]
|
query?user_id=[int]
|
||||||
ex query?user_id=3
|
ex query?user_id=3
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
order:<bool:order>
|
||||||
|
courant:<bool:courant>
|
||||||
|
group_id:<int:group_id>
|
||||||
"""
|
"""
|
||||||
# Récupération de l'étudiant
|
# Récupération de l'étudiant
|
||||||
etud: Identite = tools.get_etud(etudid, nip, ine)
|
etud: Identite = tools.get_etud(etudid, nip, ine)
|
||||||
@ -154,6 +164,17 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
"""
|
"""
|
||||||
Renvoie tous les justificatifs d'un département
|
Renvoie tous les justificatifs d'un département
|
||||||
(en ajoutant un champ "formsemestre" si possible)
|
(en ajoutant un champ "formsemestre" si possible)
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
order:<bool:order>
|
||||||
|
courant:<bool:courant>
|
||||||
|
group_id:<int:group_id>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération du département et des étudiants du département
|
# Récupération du département et des étudiants du département
|
||||||
@ -225,7 +246,19 @@ def _set_sems(justi: Justificatif, restrict: bool) -> dict:
|
|||||||
@as_json
|
@as_json
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
|
||||||
"""Retourne tous les justificatifs du formsemestre"""
|
"""Retourne tous les justificatifs du formsemestre
|
||||||
|
|
||||||
|
QUERY
|
||||||
|
-----
|
||||||
|
user_id:<int:user_id>
|
||||||
|
est_just:<bool:est_just>
|
||||||
|
date_debut:<string:date_debut_iso>
|
||||||
|
date_fin:<string:date_fin_iso>
|
||||||
|
etat:<array[string]:etat>
|
||||||
|
order:<bool:order>
|
||||||
|
courant:<bool:courant>
|
||||||
|
group_id:<int:group_id>
|
||||||
|
"""
|
||||||
|
|
||||||
# Récupération du formsemestre
|
# Récupération du formsemestre
|
||||||
formsemestre: FormSemestre = None
|
formsemestre: FormSemestre = None
|
||||||
|
@ -14,6 +14,15 @@ import cracklib # pylint: disable=import-error
|
|||||||
|
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask_login import UserMixin, AnonymousUserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
|
from sqlalchemy.exc import (
|
||||||
|
IntegrityError,
|
||||||
|
DataError,
|
||||||
|
DatabaseError,
|
||||||
|
OperationalError,
|
||||||
|
ProgrammingError,
|
||||||
|
StatementError,
|
||||||
|
InterfaceError,
|
||||||
|
)
|
||||||
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
@ -48,13 +57,13 @@ def is_valid_password(cleartxt) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def invalid_user_name(user_name: str) -> bool:
|
def is_valid_user_name(user_name: str) -> bool:
|
||||||
"Check that user_name (aka login) is invalid"
|
"Check that user_name (aka login) is valid"
|
||||||
return (
|
return (
|
||||||
not user_name
|
user_name
|
||||||
or (len(user_name) < 2)
|
and (len(user_name) >= 2)
|
||||||
or (len(user_name) >= USERNAME_STR_LEN)
|
and (len(user_name) < USERNAME_STR_LEN)
|
||||||
or not VALID_LOGIN_EXP.match(user_name)
|
and VALID_LOGIN_EXP.match(user_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +132,7 @@ class User(UserMixin, ScoDocModel):
|
|||||||
# check login:
|
# check login:
|
||||||
if not "user_name" in kwargs:
|
if not "user_name" in kwargs:
|
||||||
raise ValueError("missing user_name argument")
|
raise ValueError("missing user_name argument")
|
||||||
if invalid_user_name(kwargs["user_name"]):
|
if not is_valid_user_name(kwargs["user_name"]):
|
||||||
raise ValueError(f"invalid user_name: {kwargs['user_name']}")
|
raise ValueError(f"invalid user_name: {kwargs['user_name']}")
|
||||||
kwargs["nom"] = kwargs.get("nom", "") or ""
|
kwargs["nom"] = kwargs.get("nom", "") or ""
|
||||||
kwargs["prenom"] = kwargs.get("prenom", "") or ""
|
kwargs["prenom"] = kwargs.get("prenom", "") or ""
|
||||||
@ -329,7 +338,8 @@ class User(UserMixin, ScoDocModel):
|
|||||||
if new_user:
|
if new_user:
|
||||||
if "user_name" in data:
|
if "user_name" in data:
|
||||||
# never change name of existing users
|
# never change name of existing users
|
||||||
if invalid_user_name(data["user_name"]):
|
# (see change_user_name method to do that)
|
||||||
|
if not is_valid_user_name(data["user_name"]):
|
||||||
raise ValueError(f"invalid user_name: {data['user_name']}")
|
raise ValueError(f"invalid user_name: {data['user_name']}")
|
||||||
self.user_name = data["user_name"]
|
self.user_name = data["user_name"]
|
||||||
if "password" in data:
|
if "password" in data:
|
||||||
@ -522,6 +532,64 @@ class User(UserMixin, ScoDocModel):
|
|||||||
|
|
||||||
# nomnoacc était le nom en minuscules sans accents (inutile)
|
# nomnoacc était le nom en minuscules sans accents (inutile)
|
||||||
|
|
||||||
|
def change_user_name(self, new_user_name: str):
|
||||||
|
"""Modify user name, update all relevant tables.
|
||||||
|
commit session.
|
||||||
|
"""
|
||||||
|
# Safety check
|
||||||
|
new_user_name = new_user_name.strip()
|
||||||
|
if (
|
||||||
|
not is_valid_user_name(new_user_name)
|
||||||
|
or User.query.filter_by(user_name=new_user_name).count() > 0
|
||||||
|
):
|
||||||
|
raise ValueError("invalid user_name")
|
||||||
|
# Le user_name est utilisé dans d'autres tables (sans être une clé)
|
||||||
|
# BulAppreciations.author
|
||||||
|
# EntrepriseHistorique.authenticated_user
|
||||||
|
# EtudAnnotation.author
|
||||||
|
# ScolarNews.authenticated_user
|
||||||
|
# Scolog.authenticated_user
|
||||||
|
from app.models import (
|
||||||
|
BulAppreciations,
|
||||||
|
EtudAnnotation,
|
||||||
|
ScolarNews,
|
||||||
|
Scolog,
|
||||||
|
)
|
||||||
|
from app.entreprises.models import EntrepriseHistorique
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update all instances of EtudAnnotation
|
||||||
|
db.session.query(BulAppreciations).filter(
|
||||||
|
BulAppreciations.author == self.user_name
|
||||||
|
).update({BulAppreciations.author: new_user_name})
|
||||||
|
db.session.query(EntrepriseHistorique).filter(
|
||||||
|
EntrepriseHistorique.authenticated_user == self.user_name
|
||||||
|
).update({EntrepriseHistorique.authenticated_user: new_user_name})
|
||||||
|
db.session.query(EtudAnnotation).filter(
|
||||||
|
EtudAnnotation.author == self.user_name
|
||||||
|
).update({EtudAnnotation.author: new_user_name})
|
||||||
|
db.session.query(ScolarNews).filter(
|
||||||
|
ScolarNews.authenticated_user == self.user_name
|
||||||
|
).update({ScolarNews.authenticated_user: new_user_name})
|
||||||
|
db.session.query(Scolog).filter(
|
||||||
|
Scolog.authenticated_user == self.user_name
|
||||||
|
).update({Scolog.authenticated_user: new_user_name})
|
||||||
|
# And update ourself:
|
||||||
|
self.user_name = new_user_name
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
except (
|
||||||
|
IntegrityError,
|
||||||
|
DataError,
|
||||||
|
DatabaseError,
|
||||||
|
OperationalError,
|
||||||
|
ProgrammingError,
|
||||||
|
StatementError,
|
||||||
|
InterfaceError,
|
||||||
|
) as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
class AnonymousUser(AnonymousUserMixin):
|
class AnonymousUser(AnonymousUserMixin):
|
||||||
"Notre utilisateur anonyme"
|
"Notre utilisateur anonyme"
|
||||||
|
@ -18,7 +18,7 @@ from app.auth.forms import (
|
|||||||
ResetPasswordRequestForm,
|
ResetPasswordRequestForm,
|
||||||
UserCreationForm,
|
UserCreationForm,
|
||||||
)
|
)
|
||||||
from app.auth.models import Role, User, invalid_user_name
|
from app.auth.models import Role, User, is_valid_user_name
|
||||||
from app.auth.email import send_password_reset_email
|
from app.auth.email import send_password_reset_email
|
||||||
from app.decorators import admin_required
|
from app.decorators import admin_required
|
||||||
from app.forms.generic import SimpleConfirmationForm
|
from app.forms.generic import SimpleConfirmationForm
|
||||||
@ -35,10 +35,12 @@ def _login_form():
|
|||||||
form = LoginForm()
|
form = LoginForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# note: ceci est la première requête SQL déclenchée par un utilisateur arrivant
|
# note: ceci est la première requête SQL déclenchée par un utilisateur arrivant
|
||||||
if invalid_user_name(form.user_name.data):
|
user = (
|
||||||
user = None
|
User.query.filter_by(user_name=form.user_name.data).first()
|
||||||
else:
|
if is_valid_user_name(form.user_name.data)
|
||||||
user = User.query.filter_by(user_name=form.user_name.data).first()
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
if user is None or not user.check_password(form.password.data):
|
if user is None or not user.check_password(form.password.data):
|
||||||
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
current_app.logger.info("login: invalid (%s)", form.user_name.data)
|
||||||
flash(_("Nom ou mot de passe invalide"))
|
flash(_("Nom ou mot de passe invalide"))
|
||||||
|
@ -124,7 +124,9 @@ def _build_bulletin_but_infos(
|
|||||||
formsemestre, bulletins_sem.res
|
formsemestre, bulletins_sem.res
|
||||||
)
|
)
|
||||||
if warn_html:
|
if warn_html:
|
||||||
raise ScoValueError("<b>Formation mal configurée pour le BUT</b>" + warn_html)
|
raise ScoValueError(
|
||||||
|
"<b>Formation mal configurée pour le BUT</b>" + warn_html, safe=True
|
||||||
|
)
|
||||||
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
||||||
refcomp, etud
|
refcomp, etud
|
||||||
)
|
)
|
||||||
|
@ -73,6 +73,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
|||||||
html_class="notes_bulletin",
|
html_class="notes_bulletin",
|
||||||
html_class_ignore_default=True,
|
html_class_ignore_default=True,
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
|
table_id="bul-table",
|
||||||
)
|
)
|
||||||
table_objects = table.gen(fmt=fmt)
|
table_objects = table.gen(fmt=fmt)
|
||||||
objects += table_objects
|
objects += table_objects
|
||||||
|
@ -29,7 +29,7 @@ from app.models.but_refcomp import (
|
|||||||
ApcReferentielCompetences,
|
ApcReferentielCompetences,
|
||||||
)
|
)
|
||||||
from app.models.ues import UEParcours
|
from app.models.ues import UEParcours
|
||||||
from app.models.but_validations import ApcValidationRCUE
|
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
@ -42,9 +42,9 @@ 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"""
|
"""Pour compat ScoDoc 7"""
|
||||||
|
|
||||||
def __init__(self, etud: dict, formsemestre_id: int, res: ResultatsSemestreBUT):
|
def __init__(self, etud: Identite, 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
|
||||||
self.can_compensate_with_prev = False # jamais de compensation à la mode DUT
|
self.can_compensate_with_prev = False # jamais de compensation à la mode DUT
|
||||||
@ -54,8 +54,22 @@ class SituationEtudCursusBUT(sco_cursus_dut.SituationEtudCursusClassic):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def parcours_validated(self):
|
def parcours_validated(self):
|
||||||
"True si le parcours est validé"
|
"True si le parcours (ici diplôme BUT) est validé"
|
||||||
return False # XXX TODO
|
return but_parcours_validated(
|
||||||
|
self.etud.id, self.cur_sem.formation.referentiel_competence_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def but_parcours_validated(etudid: int, referentiel_competence_id: int) -> bool:
|
||||||
|
"""Détermine si le parcours BUT est validé:
|
||||||
|
ne regarde que si une validation BUT3 est enregistrée
|
||||||
|
"""
|
||||||
|
return any(
|
||||||
|
sco_codes.code_annee_validant(v.code)
|
||||||
|
for v in ApcValidationAnnee.query.filter_by(
|
||||||
|
etudid=etudid, ordre=3, referentiel_competence_id=referentiel_competence_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EtudCursusBUT:
|
class EtudCursusBUT:
|
||||||
@ -287,81 +301,81 @@ class FormSemestreCursusBUT:
|
|||||||
)
|
)
|
||||||
return niveaux_by_annee
|
return niveaux_by_annee
|
||||||
|
|
||||||
def get_etud_validation_par_competence_et_annee(self, etud: Identite):
|
# def get_etud_validation_par_competence_et_annee(self, etud: Identite):
|
||||||
"""{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
# """{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||||
validation_par_competence_et_annee = {}
|
# validation_par_competence_et_annee = {}
|
||||||
for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
# for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||||
# On s'assurer qu'elle concerne notre cursus !
|
# # On s'assurer qu'elle concerne notre cursus !
|
||||||
ue = validation_rcue.ue2
|
# ue = validation_rcue.ue2
|
||||||
if ue.id not in self.ue_ids:
|
# if ue.id not in self.ue_ids:
|
||||||
if (
|
# if (
|
||||||
ue.formation.referentiel_competences_id
|
# ue.formation.referentiel_competences_id
|
||||||
== self.referentiel_competences_id
|
# == self.referentiel_competences_id
|
||||||
):
|
# ):
|
||||||
self.ue_ids = ue.id
|
# self.ue_ids = ue.id
|
||||||
else:
|
# else:
|
||||||
continue # skip this validation
|
# continue # skip this validation
|
||||||
niveau = validation_rcue.niveau()
|
# niveau = validation_rcue.niveau()
|
||||||
if not niveau.competence.id in validation_par_competence_et_annee:
|
# if not niveau.competence.id in validation_par_competence_et_annee:
|
||||||
validation_par_competence_et_annee[niveau.competence.id] = {}
|
# validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||||
previous_validation = validation_par_competence_et_annee.get(
|
# previous_validation = validation_par_competence_et_annee.get(
|
||||||
niveau.competence.id
|
# niveau.competence.id
|
||||||
).get(validation_rcue.annee())
|
# ).get(validation_rcue.annee())
|
||||||
# prend la "meilleure" validation
|
# # prend la "meilleure" validation
|
||||||
if (not previous_validation) or (
|
# if (not previous_validation) or (
|
||||||
sco_codes.BUT_CODES_ORDER[validation_rcue.code]
|
# sco_codes.BUT_CODES_ORDER[validation_rcue.code]
|
||||||
> sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
# > sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
||||||
):
|
# ):
|
||||||
self.validation_par_competence_et_annee[niveau.competence.id][
|
# self.validation_par_competence_et_annee[niveau.competence.id][
|
||||||
niveau.annee
|
# niveau.annee
|
||||||
] = validation_rcue
|
# ] = validation_rcue
|
||||||
return validation_par_competence_et_annee
|
# return validation_par_competence_et_annee
|
||||||
|
|
||||||
def list_etud_inscriptions(self, etud: Identite):
|
# def list_etud_inscriptions(self, etud: Identite):
|
||||||
"Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
|
# "Le parcours à valider: celui du DERNIER semestre suivi (peut être None)"
|
||||||
self.niveaux_by_annee = {}
|
# self.niveaux_by_annee = {}
|
||||||
"{ annee : liste des niveaux à valider }"
|
# "{ annee : liste des niveaux à valider }"
|
||||||
self.niveaux: dict[int, ApcNiveau] = {}
|
# self.niveaux: dict[int, ApcNiveau] = {}
|
||||||
"cache les niveaux"
|
# "cache les niveaux"
|
||||||
for annee in (1, 2, 3):
|
# for annee in (1, 2, 3):
|
||||||
niveaux_d = formation.referentiel_competence.get_niveaux_by_parcours(
|
# niveaux_d = formation.referentiel_competence.get_niveaux_by_parcours(
|
||||||
annee, [self.parcour] if self.parcour else None # XXX WIP
|
# annee, [self.parcour] if self.parcour else None # XXX WIP
|
||||||
)[1]
|
# )[1]
|
||||||
# groupe les niveaux de tronc commun et ceux spécifiques au parcour
|
# # groupe les niveaux de tronc commun et ceux spécifiques au parcour
|
||||||
self.niveaux_by_annee[annee] = niveaux_d["TC"] + (
|
# self.niveaux_by_annee[annee] = niveaux_d["TC"] + (
|
||||||
niveaux_d[self.parcour.id] if self.parcour else []
|
# niveaux_d[self.parcour.id] if self.parcour else []
|
||||||
)
|
# )
|
||||||
self.niveaux.update(
|
# self.niveaux.update(
|
||||||
{niveau.id: niveau for niveau in self.niveaux_by_annee[annee]}
|
# {niveau.id: niveau for niveau in self.niveaux_by_annee[annee]}
|
||||||
)
|
# )
|
||||||
|
|
||||||
self.validation_par_competence_et_annee = {}
|
# self.validation_par_competence_et_annee = {}
|
||||||
"""{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
# """{ competence_id : { 'BUT1' : validation_rcue (la "meilleure"), ... } }"""
|
||||||
for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
# for validation_rcue in ApcValidationRCUE.query.filter_by(etud=etud):
|
||||||
niveau = validation_rcue.niveau()
|
# niveau = validation_rcue.niveau()
|
||||||
if not niveau.competence.id in self.validation_par_competence_et_annee:
|
# if not niveau.competence.id in self.validation_par_competence_et_annee:
|
||||||
self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
# self.validation_par_competence_et_annee[niveau.competence.id] = {}
|
||||||
previous_validation = self.validation_par_competence_et_annee.get(
|
# previous_validation = self.validation_par_competence_et_annee.get(
|
||||||
niveau.competence.id
|
# niveau.competence.id
|
||||||
).get(validation_rcue.annee())
|
# ).get(validation_rcue.annee())
|
||||||
# prend la "meilleure" validation
|
# # prend la "meilleure" validation
|
||||||
if (not previous_validation) or (
|
# if (not previous_validation) or (
|
||||||
sco_codes.BUT_CODES_ORDER[validation_rcue.code]
|
# sco_codes.BUT_CODES_ORDER[validation_rcue.code]
|
||||||
> sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
# > sco_codes.BUT_CODES_ORDER[previous_validation["code"]]
|
||||||
):
|
# ):
|
||||||
self.validation_par_competence_et_annee[niveau.competence.id][
|
# self.validation_par_competence_et_annee[niveau.competence.id][
|
||||||
niveau.annee
|
# niveau.annee
|
||||||
] = validation_rcue
|
# ] = validation_rcue
|
||||||
|
|
||||||
self.competences = {
|
# self.competences = {
|
||||||
competence.id: competence
|
# competence.id: competence
|
||||||
for competence in (
|
# for competence in (
|
||||||
self.parcour.query_competences()
|
# self.parcour.query_competences()
|
||||||
if self.parcour
|
# if self.parcour
|
||||||
else self.formation.referentiel_competence.get_competences_tronc_commun()
|
# else self.formation.referentiel_competence.get_competences_tronc_commun()
|
||||||
)
|
# )
|
||||||
}
|
# }
|
||||||
"cache { competence_id : competence }"
|
# "cache { competence_id : competence }"
|
||||||
|
|
||||||
|
|
||||||
def but_ects_valides(etud: Identite, referentiel_competence_id: int) -> float:
|
def but_ects_valides(etud: Identite, referentiel_competence_id: int) -> float:
|
||||||
|
@ -1034,8 +1034,8 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
return messages
|
return messages
|
||||||
|
|
||||||
def valide_diplome(self) -> bool:
|
def valide_diplome(self) -> bool:
|
||||||
"Vrai si l'étudiant à validé son diplôme"
|
"Vrai si l'étudiant a validé son diplôme (décision enregistrée)"
|
||||||
return False # TODO XXX
|
return self.annee_but == 3 and sco_codes.code_annee_validant(self.code_valide)
|
||||||
|
|
||||||
|
|
||||||
def list_ue_parcour_etud(
|
def list_ue_parcour_etud(
|
||||||
|
@ -155,6 +155,7 @@ def pvjury_table_but(
|
|||||||
deca = None
|
deca = None
|
||||||
|
|
||||||
ects_but_valides = but_ects_valides(etud, referentiel_competence_id)
|
ects_but_valides = but_ects_valides(etud, referentiel_competence_id)
|
||||||
|
has_diplome = deca.valide_diplome()
|
||||||
row = {
|
row = {
|
||||||
"nom_pv": (
|
"nom_pv": (
|
||||||
etud.code_ine or etud.code_nip or etud.id
|
etud.code_ine or etud.code_nip or etud.id
|
||||||
@ -181,10 +182,15 @@ def pvjury_table_but(
|
|||||||
),
|
),
|
||||||
"decision_but": deca.code_valide if deca else "",
|
"decision_but": deca.code_valide if deca else "",
|
||||||
"devenir": (
|
"devenir": (
|
||||||
", ".join([f"S{i}" for i in deca.get_autorisations_passage()])
|
"Diplôme obtenu"
|
||||||
if deca
|
if has_diplome
|
||||||
else ""
|
else (
|
||||||
|
", ".join([f"S{i}" for i in deca.get_autorisations_passage()])
|
||||||
|
if deca
|
||||||
|
else ""
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
"diplome": "ADM" if has_diplome else "",
|
||||||
# pour exports excel seulement:
|
# pour exports excel seulement:
|
||||||
"civilite": etud.civilite_etat_civil_str,
|
"civilite": etud.civilite_etat_civil_str,
|
||||||
"nom": etud.nom,
|
"nom": etud.nom,
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
"""caches pour tables APC
|
"""caches pour tables APC
|
||||||
"""
|
"""
|
||||||
|
from flask import g
|
||||||
|
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
|
|
||||||
@ -47,3 +48,27 @@ class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "EPC"
|
prefix = "EPC"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def invalidate_all(cls):
|
||||||
|
"delete all cached evaluations poids (in current dept)"
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
|
|
||||||
|
moduleimpl_ids = [
|
||||||
|
mi.id
|
||||||
|
for mi in ModuleImpl.query.join(FormSemestre).filter_by(
|
||||||
|
dept_id=g.scodoc_dept_id
|
||||||
|
)
|
||||||
|
]
|
||||||
|
cls.delete_many(moduleimpl_ids)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def invalidate_sem(cls, formsemestre_id):
|
||||||
|
"delete cached evaluations poids for this formsemestre from cache"
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
|
|
||||||
|
moduleimpl_ids = [
|
||||||
|
mi.id for mi in ModuleImpl.query.filter_by(formsemestre_id=formsemestre_id)
|
||||||
|
]
|
||||||
|
cls.delete_many(moduleimpl_ids)
|
||||||
|
@ -45,7 +45,6 @@ from app.models import Evaluation, EvaluationUEPoids, ModuleImpl
|
|||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
from app.scodoc.sco_exceptions import ScoBugCatcher
|
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -113,6 +112,8 @@ class ModuleImplResults:
|
|||||||
"""
|
"""
|
||||||
self.evals_etudids_sans_note = {}
|
self.evals_etudids_sans_note = {}
|
||||||
"""dict: evaluation_id : set des etudids non notés dans cette eval, sans les démissions."""
|
"""dict: evaluation_id : set des etudids non notés dans cette eval, sans les démissions."""
|
||||||
|
self.evals_type = {}
|
||||||
|
"""Type de chaque eval { evaluation.id : evaluation.evaluation_type }"""
|
||||||
self.load_notes(etudids, etudids_actifs)
|
self.load_notes(etudids, etudids_actifs)
|
||||||
self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index)
|
self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index)
|
||||||
"""1 bool par etud, indique si sa moyenne de module vient de la session2"""
|
"""1 bool par etud, indique si sa moyenne de module vient de la session2"""
|
||||||
@ -164,7 +165,10 @@ class ModuleImplResults:
|
|||||||
self.evaluations_completes = []
|
self.evaluations_completes = []
|
||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
self.etudids_attente = set() # empty
|
self.etudids_attente = set() # empty
|
||||||
|
self.evals_type = {}
|
||||||
|
evaluation: Evaluation
|
||||||
for evaluation in moduleimpl.evaluations:
|
for evaluation in moduleimpl.evaluations:
|
||||||
|
self.evals_type[evaluation.id] = evaluation.evaluation_type
|
||||||
eval_df = self._load_evaluation_notes(evaluation)
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
# is_complete ssi
|
# is_complete ssi
|
||||||
# tous les inscrits (non dem) au module ont une note
|
# tous les inscrits (non dem) au module ont une note
|
||||||
@ -270,6 +274,24 @@ class ModuleImplResults:
|
|||||||
* self.evaluations_completes
|
* self.evaluations_completes
|
||||||
).reshape(-1, 1)
|
).reshape(-1, 1)
|
||||||
|
|
||||||
|
def get_evaluations_special_coefs(
|
||||||
|
self, modimpl: ModuleImpl, evaluation_type=Evaluation.EVALUATION_SESSION2
|
||||||
|
) -> np.array:
|
||||||
|
"""Coefficients des évaluations de session 2 ou rattrapage.
|
||||||
|
Les évals de session 2 et rattrapage sont réputées "complètes": elles sont toujours
|
||||||
|
prises en compte mais seules les notes numériques et ABS sont utilisées.
|
||||||
|
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
np.array(
|
||||||
|
[
|
||||||
|
(e.coefficient if e.evaluation_type == evaluation_type else 0.0)
|
||||||
|
for e in modimpl.evaluations
|
||||||
|
],
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
).reshape(-1, 1)
|
||||||
|
|
||||||
# was _list_notes_evals_titles
|
# was _list_notes_evals_titles
|
||||||
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"Liste des évaluations complètes"
|
"Liste des évaluations complètes"
|
||||||
@ -296,32 +318,26 @@ class ModuleImplResults:
|
|||||||
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
def get_evaluations_rattrapage(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
"""Les évaluations de rattrapage de ce module.
|
||||||
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
||||||
des autres évals et la note eval rattrapage.
|
des autres évals et la moyenne des notes de rattrapage.
|
||||||
"""
|
"""
|
||||||
eval_list = [
|
return [
|
||||||
e
|
e
|
||||||
for e in moduleimpl.evaluations
|
for e in moduleimpl.evaluations
|
||||||
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
||||||
]
|
]
|
||||||
if eval_list:
|
|
||||||
return eval_list[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
def get_evaluations_session2(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
|
"""Les évaluations de deuxième session de ce module, ou None s'il n'en a pas.
|
||||||
Session 2: remplace la note de moyenne des autres évals.
|
La moyenne des notes de Session 2 remplace la note de moyenne des autres évals.
|
||||||
"""
|
"""
|
||||||
eval_list = [
|
return [
|
||||||
e
|
e
|
||||||
for e in moduleimpl.evaluations
|
for e in moduleimpl.evaluations
|
||||||
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
||||||
]
|
]
|
||||||
if eval_list:
|
|
||||||
return eval_list[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_evaluations_bonus(self, modimpl: ModuleImpl) -> list[Evaluation]:
|
def get_evaluations_bonus(self, modimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
"""Les évaluations bonus de ce module, ou liste vide s'il n'en a pas."""
|
"""Les évaluations bonus de ce module, ou liste vide s'il n'en a pas."""
|
||||||
@ -344,12 +360,13 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
"Calcul des moyennes de modules à la mode BUT"
|
"Calcul des moyennes de modules à la mode BUT"
|
||||||
|
|
||||||
def compute_module_moy(
|
def compute_module_moy(
|
||||||
self,
|
self, evals_poids_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||||
evals_poids_df: pd.DataFrame,
|
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcule les moyennes des étudiants dans ce module
|
"""Calcule les moyennes des étudiants dans ce module
|
||||||
|
|
||||||
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
Argument:
|
||||||
|
evals_poids: DataFrame, colonnes: UEs, lignes: EVALs
|
||||||
|
modimpl_coefs_df: DataFrame, colonnes: modimpl_id, lignes: ue_id
|
||||||
|
|
||||||
Résultat: DataFrame, colonnes UE, lignes etud
|
Résultat: DataFrame, colonnes UE, lignes etud
|
||||||
= la note de l'étudiant dans chaque UE pour ce module.
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
@ -370,6 +387,7 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
||||||
if nb_ues == 0:
|
if nb_ues == 0:
|
||||||
return pd.DataFrame(index=self.evals_notes.index, columns=[])
|
return pd.DataFrame(index=self.evals_notes.index, columns=[])
|
||||||
|
# coefs des évals complètes normales (pas rattr., session 2 ni bonus):
|
||||||
evals_coefs = self.get_evaluations_coefs(modimpl)
|
evals_coefs = self.get_evaluations_coefs(modimpl)
|
||||||
evals_poids = evals_poids_df.values * evals_coefs
|
evals_poids = evals_poids_df.values * evals_coefs
|
||||||
# -> evals_poids shape : (nb_evals, nb_ues)
|
# -> evals_poids shape : (nb_evals, nb_ues)
|
||||||
@ -398,6 +416,47 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
) / np.sum(evals_poids_etuds, axis=1)
|
) / np.sum(evals_poids_etuds, axis=1)
|
||||||
# etuds_moy_module shape: nb_etuds x nb_ues
|
# etuds_moy_module shape: nb_etuds x nb_ues
|
||||||
|
|
||||||
|
evals_session2 = self.get_evaluations_session2(modimpl)
|
||||||
|
evals_rat = self.get_evaluations_rattrapage(modimpl)
|
||||||
|
if evals_session2:
|
||||||
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
|
# Calcul moyenne notes session2 et remplace (si la note session 2 existe)
|
||||||
|
etuds_moy_module_s2 = self._compute_moy_special(
|
||||||
|
modimpl,
|
||||||
|
evals_notes_stacked,
|
||||||
|
evals_poids_df,
|
||||||
|
Evaluation.EVALUATION_SESSION2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Vrai si toutes les UEs avec coef non nul ont bien une note de session 2 calculée:
|
||||||
|
mod_coefs = modimpl_coefs_df[modimpl.id]
|
||||||
|
etuds_use_session2 = np.all(
|
||||||
|
np.isfinite(etuds_moy_module_s2[:, mod_coefs != 0]), axis=1
|
||||||
|
)
|
||||||
|
etuds_moy_module = np.where(
|
||||||
|
etuds_use_session2[:, np.newaxis],
|
||||||
|
etuds_moy_module_s2,
|
||||||
|
etuds_moy_module,
|
||||||
|
)
|
||||||
|
self.etuds_use_session2 = pd.Series(
|
||||||
|
etuds_use_session2, index=self.evals_notes.index
|
||||||
|
)
|
||||||
|
elif evals_rat:
|
||||||
|
etuds_moy_module_rat = self._compute_moy_special(
|
||||||
|
modimpl,
|
||||||
|
evals_notes_stacked,
|
||||||
|
evals_poids_df,
|
||||||
|
Evaluation.EVALUATION_RATTRAPAGE,
|
||||||
|
)
|
||||||
|
etuds_ue_use_rattrapage = (
|
||||||
|
etuds_moy_module_rat > etuds_moy_module
|
||||||
|
) # etud x UE
|
||||||
|
etuds_moy_module = np.where(
|
||||||
|
etuds_ue_use_rattrapage, etuds_moy_module_rat, etuds_moy_module
|
||||||
|
)
|
||||||
|
self.etuds_use_rattrapage = pd.Series(
|
||||||
|
np.any(etuds_ue_use_rattrapage, axis=1), index=self.evals_notes.index
|
||||||
|
)
|
||||||
# Application des évaluations bonus:
|
# Application des évaluations bonus:
|
||||||
etuds_moy_module = self.apply_bonus(
|
etuds_moy_module = self.apply_bonus(
|
||||||
etuds_moy_module,
|
etuds_moy_module,
|
||||||
@ -405,47 +464,6 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
evals_poids_df,
|
evals_poids_df,
|
||||||
evals_notes_stacked,
|
evals_notes_stacked,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
|
||||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
|
||||||
if eval_session2:
|
|
||||||
notes_session2 = self.evals_notes[eval_session2.id].values
|
|
||||||
# n'utilise que les notes valides (pas ATT, EXC, ABS, NaN)
|
|
||||||
etuds_use_session2 = notes_session2 > scu.NOTES_ABSENCE
|
|
||||||
etuds_moy_module = np.where(
|
|
||||||
etuds_use_session2[:, np.newaxis],
|
|
||||||
np.tile(
|
|
||||||
(notes_session2 / (eval_session2.note_max / 20.0))[:, np.newaxis],
|
|
||||||
nb_ues,
|
|
||||||
),
|
|
||||||
etuds_moy_module,
|
|
||||||
)
|
|
||||||
self.etuds_use_session2 = pd.Series(
|
|
||||||
etuds_use_session2, index=self.evals_notes.index
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Rattrapage: remplace la note de module ssi elle est supérieure
|
|
||||||
eval_rat = self.get_evaluation_rattrapage(modimpl)
|
|
||||||
if eval_rat:
|
|
||||||
notes_rat = self.evals_notes[eval_rat.id].values
|
|
||||||
# remplace les notes invalides (ATT, EXC...) par des NaN
|
|
||||||
notes_rat = np.where(
|
|
||||||
notes_rat > scu.NOTES_ABSENCE,
|
|
||||||
notes_rat / (eval_rat.note_max / 20.0),
|
|
||||||
np.nan,
|
|
||||||
)
|
|
||||||
# "Étend" le rattrapage sur les UE: la note de rattrapage est la même
|
|
||||||
# pour toutes les UE mais ne remplace que là où elle est supérieure
|
|
||||||
notes_rat_ues = np.stack([notes_rat] * nb_ues, axis=1)
|
|
||||||
# prend le max
|
|
||||||
etuds_use_rattrapage = notes_rat_ues > etuds_moy_module
|
|
||||||
etuds_moy_module = np.where(
|
|
||||||
etuds_use_rattrapage, notes_rat_ues, etuds_moy_module
|
|
||||||
)
|
|
||||||
# Serie indiquant que l'étudiant utilise une note de rattrapage sur l'une des UE:
|
|
||||||
self.etuds_use_rattrapage = pd.Series(
|
|
||||||
etuds_use_rattrapage.any(axis=1), index=self.evals_notes.index
|
|
||||||
)
|
|
||||||
self.etuds_moy_module = pd.DataFrame(
|
self.etuds_moy_module = pd.DataFrame(
|
||||||
etuds_moy_module,
|
etuds_moy_module,
|
||||||
index=self.evals_notes.index,
|
index=self.evals_notes.index,
|
||||||
@ -453,6 +471,34 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
)
|
)
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def _compute_moy_special(
|
||||||
|
self,
|
||||||
|
modimpl: ModuleImpl,
|
||||||
|
evals_notes_stacked: np.array,
|
||||||
|
evals_poids_df: pd.DataFrame,
|
||||||
|
evaluation_type: int,
|
||||||
|
) -> np.array:
|
||||||
|
"""Calcul moyenne APC sur évals rattrapage ou session2"""
|
||||||
|
nb_etuds = self.evals_notes.shape[0]
|
||||||
|
nb_ues = evals_poids_df.shape[1]
|
||||||
|
evals_coefs_s2 = self.get_evaluations_special_coefs(
|
||||||
|
modimpl, evaluation_type=evaluation_type
|
||||||
|
)
|
||||||
|
evals_poids_s2 = evals_poids_df.values * evals_coefs_s2
|
||||||
|
poids_stacked_s2 = np.stack(
|
||||||
|
[evals_poids_s2] * nb_etuds
|
||||||
|
) # nb_etuds, nb_evals, nb_ues
|
||||||
|
evals_poids_etuds_s2 = np.where(
|
||||||
|
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||||
|
poids_stacked_s2,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module_s2 = np.sum(
|
||||||
|
evals_poids_etuds_s2 * evals_notes_stacked, axis=1
|
||||||
|
) / np.sum(evals_poids_etuds_s2, axis=1)
|
||||||
|
return etuds_moy_module_s2
|
||||||
|
|
||||||
def apply_bonus(
|
def apply_bonus(
|
||||||
self,
|
self,
|
||||||
etuds_moy_module: pd.DataFrame,
|
etuds_moy_module: pd.DataFrame,
|
||||||
@ -525,6 +571,7 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
return evals_poids, ues
|
return evals_poids, ues
|
||||||
|
|
||||||
|
|
||||||
|
# appelé par ModuleImpl.check_apc_conformity()
|
||||||
def moduleimpl_is_conforme(
|
def moduleimpl_is_conforme(
|
||||||
moduleimpl, evals_poids: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
moduleimpl, evals_poids: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@ -546,12 +593,12 @@ def moduleimpl_is_conforme(
|
|||||||
if len(modimpl_coefs_df) != nb_ues:
|
if len(modimpl_coefs_df) != nb_ues:
|
||||||
# il arrive (#bug) que le cache ne soit pas à jour...
|
# il arrive (#bug) que le cache ne soit pas à jour...
|
||||||
sco_cache.invalidate_formsemestre()
|
sco_cache.invalidate_formsemestre()
|
||||||
raise ScoBugCatcher("moduleimpl_is_conforme: nb ue incoherent")
|
return app.critical_error("moduleimpl_is_conforme: err 1")
|
||||||
|
|
||||||
if moduleimpl.id not in modimpl_coefs_df:
|
if moduleimpl.id not in modimpl_coefs_df:
|
||||||
# soupçon de bug cache coef ?
|
# soupçon de bug cache coef ?
|
||||||
sco_cache.invalidate_formsemestre()
|
sco_cache.invalidate_formsemestre()
|
||||||
raise ScoBugCatcher("Erreur 454 - merci de ré-essayer")
|
return app.critical_error("moduleimpl_is_conforme: err 2")
|
||||||
|
|
||||||
module_evals_poids = evals_poids.transpose().sum(axis=1) != 0
|
module_evals_poids = evals_poids.transpose().sum(axis=1) != 0
|
||||||
return all((modimpl_coefs_df[moduleimpl.id] != 0).eq(module_evals_poids))
|
return all((modimpl_coefs_df[moduleimpl.id] != 0).eq(module_evals_poids))
|
||||||
@ -593,46 +640,43 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
evals_coefs_etuds * evals_notes_20, axis=1
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
) / np.sum(evals_coefs_etuds, axis=1)
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
|
|
||||||
|
evals_session2 = self.get_evaluations_session2(modimpl)
|
||||||
|
evals_rat = self.get_evaluations_rattrapage(modimpl)
|
||||||
|
if evals_session2:
|
||||||
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
|
# Calcule la moyenne des évaluations de session2
|
||||||
|
etuds_moy_module_s2 = self._compute_moy_special(
|
||||||
|
modimpl, evals_notes_20, Evaluation.EVALUATION_SESSION2
|
||||||
|
)
|
||||||
|
etuds_use_session2 = np.isfinite(etuds_moy_module_s2)
|
||||||
|
etuds_moy_module = np.where(
|
||||||
|
etuds_use_session2,
|
||||||
|
etuds_moy_module_s2,
|
||||||
|
etuds_moy_module,
|
||||||
|
)
|
||||||
|
self.etuds_use_session2 = pd.Series(
|
||||||
|
etuds_use_session2, index=self.evals_notes.index
|
||||||
|
)
|
||||||
|
elif evals_rat:
|
||||||
|
# Rattrapage: remplace la note de module ssi elle est supérieure
|
||||||
|
# Calcule la moyenne des évaluations de rattrapage
|
||||||
|
etuds_moy_module_rat = self._compute_moy_special(
|
||||||
|
modimpl, evals_notes_20, Evaluation.EVALUATION_RATTRAPAGE
|
||||||
|
)
|
||||||
|
etuds_use_rattrapage = etuds_moy_module_rat > etuds_moy_module
|
||||||
|
etuds_moy_module = np.where(
|
||||||
|
etuds_use_rattrapage, etuds_moy_module_rat, etuds_moy_module
|
||||||
|
)
|
||||||
|
self.etuds_use_rattrapage = pd.Series(
|
||||||
|
etuds_use_rattrapage, index=self.evals_notes.index
|
||||||
|
)
|
||||||
|
|
||||||
# Application des évaluations bonus:
|
# Application des évaluations bonus:
|
||||||
etuds_moy_module = self.apply_bonus(
|
etuds_moy_module = self.apply_bonus(
|
||||||
etuds_moy_module,
|
etuds_moy_module,
|
||||||
modimpl,
|
modimpl,
|
||||||
evals_notes_20,
|
evals_notes_20,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
|
||||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
|
||||||
if eval_session2:
|
|
||||||
notes_session2 = self.evals_notes[eval_session2.id].values
|
|
||||||
# n'utilise que les notes valides (pas ATT, EXC, ABS, NaN)
|
|
||||||
etuds_use_session2 = notes_session2 > scu.NOTES_ABSENCE
|
|
||||||
etuds_moy_module = np.where(
|
|
||||||
etuds_use_session2,
|
|
||||||
notes_session2 / (eval_session2.note_max / 20.0),
|
|
||||||
etuds_moy_module,
|
|
||||||
)
|
|
||||||
self.etuds_use_session2 = pd.Series(
|
|
||||||
etuds_use_session2, index=self.evals_notes.index
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Rattrapage: remplace la note de module ssi elle est supérieure
|
|
||||||
eval_rat = self.get_evaluation_rattrapage(modimpl)
|
|
||||||
if eval_rat:
|
|
||||||
notes_rat = self.evals_notes[eval_rat.id].values
|
|
||||||
# remplace les notes invalides (ATT, EXC...) par des NaN
|
|
||||||
notes_rat = np.where(
|
|
||||||
notes_rat > scu.NOTES_ABSENCE,
|
|
||||||
notes_rat / (eval_rat.note_max / 20.0),
|
|
||||||
np.nan,
|
|
||||||
)
|
|
||||||
# prend le max
|
|
||||||
etuds_use_rattrapage = notes_rat > etuds_moy_module
|
|
||||||
etuds_moy_module = np.where(
|
|
||||||
etuds_use_rattrapage, notes_rat, etuds_moy_module
|
|
||||||
)
|
|
||||||
self.etuds_use_rattrapage = pd.Series(
|
|
||||||
etuds_use_rattrapage, index=self.evals_notes.index
|
|
||||||
)
|
|
||||||
self.etuds_moy_module = pd.Series(
|
self.etuds_moy_module = pd.Series(
|
||||||
etuds_moy_module,
|
etuds_moy_module,
|
||||||
index=self.evals_notes.index,
|
index=self.evals_notes.index,
|
||||||
@ -640,6 +684,28 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
|
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def _compute_moy_special(
|
||||||
|
self, modimpl: ModuleImpl, evals_notes_20: np.array, evaluation_type: int
|
||||||
|
) -> np.array:
|
||||||
|
"""Calcul moyenne sur évals rattrapage ou session2"""
|
||||||
|
# n'utilise que les notes valides et ABS (0).
|
||||||
|
# Même calcul que pour les évals normales, mais avec seulement les
|
||||||
|
# coefs des évals de session 2 ou rattrapage:
|
||||||
|
nb_etuds = self.evals_notes.shape[0]
|
||||||
|
evals_coefs = self.get_evaluations_special_coefs(
|
||||||
|
modimpl, evaluation_type=evaluation_type
|
||||||
|
).reshape(-1)
|
||||||
|
coefs_stacked = np.stack([evals_coefs] * nb_etuds)
|
||||||
|
# zéro partout sauf si une note ou ABS:
|
||||||
|
evals_coefs_etuds = np.where(
|
||||||
|
self.evals_notes.values > scu.NOTES_NEUTRALISE, coefs_stacked, 0
|
||||||
|
)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module = np.sum(
|
||||||
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
|
return etuds_moy_module # array 1d (nb_etuds)
|
||||||
|
|
||||||
def apply_bonus(
|
def apply_bonus(
|
||||||
self,
|
self,
|
||||||
etuds_moy_module: np.ndarray,
|
etuds_moy_module: np.ndarray,
|
||||||
|
@ -183,7 +183,9 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
return modimpls_notes.swapaxes(0, 1)
|
return modimpls_notes.swapaxes(0, 1)
|
||||||
|
|
||||||
|
|
||||||
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
def notes_sem_load_cube(
|
||||||
|
formsemestre: FormSemestre, modimpl_coefs_df: pd.DataFrame
|
||||||
|
) -> tuple:
|
||||||
"""Construit le "cube" (tenseur) des notes du semestre.
|
"""Construit le "cube" (tenseur) des notes du semestre.
|
||||||
Charge toutes les notes (sql), calcule les moyennes des modules
|
Charge toutes les notes (sql), calcule les moyennes des modules
|
||||||
et assemble le cube.
|
et assemble le cube.
|
||||||
@ -207,8 +209,8 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
etudids, etudids_actifs = formsemestre.etudids_actifs()
|
etudids, etudids_actifs = formsemestre.etudids_actifs()
|
||||||
for modimpl in formsemestre.modimpls_sorted:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
|
||||||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
evals_poids = modimpl.get_evaluations_poids()
|
||||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids, modimpl_coefs_df)
|
||||||
modimpls_results[modimpl.id] = mod_results
|
modimpls_results[modimpl.id] = mod_results
|
||||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
modimpls_evals_poids[modimpl.id] = evals_poids
|
||||||
modimpls_notes.append(etuds_moy_module)
|
modimpls_notes.append(etuds_moy_module)
|
||||||
|
@ -59,16 +59,17 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
|
|
||||||
def compute(self):
|
def compute(self):
|
||||||
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||||
|
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||||
|
self.formsemestre, modimpls=self.formsemestre.modimpls_sorted
|
||||||
|
)
|
||||||
(
|
(
|
||||||
self.sem_cube,
|
self.sem_cube,
|
||||||
self.modimpls_evals_poids,
|
self.modimpls_evals_poids,
|
||||||
self.modimpls_results,
|
self.modimpls_results,
|
||||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
) = moy_ue.notes_sem_load_cube(self.formsemestre, self.modimpl_coefs_df)
|
||||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||||
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
|
self.ues_inscr_parcours_df = self.load_ues_inscr_parcours()
|
||||||
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
|
||||||
self.formsemestre, modimpls=self.formsemestre.modimpls_sorted
|
|
||||||
)
|
|
||||||
# l'idx de la colonne du mod modimpl.id est
|
# l'idx de la colonne du mod modimpl.id est
|
||||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
|
@ -242,7 +242,8 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
)
|
)
|
||||||
}">saisir le coefficient de cette UE avant de continuer</a></p>
|
}">saisir le coefficient de cette UE avant de continuer</a></p>
|
||||||
</div>
|
</div>
|
||||||
"""
|
""",
|
||||||
|
safe=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -518,7 +518,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
Corrigez ou faite corriger le programme
|
Corrigez ou faite corriger le programme
|
||||||
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=ue_capitalized.formation_id)}">via cette page</a>.
|
formation_id=ue_capitalized.formation_id)}">via cette page</a>.
|
||||||
"""
|
""",
|
||||||
|
safe=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Coefs de l'UE capitalisée en formation classique:
|
# Coefs de l'UE capitalisée en formation classique:
|
||||||
|
14
app/email.py
14
app/email.py
@ -9,9 +9,9 @@ import datetime
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask_mail import Message
|
from flask_mail import BadHeaderError, Message
|
||||||
|
|
||||||
from app import mail
|
from app import log, mail
|
||||||
from app.models.departements import Departement
|
from app.models.departements import Departement
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -20,7 +20,15 @@ from app.scodoc import sco_preferences
|
|||||||
def send_async_email(app, msg):
|
def send_async_email(app, msg):
|
||||||
"Send an email, async"
|
"Send an email, async"
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
mail.send(msg)
|
try:
|
||||||
|
mail.send(msg)
|
||||||
|
except BadHeaderError:
|
||||||
|
log(
|
||||||
|
f"""send_async_email: BadHeaderError
|
||||||
|
msg={msg}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def send_email(
|
def send_email(
|
||||||
|
@ -151,7 +151,7 @@ class EntrepriseHistorique(db.Model):
|
|||||||
__tablename__ = "are_historique"
|
__tablename__ = "are_historique"
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
authenticated_user = db.Column(db.Text)
|
authenticated_user = db.Column(db.Text) # user_name login sans contrainte
|
||||||
entreprise_id = db.Column(db.Integer)
|
entreprise_id = db.Column(db.Integer)
|
||||||
object = db.Column(db.Text)
|
object = db.Column(db.Text)
|
||||||
object_id = db.Column(db.Integer)
|
object_id = db.Column(db.Integer)
|
||||||
|
@ -338,9 +338,11 @@ def add_entreprise():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
entreprise = Entreprise(
|
entreprise = Entreprise(
|
||||||
nom=form.nom_entreprise.data.strip(),
|
nom=form.nom_entreprise.data.strip(),
|
||||||
siret=form.siret.data.strip()
|
siret=(
|
||||||
if form.siret.data.strip()
|
form.siret.data.strip()
|
||||||
else f"{SIRET_PROVISOIRE_START}{datetime.now().strftime('%d%m%y%H%M%S')}", # siret provisoire
|
if form.siret.data.strip()
|
||||||
|
else f"{SIRET_PROVISOIRE_START}{datetime.now().strftime('%d%m%y%H%M%S')}"
|
||||||
|
), # siret provisoire
|
||||||
siret_provisoire=False if form.siret.data.strip() else True,
|
siret_provisoire=False if form.siret.data.strip() else True,
|
||||||
association=form.association.data,
|
association=form.association.data,
|
||||||
adresse=form.adresse.data.strip(),
|
adresse=form.adresse.data.strip(),
|
||||||
@ -352,7 +354,7 @@ def add_entreprise():
|
|||||||
db.session.add(entreprise)
|
db.session.add(entreprise)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.refresh(entreprise)
|
db.session.refresh(entreprise)
|
||||||
except:
|
except Exception:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash("Une erreur est survenue veuillez réessayer.")
|
flash("Une erreur est survenue veuillez réessayer.")
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -804,9 +806,9 @@ def add_offre(entreprise_id):
|
|||||||
missions=form.missions.data.strip(),
|
missions=form.missions.data.strip(),
|
||||||
duree=form.duree.data.strip(),
|
duree=form.duree.data.strip(),
|
||||||
expiration_date=form.expiration_date.data,
|
expiration_date=form.expiration_date.data,
|
||||||
correspondant_id=form.correspondant.data
|
correspondant_id=(
|
||||||
if form.correspondant.data != ""
|
form.correspondant.data if form.correspondant.data != "" else None
|
||||||
else None,
|
),
|
||||||
)
|
)
|
||||||
db.session.add(offre)
|
db.session.add(offre)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -1328,9 +1330,11 @@ def add_contact(entreprise_id):
|
|||||||
).first_or_404(description=f"entreprise {entreprise_id} inconnue")
|
).first_or_404(description=f"entreprise {entreprise_id} inconnue")
|
||||||
form = ContactCreationForm(
|
form = ContactCreationForm(
|
||||||
date=f"{datetime.now().strftime('%Y-%m-%dT%H:%M')}",
|
date=f"{datetime.now().strftime('%Y-%m-%dT%H:%M')}",
|
||||||
utilisateur=f"{current_user.nom} {current_user.prenom} ({current_user.user_name})"
|
utilisateur=(
|
||||||
if current_user.nom and current_user.prenom
|
f"{current_user.nom} {current_user.prenom} ({current_user.user_name})"
|
||||||
else "",
|
if current_user.nom and current_user.prenom
|
||||||
|
else ""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if request.method == "POST" and form.cancel.data:
|
if request.method == "POST" and form.cancel.data:
|
||||||
return redirect(url_for("entreprises.contacts", entreprise_id=entreprise_id))
|
return redirect(url_for("entreprises.contacts", entreprise_id=entreprise_id))
|
||||||
@ -1496,9 +1500,9 @@ def add_stage_apprentissage(entreprise_id):
|
|||||||
date_debut=form.date_debut.data,
|
date_debut=form.date_debut.data,
|
||||||
date_fin=form.date_fin.data,
|
date_fin=form.date_fin.data,
|
||||||
formation_text=formation.formsemestre.titre if formation else None,
|
formation_text=formation.formsemestre.titre if formation else None,
|
||||||
formation_scodoc=formation.formsemestre.formsemestre_id
|
formation_scodoc=(
|
||||||
if formation
|
formation.formsemestre.formsemestre_id if formation else None
|
||||||
else None,
|
),
|
||||||
notes=form.notes.data.strip(),
|
notes=form.notes.data.strip(),
|
||||||
)
|
)
|
||||||
db.session.add(stage_apprentissage)
|
db.session.add(stage_apprentissage)
|
||||||
@ -1802,7 +1806,7 @@ def import_donnees():
|
|||||||
db.session.add(entreprise)
|
db.session.add(entreprise)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.refresh(entreprise)
|
db.session.refresh(entreprise)
|
||||||
except:
|
except Exception:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
flash("Une erreur est survenue veuillez réessayer.")
|
flash("Une erreur est survenue veuillez réessayer.")
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -62,6 +62,11 @@ class AjoutAssiOrJustForm(FlaskForm):
|
|||||||
if field:
|
if field:
|
||||||
field.errors.append(err_msg)
|
field.errors.append(err_msg)
|
||||||
|
|
||||||
|
def disable_all(self):
|
||||||
|
"Disable all fields"
|
||||||
|
for field in self:
|
||||||
|
field.render_kw = {"disabled": True}
|
||||||
|
|
||||||
date_debut = StringField(
|
date_debut = StringField(
|
||||||
"Date de début",
|
"Date de début",
|
||||||
validators=[validators.Length(max=10)],
|
validators=[validators.Length(max=10)],
|
||||||
@ -175,36 +180,3 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
|||||||
validators=[DataRequired(message="This field is required.")],
|
validators=[DataRequired(message="This field is required.")],
|
||||||
)
|
)
|
||||||
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
||||||
|
|
||||||
|
|
||||||
class ChoixDateForm(FlaskForm):
|
|
||||||
"""
|
|
||||||
Formulaire de choix de date
|
|
||||||
(utilisé par la page de choix de date
|
|
||||||
si la date courante n'est pas dans le semestre)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"Init form, adding a filed for our error messages"
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.ok = True
|
|
||||||
self.error_messages: list[str] = [] # used to report our errors
|
|
||||||
|
|
||||||
def set_error(self, err_msg, field=None):
|
|
||||||
"Set error message both in form and field"
|
|
||||||
self.ok = False
|
|
||||||
self.error_messages.append(err_msg)
|
|
||||||
if field:
|
|
||||||
field.errors.append(err_msg)
|
|
||||||
|
|
||||||
date = StringField(
|
|
||||||
"Date",
|
|
||||||
validators=[validators.Length(max=10)],
|
|
||||||
render_kw={
|
|
||||||
"class": "datepicker",
|
|
||||||
"size": 10,
|
|
||||||
"id": "date",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
submit = SubmitField("Enregistrer")
|
|
||||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
|
||||||
|
122
app/forms/assiduite/edit_assiduite_etud.py
Normal file
122
app/forms/assiduite/edit_assiduite_etud.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
""" """
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import (
|
||||||
|
StringField,
|
||||||
|
SelectField,
|
||||||
|
RadioField,
|
||||||
|
TextAreaField,
|
||||||
|
validators,
|
||||||
|
SubmitField,
|
||||||
|
)
|
||||||
|
from app.scodoc.sco_utils import EtatAssiduite
|
||||||
|
|
||||||
|
|
||||||
|
class EditAssiForm(FlaskForm):
|
||||||
|
"""
|
||||||
|
Formulaire de modification d'une assiduité
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"Init form, adding a filed for our error messages"
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.ok = True
|
||||||
|
self.error_messages: list[str] = [] # used to report our errors
|
||||||
|
|
||||||
|
def set_error(self, err_msg, field=None):
|
||||||
|
"Set error message both in form and field"
|
||||||
|
self.ok = False
|
||||||
|
self.error_messages.append(err_msg)
|
||||||
|
if field:
|
||||||
|
field.errors.append(err_msg)
|
||||||
|
|
||||||
|
def disable_all(self):
|
||||||
|
"Disable all fields"
|
||||||
|
for field in self:
|
||||||
|
field.render_kw = {"disabled": True}
|
||||||
|
|
||||||
|
assi_etat = RadioField(
|
||||||
|
"État:",
|
||||||
|
choices=[
|
||||||
|
(EtatAssiduite.ABSENT.value, EtatAssiduite.ABSENT.version_lisible()),
|
||||||
|
(EtatAssiduite.RETARD.value, EtatAssiduite.RETARD.version_lisible()),
|
||||||
|
(EtatAssiduite.PRESENT.value, EtatAssiduite.PRESENT.version_lisible()),
|
||||||
|
],
|
||||||
|
default="absent",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
modimpl = SelectField(
|
||||||
|
"Module",
|
||||||
|
choices={}, # will be populated dynamically
|
||||||
|
)
|
||||||
|
description = TextAreaField(
|
||||||
|
"Description",
|
||||||
|
render_kw={
|
||||||
|
"id": "description",
|
||||||
|
"cols": 75,
|
||||||
|
"rows": 4,
|
||||||
|
"maxlength": 500,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
date_debut = StringField(
|
||||||
|
"Date de début",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_debut = StringField(
|
||||||
|
"Heure début",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_fin = StringField(
|
||||||
|
"Heure fin",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
date_fin = StringField(
|
||||||
|
"Date de fin",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry_date = StringField(
|
||||||
|
"Date de dépôt ou saisie",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "entry_date",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry_time = StringField(
|
||||||
|
"Heure dépôt",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField("Enregistrer")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
66
app/forms/main/create_bug_report.py
Normal file
66
app/forms/main/create_bug_report.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulaire création de ticket de bug
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SubmitField, validators
|
||||||
|
from wtforms.fields.simple import StringField, TextAreaField, BooleanField
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
|
||||||
|
class CreateBugReport(FlaskForm):
|
||||||
|
"""Formulaire permettant la création d'un ticket de bug"""
|
||||||
|
|
||||||
|
title = StringField(
|
||||||
|
label="Titre du ticket",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("titre du ticket requis"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
message = TextAreaField(
|
||||||
|
label="Message",
|
||||||
|
id="ticket_message",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("message du ticket requis"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
etab = StringField(label="Etablissement")
|
||||||
|
include_dump = BooleanField(
|
||||||
|
"""Inclure une copie anonymisée de la base de données ?
|
||||||
|
Ces données faciliteront le traitement du problème et resteront strictement confidentielles.
|
||||||
|
""",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
submit = SubmitField("Envoyer")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(CreateBugReport, self).__init__(*args, **kwargs)
|
||||||
|
self.etab.data = sco_preferences.get_preference("InstituteName") or ""
|
@ -353,12 +353,22 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
elif self.external_data is not None and "module" in self.external_data:
|
elif self.external_data is not None and "module" in self.external_data:
|
||||||
return (
|
return (
|
||||||
"Tout module"
|
"Autre module (pas dans la liste)"
|
||||||
if self.external_data["module"] == "Autre"
|
if self.external_data["module"] == "Autre"
|
||||||
else self.external_data["module"]
|
else self.external_data["module"]
|
||||||
)
|
)
|
||||||
|
|
||||||
return "Non spécifié" if traduire else None
|
return "Module non spécifié" if traduire else None
|
||||||
|
|
||||||
|
def get_moduleimpl_id(self) -> int | str | None:
|
||||||
|
"""
|
||||||
|
Retourne le ModuleImpl associé à l'assiduité
|
||||||
|
"""
|
||||||
|
if self.moduleimpl_id is not None:
|
||||||
|
return self.moduleimpl_id
|
||||||
|
if self.external_data is not None and "module" in self.external_data:
|
||||||
|
return self.external_data["module"]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_saisie(self) -> str:
|
def get_saisie(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -395,6 +405,14 @@ class Assiduite(ScoDocModel):
|
|||||||
if force:
|
if force:
|
||||||
raise ScoValueError("Module non renseigné")
|
raise ScoValueError("Module non renseigné")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_assiduite(cls, assiduite_id: int) -> "Assiduite":
|
||||||
|
"""Assiduité ou 404, cherche uniquement dans le département courant"""
|
||||||
|
query = Assiduite.query.filter_by(id=assiduite_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
return query.first_or_404()
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(ScoDocModel):
|
class Justificatif(ScoDocModel):
|
||||||
"""
|
"""
|
||||||
@ -685,10 +703,14 @@ def is_period_conflicting(
|
|||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
collection: Query,
|
collection: Query,
|
||||||
collection_cls: Assiduite | Justificatif,
|
collection_cls: Assiduite | Justificatif,
|
||||||
|
obj_id: int = -1,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si une date n'entre pas en collision
|
Vérifie si une date n'entre pas en collision
|
||||||
avec les justificatifs ou assiduites déjà présentes
|
avec les justificatifs ou assiduites déjà présentes
|
||||||
|
|
||||||
|
On peut donner un objet_id pour exclure un objet de la vérification
|
||||||
|
(utile pour les modifications)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# On s'assure que les dates soient avec TimeZone
|
# On s'assure que les dates soient avec TimeZone
|
||||||
@ -696,7 +718,9 @@ def is_period_conflicting(
|
|||||||
date_fin = localize_datetime(date_fin)
|
date_fin = localize_datetime(date_fin)
|
||||||
|
|
||||||
count: int = collection.filter(
|
count: int = collection.filter(
|
||||||
collection_cls.date_debut < date_fin, collection_cls.date_fin > date_debut
|
collection_cls.date_debut < date_fin,
|
||||||
|
collection_cls.date_fin > date_debut,
|
||||||
|
collection_cls.id != obj_id,
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
return count > 0
|
return count > 0
|
||||||
|
@ -274,6 +274,11 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
return "type_departement mismatch"
|
return "type_departement mismatch"
|
||||||
# Table d'équivalences entre refs:
|
# Table d'équivalences entre refs:
|
||||||
equiv = self._load_config_equivalences()
|
equiv = self._load_config_equivalences()
|
||||||
|
# Même specialité (ou alias) ?
|
||||||
|
if self.specialite != other.specialite and other.specialite not in equiv.get(
|
||||||
|
"alias", []
|
||||||
|
):
|
||||||
|
return "specialite mismatch"
|
||||||
# mêmes parcours ?
|
# mêmes parcours ?
|
||||||
eq_parcours = equiv.get("parcours", {})
|
eq_parcours = equiv.get("parcours", {})
|
||||||
parcours_by_code_1 = {eq_parcours.get(p.code, p.code): p for p in self.parcours}
|
parcours_by_code_1 = {eq_parcours.get(p.code, p.code): p for p in self.parcours}
|
||||||
@ -317,6 +322,9 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
def _load_config_equivalences(self) -> dict:
|
def _load_config_equivalences(self) -> dict:
|
||||||
"""Load config file ressources/referentiels/equivalences.yaml
|
"""Load config file ressources/referentiels/equivalences.yaml
|
||||||
used to define equivalences between distinct referentiels
|
used to define equivalences between distinct referentiels
|
||||||
|
return a dict, with optional keys:
|
||||||
|
alias: list of equivalent names for speciality (eg SD == STID)
|
||||||
|
parcours: dict with equivalent parcours acronyms
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with open(REFCOMP_EQUIVALENCE_FILENAME, encoding="utf-8") as f:
|
with open(REFCOMP_EQUIVALENCE_FILENAME, encoding="utf-8") as f:
|
||||||
|
@ -113,6 +113,12 @@ class ApcValidationRCUE(db.Model):
|
|||||||
"formsemestre_id": self.formsemestre_id,
|
"formsemestre_id": self.formsemestre_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_codes_apogee(self) -> set[str]:
|
||||||
|
"""Les codes Apogée associés à cette validation RCUE.
|
||||||
|
Prend les codes des deux UEs
|
||||||
|
"""
|
||||||
|
return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue()
|
||||||
|
|
||||||
|
|
||||||
class ApcValidationAnnee(db.Model):
|
class ApcValidationAnnee(db.Model):
|
||||||
"""Validation des années du BUT"""
|
"""Validation des années du BUT"""
|
||||||
@ -213,6 +219,7 @@ def dict_decision_jury(etud: Identite, formsemestre: FormSemestre) -> dict:
|
|||||||
dec_rcue["code"]}"""
|
dec_rcue["code"]}"""
|
||||||
)
|
)
|
||||||
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
|
decisions["descr_decisions_rcue"] = ", ".join(titres_rcues)
|
||||||
|
decisions["descr_decisions_rcue_list"] = titres_rcues
|
||||||
decisions["descr_decisions_niveaux"] = (
|
decisions["descr_decisions_niveaux"] = (
|
||||||
"Niveaux de compétences: " + decisions["descr_decisions_rcue"]
|
"Niveaux de compétences: " + decisions["descr_decisions_rcue"]
|
||||||
)
|
)
|
||||||
|
@ -199,6 +199,11 @@ class Identite(models.ScoDocModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_etud(cls, etudid: int) -> "Identite":
|
def get_etud(cls, etudid: int) -> "Identite":
|
||||||
"""Etudiant ou 404, cherche uniquement dans le département courant"""
|
"""Etudiant ou 404, cherche uniquement dans le département courant"""
|
||||||
|
if not isinstance(etudid, int):
|
||||||
|
try:
|
||||||
|
etudid = int(etudid)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
abort(404, "etudid invalide")
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
return cls.query.filter_by(
|
return cls.query.filter_by(
|
||||||
id=etudid, dept_id=g.scodoc_dept_id
|
id=etudid, dept_id=g.scodoc_dept_id
|
||||||
@ -299,9 +304,10 @@ class Identite(models.ScoDocModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def nomprenom(self, reverse=False) -> str:
|
def nomprenom(self, reverse=False) -> str:
|
||||||
"""Civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
"""DEPRECATED
|
||||||
|
Civilité/prénom/nom pour affichages: "M. Pierre Dupont"
|
||||||
Si reverse, "Dupont Pierre", sans civilité.
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
Prend l'identité courant et non celle de l'état civile si elles diffèrent.
|
Prend l'identité courante et non celle de l'état civil si elles diffèrent.
|
||||||
"""
|
"""
|
||||||
nom = self.nom_usuel or self.nom
|
nom = self.nom_usuel or self.nom
|
||||||
prenom = self.prenom_str
|
prenom = self.prenom_str
|
||||||
@ -309,6 +315,12 @@ class Identite(models.ScoDocModel):
|
|||||||
return f"{nom} {prenom}".strip()
|
return f"{nom} {prenom}".strip()
|
||||||
return f"{self.civilite_str} {prenom} {nom}".strip()
|
return f"{self.civilite_str} {prenom} {nom}".strip()
|
||||||
|
|
||||||
|
def nom_prenom(self) -> str:
|
||||||
|
"""Civilite NOM Prénom
|
||||||
|
Prend l'identité courante et non celle de l'état civil si elles diffèrent.
|
||||||
|
"""
|
||||||
|
return f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()} {self.prenom_str}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prenom_str(self):
|
def prenom_str(self):
|
||||||
"""Prénom à afficher. Par exemple: "Jean-Christophe" """
|
"""Prénom à afficher. Par exemple: "Jean-Christophe" """
|
||||||
@ -347,14 +359,15 @@ class Identite(models.ScoDocModel):
|
|||||||
"Le mail associé à la première adresse de l'étudiant, ou None"
|
"Le mail associé à la première adresse de l'étudiant, ou None"
|
||||||
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
|
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
|
||||||
|
|
||||||
def get_formsemestres(self) -> list:
|
def get_formsemestres(self, recent_first=True) -> list:
|
||||||
"""Liste des formsemestres dans lesquels l'étudiant est (a été) inscrit,
|
"""Liste des formsemestres dans lesquels l'étudiant est (a été) inscrit,
|
||||||
triée par date_debut
|
triée par date_debut, le plus récent d'abord (comme "sems" de scodoc7)
|
||||||
|
(si recent_first=False, le plus ancien en tête)
|
||||||
"""
|
"""
|
||||||
return sorted(
|
return sorted(
|
||||||
[ins.formsemestre for ins in self.formsemestre_inscriptions],
|
[ins.formsemestre for ins in self.formsemestre_inscriptions],
|
||||||
key=attrgetter("date_debut"),
|
key=attrgetter("date_debut"),
|
||||||
reverse=True,
|
reverse=recent_first,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_modimpls_by_formsemestre(
|
def get_modimpls_by_formsemestre(
|
||||||
@ -393,6 +406,18 @@ class Identite(models.ScoDocModel):
|
|||||||
modimpls_by_formsemestre[formsemestre.id] = modimpls_sem
|
modimpls_by_formsemestre[formsemestre.id] = modimpls_sem
|
||||||
return modimpls_by_formsemestre
|
return modimpls_by_formsemestre
|
||||||
|
|
||||||
|
def get_modimpls_from_formsemestre(
|
||||||
|
self, formsemestre: "FormSemestre"
|
||||||
|
) -> list["ModuleImpl"]:
|
||||||
|
"""
|
||||||
|
Liste des ModuleImpl auxquels l'étudiant est inscrit dans le formsemestre.
|
||||||
|
"""
|
||||||
|
modimpls = ModuleImpl.query.join(ModuleImplInscription).filter(
|
||||||
|
ModuleImplInscription.etudid == self.id,
|
||||||
|
ModuleImpl.formsemestre_id == formsemestre.id,
|
||||||
|
)
|
||||||
|
return modimpls.all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_dict_fields(cls, args: dict) -> dict:
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
"""Convert fields in the given dict. No other side effect.
|
"""Convert fields in the given dict. No other side effect.
|
||||||
@ -551,7 +576,7 @@ class Identite(models.ScoDocModel):
|
|||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
def inscription_courante(self):
|
def inscription_courante(self) -> "FormSemestreInscription | None":
|
||||||
"""La première inscription à un formsemestre _actuellement_ en cours.
|
"""La première inscription à un formsemestre _actuellement_ en cours.
|
||||||
None s'il n'y en a pas (ou plus, ou pas encore).
|
None s'il n'y en a pas (ou plus, ou pas encore).
|
||||||
"""
|
"""
|
||||||
|
@ -71,6 +71,15 @@ class Evaluation(models.ScoDocModel):
|
|||||||
EVALUATION_BONUS,
|
EVALUATION_BONUS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def type_abbrev(self) -> str:
|
||||||
|
"Le nom abrégé du type de cette éval."
|
||||||
|
return {
|
||||||
|
self.EVALUATION_NORMALE: "std",
|
||||||
|
self.EVALUATION_RATTRAPAGE: "rattrapage",
|
||||||
|
self.EVALUATION_SESSION2: "session 2",
|
||||||
|
self.EVALUATION_BONUS: "bonus",
|
||||||
|
}.get(self.evaluation_type, "?")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<Evaluation {self.id} {
|
return f"""<Evaluation {self.id} {
|
||||||
self.date_debut.isoformat() if self.date_debut else ''} "{
|
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||||
@ -417,12 +426,13 @@ class Evaluation(models.ScoDocModel):
|
|||||||
return modified
|
return modified
|
||||||
|
|
||||||
def set_ue_poids(self, ue, poids: float) -> None:
|
def set_ue_poids(self, ue, poids: float) -> None:
|
||||||
"""Set poids évaluation vers cette UE"""
|
"""Set poids évaluation vers cette UE. Commit."""
|
||||||
self.update_ue_poids_dict({ue.id: poids})
|
self.update_ue_poids_dict({ue.id: poids})
|
||||||
|
|
||||||
def set_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
def set_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
||||||
"""set poids vers les UE (remplace existants)
|
"""set poids vers les UE (remplace existants)
|
||||||
ue_poids_dict = { ue_id : poids }
|
ue_poids_dict = { ue_id : poids }
|
||||||
|
Commit session.
|
||||||
"""
|
"""
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
|
|
||||||
@ -432,9 +442,12 @@ class Evaluation(models.ScoDocModel):
|
|||||||
if ue is None:
|
if ue is None:
|
||||||
raise ScoValueError("poids vers une UE inexistante")
|
raise ScoValueError("poids vers une UE inexistante")
|
||||||
ue_poids = EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)
|
ue_poids = EvaluationUEPoids(evaluation=self, ue=ue, poids=poids)
|
||||||
L.append(ue_poids)
|
|
||||||
db.session.add(ue_poids)
|
db.session.add(ue_poids)
|
||||||
|
L.append(ue_poids)
|
||||||
|
|
||||||
self.ue_poids = L # backref # pylint:disable=attribute-defined-outside-init
|
self.ue_poids = L # backref # pylint:disable=attribute-defined-outside-init
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
self.moduleimpl.invalidate_evaluations_poids() # inval cache
|
self.moduleimpl.invalidate_evaluations_poids() # inval cache
|
||||||
|
|
||||||
def update_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
def update_ue_poids_dict(self, ue_poids_dict: dict) -> None:
|
||||||
|
@ -27,7 +27,7 @@ class Scolog(db.Model):
|
|||||||
method = db.Column(db.Text)
|
method = db.Column(db.Text)
|
||||||
msg = db.Column(db.Text)
|
msg = db.Column(db.Text)
|
||||||
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
|
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
|
||||||
authenticated_user = db.Column(db.Text) # login, sans contrainte
|
authenticated_user = db.Column(db.Text) # user_name login, sans contrainte
|
||||||
# zope_remote_addr suppressed
|
# zope_remote_addr suppressed
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -76,7 +76,9 @@ class ScolarNews(db.Model):
|
|||||||
date = db.Column(
|
date = db.Column(
|
||||||
db.DateTime(timezone=True), server_default=db.func.now(), index=True
|
db.DateTime(timezone=True), server_default=db.func.now(), index=True
|
||||||
)
|
)
|
||||||
authenticated_user = db.Column(db.Text, index=True) # login, sans contrainte
|
authenticated_user = db.Column(
|
||||||
|
db.Text, index=True
|
||||||
|
) # user_name login, sans contrainte
|
||||||
# type in 'INSCR', 'NOTES', 'FORM', 'SEM', 'MISC'
|
# type in 'INSCR', 'NOTES', 'FORM', 'SEM', 'MISC'
|
||||||
type = db.Column(db.String(SHORT_STR_LEN), index=True)
|
type = db.Column(db.String(SHORT_STR_LEN), index=True)
|
||||||
object = db.Column(
|
object = db.Column(
|
||||||
|
@ -36,6 +36,7 @@ from app.models.config import ScoDocSiteConfig
|
|||||||
from app.models.departements import Departement
|
from app.models.departements import Departement
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.evaluations import Evaluation
|
from app.models.evaluations import Evaluation
|
||||||
|
from app.models.events import ScolarNews
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.groups import GroupDescr, Partition
|
from app.models.groups import GroupDescr, Partition
|
||||||
from app.models.moduleimpls import (
|
from app.models.moduleimpls import (
|
||||||
@ -207,6 +208,70 @@ class FormSemestre(models.ScoDocModel):
|
|||||||
).first_or_404()
|
).first_or_404()
|
||||||
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_formsemestre(cls, args: dict, silent=False) -> "FormSemestre":
|
||||||
|
"""Création d'un formsemestre, avec toutes les valeurs par défaut
|
||||||
|
et notification (sauf si silent).
|
||||||
|
Crée la partition par défaut.
|
||||||
|
"""
|
||||||
|
# was sco_formsemestre.do_formsemestre_create
|
||||||
|
if "dept_id" not in args:
|
||||||
|
args["dept_id"] = g.scodoc_dept_id
|
||||||
|
formsemestre: "FormSemestre" = cls.create_from_dict(args)
|
||||||
|
db.session.flush()
|
||||||
|
for etape in args["etapes"]:
|
||||||
|
formsemestre.add_etape(etape)
|
||||||
|
db.session.commit()
|
||||||
|
for u in args["responsables"]:
|
||||||
|
formsemestre.responsables.append(u)
|
||||||
|
# create default partition
|
||||||
|
partition = Partition(
|
||||||
|
formsemestre=formsemestre, partition_name=None, numero=1000000
|
||||||
|
)
|
||||||
|
db.session.add(partition)
|
||||||
|
partition.create_group(default=True)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
url = url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=formsemestre.departement.acronym,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
)
|
||||||
|
ScolarNews.add(
|
||||||
|
typ=ScolarNews.NEWS_SEM,
|
||||||
|
text=f"""Création du semestre <a href="{url}">{formsemestre.titre}</a>""",
|
||||||
|
url=url,
|
||||||
|
max_frequency=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
return formsemestre
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
|
"""Convert fields in the given dict.
|
||||||
|
args: dict with args in application.
|
||||||
|
returns: dict to store in model's db.
|
||||||
|
"""
|
||||||
|
if "date_debut" in args:
|
||||||
|
args["date_debut"] = scu.convert_fr_date(args["date_debut"])
|
||||||
|
if "date_fin" in args:
|
||||||
|
args["date_fin"] = scu.convert_fr_date(args["date_debut"])
|
||||||
|
if "etat" in args:
|
||||||
|
args["etat"] = bool(args["etat"])
|
||||||
|
if "bul_bgcolor" in args:
|
||||||
|
args["bul_bgcolor"] = args.get("bul_bgcolor") or "white"
|
||||||
|
if "titre" in args:
|
||||||
|
args["titre"] = args.get("titre") or "sans titre"
|
||||||
|
return args
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||||
|
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||||
|
Add 'etapes' to excluded."""
|
||||||
|
# on ne peut pas affecter directement etapes
|
||||||
|
return super().filter_model_attributes(data, (excluded or set()) | {"etapes"})
|
||||||
|
|
||||||
def sort_key(self) -> tuple:
|
def sort_key(self) -> tuple:
|
||||||
"""clé pour tris par ordre de date_debut, le plus ancien en tête
|
"""clé pour tris par ordre de date_debut, le plus ancien en tête
|
||||||
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
||||||
@ -610,6 +675,41 @@ class FormSemestre(models.ScoDocModel):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def est_in_semestre_scolaire(
|
||||||
|
cls,
|
||||||
|
date_debut: datetime.date,
|
||||||
|
year=False,
|
||||||
|
periode=None,
|
||||||
|
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE,
|
||||||
|
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2,
|
||||||
|
) -> bool:
|
||||||
|
"""Vrai si la date_debut est dans la période indiquée (1,2,0)
|
||||||
|
du semestre `periode` de l'année scolaire indiquée
|
||||||
|
(ou, à défaut, de celle en cours).
|
||||||
|
|
||||||
|
La période utilise les même conventions que semset["sem_id"];
|
||||||
|
* 1 : première période
|
||||||
|
* 2 : deuxième période
|
||||||
|
* 0 ou période non précisée: annualisé (donc inclut toutes les périodes)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
if not year:
|
||||||
|
year = scu.annee_scolaire()
|
||||||
|
# n'utilise pas le jour pivot
|
||||||
|
jour_pivot_annee = jour_pivot_periode = 1
|
||||||
|
# calcule l'année universitaire et la période
|
||||||
|
sem_annee, sem_periode = cls.comp_periode(
|
||||||
|
date_debut,
|
||||||
|
mois_pivot_annee,
|
||||||
|
mois_pivot_periode,
|
||||||
|
jour_pivot_annee,
|
||||||
|
jour_pivot_periode,
|
||||||
|
)
|
||||||
|
if periode is None or periode == 0:
|
||||||
|
return sem_annee == year
|
||||||
|
return sem_annee == year and sem_periode == periode
|
||||||
|
|
||||||
def est_terminal(self) -> bool:
|
def est_terminal(self) -> bool:
|
||||||
"Vrai si dernier semestre de son cursus (ou formation mono-semestre)"
|
"Vrai si dernier semestre de son cursus (ou formation mono-semestre)"
|
||||||
return (self.semestre_id < 0) or (
|
return (self.semestre_id < 0) or (
|
||||||
@ -694,7 +794,7 @@ class FormSemestre(models.ScoDocModel):
|
|||||||
FormSemestre.titre,
|
FormSemestre.titre,
|
||||||
)
|
)
|
||||||
|
|
||||||
def etapes_apo_vdi(self) -> list[ApoEtapeVDI]:
|
def etapes_apo_vdi(self) -> list["ApoEtapeVDI"]:
|
||||||
"Liste des vdis"
|
"Liste des vdis"
|
||||||
# was read_formsemestre_etapes
|
# was read_formsemestre_etapes
|
||||||
return [e.as_apovdi() for e in self.etapes if e.etape_apo]
|
return [e.as_apovdi() for e in self.etapes if e.etape_apo]
|
||||||
@ -707,9 +807,9 @@ class FormSemestre(models.ScoDocModel):
|
|||||||
return ""
|
return ""
|
||||||
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
|
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
|
||||||
|
|
||||||
def add_etape(self, etape_apo: str):
|
def add_etape(self, etape_apo: str | ApoEtapeVDI):
|
||||||
"Ajoute une étape"
|
"Ajoute une étape"
|
||||||
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=etape_apo)
|
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=str(etape_apo))
|
||||||
db.session.add(etape)
|
db.session.add(etape)
|
||||||
|
|
||||||
def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
|
def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
|
||||||
@ -938,7 +1038,7 @@ class FormSemestre(models.ScoDocModel):
|
|||||||
|
|
||||||
def etudids_actifs(self) -> tuple[list[int], set[int]]:
|
def etudids_actifs(self) -> tuple[list[int], set[int]]:
|
||||||
"""Liste les etudids inscrits (incluant DEM et DEF),
|
"""Liste les etudids inscrits (incluant DEM et DEF),
|
||||||
qui ser al'index des dataframes de notes
|
qui sera l'index des dataframes de notes
|
||||||
et donne l'ensemble des inscrits non DEM ni DEF.
|
et donne l'ensemble des inscrits non DEM ni DEF.
|
||||||
"""
|
"""
|
||||||
return [inscr.etudid for inscr in self.inscriptions], {
|
return [inscr.etudid for inscr in self.inscriptions], {
|
||||||
@ -1225,10 +1325,18 @@ class FormSemestreEtape(db.Model):
|
|||||||
"Etape False if code empty"
|
"Etape False if code empty"
|
||||||
return self.etape_apo is not None and (len(self.etape_apo) > 0)
|
return self.etape_apo is not None and (len(self.etape_apo) > 0)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, ApoEtapeVDI):
|
||||||
|
return self.as_apovdi() == other
|
||||||
|
return str(self) == str(other)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Etape {self.id} apo={self.etape_apo!r}>"
|
return f"<Etape {self.id} apo={self.etape_apo!r}>"
|
||||||
|
|
||||||
def as_apovdi(self) -> ApoEtapeVDI:
|
def __str__(self):
|
||||||
|
return self.etape_apo or ""
|
||||||
|
|
||||||
|
def as_apovdi(self) -> "ApoEtapeVDI":
|
||||||
return ApoEtapeVDI(self.etape_apo)
|
return ApoEtapeVDI(self.etape_apo)
|
||||||
|
|
||||||
|
|
||||||
@ -1381,8 +1489,9 @@ class FormSemestreInscription(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<{self.__class__.__name__} {self.id} etudid={self.etudid} sem={
|
return f"""<{self.__class__.__name__} {self.id} etudid={self.etudid} sem={
|
||||||
self.formsemestre_id} etat={self.etat} {
|
self.formsemestre_id} (S{self.formsemestre.semestre_id}) etat={self.etat} {
|
||||||
('parcours='+str(self.parcour)) if self.parcour else ''}>"""
|
('parcours="'+str(self.parcour.code)+'"') if self.parcour else ''
|
||||||
|
} {('etape="'+self.etape+'"') if self.etape else ''}>"""
|
||||||
|
|
||||||
|
|
||||||
class NotesSemSet(db.Model):
|
class NotesSemSet(db.Model):
|
||||||
|
@ -93,6 +93,10 @@ class Partition(ScoDocModel):
|
|||||||
):
|
):
|
||||||
group.remove_etud(etud)
|
group.remove_etud(etud)
|
||||||
|
|
||||||
|
def is_default(self) -> bool:
|
||||||
|
"vrai si partition par défault (tous les étudiants)"
|
||||||
|
return not self.partition_name
|
||||||
|
|
||||||
def is_parcours(self) -> bool:
|
def is_parcours(self) -> bool:
|
||||||
"Vrai s'il s'agit de la partition de parcours"
|
"Vrai s'il s'agit de la partition de parcours"
|
||||||
return self.partition_name == scu.PARTITION_PARCOURS
|
return self.partition_name == scu.PARTITION_PARCOURS
|
||||||
|
@ -6,6 +6,7 @@ from flask import abort, g
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.comp import df_cache
|
from app.comp import df_cache
|
||||||
@ -78,7 +79,9 @@ class ModuleImpl(ScoDocModel):
|
|||||||
] or self.module.get_edt_ids()
|
] or self.module.get_edt_ids()
|
||||||
|
|
||||||
def get_evaluations_poids(self) -> pd.DataFrame:
|
def get_evaluations_poids(self) -> pd.DataFrame:
|
||||||
"""Les poids des évaluations vers les UE (accès via cache)"""
|
"""Les poids des évaluations vers les UEs (accès via cache redis).
|
||||||
|
Toutes les évaluations sont considérées (normales, bonus, rattr., etc.)
|
||||||
|
"""
|
||||||
evaluations_poids = df_cache.EvaluationsPoidsCache.get(self.id)
|
evaluations_poids = df_cache.EvaluationsPoidsCache.get(self.id)
|
||||||
if evaluations_poids is None:
|
if evaluations_poids is None:
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
@ -108,20 +111,37 @@ class ModuleImpl(ScoDocModel):
|
|||||||
"""Invalide poids cachés"""
|
"""Invalide poids cachés"""
|
||||||
df_cache.EvaluationsPoidsCache.delete(self.id)
|
df_cache.EvaluationsPoidsCache.delete(self.id)
|
||||||
|
|
||||||
def check_apc_conformity(self, res: "ResultatsSemestreBUT") -> bool:
|
def check_apc_conformity(
|
||||||
"""true si les poids des évaluations du module permettent de satisfaire
|
self, res: "ResultatsSemestreBUT", evaluation_type=Evaluation.EVALUATION_NORMALE
|
||||||
les coefficients du PN.
|
) -> bool:
|
||||||
|
"""true si les poids des évaluations du type indiqué (normales par défaut)
|
||||||
|
du module permettent de satisfaire les coefficients du PN.
|
||||||
"""
|
"""
|
||||||
|
# appelé par formsemestre_status, liste notes, et moduleimpl_status
|
||||||
if not self.module.formation.get_cursus().APC_SAE or (
|
if not self.module.formation.get_cursus().APC_SAE or (
|
||||||
self.module.module_type != scu.ModuleType.RESSOURCE
|
self.module.module_type
|
||||||
and self.module.module_type != scu.ModuleType.SAE
|
not in {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
|
||||||
):
|
):
|
||||||
return True # Non BUT, toujours conforme
|
return True # Non BUT, toujours conforme
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
|
||||||
|
mod_results = res.modimpls_results.get(self.id)
|
||||||
|
if mod_results is None:
|
||||||
|
app.critical_error("check_apc_conformity: err 1")
|
||||||
|
|
||||||
|
selected_evaluations_ids = [
|
||||||
|
eval_id
|
||||||
|
for eval_id, eval_type in mod_results.evals_type.items()
|
||||||
|
if eval_type == evaluation_type
|
||||||
|
]
|
||||||
|
if not selected_evaluations_ids:
|
||||||
|
return True # conforme si pas d'évaluations
|
||||||
|
selected_evaluations_poids = self.get_evaluations_poids().loc[
|
||||||
|
selected_evaluations_ids
|
||||||
|
]
|
||||||
return moy_mod.moduleimpl_is_conforme(
|
return moy_mod.moduleimpl_is_conforme(
|
||||||
self,
|
self,
|
||||||
self.get_evaluations_poids(),
|
selected_evaluations_poids,
|
||||||
res.modimpl_coefs_df,
|
res.modimpl_coefs_df,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -233,6 +253,27 @@ class ModuleImpl(ScoDocModel):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def can_change_inscriptions(self, user: User | None = None, raise_exc=True) -> bool:
|
||||||
|
"""check si user peut inscrire/désinsincrire des étudiants à ce module.
|
||||||
|
Autorise ScoEtudInscrit ou responsables semestre.
|
||||||
|
"""
|
||||||
|
user = current_user if user is None else user
|
||||||
|
if not self.formsemestre.etat:
|
||||||
|
if raise_exc:
|
||||||
|
raise ScoLockedSemError("Modification impossible: semestre verrouille")
|
||||||
|
return False
|
||||||
|
# -- check access
|
||||||
|
# resp. module ou ou perm. EtudInscrit ou resp. semestre
|
||||||
|
if (
|
||||||
|
user.id != self.responsable_id
|
||||||
|
and not user.has_permission(Permission.EtudInscrit)
|
||||||
|
and user.id not in (u.id for u in self.formsemestre.responsables)
|
||||||
|
):
|
||||||
|
if raise_exc:
|
||||||
|
raise AccessDenied(f"Modification impossible pour {user}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def est_inscrit(self, etud: Identite) -> bool:
|
def est_inscrit(self, etud: Identite) -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
|
Vérifie si l'étudiant est bien inscrit au moduleimpl (même si DEM ou DEF au semestre).
|
||||||
|
@ -340,6 +340,21 @@ class Module(models.ScoDocModel):
|
|||||||
# Liste seulement les coefs définis:
|
# Liste seulement les coefs définis:
|
||||||
return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]
|
return [(c.ue, c.coef) for c in self.get_ue_coefs_sorted()]
|
||||||
|
|
||||||
|
def get_ue_coefs_descr(self) -> str:
|
||||||
|
"""Description des coefficients vers les UEs (APC)"""
|
||||||
|
coefs_descr = ", ".join(
|
||||||
|
[
|
||||||
|
f"{ue.acronyme}: {co}"
|
||||||
|
for ue, co in self.ue_coefs_list()
|
||||||
|
if isinstance(co, float) and co > 0
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if coefs_descr:
|
||||||
|
descr = "Coefs: " + coefs_descr
|
||||||
|
else:
|
||||||
|
descr = "(pas de coefficients) "
|
||||||
|
return descr
|
||||||
|
|
||||||
def get_codes_apogee(self) -> set[str]:
|
def get_codes_apogee(self) -> set[str]:
|
||||||
"""Les codes Apogée (codés en base comme "VRT1,VRT2")"""
|
"""Les codes Apogée (codés en base comme "VRT1,VRT2")"""
|
||||||
if self.code_apogee:
|
if self.code_apogee:
|
||||||
|
@ -46,6 +46,8 @@ class UniteEns(models.ScoDocModel):
|
|||||||
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
|
|
||||||
|
# id de l'élément Apogée du RCUE (utilisé pour les UEs de sem. pair du BUT)
|
||||||
|
code_apogee_rcue = db.Column(db.String(APO_CODE_STR_LEN))
|
||||||
# coef. pour le calcul de moyennes de RCUE. Par défaut, 1.
|
# coef. pour le calcul de moyennes de RCUE. Par défaut, 1.
|
||||||
coef_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0")
|
coef_rcue = db.Column(db.Float, nullable=False, default=1.0, server_default="1.0")
|
||||||
|
|
||||||
@ -274,6 +276,12 @@ class UniteEns(models.ScoDocModel):
|
|||||||
return {x.strip() for x in self.code_apogee.split(",") if x}
|
return {x.strip() for x in self.code_apogee.split(",") if x}
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
def get_codes_apogee_rcue(self) -> set[str]:
|
||||||
|
"""Les codes Apogée RCUE (codés en base comme "VRT1,VRT2")"""
|
||||||
|
if self.code_apogee_rcue:
|
||||||
|
return {x.strip() for x in self.code_apogee_rcue.split(",") if x}
|
||||||
|
return set()
|
||||||
|
|
||||||
def _parcours_niveaux_ids(self, parcours=list[ApcParcours]) -> set[int]:
|
def _parcours_niveaux_ids(self, parcours=list[ApcParcours]) -> set[int]:
|
||||||
"""set des ids de niveaux communs à tous les parcours listés"""
|
"""set des ids de niveaux communs à tous les parcours listés"""
|
||||||
return set.intersection(
|
return set.intersection(
|
||||||
|
@ -176,6 +176,7 @@ class GenTable:
|
|||||||
self.xml_link = xml_link
|
self.xml_link = xml_link
|
||||||
# HTML parameters:
|
# HTML parameters:
|
||||||
if not table_id: # random id
|
if not table_id: # random id
|
||||||
|
log("Warning: GenTable() called without table_id")
|
||||||
self.table_id = "gt_" + str(random.randint(0, 1000000))
|
self.table_id = "gt_" + str(random.randint(0, 1000000))
|
||||||
else:
|
else:
|
||||||
self.table_id = table_id
|
self.table_id = table_id
|
||||||
@ -312,9 +313,12 @@ class GenTable:
|
|||||||
T.append(l + [self.bottom_titles.get(cid, "") for cid in self.columns_ids])
|
T.append(l + [self.bottom_titles.get(cid, "") for cid in self.columns_ids])
|
||||||
return T
|
return T
|
||||||
|
|
||||||
def get_titles_list(self):
|
def get_titles_list(self, with_lines_titles=True):
|
||||||
"list of titles"
|
"list of titles"
|
||||||
return [self.titles.get(cid, "") for cid in self.columns_ids]
|
titles = [self.titles.get(cid, "") for cid in self.columns_ids]
|
||||||
|
if with_lines_titles:
|
||||||
|
titles.insert(0, "")
|
||||||
|
return titles
|
||||||
|
|
||||||
def gen(self, fmt="html", columns_ids=None):
|
def gen(self, fmt="html", columns_ids=None):
|
||||||
"""Build representation of the table in the specified format.
|
"""Build representation of the table in the specified format.
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""HTML Header/Footer for ScoDoc pages
|
"""HTML Header/Footer for ScoDoc pages"""
|
||||||
"""
|
|
||||||
|
|
||||||
import html
|
import html
|
||||||
|
|
||||||
@ -101,7 +100,7 @@ _HTML_BEGIN = f"""<!DOCTYPE html>
|
|||||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
window.onload=function(){{enableTooltips("gtrcontent"); enableTooltips("sidebar");}};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
<script src="{scu.STATIC_DIR}/jQuery/jquery.js"></script>
|
||||||
@ -218,7 +217,7 @@ def sco_header(
|
|||||||
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
<script src="{scu.STATIC_DIR}/libjs/menu.js"></script>
|
||||||
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
<script src="{scu.STATIC_DIR}/libjs/bubble.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.onload=function(){{enableTooltips("gtrcontent")}};
|
window.onload=function(){{enableTooltips("gtrcontent"); enableTooltips("sidebar");}};
|
||||||
|
|
||||||
const SCO_URL="{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}";
|
const SCO_URL="{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}";
|
||||||
const SCO_TIMEZONE="{scu.TIME_ZONE}";
|
const SCO_TIMEZONE="{scu.TIME_ZONE}";
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"""
|
"""
|
||||||
Génération de la "sidebar" (marge gauche des pages HTML)
|
Génération de la "sidebar" (marge gauche des pages HTML)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import render_template, url_for
|
from flask import render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -151,7 +152,7 @@ def sidebar(etudid: int = None):
|
|||||||
H = [
|
H = [
|
||||||
f"""
|
f"""
|
||||||
<!-- sidebar py -->
|
<!-- sidebar py -->
|
||||||
<div class="sidebar">
|
<div class="sidebar" id="sidebar">
|
||||||
{ sidebar_common() }
|
{ sidebar_common() }
|
||||||
<div class="box-chercheetud">Chercher étudiant:<br>
|
<div class="box-chercheetud">Chercher étudiant:<br>
|
||||||
<form method="get" id="form-chercheetud"
|
<form method="get" id="form-chercheetud"
|
||||||
@ -193,7 +194,7 @@ def sidebar(etudid: int = None):
|
|||||||
formsemestre.date_debut.strftime(scu.DATE_FMT)
|
formsemestre.date_debut.strftime(scu.DATE_FMT)
|
||||||
} au {
|
} au {
|
||||||
formsemestre.date_fin.strftime(scu.DATE_FMT)
|
formsemestre.date_fin.strftime(scu.DATE_FMT)
|
||||||
}">({
|
}" data-tooltip>({
|
||||||
sco_preferences.get_preference("assi_metrique", None)})
|
sco_preferences.get_preference("assi_metrique", None)})
|
||||||
<br>{nbabsjust:1g} J., {nbabsnj:1g} N.J.</span>"""
|
<br>{nbabsjust:1g} J., {nbabsnj:1g} N.J.</span>"""
|
||||||
)
|
)
|
||||||
@ -227,12 +228,9 @@ def sidebar(etudid: int = None):
|
|||||||
<li><a href="{ url_for('assiduites.calendrier_assi_etud',
|
<li><a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">Calendrier</a></li>
|
}">Calendrier</a></li>
|
||||||
<li><a href="{ url_for('assiduites.liste_assiduites_etud',
|
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
|
||||||
}">Liste</a></li>
|
|
||||||
<li><a href="{ url_for('assiduites.bilan_etud',
|
<li><a href="{ url_for('assiduites.bilan_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
}">Bilan</a></li>
|
}" title="Les pages bilan et liste ont été fusionnées">Liste/Bilan</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -157,5 +157,6 @@ def table_billets(
|
|||||||
rows=rows,
|
rows=rows,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
|
table_id="table_billets",
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
@ -288,6 +288,7 @@ def apo_table_compare_etud_results(A, B):
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="apo_table_compare_etud_results",
|
||||||
)
|
)
|
||||||
return T
|
return T
|
||||||
|
|
||||||
|
@ -43,14 +43,13 @@ import re
|
|||||||
import time
|
import time
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from flask import send_file
|
from flask import g, send_file
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
|
||||||
from app.models import (
|
from app.models import (
|
||||||
ApcValidationAnnee,
|
ApcValidationAnnee,
|
||||||
ApcValidationRCUE,
|
ApcValidationRCUE,
|
||||||
@ -79,7 +78,6 @@ from app.scodoc.codes_cursus import (
|
|||||||
)
|
)
|
||||||
from app.scodoc import sco_cursus
|
from app.scodoc import sco_cursus
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_etud
|
|
||||||
|
|
||||||
|
|
||||||
def _apo_fmt_note(note, fmt="%3.2f"):
|
def _apo_fmt_note(note, fmt="%3.2f"):
|
||||||
@ -99,7 +97,7 @@ class EtuCol:
|
|||||||
"""Valeurs colonnes d'un element pour un etudiant"""
|
"""Valeurs colonnes d'un element pour un etudiant"""
|
||||||
|
|
||||||
def __init__(self, nip, apo_elt, init_vals):
|
def __init__(self, nip, apo_elt, init_vals):
|
||||||
pass # XXX
|
pass
|
||||||
|
|
||||||
|
|
||||||
ETUD_OK = "ok"
|
ETUD_OK = "ok"
|
||||||
@ -132,7 +130,7 @@ class ApoEtud(dict):
|
|||||||
"Vrai si BUT"
|
"Vrai si BUT"
|
||||||
self.col_elts = {}
|
self.col_elts = {}
|
||||||
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
|
"{'V1RT': {'R': 'ADM', 'J': '', 'B': 20, 'N': '12.14'}}"
|
||||||
self.etud: Identite = None
|
self.etud: Identite | None = None
|
||||||
"etudiant ScoDoc associé"
|
"etudiant ScoDoc associé"
|
||||||
self.etat = None # ETUD_OK, ...
|
self.etat = None # ETUD_OK, ...
|
||||||
self.is_nar = False
|
self.is_nar = False
|
||||||
@ -150,9 +148,9 @@ class ApoEtud(dict):
|
|||||||
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
|
_apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
|
||||||
)
|
)
|
||||||
# Initialisés par associate_sco:
|
# Initialisés par associate_sco:
|
||||||
self.autre_sem: dict = None
|
self.autre_formsemestre: FormSemestre = None
|
||||||
self.autre_res: NotesTableCompat = None
|
self.autre_res: NotesTableCompat = None
|
||||||
self.cur_sem: dict = None
|
self.cur_formsemestre: FormSemestre = None
|
||||||
self.cur_res: NotesTableCompat = None
|
self.cur_res: NotesTableCompat = None
|
||||||
self.new_cols = {}
|
self.new_cols = {}
|
||||||
"{ col_id : value to record in csv }"
|
"{ col_id : value to record in csv }"
|
||||||
@ -171,24 +169,18 @@ class ApoEtud(dict):
|
|||||||
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
|
Sinon, cherche le semestre, et met l'état à ETUD_OK ou ETUD_NON_INSCRIT.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# futur: #WIP
|
self.etud = Identite.query.filter_by(
|
||||||
# etud: Identite = Identite.query.filter_by(code_nip=self["nip"], dept_id=g.scodoc_dept_id).first()
|
code_nip=self["nip"], dept_id=g.scodoc_dept_id
|
||||||
# self.etud = etud
|
).first()
|
||||||
etuds = sco_etud.get_etud_info(code_nip=self["nip"], filled=True)
|
if not self.etud:
|
||||||
if not etuds:
|
|
||||||
# pas dans ScoDoc
|
# pas dans ScoDoc
|
||||||
self.etud = None
|
|
||||||
self.log.append("non inscrit dans ScoDoc")
|
self.log.append("non inscrit dans ScoDoc")
|
||||||
self.etat = ETUD_ORPHELIN
|
self.etat = ETUD_ORPHELIN
|
||||||
else:
|
else:
|
||||||
# futur: #WIP
|
|
||||||
# formsemestre_ids = {
|
|
||||||
# ins.formsemestre_id for ins in etud.formsemestre_inscriptions
|
|
||||||
# }
|
|
||||||
# in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
|
|
||||||
self.etud = etuds[0]
|
|
||||||
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
|
# cherche le semestre ScoDoc correspondant à l'un de ceux de l'etape:
|
||||||
formsemestre_ids = {s["formsemestre_id"] for s in self.etud["sems"]}
|
formsemestre_ids = {
|
||||||
|
ins.formsemestre_id for ins in self.etud.formsemestre_inscriptions
|
||||||
|
}
|
||||||
in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
|
in_formsemestre_ids = formsemestre_ids.intersection(etape_formsemestre_ids)
|
||||||
if not in_formsemestre_ids:
|
if not in_formsemestre_ids:
|
||||||
self.log.append(
|
self.log.append(
|
||||||
@ -228,7 +220,8 @@ class ApoEtud(dict):
|
|||||||
self.new_cols[col_id] = self.cols[col_id]
|
self.new_cols[col_id] = self.cols[col_id]
|
||||||
except KeyError as exc:
|
except KeyError as exc:
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{col_id}</tt> non déclarée ?"""
|
f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{
|
||||||
|
col_id}</tt> non déclarée ?"""
|
||||||
) from exc
|
) from exc
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -254,7 +247,7 @@ class ApoEtud(dict):
|
|||||||
# codes = set([apo_data.apo_csv.cols[col_id].code for col_id in apo_data.apo_csv.col_ids])
|
# codes = set([apo_data.apo_csv.cols[col_id].code for col_id in apo_data.apo_csv.col_ids])
|
||||||
# return codes - set(sco_elts)
|
# return codes - set(sco_elts)
|
||||||
|
|
||||||
def search_elt_in_sem(self, code, sem) -> dict:
|
def search_elt_in_sem(self, code: str, sem: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
VET code jury etape (en BUT, le code annuel)
|
VET code jury etape (en BUT, le code annuel)
|
||||||
ELP élément pédagogique: UE, module
|
ELP élément pédagogique: UE, module
|
||||||
@ -267,13 +260,17 @@ class ApoEtud(dict):
|
|||||||
Args:
|
Args:
|
||||||
code (str): code apo de l'element cherché
|
code (str): code apo de l'element cherché
|
||||||
sem (dict): semestre dans lequel on cherche l'élément
|
sem (dict): semestre dans lequel on cherche l'élément
|
||||||
cur_sem (dict): semestre "courant" pour résultats annuels (VET)
|
|
||||||
autre_sem (dict): autre semestre utilisé pour calculer les résultats annuels (VET)
|
Utilise notamment:
|
||||||
|
cur_formsemestre : semestre "courant" pour résultats annuels (VET)
|
||||||
|
autre_formsemestre : autre formsemestre utilisé pour les résultats annuels (VET)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: with N, B, J, R keys, ou None si elt non trouvé
|
dict: with N, B, J, R keys, ou None si elt non trouvé
|
||||||
"""
|
"""
|
||||||
etudid = self.etud["etudid"]
|
if not self.etud:
|
||||||
|
return None
|
||||||
|
etudid = self.etud.id
|
||||||
if not self.cur_res:
|
if not self.cur_res:
|
||||||
log("search_elt_in_sem: no cur_res !")
|
log("search_elt_in_sem: no cur_res !")
|
||||||
return None
|
return None
|
||||||
@ -316,10 +313,10 @@ class ApoEtud(dict):
|
|||||||
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
|
code in {x.strip() for x in sem["elt_annee_apo"].split(",")}
|
||||||
):
|
):
|
||||||
export_res_etape = self.export_res_etape
|
export_res_etape = self.export_res_etape
|
||||||
if (not export_res_etape) and self.cur_sem:
|
if (not export_res_etape) and self.cur_formsemestre:
|
||||||
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
|
# exporte toujours le résultat de l'étape si l'étudiant est diplômé
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
Se = sco_cursus.get_situation_etud_cursus(
|
||||||
self.etud, self.cur_sem["formsemestre_id"]
|
self.etud, self.cur_formsemestre.id
|
||||||
)
|
)
|
||||||
export_res_etape = Se.all_other_validated()
|
export_res_etape = Se.all_other_validated()
|
||||||
|
|
||||||
@ -377,6 +374,20 @@ class ApoEtud(dict):
|
|||||||
|
|
||||||
if module_code_found:
|
if module_code_found:
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
|
# RCUE du BUT (validations enregistrées seulement, pas avant jury)
|
||||||
|
if res.is_apc:
|
||||||
|
for val_rcue in ApcValidationRCUE.query.filter_by(
|
||||||
|
etudid=etudid, formsemestre_id=sem["formsemestre_id"]
|
||||||
|
):
|
||||||
|
if code in val_rcue.get_codes_apogee():
|
||||||
|
return dict(
|
||||||
|
N="", # n'exporte pas de moyenne RCUE
|
||||||
|
B=20,
|
||||||
|
J="",
|
||||||
|
R=ScoDocSiteConfig.get_code_apo(val_rcue.code),
|
||||||
|
M="",
|
||||||
|
)
|
||||||
#
|
#
|
||||||
return None # element Apogee non trouvé dans ce semestre
|
return None # element Apogee non trouvé dans ce semestre
|
||||||
|
|
||||||
@ -418,11 +429,10 @@ class ApoEtud(dict):
|
|||||||
#
|
#
|
||||||
# XXX cette règle est discutable, à valider
|
# XXX cette règle est discutable, à valider
|
||||||
|
|
||||||
# log('comp_elt_annuel cur_sem=%s autre_sem=%s' % (cur_sem['formsemestre_id'], autre_sem['formsemestre_id']))
|
if not self.cur_formsemestre:
|
||||||
if not self.cur_sem:
|
|
||||||
# l'étudiant n'a pas de semestre courant ?!
|
# l'étudiant n'a pas de semestre courant ?!
|
||||||
self.log.append("pas de semestre courant")
|
self.log.append("pas de semestre courant")
|
||||||
log(f"comp_elt_annuel: etudid {etudid} has no cur_sem")
|
log(f"comp_elt_annuel: etudid {etudid} has no cur_formsemestre")
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
if self.is_apc:
|
if self.is_apc:
|
||||||
@ -438,7 +448,7 @@ class ApoEtud(dict):
|
|||||||
# ne touche pas aux RATs
|
# ne touche pas aux RATs
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
if not self.autre_sem:
|
if not self.autre_formsemestre:
|
||||||
# formations monosemestre, ou code VET semestriel,
|
# formations monosemestre, ou code VET semestriel,
|
||||||
# ou jury intermediaire et etudiant non redoublant...
|
# ou jury intermediaire et etudiant non redoublant...
|
||||||
return self.comp_elt_semestre(self.cur_res, cur_decision, etudid)
|
return self.comp_elt_semestre(self.cur_res, cur_decision, etudid)
|
||||||
@ -518,7 +528,7 @@ class ApoEtud(dict):
|
|||||||
self.validation_annee_but: ApcValidationAnnee = (
|
self.validation_annee_but: ApcValidationAnnee = (
|
||||||
ApcValidationAnnee.query.filter_by(
|
ApcValidationAnnee.query.filter_by(
|
||||||
formsemestre_id=formsemestre.id,
|
formsemestre_id=formsemestre.id,
|
||||||
etudid=self.etud["etudid"],
|
etudid=self.etud.id,
|
||||||
referentiel_competence_id=self.cur_res.formsemestre.formation.referentiel_competence_id,
|
referentiel_competence_id=self.cur_res.formsemestre.formation.referentiel_competence_id,
|
||||||
).first()
|
).first()
|
||||||
)
|
)
|
||||||
@ -527,7 +537,7 @@ class ApoEtud(dict):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def etud_set_semestres_de_etape(self, apo_data: "ApoData"):
|
def etud_set_semestres_de_etape(self, apo_data: "ApoData"):
|
||||||
"""Set .cur_sem and .autre_sem et charge les résultats.
|
"""Set .cur_formsemestre and .autre_formsemestre et charge les résultats.
|
||||||
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
|
Lorsqu'on a une formation semestrialisée mais avec un code étape annuel,
|
||||||
il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer
|
il faut considérer les deux semestres ((S1,S2) ou (S3,S4)) pour calculer
|
||||||
le code annuel (VET ou VRT1A (voir elt_annee_apo)).
|
le code annuel (VET ou VRT1A (voir elt_annee_apo)).
|
||||||
@ -535,52 +545,48 @@ class ApoEtud(dict):
|
|||||||
Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même
|
Pour les jurys intermediaires (janvier, S1 ou S3): (S2 ou S4) de la même
|
||||||
étape lors d'une année précédente ?
|
étape lors d'une année précédente ?
|
||||||
|
|
||||||
Set cur_sem: le semestre "courant" et autre_sem, ou None s'il n'y en a pas.
|
Set cur_formsemestre: le formsemestre "courant"
|
||||||
|
et autre_formsemestre, ou None s'il n'y en a pas.
|
||||||
"""
|
"""
|
||||||
# Cherche le semestre "courant":
|
# Cherche le formsemestre "courant":
|
||||||
cur_sems = [
|
cur_formsemestres = [
|
||||||
sem
|
formsemestre
|
||||||
for sem in self.etud["sems"]
|
for formsemestre in self.etud.get_formsemestres()
|
||||||
if (
|
if (
|
||||||
(sem["semestre_id"] == apo_data.cur_semestre_id)
|
(formsemestre.semestre_id == apo_data.cur_semestre_id)
|
||||||
and (apo_data.etape in sem["etapes"])
|
and (apo_data.etape in formsemestre.etapes)
|
||||||
and (
|
and (
|
||||||
sco_formsemestre.sem_in_semestre_scolaire(
|
FormSemestre.est_in_semestre_scolaire(
|
||||||
sem,
|
formsemestre.date_debut,
|
||||||
apo_data.annee_scolaire,
|
apo_data.annee_scolaire,
|
||||||
0, # annee complete
|
0, # annee complete
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
if not cur_sems:
|
cur_formsemestre = None
|
||||||
cur_sem = None
|
if cur_formsemestres:
|
||||||
else:
|
# prend le plus récent avec décision
|
||||||
# prend le plus recent avec decision
|
for formsemestre in cur_formsemestres:
|
||||||
cur_sem = None
|
|
||||||
for sem in cur_sems:
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
has_decision = res.etud_has_decision(self.etud["etudid"])
|
if apo_data.export_res_sdj or res.etud_has_decision(self.etud.id):
|
||||||
if has_decision:
|
cur_formsemestre = formsemestre
|
||||||
cur_sem = sem
|
|
||||||
self.cur_res = res
|
self.cur_res = res
|
||||||
break
|
break
|
||||||
if cur_sem is None:
|
if cur_formsemestres is None:
|
||||||
cur_sem = cur_sems[0] # aucun avec décision, prend le plus recent
|
cur_formsemestre = cur_formsemestres[
|
||||||
if res.formsemestre.id == cur_sem["formsemestre_id"]:
|
0
|
||||||
|
] # aucun avec décision, prend le plus recent
|
||||||
|
if res.formsemestre.id == cur_formsemestre.id:
|
||||||
self.cur_res = res
|
self.cur_res = res
|
||||||
else:
|
else:
|
||||||
formsemestre = FormSemestre.query.get_or_404(
|
self.cur_res = res_sem.load_formsemestre_results(cur_formsemestre)
|
||||||
cur_sem["formsemestre_id"]
|
|
||||||
)
|
|
||||||
self.cur_res = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
|
|
||||||
self.cur_sem = cur_sem
|
self.cur_formsemestre = cur_formsemestre
|
||||||
|
|
||||||
if apo_data.cur_semestre_id <= 0:
|
if apo_data.cur_semestre_id <= 0:
|
||||||
# "autre_sem" non pertinent pour sessions sans semestres:
|
# autre_formsemestre non pertinent pour sessions sans semestres:
|
||||||
self.autre_sem = None
|
self.autre_formsemestre = None
|
||||||
self.autre_res = None
|
self.autre_res = None
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -601,52 +607,49 @@ class ApoEtud(dict):
|
|||||||
courant_mois_debut = 1 # ou 2 (fev-jul)
|
courant_mois_debut = 1 # ou 2 (fev-jul)
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid periode value !") # bug ?
|
raise ValueError("invalid periode value !") # bug ?
|
||||||
courant_date_debut = "%d-%02d-01" % (
|
courant_date_debut = datetime.date(
|
||||||
courant_annee_debut,
|
day=1, month=courant_mois_debut, year=courant_annee_debut
|
||||||
courant_mois_debut,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
courant_date_debut = "9999-99-99"
|
courant_date_debut = datetime.date(day=31, month=12, year=9999)
|
||||||
|
|
||||||
# etud['sems'] est la liste des semestres de l'étudiant, triés par date,
|
|
||||||
# le plus récemment effectué en tête.
|
|
||||||
# Cherche les semestres (antérieurs) de l'indice autre de la même étape apogée
|
# Cherche les semestres (antérieurs) de l'indice autre de la même étape apogée
|
||||||
# s'il y en a plusieurs, choisit le plus récent ayant une décision
|
# s'il y en a plusieurs, choisit le plus récent ayant une décision
|
||||||
|
|
||||||
autres_sems = []
|
autres_sems = []
|
||||||
for sem in self.etud["sems"]:
|
for formsemestre in self.etud.get_formsemestres():
|
||||||
if (
|
if (
|
||||||
sem["semestre_id"] == autre_semestre_id
|
formsemestre.semestre_id == autre_semestre_id
|
||||||
and apo_data.etape_apogee in sem["etapes"]
|
and apo_data.etape_apogee in formsemestre.etapes
|
||||||
):
|
):
|
||||||
if (
|
if (
|
||||||
sem["date_debut_iso"] < courant_date_debut
|
formsemestre.date_debut < courant_date_debut
|
||||||
): # on demande juste qu'il ait démarré avant
|
): # on demande juste qu'il ait démarré avant
|
||||||
autres_sems.append(sem)
|
autres_sems.append(formsemestre)
|
||||||
if not autres_sems:
|
if not autres_sems:
|
||||||
autre_sem = None
|
autre_formsemestre = None
|
||||||
elif len(autres_sems) == 1:
|
elif len(autres_sems) == 1:
|
||||||
autre_sem = autres_sems[0]
|
autre_formsemestre = autres_sems[0]
|
||||||
else:
|
else:
|
||||||
autre_sem = None
|
autre_formsemestre = None
|
||||||
for sem in autres_sems:
|
for formsemestre in autres_sems:
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
if res.is_apc:
|
if res.is_apc:
|
||||||
has_decision = res.etud_has_decision(self.etud["etudid"])
|
has_decision = res.etud_has_decision(self.etud.id)
|
||||||
else:
|
else:
|
||||||
has_decision = res.get_etud_decision_sem(self.etud["etudid"])
|
has_decision = res.get_etud_decision_sem(self.etud.id)
|
||||||
if has_decision:
|
if has_decision or apo_data.export_res_sdj:
|
||||||
autre_sem = sem
|
autre_formsemestre = formsemestre
|
||||||
break
|
break
|
||||||
if autre_sem is None:
|
if autre_formsemestre is None:
|
||||||
autre_sem = autres_sems[0] # aucun avec decision, prend le plus recent
|
autre_formsemestre = autres_sems[
|
||||||
|
0
|
||||||
|
] # aucun avec decision, prend le plus recent
|
||||||
|
|
||||||
self.autre_sem = autre_sem
|
self.autre_formsemestre = autre_formsemestre
|
||||||
# Charge les résultats:
|
# Charge les résultats:
|
||||||
if autre_sem:
|
if autre_formsemestre:
|
||||||
formsemestre = FormSemestre.query.get_or_404(autre_sem["formsemestre_id"])
|
self.autre_res = res_sem.load_formsemestre_results(self.autre_formsemestre)
|
||||||
self.autre_res = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
else:
|
else:
|
||||||
self.autre_res = None
|
self.autre_res = None
|
||||||
|
|
||||||
@ -873,6 +876,16 @@ class ApoData:
|
|||||||
codes_ues = set().union(
|
codes_ues = set().union(
|
||||||
*[ue.get_codes_apogee() for ue in formsemestre.get_ues(with_sport=True)]
|
*[ue.get_codes_apogee() for ue in formsemestre.get_ues(with_sport=True)]
|
||||||
)
|
)
|
||||||
|
codes_rcues = (
|
||||||
|
set().union(
|
||||||
|
*[
|
||||||
|
ue.get_codes_apogee_rcue()
|
||||||
|
for ue in formsemestre.get_ues(with_sport=True)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.is_apc
|
||||||
|
else set()
|
||||||
|
)
|
||||||
s = set()
|
s = set()
|
||||||
codes_by_sem[sem["formsemestre_id"]] = s
|
codes_by_sem[sem["formsemestre_id"]] = s
|
||||||
for col_id in self.apo_csv.col_ids[4:]:
|
for col_id in self.apo_csv.col_ids[4:]:
|
||||||
@ -885,13 +898,18 @@ class ApoData:
|
|||||||
if code in codes_ues:
|
if code in codes_ues:
|
||||||
s.add(code)
|
s.add(code)
|
||||||
continue
|
continue
|
||||||
|
# associé à un RCUE BUT
|
||||||
|
if code in codes_rcues:
|
||||||
|
s.add(code)
|
||||||
|
continue
|
||||||
# associé à un module:
|
# associé à un module:
|
||||||
if code in codes_modules:
|
if code in codes_modules:
|
||||||
s.add(code)
|
s.add(code)
|
||||||
|
|
||||||
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
|
# log('codes_by_sem=%s' % pprint.pformat(codes_by_sem))
|
||||||
return codes_by_sem
|
return codes_by_sem
|
||||||
|
|
||||||
def build_cr_table(self):
|
def build_cr_table(self) -> GenTable:
|
||||||
"""Table compte rendu des décisions"""
|
"""Table compte rendu des décisions"""
|
||||||
rows = [] # tableau compte rendu des decisions
|
rows = [] # tableau compte rendu des decisions
|
||||||
for apo_etud in self.etuds:
|
for apo_etud in self.etuds:
|
||||||
@ -913,13 +931,14 @@ class ApoData:
|
|||||||
columns_ids = ["NIP", "nom", "prenom"]
|
columns_ids = ["NIP", "nom", "prenom"]
|
||||||
columns_ids.extend(("etape", "etape_note", "est_NAR", "commentaire"))
|
columns_ids.extend(("etape", "etape_note", "est_NAR", "commentaire"))
|
||||||
|
|
||||||
T = GenTable(
|
table = GenTable(
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
titles=dict(zip(columns_ids, columns_ids)),
|
titles=dict(zip(columns_ids, columns_ids)),
|
||||||
rows=rows,
|
rows=rows,
|
||||||
|
table_id="build_cr_table",
|
||||||
xls_sheet_name="Decisions ScoDoc",
|
xls_sheet_name="Decisions ScoDoc",
|
||||||
)
|
)
|
||||||
return T
|
return table
|
||||||
|
|
||||||
def build_adsup_table(self):
|
def build_adsup_table(self):
|
||||||
"""Construit une table listant les ADSUP émis depuis les formsemestres
|
"""Construit une table listant les ADSUP émis depuis les formsemestres
|
||||||
@ -969,6 +988,7 @@ class ApoData:
|
|||||||
"rcue": "RCUE",
|
"rcue": "RCUE",
|
||||||
},
|
},
|
||||||
rows=rows,
|
rows=rows,
|
||||||
|
table_id="adsup_table",
|
||||||
xls_sheet_name="ADSUPs",
|
xls_sheet_name="ADSUPs",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1054,6 +1074,7 @@ def nar_etuds_table(apo_data, nar_etuds):
|
|||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
titles=dict(zip(columns_ids, columns_ids)),
|
titles=dict(zip(columns_ids, columns_ids)),
|
||||||
rows=rows,
|
rows=rows,
|
||||||
|
table_id="nar_etuds_table",
|
||||||
xls_sheet_name="NAR ScoDoc",
|
xls_sheet_name="NAR ScoDoc",
|
||||||
)
|
)
|
||||||
return table.excel()
|
return table.excel()
|
||||||
|
@ -139,7 +139,7 @@ class BaseArchiver:
|
|||||||
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: int, dept_id: int = None):
|
def list_obj_archives(self, oid: int, dept_id: int = None) -> list[str]:
|
||||||
"""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)
|
||||||
"""
|
"""
|
||||||
|
@ -751,7 +751,7 @@ def formsemestre_get_assiduites_count(
|
|||||||
) -> tuple[int, int, int]:
|
) -> tuple[int, int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||||
Utilise un cache.
|
Utilise un cache (si moduleimpl_id n'est pas spécifié).
|
||||||
"""
|
"""
|
||||||
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||||
return get_assiduites_count_in_interval(
|
return get_assiduites_count_in_interval(
|
||||||
@ -779,7 +779,7 @@ def get_assiduites_count_in_interval(
|
|||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||||
On peut spécifier les dates comme datetime ou iso.
|
On peut spécifier les dates comme datetime ou iso.
|
||||||
Utilise un cache.
|
Utilise un cache (si moduleimpl_id n'est pas spécifié).
|
||||||
"""
|
"""
|
||||||
date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d")
|
date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d")
|
||||||
date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d")
|
date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d")
|
||||||
|
102
app/scodoc/sco_bug_report.py
Normal file
102
app/scodoc/sco_bug_report.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Rapport de bug ScoDoc
|
||||||
|
|
||||||
|
Permet de créer un rapport de bug (ticket) sur la plateforme git scodoc.org.
|
||||||
|
|
||||||
|
Le principe est le suivant:
|
||||||
|
1- Si l'utilisateur le demande, on dump la base de données et on l'envoie
|
||||||
|
|
||||||
|
2- ScoDoc envoie une requête POST à scodoc.org pour qu'un ticket git soit créé avec les
|
||||||
|
informations fournies par l'utilisateur + quelques métadonnées.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from flask import g
|
||||||
|
from flask_login import current_user
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
import sco_version
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
from app.scodoc.sco_dump_db import sco_dump_and_send_db
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
|
def sco_bug_report(
|
||||||
|
title: str = "", message: str = "", etab: str = "", include_dump: bool = False
|
||||||
|
) -> requests.Response:
|
||||||
|
"""Envoi d'un bug report (ticket)"""
|
||||||
|
dump_id = None
|
||||||
|
|
||||||
|
if include_dump:
|
||||||
|
dump = sco_dump_and_send_db()
|
||||||
|
|
||||||
|
try:
|
||||||
|
dump_id = dump.json()["dump_id"]
|
||||||
|
except (requests.exceptions.JSONDecodeError, KeyError):
|
||||||
|
dump_id = "inconnu (erreur)"
|
||||||
|
|
||||||
|
log(f"sco_bug_report: {scu.SCO_BUG_REPORT_URL} by {current_user.user_name}")
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
scu.SCO_BUG_REPORT_URL,
|
||||||
|
json={
|
||||||
|
"ticket": {
|
||||||
|
"title": title,
|
||||||
|
"message": message,
|
||||||
|
"etab": etab,
|
||||||
|
"dept": getattr(g, "scodoc_dept", "-"),
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": current_user.get_nomcomplet(),
|
||||||
|
"email": current_user.email,
|
||||||
|
},
|
||||||
|
"dump": {
|
||||||
|
"included": include_dump,
|
||||||
|
"id": dump_id,
|
||||||
|
},
|
||||||
|
"scodoc": {
|
||||||
|
"version": sco_version.SCOVERSION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeout=scu.SCO_ORG_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as exc:
|
||||||
|
log("ConnectionError: Impossible de joindre le serveur d'assistance")
|
||||||
|
raise ScoValueError(
|
||||||
|
"""
|
||||||
|
Impossible de joindre le serveur d'assistance (scodoc.org).
|
||||||
|
Veuillez contacter le service informatique de votre établissement pour
|
||||||
|
corriger la configuration de ScoDoc. Dans la plupart des cas, il
|
||||||
|
s'agit d'un proxy mal configuré.
|
||||||
|
"""
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
return r
|
@ -226,6 +226,7 @@ class BulletinGenerator:
|
|||||||
server_name=self.server_name,
|
server_name=self.server_name,
|
||||||
filigranne=self.filigranne,
|
filigranne=self.filigranne,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
with_page_numbers=self.multi_pages,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
@ -106,6 +106,7 @@ def assemble_bulletins_pdf(
|
|||||||
pagesbookmarks=pagesbookmarks,
|
pagesbookmarks=pagesbookmarks,
|
||||||
filigranne=filigranne,
|
filigranne=filigranne,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
with_page_numbers=False, # on ne veut pas de no de pages sur les bulletins imprimés en masse
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
document.multiBuild(story)
|
document.multiBuild(story)
|
||||||
@ -122,7 +123,8 @@ def replacement_function(match) -> str:
|
|||||||
return r'<img %s src="%s"%s/>' % (match.group(2), logo.filepath, match.group(4))
|
return r'<img %s src="%s"%s/>' % (match.group(2), logo.filepath, match.group(4))
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
'balise "%s": logo "%s" introuvable'
|
'balise "%s": logo "%s" introuvable'
|
||||||
% (pydoc.html.escape(balise), pydoc.html.escape(name))
|
% (pydoc.html.escape(balise), pydoc.html.escape(name)),
|
||||||
|
safe=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,6 +114,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
html_class="notes_bulletin",
|
html_class="notes_bulletin",
|
||||||
html_class_ignore_default=True,
|
html_class_ignore_default=True,
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
|
table_id="std_bul_table",
|
||||||
)
|
)
|
||||||
|
|
||||||
return T.gen(fmt=fmt)
|
return T.gen(fmt=fmt)
|
||||||
|
@ -274,6 +274,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
"""expire cache pour un semestre (ou tous ceux du département si formsemestre_id non spécifié).
|
"""expire cache pour un semestre (ou tous ceux du département si formsemestre_id non spécifié).
|
||||||
Si pdfonly, n'expire que les bulletins pdf cachés.
|
Si pdfonly, n'expire que les bulletins pdf cachés.
|
||||||
"""
|
"""
|
||||||
|
from app.comp import df_cache
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_cursus
|
from app.scodoc import sco_cursus
|
||||||
|
|
||||||
@ -315,12 +316,14 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
and fid in g.formsemestre_results_cache
|
and fid in g.formsemestre_results_cache
|
||||||
):
|
):
|
||||||
del g.formsemestre_results_cache[fid]
|
del g.formsemestre_results_cache[fid]
|
||||||
|
df_cache.EvaluationsPoidsCache.invalidate_sem(formsemestre_id)
|
||||||
else:
|
else:
|
||||||
# optimization when we invalidate all evaluations:
|
# optimization when we invalidate all evaluations:
|
||||||
EvaluationCache.invalidate_all_sems()
|
EvaluationCache.invalidate_all_sems()
|
||||||
|
df_cache.EvaluationsPoidsCache.invalidate_all()
|
||||||
if hasattr(g, "formsemestre_results_cache"):
|
if hasattr(g, "formsemestre_results_cache"):
|
||||||
del g.formsemestre_results_cache
|
del g.formsemestre_results_cache
|
||||||
|
|
||||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||||
|
@ -141,6 +141,7 @@ def formsemestre_table_estim_cost(
|
|||||||
""",
|
""",
|
||||||
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
||||||
filename=f"EstimCout-S{formsemestre.semestre_id}",
|
filename=f"EstimCout-S{formsemestre.semestre_id}",
|
||||||
|
table_id="formsemestre_table_estim_cost",
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ from app.scodoc import sco_cursus_dut
|
|||||||
|
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre, Identite
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
|
||||||
|
|
||||||
# SituationEtudParcours -> get_situation_etud_cursus
|
# SituationEtudParcours -> get_situation_etud_cursus
|
||||||
def get_situation_etud_cursus(
|
def get_situation_etud_cursus(
|
||||||
etud: dict, formsemestre_id: int
|
etud: Identite, formsemestre_id: int
|
||||||
) -> sco_cursus_dut.SituationEtudCursus:
|
) -> sco_cursus_dut.SituationEtudCursus:
|
||||||
"""renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)"""
|
"""renvoie une instance de SituationEtudCursus (ou sous-classe spécialisée)"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
from app import db
|
from app import db
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import FormSemestre, UniteEns, ScolarAutorisationInscription
|
from app.models import FormSemestre, Identite, ScolarAutorisationInscription, UniteEns
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -115,14 +115,22 @@ class SituationEtudCursus:
|
|||||||
class SituationEtudCursusClassic(SituationEtudCursus):
|
class SituationEtudCursusClassic(SituationEtudCursus):
|
||||||
"Semestre dans un parcours"
|
"Semestre dans un parcours"
|
||||||
|
|
||||||
def __init__(self, etud: dict, formsemestre_id: int, nt: NotesTableCompat):
|
def __init__(self, etud: Identite, formsemestre_id: int, nt: NotesTableCompat):
|
||||||
"""
|
"""
|
||||||
etud: dict filled by fill_etuds_info()
|
etud: dict filled by fill_etuds_info()
|
||||||
"""
|
"""
|
||||||
|
assert formsemestre_id == nt.formsemestre.id
|
||||||
self.etud = etud
|
self.etud = etud
|
||||||
self.etudid = etud["etudid"]
|
self.etudid = etud.id
|
||||||
self.formsemestre_id = formsemestre_id
|
self.formsemestre_id = formsemestre_id
|
||||||
self.sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
self.formsemestres: list[FormSemestre] = []
|
||||||
|
"les semestres parcourus, le plus ancien en tête"
|
||||||
|
self.sem = sco_formsemestre.get_formsemestre(
|
||||||
|
formsemestre_id
|
||||||
|
) # TODO utiliser formsemestres
|
||||||
|
self.cur_sem: FormSemestre = nt.formsemestre
|
||||||
|
self.can_compensate: set[int] = set()
|
||||||
|
"les formsemestre_id qui peuvent compenser le courant"
|
||||||
self.nt: NotesTableCompat = nt
|
self.nt: NotesTableCompat = nt
|
||||||
self.formation = self.nt.formsemestre.formation
|
self.formation = self.nt.formsemestre.formation
|
||||||
self.parcours = self.nt.parcours
|
self.parcours = self.nt.parcours
|
||||||
@ -130,18 +138,20 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
# pour le DUT, le dernier est toujours S4.
|
# pour le DUT, le dernier est toujours S4.
|
||||||
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
|
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
|
||||||
# (licences et autres formations en 1 seule session))
|
# (licences et autres formations en 1 seule session))
|
||||||
self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM
|
self.semestre_non_terminal = self.cur_sem.semestre_id != self.parcours.NB_SEM
|
||||||
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
|
||||||
self.semestre_non_terminal = False
|
self.semestre_non_terminal = False
|
||||||
# Liste des semestres du parcours de cet étudiant:
|
# Liste des semestres du parcours de cet étudiant:
|
||||||
self._comp_semestres()
|
self._comp_semestres()
|
||||||
# Determine le semestre "precedent"
|
# Determine le semestre "precedent"
|
||||||
self.prev_formsemestre_id = self._search_prev()
|
self._search_prev()
|
||||||
# Verifie barres
|
# Verifie barres
|
||||||
self._comp_barres()
|
self._comp_barres()
|
||||||
# Verifie compensation
|
# Verifie compensation
|
||||||
if self.prev and self.sem["gestion_compensation"]:
|
if self.prev_formsemestre and self.cur_sem.gestion_compensation:
|
||||||
self.can_compensate_with_prev = self.prev["can_compensate"]
|
self.can_compensate_with_prev = (
|
||||||
|
self.prev_formsemestre.id in self.can_compensate
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.can_compensate_with_prev = False
|
self.can_compensate_with_prev = False
|
||||||
|
|
||||||
@ -170,14 +180,14 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
if rule.conclusion[0] in self.parcours.UNUSED_CODES:
|
if rule.conclusion[0] in self.parcours.UNUSED_CODES:
|
||||||
continue
|
continue
|
||||||
# Saute regles REDOSEM si pas de semestres decales:
|
# Saute regles REDOSEM si pas de semestres decales:
|
||||||
if (not self.sem["gestion_semestrielle"]) and rule.conclusion[
|
if (not self.cur_sem.gestion_semestrielle) and rule.conclusion[
|
||||||
3
|
3
|
||||||
] == "REDOSEM":
|
] == "REDOSEM":
|
||||||
continue
|
continue
|
||||||
if rule.match(state):
|
if rule.match(state):
|
||||||
if rule.conclusion[0] == ADC:
|
if rule.conclusion[0] == ADC:
|
||||||
# dans les regles on ne peut compenser qu'avec le PRECEDENT:
|
# dans les regles on ne peut compenser qu'avec le PRECEDENT:
|
||||||
fiduc = self.prev_formsemestre_id
|
fiduc = self.prev_formsemestre.id
|
||||||
assert fiduc
|
assert fiduc
|
||||||
else:
|
else:
|
||||||
fiduc = None
|
fiduc = None
|
||||||
@ -203,8 +213,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
"Phrase d'explication pour le code devenir"
|
"Phrase d'explication pour le code devenir"
|
||||||
if not devenir:
|
if not devenir:
|
||||||
return ""
|
return ""
|
||||||
s = self.sem["semestre_id"] # numero semestre courant
|
s_idx = self.cur_sem.semestre_id # numero semestre courant
|
||||||
if s < 0: # formation sans semestres (eg licence)
|
if s_idx < 0: # formation sans semestres (eg licence)
|
||||||
next_s = 1
|
next_s = 1
|
||||||
else:
|
else:
|
||||||
next_s = self._get_next_semestre_id()
|
next_s = self._get_next_semestre_id()
|
||||||
@ -219,27 +229,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
elif devenir == REO:
|
elif devenir == REO:
|
||||||
return "Réorienté"
|
return "Réorienté"
|
||||||
elif devenir == REDOANNEE:
|
elif devenir == REDOANNEE:
|
||||||
return "Redouble année (recommence %s%s)" % (SA, (s - 1))
|
return "Redouble année (recommence %s%s)" % (SA, (s_idx - 1))
|
||||||
elif devenir == REDOSEM:
|
elif devenir == REDOSEM:
|
||||||
return "Redouble semestre (recommence en %s%s)" % (SA, s)
|
return "Redouble semestre (recommence en %s%s)" % (SA, s_idx)
|
||||||
elif devenir == RA_OR_NEXT:
|
elif devenir == RA_OR_NEXT:
|
||||||
return passage + ", ou redouble année (en %s%s)" % (SA, (s - 1))
|
return passage + ", ou redouble année (en %s%s)" % (SA, (s_idx - 1))
|
||||||
elif devenir == RA_OR_RS:
|
elif devenir == RA_OR_RS:
|
||||||
return "Redouble semestre %s%s, ou redouble année (en %s%s)" % (
|
return "Redouble semestre %s%s, ou redouble année (en %s%s)" % (
|
||||||
SA,
|
SA,
|
||||||
s,
|
s_idx,
|
||||||
SA,
|
SA,
|
||||||
s - 1,
|
s_idx - 1,
|
||||||
)
|
)
|
||||||
elif devenir == RS_OR_NEXT:
|
elif devenir == RS_OR_NEXT:
|
||||||
return passage + ", ou semestre %s%s" % (SA, s)
|
return passage + ", ou semestre %s%s" % (SA, s_idx)
|
||||||
elif devenir == NEXT_OR_NEXT2:
|
elif devenir == NEXT_OR_NEXT2:
|
||||||
return passage + ", ou en semestre %s%s" % (
|
return passage + ", ou en semestre %s%s" % (
|
||||||
SA,
|
SA,
|
||||||
s + 2,
|
s_idx + 2,
|
||||||
) # coherent avec get_next_semestre_ids
|
) # coherent avec get_next_semestre_ids
|
||||||
elif devenir == NEXT2:
|
elif devenir == NEXT2:
|
||||||
return "Passe en %s%s" % (SA, s + 2)
|
return "Passe en %s%s" % (SA, s_idx + 2)
|
||||||
else:
|
else:
|
||||||
log("explique_devenir: code devenir inconnu: %s" % devenir)
|
log("explique_devenir: code devenir inconnu: %s" % devenir)
|
||||||
return "Code devenir inconnu !"
|
return "Code devenir inconnu !"
|
||||||
@ -258,7 +268,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
|
|
||||||
def _sems_validated(self, exclude_current=False):
|
def _sems_validated(self, exclude_current=False):
|
||||||
"True si semestres du parcours validés"
|
"True si semestres du parcours validés"
|
||||||
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
|
||||||
# mono-semestre: juste celui ci
|
# mono-semestre: juste celui ci
|
||||||
decision = self.nt.get_etud_decision_sem(self.etudid)
|
decision = self.nt.get_etud_decision_sem(self.etudid)
|
||||||
return decision and code_semestre_validant(decision["code"])
|
return decision and code_semestre_validant(decision["code"])
|
||||||
@ -266,8 +276,8 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
to_validate = set(
|
to_validate = set(
|
||||||
range(1, self.parcours.NB_SEM + 1)
|
range(1, self.parcours.NB_SEM + 1)
|
||||||
) # ensemble des indices à valider
|
) # ensemble des indices à valider
|
||||||
if exclude_current and self.sem["semestre_id"] in to_validate:
|
if exclude_current and self.cur_sem.semestre_id in to_validate:
|
||||||
to_validate.remove(self.sem["semestre_id"])
|
to_validate.remove(self.cur_sem.semestre_id)
|
||||||
return self._sem_list_validated(to_validate)
|
return self._sem_list_validated(to_validate)
|
||||||
|
|
||||||
def can_jump_to_next2(self):
|
def can_jump_to_next2(self):
|
||||||
@ -275,20 +285,20 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
Il faut donc que tous les semestres 1...n-1 soient validés et que n+1 soit en attente.
|
Il faut donc que tous les semestres 1...n-1 soient validés et que n+1 soit en attente.
|
||||||
(et que le sem courant n soit validé, ce qui n'est pas testé ici)
|
(et que le sem courant n soit validé, ce qui n'est pas testé ici)
|
||||||
"""
|
"""
|
||||||
n = self.sem["semestre_id"]
|
s_idx = self.cur_sem.semestre_id
|
||||||
if not self.sem["gestion_semestrielle"]:
|
if not self.cur_sem.gestion_semestrielle:
|
||||||
return False # pas de semestre décalés
|
return False # pas de semestre décalés
|
||||||
if n == NO_SEMESTRE_ID or n > self.parcours.NB_SEM - 2:
|
if s_idx == NO_SEMESTRE_ID or s_idx > self.parcours.NB_SEM - 2:
|
||||||
return False # n+2 en dehors du parcours
|
return False # n+2 en dehors du parcours
|
||||||
if self._sem_list_validated(set(range(1, n))):
|
if self._sem_list_validated(set(range(1, s_idx))):
|
||||||
# antérieurs validé, teste suivant
|
# antérieurs validés, teste suivant
|
||||||
n1 = n + 1
|
n1 = s_idx + 1
|
||||||
for sem in self.get_semestres():
|
for formsemestre in self.formsemestres:
|
||||||
if (
|
if (
|
||||||
sem["semestre_id"] == n1
|
formsemestre.semestre_id == n1
|
||||||
and sem["formation_code"] == self.formation.formation_code
|
and formsemestre.formation.formation_code
|
||||||
|
== self.formation.formation_code
|
||||||
):
|
):
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||||
formsemestre
|
formsemestre
|
||||||
)
|
)
|
||||||
@ -315,19 +325,17 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
return not sem_idx_set
|
return not sem_idx_set
|
||||||
|
|
||||||
def _comp_semestres(self):
|
def _comp_semestres(self):
|
||||||
# etud['sems'] est trie par date decroissante (voir fill_etuds_info)
|
# plus ancien en tête:
|
||||||
if not "sems" in self.etud:
|
self.formsemestres = self.etud.get_formsemestres(recent_first=False)
|
||||||
self.etud["sems"] = sco_etud.etud_inscriptions_infos(
|
|
||||||
self.etud["etudid"], self.etud["ne"]
|
|
||||||
)["sems"]
|
|
||||||
sems = self.etud["sems"][:] # copy
|
|
||||||
sems.reverse()
|
|
||||||
# Nb max d'UE et acronymes
|
# Nb max d'UE et acronymes
|
||||||
ue_acros = {} # acronyme ue : 1
|
ue_acros = {} # acronyme ue : 1
|
||||||
nb_max_ue = 0
|
nb_max_ue = 0
|
||||||
for sem in sems:
|
sems = []
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
for formsemestre in self.formsemestres: # plus ancien en tête
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
sem = formsemestre.to_dict()
|
||||||
|
sems.append(sem)
|
||||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_acros[ue["acronyme"]] = 1
|
ue_acros[ue["acronyme"]] = 1
|
||||||
@ -338,37 +346,48 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
sem["formation_code"] = formsemestre.formation.formation_code
|
sem["formation_code"] = formsemestre.formation.formation_code
|
||||||
# si sem peut servir à compenser le semestre courant, positionne
|
# si sem peut servir à compenser le semestre courant, positionne
|
||||||
# can_compensate
|
# can_compensate
|
||||||
sem["can_compensate"] = self.check_compensation_dut(sem, nt)
|
if self.check_compensation_dut(sem, nt):
|
||||||
|
self.can_compensate.add(formsemestre.id)
|
||||||
|
|
||||||
self.ue_acros = list(ue_acros.keys())
|
self.ue_acros = list(ue_acros.keys())
|
||||||
self.ue_acros.sort()
|
self.ue_acros.sort()
|
||||||
self.nb_max_ue = nb_max_ue
|
self.nb_max_ue = nb_max_ue
|
||||||
self.sems = sems
|
self.sems = sems
|
||||||
|
|
||||||
def get_semestres(self):
|
def get_semestres(self) -> list[dict]:
|
||||||
"""Liste des semestres dans lesquels a été inscrit
|
"""Liste des semestres dans lesquels a été inscrit
|
||||||
l'étudiant (quelle que soit la formation), le plus ancien en tête"""
|
l'étudiant (quelle que soit la formation), le plus ancien en tête"""
|
||||||
return self.sems
|
return self.sems
|
||||||
|
|
||||||
def get_cursus_descr(self, filter_futur=False):
|
def get_cursus_descr(self, filter_futur=False, filter_formation_code=False):
|
||||||
"""Description brève du parcours: "S1, S2, ..."
|
"""Description brève du parcours: "S1, S2, ..."
|
||||||
Si filter_futur, ne mentionne pas les semestres qui sont après le semestre courant.
|
Si filter_futur, ne mentionne pas les semestres qui sont après le semestre courant.
|
||||||
|
Si filter_formation_code, restreint aux semestres de même code formation que le courant.
|
||||||
"""
|
"""
|
||||||
cur_begin_date = self.sem["dateord"]
|
cur_begin_date = self.cur_sem.date_debut
|
||||||
|
cur_formation_code = self.cur_sem.formation.formation_code
|
||||||
p = []
|
p = []
|
||||||
for s in self.sems:
|
for formsemestre in self.formsemestres:
|
||||||
if s["ins"]["etat"] == scu.DEMISSION:
|
inscription = formsemestre.etuds_inscriptions.get(self.etud.id)
|
||||||
|
if inscription is None:
|
||||||
|
raise ValueError("Etudiant non inscrit au semestre") # bug
|
||||||
|
if inscription.etat == scu.DEMISSION:
|
||||||
dem = " (dem.)"
|
dem = " (dem.)"
|
||||||
else:
|
else:
|
||||||
dem = ""
|
dem = ""
|
||||||
if filter_futur and s["dateord"] > cur_begin_date:
|
if filter_futur and formsemestre.date_debut > cur_begin_date:
|
||||||
continue # skip semestres demarrant apres le courant
|
continue # skip semestres demarrant apres le courant
|
||||||
SA = self.parcours.SESSION_ABBRV # 'S' ou 'A'
|
if (
|
||||||
if s["semestre_id"] < 0:
|
filter_formation_code
|
||||||
SA = "A" # force, cas des DUT annuels par exemple
|
and formsemestre.formation.formation_code != cur_formation_code
|
||||||
p.append("%s%d%s" % (SA, -s["semestre_id"], dem))
|
):
|
||||||
|
continue # restreint aux semestres de la formation courante (pour les PV)
|
||||||
|
session_abbrv = self.parcours.SESSION_ABBRV # 'S' ou 'A'
|
||||||
|
if formsemestre.semestre_id < 0:
|
||||||
|
session_abbrv = "A" # force, cas des DUT annuels par exemple
|
||||||
|
p.append("%s%d%s" % (session_abbrv, -formsemestre.semestre_id, dem))
|
||||||
else:
|
else:
|
||||||
p.append("%s%d%s" % (SA, s["semestre_id"], dem))
|
p.append("%s%d%s" % (session_abbrv, formsemestre.semestre_id, dem))
|
||||||
return ", ".join(p)
|
return ", ".join(p)
|
||||||
|
|
||||||
def get_parcours_decisions(self):
|
def get_parcours_decisions(self):
|
||||||
@ -377,7 +396,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
Returns: { semestre_id : code }
|
Returns: { semestre_id : code }
|
||||||
"""
|
"""
|
||||||
r = {}
|
r = {}
|
||||||
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
if self.cur_sem.semestre_id == NO_SEMESTRE_ID:
|
||||||
indices = [NO_SEMESTRE_ID]
|
indices = [NO_SEMESTRE_ID]
|
||||||
else:
|
else:
|
||||||
indices = list(range(1, self.parcours.NB_SEM + 1))
|
indices = list(range(1, self.parcours.NB_SEM + 1))
|
||||||
@ -420,22 +439,22 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
"true si ce semestre pourrait etre compensé par un autre (e.g. barres UE > 8)"
|
"true si ce semestre pourrait etre compensé par un autre (e.g. barres UE > 8)"
|
||||||
return self.barres_ue_ok
|
return self.barres_ue_ok
|
||||||
|
|
||||||
def _search_prev(self):
|
def _search_prev(self) -> FormSemestre | None:
|
||||||
"""Recherche semestre 'precedent'.
|
"""Recherche semestre 'precedent'.
|
||||||
return prev_formsemestre_id
|
positionne .prev_decision
|
||||||
"""
|
"""
|
||||||
self.prev = None
|
self.prev_formsemestre = None
|
||||||
self.prev_decision = None
|
self.prev_decision = None
|
||||||
if len(self.sems) < 2:
|
if len(self.formsemestres) < 2:
|
||||||
return None
|
return None
|
||||||
# Cherche sem courant dans la liste triee par date_debut
|
# Cherche sem courant dans la liste triee par date_debut
|
||||||
cur = None
|
cur = None
|
||||||
icur = -1
|
icur = -1
|
||||||
for cur in self.sems:
|
for cur in self.formsemestres:
|
||||||
icur += 1
|
icur += 1
|
||||||
if cur["formsemestre_id"] == self.formsemestre_id:
|
if cur.id == self.formsemestre_id:
|
||||||
break
|
break
|
||||||
if not cur or cur["formsemestre_id"] != self.formsemestre_id:
|
if not cur or cur.id != self.formsemestre_id:
|
||||||
log(
|
log(
|
||||||
f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})"
|
f"*** SituationEtudCursus: search_prev: cur not found (formsemestre_id={self.formsemestre_id}, etudid={self.etudid})"
|
||||||
)
|
)
|
||||||
@ -443,60 +462,59 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
# Cherche semestre antérieur de même formation (code) et semestre_id precedent
|
# Cherche semestre antérieur de même formation (code) et semestre_id precedent
|
||||||
#
|
#
|
||||||
# i = icur - 1 # part du courant, remonte vers le passé
|
# i = icur - 1 # part du courant, remonte vers le passé
|
||||||
i = len(self.sems) - 1 # par du dernier, remonte vers le passé
|
i = len(self.formsemestres) - 1 # par du dernier, remonte vers le passé
|
||||||
prev = None
|
prev_formsemestre = None
|
||||||
while i >= 0:
|
while i >= 0:
|
||||||
if (
|
if (
|
||||||
self.sems[i]["formation_code"] == self.formation.formation_code
|
self.formsemestres[i].formation.formation_code
|
||||||
and self.sems[i]["semestre_id"] == cur["semestre_id"] - 1
|
== self.formation.formation_code
|
||||||
|
and self.formsemestres[i].semestre_id == cur.semestre_id - 1
|
||||||
):
|
):
|
||||||
prev = self.sems[i]
|
prev_formsemestre = self.formsemestres[i]
|
||||||
break
|
break
|
||||||
i -= 1
|
i -= 1
|
||||||
if not prev:
|
if not prev_formsemestre:
|
||||||
return None # pas de precedent trouvé
|
return None # pas de precedent trouvé
|
||||||
self.prev = prev
|
self.prev_formsemestre = prev_formsemestre
|
||||||
# Verifications basiques:
|
# Verifications basiques:
|
||||||
# ?
|
# ?
|
||||||
# Code etat du semestre precedent:
|
# Code etat du semestre precedent:
|
||||||
formsemestre = FormSemestre.query.get_or_404(prev["formsemestre_id"])
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_formsemestre)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
self.prev_decision = nt.get_etud_decision_sem(self.etudid)
|
self.prev_decision = nt.get_etud_decision_sem(self.etudid)
|
||||||
self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid)
|
self.prev_moy_gen = nt.get_etud_moy_gen(self.etudid)
|
||||||
self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0]
|
self.prev_barres_ue_ok = nt.etud_check_conditions_ues(self.etudid)[0]
|
||||||
return self.prev["formsemestre_id"]
|
|
||||||
|
|
||||||
def get_next_semestre_ids(self, devenir):
|
def get_next_semestre_ids(self, devenir: str) -> list[int]:
|
||||||
"""Liste des numeros de semestres autorises avec ce devenir
|
"""Liste des numeros de semestres autorises avec ce devenir
|
||||||
Ne vérifie pas que le devenir est possible (doit être fait avant),
|
Ne vérifie pas que le devenir est possible (doit être fait avant),
|
||||||
juste que le rang du semestre est dans le parcours [1..NB_SEM]
|
juste que le rang du semestre est dans le parcours [1..NB_SEM]
|
||||||
"""
|
"""
|
||||||
s = self.sem["semestre_id"]
|
s_idx = self.cur_sem.semestre_id
|
||||||
if devenir == NEXT:
|
if devenir == NEXT:
|
||||||
ids = [self._get_next_semestre_id()]
|
ids = [self._get_next_semestre_id()]
|
||||||
elif devenir == REDOANNEE:
|
elif devenir == REDOANNEE:
|
||||||
ids = [s - 1]
|
ids = [s_idx - 1]
|
||||||
elif devenir == REDOSEM:
|
elif devenir == REDOSEM:
|
||||||
ids = [s]
|
ids = [s_idx]
|
||||||
elif devenir == RA_OR_NEXT:
|
elif devenir == RA_OR_NEXT:
|
||||||
ids = [s - 1, self._get_next_semestre_id()]
|
ids = [s_idx - 1, self._get_next_semestre_id()]
|
||||||
elif devenir == RA_OR_RS:
|
elif devenir == RA_OR_RS:
|
||||||
ids = [s - 1, s]
|
ids = [s_idx - 1, s_idx]
|
||||||
elif devenir == RS_OR_NEXT:
|
elif devenir == RS_OR_NEXT:
|
||||||
ids = [s, self._get_next_semestre_id()]
|
ids = [s_idx, self._get_next_semestre_id()]
|
||||||
elif devenir == NEXT_OR_NEXT2:
|
elif devenir == NEXT_OR_NEXT2:
|
||||||
ids = [
|
ids = [
|
||||||
self._get_next_semestre_id(),
|
self._get_next_semestre_id(),
|
||||||
s + 2,
|
s_idx + 2,
|
||||||
] # cohérent avec explique_devenir()
|
] # cohérent avec explique_devenir()
|
||||||
elif devenir == NEXT2:
|
elif devenir == NEXT2:
|
||||||
ids = [s + 2]
|
ids = [s_idx + 2]
|
||||||
else:
|
else:
|
||||||
ids = [] # reoriente ou autre: pas de next !
|
ids = [] # reoriente ou autre: pas de next !
|
||||||
# clip [1..NB_SEM]
|
# clip [1..NB_SEM]
|
||||||
r = []
|
r = []
|
||||||
for idx in ids:
|
for idx in ids:
|
||||||
if idx > 0 and idx <= self.parcours.NB_SEM:
|
if 0 < idx <= self.parcours.NB_SEM:
|
||||||
r.append(idx)
|
r.append(idx)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@ -504,27 +522,27 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
"""Indice du semestre suivant non validé.
|
"""Indice du semestre suivant non validé.
|
||||||
S'il n'y en a pas, ramène NB_SEM+1
|
S'il n'y en a pas, ramène NB_SEM+1
|
||||||
"""
|
"""
|
||||||
s = self.sem["semestre_id"]
|
s_idx = self.cur_sem.semestre_id
|
||||||
if s >= self.parcours.NB_SEM:
|
if s_idx >= self.parcours.NB_SEM:
|
||||||
return self.parcours.NB_SEM + 1
|
return self.parcours.NB_SEM + 1
|
||||||
validated = True
|
validated = True
|
||||||
while validated and (s < self.parcours.NB_SEM):
|
while validated and (s_idx < self.parcours.NB_SEM):
|
||||||
s = s + 1
|
s_idx = s_idx + 1
|
||||||
# semestre s validé ?
|
# semestre s validé ?
|
||||||
validated = False
|
validated = False
|
||||||
for sem in self.sems:
|
for formsemestre in self.formsemestres:
|
||||||
if (
|
if (
|
||||||
sem["formation_code"] == self.formation.formation_code
|
formsemestre.formation.formation_code
|
||||||
and sem["semestre_id"] == s
|
== self.formation.formation_code
|
||||||
|
and formsemestre.semestre_id == s_idx
|
||||||
):
|
):
|
||||||
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||||
formsemestre
|
formsemestre
|
||||||
)
|
)
|
||||||
decision = nt.get_etud_decision_sem(self.etudid)
|
decision = nt.get_etud_decision_sem(self.etudid)
|
||||||
if decision and code_semestre_validant(decision["code"]):
|
if decision and code_semestre_validant(decision["code"]):
|
||||||
validated = True
|
validated = True
|
||||||
return s
|
return s_idx
|
||||||
|
|
||||||
def valide_decision(self, decision):
|
def valide_decision(self, decision):
|
||||||
"""Enregistre la decision (instance de DecisionSem)
|
"""Enregistre la decision (instance de DecisionSem)
|
||||||
@ -539,8 +557,11 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
fsid = decision.formsemestre_id_utilise_pour_compenser
|
fsid = decision.formsemestre_id_utilise_pour_compenser
|
||||||
if fsid:
|
if fsid:
|
||||||
ok = False
|
ok = False
|
||||||
for sem in self.sems:
|
for formsemestre in self.formsemestres:
|
||||||
if sem["formsemestre_id"] == fsid and sem["can_compensate"]:
|
if (
|
||||||
|
formsemestre.id == fsid
|
||||||
|
and formsemestre.id in self.can_compensate
|
||||||
|
):
|
||||||
ok = True
|
ok = True
|
||||||
break
|
break
|
||||||
if not ok:
|
if not ok:
|
||||||
@ -581,7 +602,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
decision.assiduite,
|
decision.assiduite,
|
||||||
)
|
)
|
||||||
# -- modification du code du semestre precedent
|
# -- modification du code du semestre precedent
|
||||||
if self.prev and decision.new_code_prev:
|
if self.prev_formsemestre and decision.new_code_prev:
|
||||||
if decision.new_code_prev == ADC:
|
if decision.new_code_prev == ADC:
|
||||||
# ne compense le prec. qu'avec le sem. courant
|
# ne compense le prec. qu'avec le sem. courant
|
||||||
fsid = self.formsemestre_id
|
fsid = self.formsemestre_id
|
||||||
@ -589,7 +610,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
fsid = None
|
fsid = None
|
||||||
to_invalidate += formsemestre_update_validation_sem(
|
to_invalidate += formsemestre_update_validation_sem(
|
||||||
cnx,
|
cnx,
|
||||||
self.prev["formsemestre_id"],
|
self.prev_formsemestre.id,
|
||||||
self.etudid,
|
self.etudid,
|
||||||
decision.new_code_prev,
|
decision.new_code_prev,
|
||||||
assidu=True,
|
assidu=True,
|
||||||
@ -601,18 +622,18 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
etudid=self.etudid,
|
etudid=self.etudid,
|
||||||
commit=False,
|
commit=False,
|
||||||
msg="formsemestre_id=%s code=%s"
|
msg="formsemestre_id=%s code=%s"
|
||||||
% (self.prev["formsemestre_id"], decision.new_code_prev),
|
% (self.prev_formsemestre.id, decision.new_code_prev),
|
||||||
)
|
)
|
||||||
# modifs des codes d'UE (pourraient passer de ADM a CMP, meme sans modif des notes)
|
# modifs des codes d'UE (pourraient passer de ADM a CMP, meme sans modif des notes)
|
||||||
formsemestre_validate_ues(
|
formsemestre_validate_ues(
|
||||||
self.prev["formsemestre_id"],
|
self.prev_formsemestre.id,
|
||||||
self.etudid,
|
self.etudid,
|
||||||
decision.new_code_prev,
|
decision.new_code_prev,
|
||||||
decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas...
|
decision.assiduite, # attention: en toute rigueur il faudrait utiliser une indication de l'assiduite au sem. precedent, que nous n'avons pas...
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.prev["formsemestre_id"]
|
formsemestre_id=self.prev_formsemestre.id
|
||||||
) # > modif decisions jury (sem, UE)
|
) # > modif decisions jury (sem, UE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -694,7 +715,7 @@ class SituationEtudCursusClassic(SituationEtudCursus):
|
|||||||
class SituationEtudCursusECTS(SituationEtudCursusClassic):
|
class SituationEtudCursusECTS(SituationEtudCursusClassic):
|
||||||
"""Gestion parcours basés sur ECTS"""
|
"""Gestion parcours basés sur ECTS"""
|
||||||
|
|
||||||
def __init__(self, etud, formsemestre_id, nt):
|
def __init__(self, etud: Identite, formsemestre_id: int, nt):
|
||||||
SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt)
|
SituationEtudCursusClassic.__init__(self, etud, formsemestre_id, nt)
|
||||||
|
|
||||||
def could_be_compensated(self):
|
def could_be_compensated(self):
|
||||||
|
@ -222,6 +222,7 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
|||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign table_listegroupe",
|
html_class="table_leftalign table_listegroupe",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="table_debouche_etudids",
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
@ -198,6 +198,18 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
|
|||||||
if current_user.has_permission(Permission.EditApogee):
|
if current_user.has_permission(Permission.EditApogee):
|
||||||
html_class += " apo_editable"
|
html_class += " apo_editable"
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
|
columns_ids=columns_ids,
|
||||||
|
html_class_ignore_default=True,
|
||||||
|
html_class=html_class,
|
||||||
|
html_sortable=True,
|
||||||
|
html_table_attrs=f"""
|
||||||
|
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
|
||||||
|
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
|
||||||
|
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
|
||||||
|
""",
|
||||||
|
html_with_td_classes=True,
|
||||||
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
rows=sems,
|
||||||
titles={
|
titles={
|
||||||
"formsemestre_id": "id",
|
"formsemestre_id": "id",
|
||||||
"semestre_id_n": "S#",
|
"semestre_id_n": "S#",
|
||||||
@ -211,19 +223,7 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
|
|||||||
"elt_sem_apo": "Elt. sem. Apo.",
|
"elt_sem_apo": "Elt. sem. Apo.",
|
||||||
"formation": "Formation",
|
"formation": "Formation",
|
||||||
},
|
},
|
||||||
columns_ids=columns_ids,
|
|
||||||
rows=sems,
|
|
||||||
table_id="semlist",
|
table_id="semlist",
|
||||||
html_class_ignore_default=True,
|
|
||||||
html_class=html_class,
|
|
||||||
html_sortable=True,
|
|
||||||
html_table_attrs=f"""
|
|
||||||
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
|
|
||||||
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
|
|
||||||
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
|
|
||||||
""",
|
|
||||||
html_with_td_classes=True,
|
|
||||||
preferences=sco_preferences.SemPreferences(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
|
@ -67,7 +67,7 @@ SCO_DUMP_LOCK = "/tmp/scodump.lock"
|
|||||||
|
|
||||||
def sco_dump_and_send_db(
|
def sco_dump_and_send_db(
|
||||||
message: str = "", request_url: str = "", traceback_str_base64: str = ""
|
message: str = "", request_url: str = "", traceback_str_base64: str = ""
|
||||||
):
|
) -> requests.Response:
|
||||||
"""Dump base de données et l'envoie anonymisée pour debug"""
|
"""Dump base de données et l'envoie anonymisée pour debug"""
|
||||||
traceback_str = base64.urlsafe_b64decode(traceback_str_base64).decode(
|
traceback_str = base64.urlsafe_b64decode(traceback_str_base64).decode(
|
||||||
scu.SCO_ENCODING
|
scu.SCO_ENCODING
|
||||||
@ -97,7 +97,6 @@ def sco_dump_and_send_db(
|
|||||||
|
|
||||||
# Send
|
# Send
|
||||||
r = _send_db(ano_db_name, message, request_url, traceback_str=traceback_str)
|
r = _send_db(ano_db_name, message, request_url, traceback_str=traceback_str)
|
||||||
code = r.status_code
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Drop anonymized database
|
# Drop anonymized database
|
||||||
@ -107,7 +106,7 @@ def sco_dump_and_send_db(
|
|||||||
|
|
||||||
log("sco_dump_and_send_db: done.")
|
log("sco_dump_and_send_db: done.")
|
||||||
|
|
||||||
return code
|
return r
|
||||||
|
|
||||||
|
|
||||||
def _duplicate_db(db_name, ano_db_name):
|
def _duplicate_db(db_name, ano_db_name):
|
||||||
@ -216,11 +215,11 @@ def _drop_ano_db(ano_db_name):
|
|||||||
log("_drop_ano_db: no temp db, nothing to drop")
|
log("_drop_ano_db: no temp db, nothing to drop")
|
||||||
return
|
return
|
||||||
cmd = ["dropdb", ano_db_name]
|
cmd = ["dropdb", ano_db_name]
|
||||||
log("sco_dump_and_send_db: {}".format(cmd))
|
log(f"sco_dump_and_send_db: {cmd}")
|
||||||
try:
|
try:
|
||||||
_ = subprocess.check_output(cmd)
|
_ = subprocess.check_output(cmd)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as exc:
|
||||||
log("sco_dump_and_send_db: exception dropdb {}".format(e))
|
log(f"sco_dump_and_send_db: exception dropdb {exc}")
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"erreur lors de la suppression de la base {}".format(ano_db_name)
|
f"erreur lors de la suppression de la base {ano_db_name}"
|
||||||
)
|
) from exc
|
||||||
|
@ -326,6 +326,7 @@ def do_formation_create(args: dict) -> Formation:
|
|||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation.id,
|
formation_id=formation.id,
|
||||||
),
|
),
|
||||||
|
safe=True,
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
|
@ -890,23 +890,6 @@ def module_edit(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Edition en ligne du code Apogee
|
|
||||||
def edit_module_set_code_apogee(id=None, value=None):
|
|
||||||
"Set UE code apogee"
|
|
||||||
module_id = id
|
|
||||||
value = str(value).strip("-_ \t")
|
|
||||||
log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value))
|
|
||||||
|
|
||||||
modules = module_list(args={"module_id": module_id})
|
|
||||||
if not modules:
|
|
||||||
return "module invalide" # should not occur
|
|
||||||
|
|
||||||
do_module_edit({"module_id": module_id, "code_apogee": value})
|
|
||||||
if not value:
|
|
||||||
value = scu.APO_MISSING_CODE_STR
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def module_table(formation_id):
|
def module_table(formation_id):
|
||||||
"""Liste des modules de la formation
|
"""Liste des modules de la formation
|
||||||
(XXX inutile ou a revoir)
|
(XXX inutile ou a revoir)
|
||||||
|
@ -84,6 +84,7 @@ _ueEditor = ndb.EditableTable(
|
|||||||
"ects",
|
"ects",
|
||||||
"is_external",
|
"is_external",
|
||||||
"code_apogee",
|
"code_apogee",
|
||||||
|
"code_apogee_rcue",
|
||||||
"coefficient",
|
"coefficient",
|
||||||
"coef_rcue",
|
"coef_rcue",
|
||||||
"color",
|
"color",
|
||||||
@ -425,6 +426,20 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
"max_length": APO_CODE_STR_LEN,
|
"max_length": APO_CODE_STR_LEN,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
]
|
||||||
|
if is_apc:
|
||||||
|
form_descr += [
|
||||||
|
(
|
||||||
|
"code_apogee_rcue",
|
||||||
|
{
|
||||||
|
"title": "Code Apogée du RCUE",
|
||||||
|
"size": 25,
|
||||||
|
"explanation": "(optionnel) code(s) élément pédagogique Apogée du RCUE",
|
||||||
|
"max_length": APO_CODE_STR_LEN,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
form_descr += [
|
||||||
(
|
(
|
||||||
"is_external",
|
"is_external",
|
||||||
{
|
{
|
||||||
@ -1041,10 +1056,10 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
if current_user.has_permission(Permission.EditFormSemestre):
|
if current_user.has_permission(Permission.EditFormSemestre):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<ul>
|
f"""<ul>
|
||||||
<li><a class="stdlink" href="{
|
<li><b><a class="stdlink" href="{
|
||||||
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
|
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation_id, semestre_id=1)
|
formation_id=formation_id, semestre_id=1)
|
||||||
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a>
|
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a></b>
|
||||||
</li>
|
</li>
|
||||||
</ul>"""
|
</ul>"""
|
||||||
)
|
)
|
||||||
@ -1109,12 +1124,18 @@ def _ue_table_ues(
|
|||||||
klass = "span_apo_edit"
|
klass = "span_apo_edit"
|
||||||
else:
|
else:
|
||||||
klass = ""
|
klass = ""
|
||||||
ue["code_apogee_str"] = (
|
edit_url = url_for(
|
||||||
""", Apo: <span class="%s" data-url="edit_ue_set_code_apogee" id="%s" data-placeholder="%s">"""
|
"apiweb.ue_set_code_apogee",
|
||||||
% (klass, ue["ue_id"], scu.APO_MISSING_CODE_STR)
|
scodoc_dept=g.scodoc_dept,
|
||||||
+ (ue["code_apogee"] or "")
|
ue_id=ue["ue_id"],
|
||||||
+ "</span>"
|
|
||||||
)
|
)
|
||||||
|
ue[
|
||||||
|
"code_apogee_str"
|
||||||
|
] = f""", Apo: <span
|
||||||
|
class="{klass}" data-url="{edit_url}" id="{ue['ue_id']}"
|
||||||
|
data-placeholder="{scu.APO_MISSING_CODE_STR}">{
|
||||||
|
ue["code_apogee"] or ""
|
||||||
|
}</span>"""
|
||||||
|
|
||||||
if cur_ue_semestre_id != ue["semestre_id"]:
|
if cur_ue_semestre_id != ue["semestre_id"]:
|
||||||
cur_ue_semestre_id = ue["semestre_id"]
|
cur_ue_semestre_id = ue["semestre_id"]
|
||||||
@ -1348,16 +1369,17 @@ def _ue_table_modules(
|
|||||||
heurescoef = (
|
heurescoef = (
|
||||||
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod
|
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod
|
||||||
)
|
)
|
||||||
if mod_editable:
|
edit_url = url_for(
|
||||||
klass = "span_apo_edit"
|
"apiweb.module_set_code_apogee",
|
||||||
else:
|
scodoc_dept=g.scodoc_dept,
|
||||||
klass = ""
|
module_id=mod["module_id"],
|
||||||
heurescoef += (
|
|
||||||
', Apo: <span class="%s" data-url="edit_module_set_code_apogee" id="%s" data-placeholder="%s">'
|
|
||||||
% (klass, mod["module_id"], scu.APO_MISSING_CODE_STR)
|
|
||||||
+ (mod["code_apogee"] or "")
|
|
||||||
+ "</span>"
|
|
||||||
)
|
)
|
||||||
|
heurescoef += f""", Apo: <span
|
||||||
|
class="{'span_apo_edit' if editable else ''}"
|
||||||
|
data-url="{edit_url}" id="{mod["module_id"]}"
|
||||||
|
data-placeholder="{scu.APO_MISSING_CODE_STR}">{
|
||||||
|
mod["code_apogee"] or ""
|
||||||
|
}</span>"""
|
||||||
if tag_editable:
|
if tag_editable:
|
||||||
tag_cls = "module_tag_editor"
|
tag_cls = "module_tag_editor"
|
||||||
else:
|
else:
|
||||||
@ -1493,28 +1515,6 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||||||
formation.invalidate_module_coefs()
|
formation.invalidate_module_coefs()
|
||||||
|
|
||||||
|
|
||||||
# essai edition en ligne:
|
|
||||||
def edit_ue_set_code_apogee(id=None, value=None):
|
|
||||||
"set UE code apogee"
|
|
||||||
ue_id = id
|
|
||||||
value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
|
||||||
|
|
||||||
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
|
||||||
|
|
||||||
ues = ue_list(args={"ue_id": ue_id})
|
|
||||||
if not ues:
|
|
||||||
return "ue invalide"
|
|
||||||
|
|
||||||
do_ue_edit(
|
|
||||||
{"ue_id": ue_id, "code_apogee": value},
|
|
||||||
bypass_lock=True,
|
|
||||||
dont_invalidate_cache=False,
|
|
||||||
)
|
|
||||||
if not value:
|
|
||||||
value = scu.APO_MISSING_CODE_STR
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def ue_is_locked(ue_id):
|
def ue_is_locked(ue_id):
|
||||||
"""True if UE should not be modified
|
"""True if UE should not be modified
|
||||||
(contains modules used in a locked formsemestre)
|
(contains modules used in a locked formsemestre)
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
Lecture et conversion des ics.
|
Lecture et conversion des ics.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
@ -229,7 +230,7 @@ def translate_calendar(
|
|||||||
heure_deb=event["heure_deb"],
|
heure_deb=event["heure_deb"],
|
||||||
heure_fin=event["heure_fin"],
|
heure_fin=event["heure_fin"],
|
||||||
moduleimpl_id=modimpl.id,
|
moduleimpl_id=modimpl.id,
|
||||||
jour=event["jour"],
|
day=event["jour"],
|
||||||
)
|
)
|
||||||
if modimpl and group
|
if modimpl and group
|
||||||
else None
|
else None
|
||||||
|
@ -247,9 +247,7 @@ def apo_csv_check_etape(semset, set_nips, etape_apo):
|
|||||||
return nips_ok, apo_nips, nips_no_apo, nips_no_sco, maq_elems, sem_elems
|
return nips_ok, apo_nips, nips_no_apo, nips_no_sco, maq_elems, sem_elems
|
||||||
|
|
||||||
|
|
||||||
def apo_csv_semset_check(
|
def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=False):
|
||||||
semset, allow_missing_apo=False, allow_missing_csv=False
|
|
||||||
): # was apo_csv_check
|
|
||||||
"""
|
"""
|
||||||
check students in stored maqs vs students in semset
|
check students in stored maqs vs students in semset
|
||||||
Cas à détecter:
|
Cas à détecter:
|
||||||
@ -346,120 +344,3 @@ def apo_csv_retreive_etuds_by_nip(semset, nips):
|
|||||||
etuds[nip] = apo_etuds_by_nips.get(nip, {"nip": nip, "etape_apo": "?"})
|
etuds[nip] = apo_etuds_by_nips.get(nip, {"nip": nip, "etape_apo": "?"})
|
||||||
|
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
from debug import *
|
|
||||||
from app.scodoc import sco_groups
|
|
||||||
from app.scodoc import sco_groups_view
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc.sco_etape_apogee import *
|
|
||||||
from app.scodoc.sco_apogee_csv import *
|
|
||||||
from app.scodoc.sco_semset import *
|
|
||||||
|
|
||||||
app.set_sco_dept('RT')
|
|
||||||
csv_data = open('/opt/misc/VDTRT_V1RT.TXT').read()
|
|
||||||
annee_scolaire=2015
|
|
||||||
sem_id=1
|
|
||||||
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=sem_id)
|
|
||||||
print apo_data.etape_apogee
|
|
||||||
|
|
||||||
apo_data.setup()
|
|
||||||
e = apo_data.etuds[0]
|
|
||||||
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
|
|
||||||
e.associate_sco( apo_data)
|
|
||||||
|
|
||||||
print apo_csv_list_stored_archives()
|
|
||||||
|
|
||||||
|
|
||||||
# apo_csv_store(csv_data, annee_scolaire, sem_id)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos( [sco_groups.get_default_group(formsemestre_id)], formsemestre_id=formsemestre_id)
|
|
||||||
|
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
#
|
|
||||||
s = SemSet('NSS29902')
|
|
||||||
apo_data = sco_apogee_csv.ApoData(open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2015-2/2016-07-10-11-26-15/V1RT.csv').read(), periode=1)
|
|
||||||
|
|
||||||
# cas Tiziri K. (inscrite en S1, démission en fin de S1, pas inscrite en S2)
|
|
||||||
# => pas de décision, ce qui est voulu (?)
|
|
||||||
#
|
|
||||||
|
|
||||||
apo_data.setup()
|
|
||||||
e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0]
|
|
||||||
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
|
|
||||||
e.associate_sco(apo_data)
|
|
||||||
|
|
||||||
self=e
|
|
||||||
col_id='apoL_c0129'
|
|
||||||
|
|
||||||
# --
|
|
||||||
from app.scodoc import sco_portal_apogee
|
|
||||||
_ = go_dept(app, 'GEA').Notes
|
|
||||||
#csv_data = sco_portal_apogee.get_maquette_apogee(etape='V1GE', annee_scolaire=2015)
|
|
||||||
csv_data = open('/tmp/V1GE.txt').read()
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
|
|
||||||
|
|
||||||
|
|
||||||
# ------
|
|
||||||
# les elements inconnus:
|
|
||||||
|
|
||||||
from debug import *
|
|
||||||
from app.scodoc import sco_groups
|
|
||||||
from app.scodoc import sco_groups_view
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc.sco_etape_apogee import *
|
|
||||||
from app.scodoc.sco_apogee_csv import *
|
|
||||||
from app.scodoc.sco_semset import *
|
|
||||||
|
|
||||||
_ = go_dept(app, 'RT').Notes
|
|
||||||
csv_data = open('/opt/misc/V2RT.csv').read()
|
|
||||||
annee_scolaire=2015
|
|
||||||
sem_id=1
|
|
||||||
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
|
|
||||||
print apo_data.etape_apogee
|
|
||||||
|
|
||||||
apo_data.setup()
|
|
||||||
for e in apo_data.etuds:
|
|
||||||
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
|
|
||||||
e.associate_sco(apo_data)
|
|
||||||
|
|
||||||
# ------
|
|
||||||
# test export jury intermediaire
|
|
||||||
from debug import *
|
|
||||||
from app.scodoc import sco_groups
|
|
||||||
from app.scodoc import sco_groups_view
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc.sco_etape_apogee import *
|
|
||||||
from app.scodoc.sco_apogee_csv import *
|
|
||||||
from app.scodoc.sco_semset import *
|
|
||||||
|
|
||||||
_ = go_dept(app, 'CJ').Notes
|
|
||||||
csv_data = open('/opt/scodoc/var/scodoc/archives/apo_csv/CJ/2016-1/2017-03-06-21-46-32/V1CJ.csv').read()
|
|
||||||
annee_scolaire=2016
|
|
||||||
sem_id=1
|
|
||||||
|
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=1)
|
|
||||||
print apo_data.etape_apogee
|
|
||||||
|
|
||||||
apo_data.setup()
|
|
||||||
e = [ e for e in apo_data.etuds if e['nom'] == 'XYZ' ][0] #
|
|
||||||
e.lookup_scodoc( apo_data.etape_formsemestre_ids)
|
|
||||||
e.associate_sco(apo_data)
|
|
||||||
|
|
||||||
self=e
|
|
||||||
|
|
||||||
sco_elts = {}
|
|
||||||
col_id='apoL_c0001'
|
|
||||||
code = apo_data.cols[col_id]['Code'] # 'V1RT'
|
|
||||||
|
|
||||||
sem = apo_data.sems_periode[0] # le S1
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
@ -125,14 +125,19 @@ def apo_semset_maq_status(
|
|||||||
H.append("""<p><em>Aucune maquette chargée</em></p>""")
|
H.append("""<p><em>Aucune maquette chargée</em></p>""")
|
||||||
# Upload fichier:
|
# Upload fichier:
|
||||||
H.append(
|
H.append(
|
||||||
"""<form id="apo_csv_add" action="view_apo_csv_store" method="post" enctype="multipart/form-data">
|
f"""<form id="apo_csv_add" action="view_apo_csv_store"
|
||||||
Charger votre fichier maquette Apogée:
|
method="post" enctype="multipart/form-data"
|
||||||
|
style="margin-bottom: 8px;"
|
||||||
|
>
|
||||||
|
<div style="margin-top: 12px; margin-bottom: 8px;">
|
||||||
|
{'Charger votre fichier' if tab_archives.is_empty() else 'Ajouter un autre fichier'}
|
||||||
|
maquette Apogée:
|
||||||
|
</div>
|
||||||
<input type="file" size="30" name="csvfile"/>
|
<input type="file" size="30" name="csvfile"/>
|
||||||
<input type="hidden" name="semset_id" value="%s"/>
|
<input type="hidden" name="semset_id" value="{semset_id}"/>
|
||||||
<input type="submit" value="Ajouter ce fichier"/>
|
<input type="submit" value="Ajouter ce fichier"/>
|
||||||
<input type="checkbox" name="autodetect" checked/>autodétecter encodage</input>
|
<input type="checkbox" name="autodetect" checked/>autodétecter encodage</input>
|
||||||
</form>"""
|
</form>"""
|
||||||
% (semset_id,)
|
|
||||||
)
|
)
|
||||||
# Récupération sur portail:
|
# Récupération sur portail:
|
||||||
maquette_url = sco_portal_apogee.get_maquette_url()
|
maquette_url = sco_portal_apogee.get_maquette_url()
|
||||||
@ -335,7 +340,7 @@ def apo_semset_maq_status(
|
|||||||
missing = maq_elems - sem_elems
|
missing = maq_elems - sem_elems
|
||||||
H.append('<div id="apo_elements">')
|
H.append('<div id="apo_elements">')
|
||||||
H.append(
|
H.append(
|
||||||
'<p>Elements Apogée: <span class="apo_elems">%s</span></p>'
|
'<p>Élements Apogée: <span class="apo_elems">%s</span></p>'
|
||||||
% ", ".join(
|
% ", ".join(
|
||||||
[
|
[
|
||||||
e if not e in missing else '<span class="missing">' + e + "</span>"
|
e if not e in missing else '<span class="missing">' + e + "</span>"
|
||||||
@ -351,7 +356,7 @@ def apo_semset_maq_status(
|
|||||||
]
|
]
|
||||||
H.append(
|
H.append(
|
||||||
f"""<div class="apo_csv_status_missing_elems">
|
f"""<div class="apo_csv_status_missing_elems">
|
||||||
<span class="fontred">Elements Apogée absents dans ScoDoc: </span>
|
<span class="fontred">Élements Apogée absents dans ScoDoc: </span>
|
||||||
<span class="apo_elems fontred">{
|
<span class="apo_elems fontred">{
|
||||||
", ".join(sorted(missing))
|
", ".join(sorted(missing))
|
||||||
}</span>
|
}</span>
|
||||||
@ -442,11 +447,11 @@ def table_apo_csv_list(semset):
|
|||||||
annee_scolaire = semset["annee_scolaire"]
|
annee_scolaire = semset["annee_scolaire"]
|
||||||
sem_id = semset["sem_id"]
|
sem_id = semset["sem_id"]
|
||||||
|
|
||||||
T = sco_etape_apogee.apo_csv_list_stored_archives(
|
rows = sco_etape_apogee.apo_csv_list_stored_archives(
|
||||||
annee_scolaire, sem_id, etapes=semset.list_etapes()
|
annee_scolaire, sem_id, etapes=semset.list_etapes()
|
||||||
)
|
)
|
||||||
|
|
||||||
for t in T:
|
for t in rows:
|
||||||
# Ajoute qq infos pour affichage:
|
# Ajoute qq infos pour affichage:
|
||||||
csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id)
|
csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id)
|
||||||
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
|
||||||
@ -484,12 +489,13 @@ def table_apo_csv_list(semset):
|
|||||||
"date_str": "Enregistré le",
|
"date_str": "Enregistré le",
|
||||||
},
|
},
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=T,
|
rows=rows,
|
||||||
html_class="table_leftalign apo_maq_list",
|
html_class="table_leftalign apo_maq_list",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
|
# base_url = '%s?formsemestre_id=%s' % (request.base_url, formsemestre_id),
|
||||||
# caption='Maquettes enregistrées',
|
# caption='Maquettes enregistrées',
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="apo_csv_list",
|
||||||
)
|
)
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
@ -582,6 +588,7 @@ def _view_etuds_page(
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
filename="students_apo",
|
filename="students_apo",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="view_etuds_page",
|
||||||
)
|
)
|
||||||
if fmt != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(fmt=fmt)
|
return tab.make_page(fmt=fmt)
|
||||||
@ -798,6 +805,7 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
|
|||||||
filename="students_" + etape_apo,
|
filename="students_" + etape_apo,
|
||||||
caption="Étudiants Apogée en " + etape_apo,
|
caption="Étudiants Apogée en " + etape_apo,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="view_apo_csv",
|
||||||
)
|
)
|
||||||
|
|
||||||
if fmt != "html":
|
if fmt != "html":
|
||||||
|
@ -93,7 +93,7 @@ import json
|
|||||||
|
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
|
|
||||||
from app.scodoc.sco_portal_apogee import get_inscrits_etape
|
from app.scodoc import sco_portal_apogee
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_utils import annee_scolaire_debut
|
from app.scodoc.sco_utils import annee_scolaire_debut
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
@ -136,11 +136,16 @@ class DataEtudiant(object):
|
|||||||
self.etudid = etudid
|
self.etudid = etudid
|
||||||
self.data_apogee = None
|
self.data_apogee = None
|
||||||
self.data_scodoc = None
|
self.data_scodoc = None
|
||||||
self.etapes = set() # l'ensemble des étapes où il est inscrit
|
self.etapes = set()
|
||||||
self.semestres = set() # l'ensemble des formsemestre_id où il est inscrit
|
"l'ensemble des étapes où il est inscrit"
|
||||||
self.tags = set() # les anomalies relevées
|
self.semestres = set()
|
||||||
self.ind_row = "-" # là où il compte dans les effectifs (ligne et colonne)
|
"l'ensemble des formsemestre_id où il est inscrit"
|
||||||
|
self.tags = set()
|
||||||
|
"les anomalies relevées"
|
||||||
|
self.ind_row = "-"
|
||||||
|
"ligne où il compte dans les effectifs"
|
||||||
self.ind_col = "-"
|
self.ind_col = "-"
|
||||||
|
"colonne où il compte dans les effectifs"
|
||||||
|
|
||||||
def add_etape(self, etape):
|
def add_etape(self, etape):
|
||||||
self.etapes.add(etape)
|
self.etapes.add(etape)
|
||||||
@ -163,9 +168,9 @@ class DataEtudiant(object):
|
|||||||
def set_ind_col(self, indicatif):
|
def set_ind_col(self, indicatif):
|
||||||
self.ind_col = indicatif
|
self.ind_col = indicatif
|
||||||
|
|
||||||
def get_identity(self):
|
def get_identity(self) -> str:
|
||||||
"""
|
"""
|
||||||
Calcul le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée)
|
Calcule le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée)
|
||||||
:return: L'identité calculée
|
:return: L'identité calculée
|
||||||
"""
|
"""
|
||||||
if self.data_scodoc is not None:
|
if self.data_scodoc is not None:
|
||||||
@ -176,9 +181,12 @@ class DataEtudiant(object):
|
|||||||
|
|
||||||
def _help() -> str:
|
def _help() -> str:
|
||||||
return """
|
return """
|
||||||
<div id="export_help" class="pas_help"> <span>Explications sur les tableaux des effectifs et liste des
|
<div id="export_help" class="pas_help">
|
||||||
étudiants</span>
|
<span>Explications sur les tableaux des effectifs
|
||||||
<div> <p>Le tableau des effectifs présente le nombre d'étudiants selon deux critères:</p>
|
et liste des étudiants</span>
|
||||||
|
<div>
|
||||||
|
<p>Le tableau des effectifs présente le nombre d'étudiants selon deux critères:
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>En colonne le statut de l'étudiant par rapport à Apogée:
|
<li>En colonne le statut de l'étudiant par rapport à Apogée:
|
||||||
<ul>
|
<ul>
|
||||||
@ -406,7 +414,8 @@ class EtapeBilan:
|
|||||||
for key_etape in self.etapes:
|
for key_etape in self.etapes:
|
||||||
annee_apogee, etapestr = key_to_values(key_etape)
|
annee_apogee, etapestr = key_to_values(key_etape)
|
||||||
self.etu_etapes[key_etape] = set()
|
self.etu_etapes[key_etape] = set()
|
||||||
for etud in get_inscrits_etape(etapestr, annee_apogee):
|
# get_inscrits_etape interroge portail Apo:
|
||||||
|
for etud in sco_portal_apogee.get_inscrits_etape(etapestr, annee_apogee):
|
||||||
key_etu = self.register_etud_apogee(etud, key_etape)
|
key_etu = self.register_etud_apogee(etud, key_etape)
|
||||||
self.etu_etapes[key_etape].add(key_etu)
|
self.etu_etapes[key_etape].add(key_etu)
|
||||||
|
|
||||||
@ -444,7 +453,6 @@ class EtapeBilan:
|
|||||||
data_etu = self.etudiants[key_etu]
|
data_etu = self.etudiants[key_etu]
|
||||||
ind_col = "-"
|
ind_col = "-"
|
||||||
ind_row = "-"
|
ind_row = "-"
|
||||||
|
|
||||||
# calcul de la colonne
|
# calcul de la colonne
|
||||||
if len(data_etu.etapes) == 1:
|
if len(data_etu.etapes) == 1:
|
||||||
ind_col = self.indicatifs[list(data_etu.etapes)[0]]
|
ind_col = self.indicatifs[list(data_etu.etapes)[0]]
|
||||||
@ -478,32 +486,34 @@ class EtapeBilan:
|
|||||||
affichage de l'html
|
affichage de l'html
|
||||||
:return: Le code html à afficher
|
:return: Le code html à afficher
|
||||||
"""
|
"""
|
||||||
|
if not sco_portal_apogee.has_portal():
|
||||||
|
return """<div id="synthese" class="semset_description">
|
||||||
|
<em>Pas de portail Apogée configuré</em>
|
||||||
|
</div>"""
|
||||||
self.load_listes() # chargement des données
|
self.load_listes() # chargement des données
|
||||||
self.dispatch() # analyse et répartition
|
self.dispatch() # analyse et répartition
|
||||||
# calcul de la liste des colonnes et des lignes de la table des effectifs
|
# calcul de la liste des colonnes et des lignes de la table des effectifs
|
||||||
self.all_rows_str = "'" + ",".join(["." + r for r in self.all_rows_ind]) + "'"
|
self.all_rows_str = "'" + ",".join(["." + r for r in self.all_rows_ind]) + "'"
|
||||||
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
|
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
|
||||||
|
|
||||||
H = [
|
return f"""
|
||||||
"""<div id="synthese" class="semset_description">
|
<div id="synthese" class="semset_description">
|
||||||
<details open="true">
|
<details open="true">
|
||||||
<summary><b>Tableau des effectifs</b>
|
<summary><b>Tableau des effectifs</b>
|
||||||
</summary>
|
</summary>
|
||||||
""",
|
{self._diagtable()}
|
||||||
self._diagtable(),
|
</details>
|
||||||
"""</details>""",
|
{self.display_tags()}
|
||||||
self.display_tags(),
|
<details open="true">
|
||||||
"""<details open="true">
|
<summary>
|
||||||
<summary><b id="effectifs">Liste des étudiants <span id="compte"></span></b>
|
<b id="effectifs">Liste des étudiants <span id="compte"></span></b>
|
||||||
</summary>
|
</summary>
|
||||||
""",
|
{entete_liste_etudiant()}
|
||||||
entete_liste_etudiant(),
|
{self.table_effectifs()}
|
||||||
self.table_effectifs(),
|
</details>
|
||||||
"""</details>""",
|
{_help()}
|
||||||
_help(),
|
</div>
|
||||||
]
|
"""
|
||||||
|
|
||||||
return "\n".join(H)
|
|
||||||
|
|
||||||
def _inc_count(self, ind_row, ind_col):
|
def _inc_count(self, ind_row, ind_col):
|
||||||
if (ind_row, ind_col) not in self.repartition:
|
if (ind_row, ind_col) not in self.repartition:
|
||||||
@ -666,7 +676,9 @@ class EtapeBilan:
|
|||||||
col_ids,
|
col_ids,
|
||||||
self.titres,
|
self.titres,
|
||||||
html_class="repartition",
|
html_class="repartition",
|
||||||
|
html_sortable=True,
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
|
table_id="apo-repartition",
|
||||||
).gen(fmt="html")
|
).gen(fmt="html")
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
@ -690,26 +702,34 @@ class EtapeBilan:
|
|||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def link_etu(etudid, nom):
|
def link_etu(etudid, nom) -> str:
|
||||||
return '<a class="stdlink" href="%s">%s</a>' % (
|
"Lien html vers fiche de l'étudiant"
|
||||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
return f"""<a class="stdlink" href="{
|
||||||
nom,
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
)
|
}">{nom}</a>"""
|
||||||
|
|
||||||
def link_semestre(self, semestre, short=False):
|
def link_semestre(self, semestre, short=False) -> str:
|
||||||
if short:
|
"Lien html vers tableau de bord semestre"
|
||||||
return (
|
key = "session_id" if short else "titremois"
|
||||||
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%('
|
sem = self.semestres[semestre]
|
||||||
"formsemestre_id)s</a> " % self.semestres[semestre]
|
return f"""<a class="stdlink" href="{
|
||||||
)
|
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
|
||||||
else:
|
formsemestre_id=sem['formsemestre_id']
|
||||||
return (
|
)}">{sem[key]}</a>
|
||||||
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s'
|
"""
|
||||||
" %(mois_debut)s - %(mois_fin)s)</a>" % self.semestres[semestre]
|
|
||||||
)
|
|
||||||
|
|
||||||
def table_effectifs(self):
|
def table_effectifs(self) -> str:
|
||||||
H = []
|
"Table html donnant les étudiants dans chaque semestre"
|
||||||
|
H = [
|
||||||
|
"""
|
||||||
|
<style>
|
||||||
|
table#apo-detail td.semestre {
|
||||||
|
white-space: nowrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
]
|
||||||
|
|
||||||
col_ids = ["tag", "etudiant", "prenom", "nip", "semestre", "apogee", "annee"]
|
col_ids = ["tag", "etudiant", "prenom", "nip", "semestre", "apogee", "annee"]
|
||||||
titles = {
|
titles = {
|
||||||
@ -762,9 +782,10 @@ class EtapeBilan:
|
|||||||
rows,
|
rows,
|
||||||
col_ids,
|
col_ids,
|
||||||
titles,
|
titles,
|
||||||
table_id="detail",
|
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
|
html_with_td_classes=True,
|
||||||
|
table_id="apo-detail",
|
||||||
).gen(fmt="html")
|
).gen(fmt="html")
|
||||||
)
|
)
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
@ -367,6 +367,9 @@ def evaluation_create_form(
|
|||||||
+ "\n".join(H)
|
+ "\n".join(H)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ tf[1]
|
+ tf[1]
|
||||||
|
+ render_template(
|
||||||
|
"scodoc/forms/evaluation_edit.j2",
|
||||||
|
)
|
||||||
+ render_template(
|
+ render_template(
|
||||||
"scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl
|
"scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl
|
||||||
)
|
)
|
||||||
|
@ -70,8 +70,8 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
Colonnes:
|
Colonnes:
|
||||||
- code (UE ou module),
|
- code (UE ou module),
|
||||||
- titre
|
- titre
|
||||||
|
- type évaluation
|
||||||
- complete
|
- complete
|
||||||
- publiée
|
|
||||||
- inscrits (non dem. ni def.)
|
- inscrits (non dem. ni def.)
|
||||||
- nb notes manquantes
|
- nb notes manquantes
|
||||||
- nb ATT
|
- nb ATT
|
||||||
@ -81,9 +81,10 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
rows = []
|
rows = []
|
||||||
titles = {
|
titles = {
|
||||||
"type": "",
|
"type": "",
|
||||||
"code": "Code",
|
"code": "Module",
|
||||||
"titre": "",
|
"titre": "",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
|
"type_evaluation": "Type",
|
||||||
"complete": "Comptée",
|
"complete": "Comptée",
|
||||||
"inscrits": "Inscrits",
|
"inscrits": "Inscrits",
|
||||||
"manquantes": "Manquantes", # notes eval non entrées
|
"manquantes": "Manquantes", # notes eval non entrées
|
||||||
@ -114,7 +115,9 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
rows.append(row)
|
rows.append(row)
|
||||||
line_idx += 1
|
line_idx += 1
|
||||||
for evaluation_id in modimpl_results.evals_notes:
|
for evaluation_id in modimpl_results.evals_notes:
|
||||||
e = db.session.get(Evaluation, evaluation_id)
|
e: Evaluation = db.session.get(Evaluation, evaluation_id)
|
||||||
|
if e is None:
|
||||||
|
continue # ignore errors (rare race conditions?)
|
||||||
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
eval_etat = modimpl_results.evaluations_etat[evaluation_id]
|
||||||
row = {
|
row = {
|
||||||
"type": "",
|
"type": "",
|
||||||
@ -128,6 +131,7 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
"_titre_target_attrs": 'class="discretelink"',
|
"_titre_target_attrs": 'class="discretelink"',
|
||||||
"date": e.date_debut.strftime(scu.DATE_FMT) if e.date_debut else "",
|
"date": e.date_debut.strftime(scu.DATE_FMT) if e.date_debut else "",
|
||||||
"_date_order": e.date_debut.isoformat() if e.date_debut else "",
|
"_date_order": e.date_debut.isoformat() if e.date_debut else "",
|
||||||
|
"type_evaluation": e.type_abbrev(),
|
||||||
"complete": "oui" if eval_etat.is_complete else "non",
|
"complete": "oui" if eval_etat.is_complete else "non",
|
||||||
"_complete_target": "#",
|
"_complete_target": "#",
|
||||||
"_complete_target_attrs": (
|
"_complete_target_attrs": (
|
||||||
|
@ -427,7 +427,9 @@ class JourEval(sco_gen_cal.Jour):
|
|||||||
)
|
)
|
||||||
heure_fin_txt = e.date_fin.strftime(scu.TIME_FMT) if e.date_fin else ""
|
heure_fin_txt = e.date_fin.strftime(scu.TIME_FMT) if e.date_fin else ""
|
||||||
|
|
||||||
title = f"{e.description or e.moduleimpl.module.titre_str()}"
|
title = f"{e.moduleimpl.module.titre_str()}"
|
||||||
|
if e.description:
|
||||||
|
title += f" : {e.description}"
|
||||||
if heure_debut_txt:
|
if heure_debut_txt:
|
||||||
title += f" de {heure_debut_txt} à {heure_fin_txt}"
|
title += f" de {heure_debut_txt} à {heure_fin_txt}"
|
||||||
|
|
||||||
@ -633,6 +635,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
|||||||
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
|
||||||
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
|
||||||
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||||
|
table_id="formsemestre_evaluations_delai_correction",
|
||||||
)
|
)
|
||||||
return tab.make_page(fmt=fmt)
|
return tab.make_page(fmt=fmt)
|
||||||
|
|
||||||
|
@ -45,13 +45,17 @@ class ScoInvalidCSRF(ScoException):
|
|||||||
|
|
||||||
|
|
||||||
class ScoValueError(ScoException):
|
class ScoValueError(ScoException):
|
||||||
"Exception avec page d'erreur utilisateur, et qui stoque dest_url"
|
"""Exception avec page d'erreur utilisateur
|
||||||
|
- dest_url : url où aller après la page d'erreur
|
||||||
|
- safe (default False): si vrai, affiche le message non html quoté.
|
||||||
|
"""
|
||||||
|
|
||||||
# mal nommée: super classe de toutes les exceptions avec page
|
# mal nommée: super classe de toutes les exceptions avec page
|
||||||
# d'erreur gentille.
|
# d'erreur gentille.
|
||||||
def __init__(self, msg, dest_url=None):
|
def __init__(self, msg, dest_url=None, safe=False):
|
||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
self.dest_url = dest_url
|
self.dest_url = dest_url
|
||||||
|
self.safe = safe # utilisé par template sco_value_error.j2
|
||||||
|
|
||||||
|
|
||||||
class ScoPermissionDenied(ScoValueError):
|
class ScoPermissionDenied(ScoValueError):
|
||||||
|
@ -106,6 +106,7 @@ def _build_results_table(start_date=None, end_date=None, types_parcours=[]):
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="export_result_table",
|
||||||
)
|
)
|
||||||
return tab, semlist
|
return tab, semlist
|
||||||
|
|
||||||
|
@ -32,8 +32,7 @@ from flask import url_for, g, request
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app
|
import app
|
||||||
from app.models import Departement
|
from app.models import Departement, Identite
|
||||||
import app.scodoc.sco_utils as scu
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
@ -55,7 +54,9 @@ def form_search_etud(
|
|||||||
"form recherche par nom"
|
"form recherche par nom"
|
||||||
H = []
|
H = []
|
||||||
H.append(
|
H.append(
|
||||||
f"""<form action="{ url_for("scolar.search_etud_in_dept", scodoc_dept=g.scodoc_dept) }" method="POST">
|
f"""<form action="{
|
||||||
|
url_for("scolar.search_etud_in_dept", scodoc_dept=g.scodoc_dept)
|
||||||
|
}" method="POST">
|
||||||
<b>{title}</b>
|
<b>{title}</b>
|
||||||
<input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
|
<input type="text" name="expnom" class="in-expnom" width="12" spellcheck="false" value="">
|
||||||
<input type="submit" value="Chercher">
|
<input type="submit" value="Chercher">
|
||||||
@ -100,9 +101,9 @@ def form_search_etud(
|
|||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
def search_etuds_infos_from_exp(expnom: str = "") -> list[dict]:
|
def search_etuds_infos_from_exp(expnom: str = "") -> list[Identite]:
|
||||||
"""Cherche étudiants, expnom peut être, dans cet ordre:
|
"""Cherche étudiants, expnom peut être, dans cet ordre:
|
||||||
un etudid (int), un code NIP, ou le début d'un nom.
|
un etudid (int), un code NIP, ou une partie d'un nom (case insensitive).
|
||||||
"""
|
"""
|
||||||
if not isinstance(expnom, int) and len(expnom) <= 1:
|
if not isinstance(expnom, int) and len(expnom) <= 1:
|
||||||
return [] # si expnom est trop court, n'affiche rien
|
return [] # si expnom est trop court, n'affiche rien
|
||||||
@ -111,13 +112,22 @@ def search_etuds_infos_from_exp(expnom: str = "") -> list[dict]:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
etudid = None
|
etudid = None
|
||||||
if etudid is not None:
|
if etudid is not None:
|
||||||
etuds = sco_etud.get_etud_info(filled=True, etudid=expnom)
|
etud = Identite.query.filter_by(dept_id=g.scodoc_dept_id, id=etudid).first()
|
||||||
if len(etuds) == 1:
|
if etud:
|
||||||
return etuds
|
return [etud]
|
||||||
expnom_str = str(expnom)
|
expnom_str = str(expnom)
|
||||||
if scu.is_valid_code_nip(expnom_str):
|
if scu.is_valid_code_nip(expnom_str):
|
||||||
return search_etuds_infos(code_nip=expnom_str)
|
etuds = Identite.query.filter_by(
|
||||||
return search_etuds_infos(expnom=expnom_str)
|
dept_id=g.scodoc_dept_id, code_nip=expnom_str
|
||||||
|
).all()
|
||||||
|
if etuds:
|
||||||
|
return etuds
|
||||||
|
|
||||||
|
return (
|
||||||
|
Identite.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
.filter(Identite.nom.op("~*")(expnom_str))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def search_etud_in_dept(expnom=""):
|
def search_etud_in_dept(expnom=""):
|
||||||
@ -152,7 +162,7 @@ def search_etud_in_dept(expnom=""):
|
|||||||
|
|
||||||
if len(etuds) == 1:
|
if len(etuds) == 1:
|
||||||
# va directement a la fiche
|
# va directement a la fiche
|
||||||
url_args["etudid"] = etuds[0]["etudid"]
|
url_args["etudid"] = etuds[0].id
|
||||||
return flask.redirect(url_for(endpoint, **url_args))
|
return flask.redirect(url_for(endpoint, **url_args))
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
@ -179,14 +189,39 @@ def search_etud_in_dept(expnom=""):
|
|||||||
)
|
)
|
||||||
if len(etuds) > 0:
|
if len(etuds) > 0:
|
||||||
# Choix dans la liste des résultats:
|
# Choix dans la liste des résultats:
|
||||||
|
rows = []
|
||||||
|
e: Identite
|
||||||
for e in etuds:
|
for e in etuds:
|
||||||
url_args["etudid"] = e["etudid"]
|
url_args["etudid"] = e.id
|
||||||
target = url_for(endpoint, **url_args)
|
target = url_for(endpoint, **url_args)
|
||||||
e["_nomprenom_target"] = target
|
cur_inscription = e.inscription_courante()
|
||||||
e["inscription_target"] = target
|
inscription = (
|
||||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
e.inscription_descr().get("inscription_str", "")
|
||||||
sco_groups.etud_add_group_infos(
|
if cur_inscription
|
||||||
e, e["cursem"]["formsemestre_id"] if e["cursem"] else None
|
else ""
|
||||||
|
)
|
||||||
|
groupes = (
|
||||||
|
", ".join(
|
||||||
|
gr.group_name
|
||||||
|
for gr in sco_groups.get_etud_formsemestre_groups(
|
||||||
|
e, cur_inscription.formsemestre
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if cur_inscription
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"code_nip": e.code_nip or "",
|
||||||
|
"etudid": e.id,
|
||||||
|
"inscription": inscription,
|
||||||
|
"inscription_target": target,
|
||||||
|
"groupes": groupes,
|
||||||
|
"nomprenom": e.nomprenom,
|
||||||
|
"_nomprenom_target": target,
|
||||||
|
"_nomprenom_td_attrs": f'id="{e.id}" class="etudinfo"',
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -197,10 +232,11 @@ def search_etud_in_dept(expnom=""):
|
|||||||
"inscription": "Inscription",
|
"inscription": "Inscription",
|
||||||
"groupes": "Groupes",
|
"groupes": "Groupes",
|
||||||
},
|
},
|
||||||
rows=etuds,
|
rows=rows,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="search_etud_in_dept",
|
||||||
)
|
)
|
||||||
H.append(tab.html())
|
H.append(tab.html())
|
||||||
if len(etuds) > 20: # si la page est grande
|
if len(etuds) > 20: # si la page est grande
|
||||||
@ -213,15 +249,16 @@ def search_etud_in_dept(expnom=""):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append('<h2 style="color: red;">Aucun résultat pour "%s".</h2>' % expnom)
|
H.append(f'<h2 style="color: red;">Aucun résultat pour "{expnom}".</h2>')
|
||||||
H.append(
|
H.append(
|
||||||
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant. Saisir au moins deux caractères.</p>"""
|
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP
|
||||||
|
de l'étudiant. Saisir au moins deux caractères.</p>"""
|
||||||
)
|
)
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
# Was chercheEtudsInfo()
|
# Was chercheEtudsInfo()
|
||||||
def search_etuds_infos(expnom=None, code_nip=None):
|
def search_etuds_infos(expnom=None, code_nip=None) -> list[dict]:
|
||||||
"""recherche les étudiants correspondants à expnom ou au code_nip
|
"""recherche les étudiants correspondants à expnom ou au code_nip
|
||||||
et ramene liste de mappings utilisables en DTML.
|
et ramene liste de mappings utilisables en DTML.
|
||||||
"""
|
"""
|
||||||
@ -264,7 +301,7 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
FROM identite
|
FROM identite
|
||||||
WHERE
|
WHERE
|
||||||
dept_id = %(dept_id)s
|
dept_id = %(dept_id)s
|
||||||
AND code_nip LIKE %(beginning)s
|
AND code_nip ILIKE %(beginning)s
|
||||||
ORDER BY nom
|
ORDER BY nom
|
||||||
""",
|
""",
|
||||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||||
@ -283,7 +320,7 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
FROM identite
|
FROM identite
|
||||||
WHERE
|
WHERE
|
||||||
dept_id = %(dept_id)s
|
dept_id = %(dept_id)s
|
||||||
AND nom LIKE %(beginning)s
|
AND nom ILIKE %(beginning)s
|
||||||
ORDER BY nom
|
ORDER BY nom
|
||||||
""",
|
""",
|
||||||
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
{"beginning": term + "%", "dept_id": g.scodoc_dept_id},
|
||||||
@ -348,6 +385,7 @@ def table_etud_in_accessible_depts(expnom=None):
|
|||||||
rows=etuds,
|
rows=etuds,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
|
table_id="etud_in_accessible_depts",
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append('<div class="table_etud_in_dept">')
|
H.append('<div class="table_etud_in_dept">')
|
||||||
@ -383,13 +421,13 @@ def search_inscr_etud_by_nip(code_nip, fmt="json"):
|
|||||||
"""
|
"""
|
||||||
result, _ = search_etud_in_accessible_depts(code_nip=code_nip)
|
result, _ = search_etud_in_accessible_depts(code_nip=code_nip)
|
||||||
|
|
||||||
T = []
|
rows = []
|
||||||
for etuds in result:
|
for etuds in result:
|
||||||
if etuds:
|
if etuds:
|
||||||
dept_id = etuds[0]["dept"]
|
dept_id = etuds[0]["dept"]
|
||||||
for e in etuds:
|
for e in etuds:
|
||||||
for sem in e["sems"]:
|
for sem in e["sems"]:
|
||||||
T.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"dept": dept_id,
|
"dept": dept_id,
|
||||||
"etudid": e["etudid"],
|
"etudid": e["etudid"],
|
||||||
@ -414,6 +452,6 @@ def search_inscr_etud_by_nip(code_nip, fmt="json"):
|
|||||||
"date_debut_iso",
|
"date_debut_iso",
|
||||||
"date_fin_iso",
|
"date_fin_iso",
|
||||||
)
|
)
|
||||||
tab = GenTable(columns_ids=columns_ids, rows=T)
|
tab = GenTable(columns_ids=columns_ids, rows=rows, table_id="inscr_etud_by_nip")
|
||||||
|
|
||||||
return tab.make_page(fmt=fmt, with_html_headers=False, publish=True)
|
return tab.make_page(fmt=fmt, with_html_headers=False, publish=True)
|
||||||
|
@ -143,6 +143,7 @@ def formation_export_dict(
|
|||||||
|
|
||||||
if not export_codes_apo:
|
if not export_codes_apo:
|
||||||
ue_dict.pop("code_apogee", None)
|
ue_dict.pop("code_apogee", None)
|
||||||
|
ue_dict.pop("code_apogee_rcue", None)
|
||||||
if ue_dict.get("ects") is None:
|
if ue_dict.get("ects") is None:
|
||||||
ue_dict.pop("ects", None)
|
ue_dict.pop("ects", None)
|
||||||
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
mats = sco_edit_matiere.matiere_list({"ue_id": ue.id})
|
||||||
@ -649,20 +650,20 @@ def formation_list_table(detail: bool) -> GenTable:
|
|||||||
"semestres_ues": "Semestres avec UEs",
|
"semestres_ues": "Semestres avec UEs",
|
||||||
}
|
}
|
||||||
return GenTable(
|
return GenTable(
|
||||||
columns_ids=columns_ids,
|
|
||||||
rows=rows,
|
|
||||||
titles=titles,
|
|
||||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
||||||
caption=title,
|
|
||||||
html_caption=title,
|
|
||||||
table_id="formation_list_table",
|
|
||||||
html_class="formation_list_table table_leftalign",
|
|
||||||
html_with_td_classes=True,
|
|
||||||
html_sortable=True,
|
|
||||||
base_url=f"{request.base_url}" + ("?detail=on" if detail else ""),
|
base_url=f"{request.base_url}" + ("?detail=on" if detail else ""),
|
||||||
|
caption=title,
|
||||||
|
columns_ids=columns_ids,
|
||||||
|
html_caption=title,
|
||||||
|
html_class="formation_list_table table_leftalign",
|
||||||
|
html_sortable=True,
|
||||||
|
html_with_td_classes=True,
|
||||||
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
page_title=title,
|
page_title=title,
|
||||||
pdf_title=title,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
rows=rows,
|
||||||
|
table_id="formation_list_table",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -229,11 +229,14 @@ def etapes_apo_str(etapes):
|
|||||||
return ", ".join([str(x) for x in etapes])
|
return ", ".join([str(x) for x in etapes])
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_create(args, silent=False):
|
def do_formsemestre_create( # DEPRECATED, use FormSemestre.create_formsemestre()
|
||||||
|
args, silent=False
|
||||||
|
):
|
||||||
"create a formsemestre"
|
"create a formsemestre"
|
||||||
from app.models import ScolarNews
|
from app.models import ScolarNews
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
|
|
||||||
|
log("Warning: do_formsemestre_create is deprecated")
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
formsemestre_id = _formsemestreEditor.create(cnx, args)
|
formsemestre_id = _formsemestreEditor.create(cnx, args)
|
||||||
if args["etapes"]:
|
if args["etapes"]:
|
||||||
@ -419,49 +422,23 @@ def sem_set_responsable_name(sem):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sem_in_semestre_scolaire(
|
def sem_in_annee_scolaire(sem: dict, year=False): # OBSOLETE
|
||||||
sem,
|
|
||||||
year=False,
|
|
||||||
periode=None,
|
|
||||||
mois_pivot_annee=scu.MONTH_DEBUT_ANNEE_SCOLAIRE,
|
|
||||||
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2,
|
|
||||||
) -> bool:
|
|
||||||
"""Vrai si la date du début du semestre est dans la période indiquée (1,2,0)
|
|
||||||
du semestre `periode` de l'année scolaire indiquée
|
|
||||||
(ou, à défaut, de celle en cours).
|
|
||||||
|
|
||||||
La période utilise les même conventions que semset["sem_id"];
|
|
||||||
* 1 : première période
|
|
||||||
* 2 : deuxième période
|
|
||||||
* 0 ou période non précisée: annualisé (donc inclut toutes les périodes)
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
if not year:
|
|
||||||
year = scu.annee_scolaire()
|
|
||||||
# n'utilise pas le jour pivot
|
|
||||||
jour_pivot_annee = jour_pivot_periode = 1
|
|
||||||
# calcule l'année universitaire et la période
|
|
||||||
sem_annee, sem_periode = FormSemestre.comp_periode(
|
|
||||||
datetime.datetime.fromisoformat(sem["date_debut_iso"]),
|
|
||||||
mois_pivot_annee,
|
|
||||||
mois_pivot_periode,
|
|
||||||
jour_pivot_annee,
|
|
||||||
jour_pivot_periode,
|
|
||||||
)
|
|
||||||
if periode is None or periode == 0:
|
|
||||||
return sem_annee == year
|
|
||||||
return sem_annee == year and sem_periode == periode
|
|
||||||
|
|
||||||
|
|
||||||
def sem_in_annee_scolaire(sem, year=False):
|
|
||||||
"""Test si sem appartient à l'année scolaire year (int).
|
"""Test si sem appartient à l'année scolaire year (int).
|
||||||
N'utilise que la date de début, pivot au 1er août.
|
N'utilise que la date de début, pivot au 1er août.
|
||||||
Si année non specifiée, année scolaire courante
|
Si année non specifiée, année scolaire courante
|
||||||
"""
|
"""
|
||||||
return sem_in_semestre_scolaire(sem, year, periode=0)
|
return FormSemestre.est_in_semestre_scolaire(
|
||||||
|
datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sem_est_courant(sem): # -> FormSemestre.est_courant
|
def sem_in_semestre_scolaire(sem, year=False, periode=None): # OBSOLETE
|
||||||
|
return FormSemestre.est_in_semestre_scolaire(
|
||||||
|
datetime.date.fromisoformat(sem["date_debut_iso"]), year, periode=periode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sem_est_courant(sem: dict): # -> FormSemestre.est_courant
|
||||||
"""Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
|
"""Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
|
||||||
now = time.strftime("%Y-%m-%d")
|
now = time.strftime("%Y-%m-%d")
|
||||||
debut = ndb.DateDMYtoISO(sem["date_debut"])
|
debut = ndb.DateDMYtoISO(sem["date_debut"])
|
||||||
@ -527,15 +504,16 @@ def table_formsemestres(
|
|||||||
preferences = sco_preferences.SemPreferences()
|
preferences = sco_preferences.SemPreferences()
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=sems,
|
|
||||||
titles=titles,
|
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
|
html_empty_element="<p><em>aucun résultat</em></p>",
|
||||||
|
html_next_section=html_next_section,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_title=html_title,
|
html_title=html_title,
|
||||||
html_next_section=html_next_section,
|
|
||||||
html_empty_element="<p><em>aucun résultat</em></p>",
|
|
||||||
page_title="Semestres",
|
page_title="Semestres",
|
||||||
preferences=preferences,
|
preferences=preferences,
|
||||||
|
rows=sems,
|
||||||
|
table_id="table_formsemestres",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
@ -37,16 +37,17 @@ from app import db
|
|||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Module,
|
|
||||||
ModuleImpl,
|
|
||||||
Evaluation,
|
|
||||||
UniteEns,
|
|
||||||
ScoDocSiteConfig,
|
|
||||||
ScolarFormSemestreValidation,
|
|
||||||
ScolarAutorisationInscription,
|
|
||||||
ApcValidationAnnee,
|
ApcValidationAnnee,
|
||||||
ApcValidationRCUE,
|
ApcValidationRCUE,
|
||||||
|
Evaluation,
|
||||||
|
FormSemestreUECoef,
|
||||||
|
Module,
|
||||||
|
ModuleImpl,
|
||||||
|
ScoDocSiteConfig,
|
||||||
|
ScolarAutorisationInscription,
|
||||||
|
ScolarFormSemestreValidation,
|
||||||
ScolarNews,
|
ScolarNews,
|
||||||
|
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
|
||||||
@ -438,12 +439,14 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
"elt_sem_apo",
|
"elt_sem_apo",
|
||||||
{
|
{
|
||||||
"size": 32,
|
"size": 32,
|
||||||
"title": "Element(s) Apogée:",
|
"title": "Element(s) Apogée sem.:",
|
||||||
"explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.",
|
"explanation": """associé(s) au résultat du semestre (ex: VRTW1).
|
||||||
"allow_null": not sco_preferences.get_preference(
|
Inutile en BUT. Séparés par des virgules.""",
|
||||||
"always_require_apo_sem_codes"
|
"allow_null": (
|
||||||
)
|
not sco_preferences.get_preference("always_require_apo_sem_codes")
|
||||||
or (formsemestre and formsemestre.modalite == "EXT"),
|
or (formsemestre and formsemestre.modalite == "EXT")
|
||||||
|
or (formsemestre and formsemestre.formation.is_apc())
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -452,7 +455,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
"elt_annee_apo",
|
"elt_annee_apo",
|
||||||
{
|
{
|
||||||
"size": 32,
|
"size": 32,
|
||||||
"title": "Element(s) Apogée:",
|
"title": "Element(s) Apogée année:",
|
||||||
"explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.",
|
"explanation": "associé(s) au résultat de l'année (ex: VRT1A). Séparés par des virgules.",
|
||||||
"allow_null": not sco_preferences.get_preference(
|
"allow_null": not sco_preferences.get_preference(
|
||||||
"always_require_apo_sem_codes"
|
"always_require_apo_sem_codes"
|
||||||
@ -1249,7 +1252,7 @@ def formsemestre_clone(formsemestre_id):
|
|||||||
raise ScoValueError("id responsable invalide")
|
raise ScoValueError("id responsable invalide")
|
||||||
new_formsemestre_id = do_formsemestre_clone(
|
new_formsemestre_id = do_formsemestre_clone(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
resp.id,
|
resp,
|
||||||
tf[2]["date_debut"],
|
tf[2]["date_debut"],
|
||||||
tf[2]["date_fin"],
|
tf[2]["date_fin"],
|
||||||
clone_evaluations=tf[2]["clone_evaluations"],
|
clone_evaluations=tf[2]["clone_evaluations"],
|
||||||
@ -1267,7 +1270,7 @@ def formsemestre_clone(formsemestre_id):
|
|||||||
|
|
||||||
def do_formsemestre_clone(
|
def do_formsemestre_clone(
|
||||||
orig_formsemestre_id,
|
orig_formsemestre_id,
|
||||||
responsable_id, # new resp.
|
responsable: User, # new resp.
|
||||||
date_debut,
|
date_debut,
|
||||||
date_fin, # 'dd/mm/yyyy'
|
date_fin, # 'dd/mm/yyyy'
|
||||||
clone_evaluations=False,
|
clone_evaluations=False,
|
||||||
@ -1280,49 +1283,63 @@ def do_formsemestre_clone(
|
|||||||
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
||||||
orig_formsemestre_id
|
orig_formsemestre_id
|
||||||
)
|
)
|
||||||
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
# 1- create sem
|
# 1- create sem
|
||||||
args = orig_sem.copy()
|
args = formsemestre_orig.to_dict()
|
||||||
del args["formsemestre_id"]
|
del args["formsemestre_id"]
|
||||||
args["responsables"] = [responsable_id]
|
del args["id"]
|
||||||
|
del args["parcours"] # copiés ensuite
|
||||||
|
args["responsables"] = [responsable]
|
||||||
args["date_debut"] = date_debut
|
args["date_debut"] = date_debut
|
||||||
args["date_fin"] = date_fin
|
args["date_fin"] = date_fin
|
||||||
args["etat"] = 1 # non verrouillé
|
args["etat"] = 1 # non verrouillé
|
||||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
|
||||||
log(f"created formsemestre {formsemestre_id}")
|
formsemestre = FormSemestre.create_formsemestre(args)
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
log(f"created formsemestre {formsemestre}")
|
||||||
# 2- create moduleimpls
|
# 2- create moduleimpls
|
||||||
modimpl_orig: ModuleImpl
|
modimpl_orig: ModuleImpl
|
||||||
for modimpl_orig in formsemestre_orig.modimpls:
|
for modimpl_orig in formsemestre_orig.modimpls:
|
||||||
|
assert isinstance(modimpl_orig, ModuleImpl)
|
||||||
|
assert isinstance(modimpl_orig.id, int)
|
||||||
|
log(f"cloning {modimpl_orig}")
|
||||||
args = modimpl_orig.to_dict(with_module=False)
|
args = modimpl_orig.to_dict(with_module=False)
|
||||||
args["formsemestre_id"] = formsemestre_id
|
args["formsemestre_id"] = formsemestre.id
|
||||||
modimpl_new = ModuleImpl.create_from_dict(args)
|
modimpl_new = ModuleImpl.create_from_dict(args)
|
||||||
|
log(f"created ModuleImpl from {args}")
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
# copy enseignants
|
# copy enseignants
|
||||||
for ens in modimpl_orig.enseignants:
|
for ens in modimpl_orig.enseignants:
|
||||||
modimpl_new.enseignants.append(ens)
|
modimpl_new.enseignants.append(ens)
|
||||||
db.session.add(modimpl_new)
|
db.session.add(modimpl_new)
|
||||||
|
db.session.flush()
|
||||||
|
log(f"new moduleimpl.id = {modimpl_new.id}")
|
||||||
# optionally, copy evaluations
|
# optionally, copy evaluations
|
||||||
if clone_evaluations:
|
if clone_evaluations:
|
||||||
|
e: Evaluation
|
||||||
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
||||||
|
log(f"cloning evaluation {e.id}")
|
||||||
# copie en enlevant la date
|
# copie en enlevant la date
|
||||||
new_eval = e.clone(
|
args = dict(e.__dict__)
|
||||||
not_copying=("date_debut", "date_fin", "moduleimpl_id")
|
args.pop("_sa_instance_state")
|
||||||
)
|
args.pop("id")
|
||||||
new_eval.moduleimpl_id = modimpl_new.id
|
args["moduleimpl_id"] = modimpl_new.id
|
||||||
|
new_eval = Evaluation(**args)
|
||||||
|
db.session.add(new_eval)
|
||||||
|
db.session.commit()
|
||||||
# Copie les poids APC de l'évaluation
|
# Copie les poids APC de l'évaluation
|
||||||
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# 3- copy uecoefs
|
# 3- copy uecoefs
|
||||||
objs = sco_formsemestre.formsemestre_uecoef_list(
|
for ue_coef in FormSemestreUECoef.query.filter_by(
|
||||||
cnx, args={"formsemestre_id": orig_formsemestre_id}
|
formsemestre_id=formsemestre_orig.id
|
||||||
)
|
):
|
||||||
for obj in objs:
|
new_ue_coef = FormSemestreUECoef(
|
||||||
args = obj.copy()
|
formsemestre_id=formsemestre.id,
|
||||||
args["formsemestre_id"] = formsemestre_id
|
ue_id=ue_coef.ue_id,
|
||||||
_ = sco_formsemestre.formsemestre_uecoef_create(cnx, args)
|
coefficient=ue_coef.coefficient,
|
||||||
|
)
|
||||||
|
db.session.add(new_ue_coef)
|
||||||
|
db.session.flush()
|
||||||
|
|
||||||
# NB: don't copy notes_formsemestre_custommenu (usually specific)
|
# NB: don't copy notes_formsemestre_custommenu (usually specific)
|
||||||
|
|
||||||
@ -1334,11 +1351,11 @@ def do_formsemestre_clone(
|
|||||||
if not prefs.is_global(pname):
|
if not prefs.is_global(pname):
|
||||||
pvalue = prefs[pname]
|
pvalue = prefs[pname]
|
||||||
try:
|
try:
|
||||||
prefs.base_prefs.set(formsemestre_id, pname, pvalue)
|
prefs.base_prefs.set(formsemestre.id, pname, pvalue)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
log(
|
log(
|
||||||
"do_formsemestre_clone: ignoring old preference %s=%s for %s"
|
f"""do_formsemestre_clone: ignoring old preference {
|
||||||
% (pname, pvalue, formsemestre_id)
|
pname}={pvalue} for {formsemestre}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5- Copie les parcours
|
# 5- Copie les parcours
|
||||||
@ -1349,10 +1366,10 @@ def do_formsemestre_clone(
|
|||||||
# 6- Copy partitions and groups
|
# 6- Copy partitions and groups
|
||||||
if clone_partitions:
|
if clone_partitions:
|
||||||
sco_groups_copy.clone_partitions_and_groups(
|
sco_groups_copy.clone_partitions_and_groups(
|
||||||
orig_formsemestre_id, formsemestre_id
|
orig_formsemestre_id, formsemestre.id
|
||||||
)
|
)
|
||||||
|
|
||||||
return formsemestre_id
|
return formsemestre.id
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||||
|
@ -731,20 +731,21 @@ def formsemestre_description_table(
|
|||||||
rows.append(sums)
|
rows.append(sums)
|
||||||
|
|
||||||
return GenTable(
|
return GenTable(
|
||||||
columns_ids=columns_ids,
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}&with_evals={with_evals}",
|
||||||
rows=rows,
|
|
||||||
titles=titles,
|
|
||||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
||||||
caption=title,
|
caption=title,
|
||||||
|
columns_ids=columns_ids,
|
||||||
html_caption=title,
|
html_caption=title,
|
||||||
html_class="table_leftalign formsemestre_description",
|
html_class="table_leftalign formsemestre_description",
|
||||||
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}&with_evals={with_evals}",
|
|
||||||
page_title=title,
|
|
||||||
html_title=html_sco_header.html_sem_header(
|
html_title=html_sco_header.html_sem_header(
|
||||||
"Description du semestre", with_page_header=False
|
"Description du semestre", with_page_header=False
|
||||||
),
|
),
|
||||||
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
|
page_title=title,
|
||||||
pdf_title=title,
|
pdf_title=title,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
rows=rows,
|
||||||
|
table_id="formsemestre_description_table",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -798,7 +799,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
<div class="sem-groups-partition-titre">{
|
<div class="sem-groups-partition-titre">{
|
||||||
'Groupes de ' + partition.partition_name
|
'Groupes de ' + partition.partition_name
|
||||||
if partition.partition_name else
|
if partition.partition_name else
|
||||||
'Tous les étudiants'}
|
('aucun étudiant inscrit' if partition_is_empty else 'Tous les étudiants')}
|
||||||
</div>
|
</div>
|
||||||
<div class="sem-groups-partition-titre">{
|
<div class="sem-groups-partition-titre">{
|
||||||
"Assiduité" if not partition_is_empty else ""
|
"Assiduité" if not partition_is_empty else ""
|
||||||
@ -826,6 +827,55 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sem-groups-assi">
|
<div class="sem-groups-assi">
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if can_edit_abs:
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div>
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
day=datetime.date.today().isoformat(),
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
Saisir l'assiduité</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# YYYY-Www (ISO 8601) :
|
||||||
|
current_week: str = datetime.datetime.now().strftime("%G-W%V")
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div>
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_hebdo",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
week=current_week,
|
||||||
|
)}">Saisie hebdomadaire</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if can_edit_abs:
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div>
|
||||||
|
<a class="stdlink" href="{
|
||||||
|
url_for("assiduites.bilan_dept",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
group_ids=group.id,
|
||||||
|
)}">
|
||||||
|
Justificatifs en attente</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
<div>
|
<div>
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for("assiduites.visu_assi_group",
|
url_for("assiduites.visu_assi_group",
|
||||||
@ -838,52 +888,9 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
if can_edit_abs:
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<div>
|
|
||||||
<a class="stdlink" href="{
|
|
||||||
url_for("assiduites.visu_assiduites_group",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
jour = datetime.date.today().isoformat(),
|
|
||||||
group_ids=group.id,
|
|
||||||
)}">
|
|
||||||
Visualiser</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a class="stdlink" href="{
|
|
||||||
url_for("assiduites.signal_assiduites_group",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
jour=datetime.date.today().isoformat(),
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
group_ids=group.id,
|
|
||||||
)}">
|
|
||||||
Saisie journalière</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a class="stdlink" href="{
|
|
||||||
url_for("assiduites.signal_assiduites_diff",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
group_ids=group.id,
|
|
||||||
)}">
|
|
||||||
Saisie différée</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a class="stdlink" href="{
|
|
||||||
url_for("assiduites.bilan_dept",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
group_ids=group.id,
|
|
||||||
)}">
|
|
||||||
Justificatifs en attente</a>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append("</div>") # /sem-groups-assi
|
H.append("</div>") # /sem-groups-assi
|
||||||
if partition_is_empty:
|
if partition_is_empty and not partition.is_default():
|
||||||
H.append(
|
H.append(
|
||||||
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
||||||
)
|
)
|
||||||
@ -1191,17 +1198,7 @@ def formsemestre_tableau_modules(
|
|||||||
mod_descr = "Module " + (mod.titre or "")
|
mod_descr = "Module " + (mod.titre or "")
|
||||||
is_apc = mod.is_apc() # SAE ou ressource
|
is_apc = mod.is_apc() # SAE ou ressource
|
||||||
if is_apc:
|
if is_apc:
|
||||||
coef_descr = ", ".join(
|
mod_descr += " " + mod.get_ue_coefs_descr()
|
||||||
[
|
|
||||||
f"{ue.acronyme}: {co}"
|
|
||||||
for ue, co in mod.ue_coefs_list()
|
|
||||||
if isinstance(co, float) and co > 0
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if coef_descr:
|
|
||||||
mod_descr += " Coefs: " + coef_descr
|
|
||||||
else:
|
|
||||||
mod_descr += " (pas de coefficients) "
|
|
||||||
else:
|
else:
|
||||||
mod_descr += ", coef. " + str(mod.coefficient)
|
mod_descr += ", coef. " + str(mod.coefficient)
|
||||||
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
|
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
|
||||||
|
@ -116,7 +116,7 @@ def formsemestre_validation_etud_form(
|
|||||||
check = True
|
check = True
|
||||||
|
|
||||||
etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud_d = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud_d, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
if not Se.sem["etat"]:
|
if not Se.sem["etat"]:
|
||||||
raise ScoValueError("validation: semestre verrouille")
|
raise ScoValueError("validation: semestre verrouille")
|
||||||
|
|
||||||
@ -262,8 +262,8 @@ def formsemestre_validation_etud_form(
|
|||||||
return "\n".join(H + footer)
|
return "\n".join(H + footer)
|
||||||
|
|
||||||
# Infos si pas de semestre précédent
|
# Infos si pas de semestre précédent
|
||||||
if not Se.prev:
|
if not Se.prev_formsemestre:
|
||||||
if Se.sem["semestre_id"] == 1:
|
if Se.cur_sem.semestre_id == 1:
|
||||||
H.append("<p>Premier semestre (pas de précédent)</p>")
|
H.append("<p>Premier semestre (pas de précédent)</p>")
|
||||||
else:
|
else:
|
||||||
H.append("<p>Pas de semestre précédent !</p>")
|
H.append("<p>Pas de semestre précédent !</p>")
|
||||||
@ -274,7 +274,7 @@ def formsemestre_validation_etud_form(
|
|||||||
f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
|
f"""Le jury n'a pas statué sur le semestre précédent ! (<a href="{
|
||||||
url_for("notes.formsemestre_validation_etud_form",
|
url_for("notes.formsemestre_validation_etud_form",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=Se.prev["formsemestre_id"],
|
formsemestre_id=Se.prev_formsemestre.id,
|
||||||
etudid=etudid)
|
etudid=etudid)
|
||||||
}">le faire maintenant</a>)
|
}">le faire maintenant</a>)
|
||||||
"""
|
"""
|
||||||
@ -310,9 +310,9 @@ def formsemestre_validation_etud_form(
|
|||||||
H.append("</p>")
|
H.append("</p>")
|
||||||
|
|
||||||
# Cas particulier pour ATJ: corriger precedent avant de continuer
|
# Cas particulier pour ATJ: corriger precedent avant de continuer
|
||||||
if Se.prev_decision and Se.prev_decision["code"] == ATJ:
|
if Se.prev_formsemestre and Se.prev_decision and Se.prev_decision["code"] == ATJ:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="sfv_warning"><p>La décision du semestre précédent est en
|
f"""<div class="sfv_warning"><p>La décision du semestre précédent est en
|
||||||
<b>attente</b> à cause d\'un <b>problème d\'assiduité<b>.</p>
|
<b>attente</b> à cause d\'un <b>problème d\'assiduité<b>.</p>
|
||||||
<p>Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le
|
<p>Vous devez la corriger avant de continuer ce jury. Soit vous considérez que le
|
||||||
problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre
|
problème d'assiduité n'est pas réglé et choisissez de ne pas valider le semestre
|
||||||
@ -320,14 +320,16 @@ def formsemestre_validation_etud_form(
|
|||||||
l'assiduité.</p>
|
l'assiduité.</p>
|
||||||
<form method="get" action="formsemestre_validation_etud_form">
|
<form method="get" action="formsemestre_validation_etud_form">
|
||||||
<input type="submit" value="Statuer sur le semestre précédent"/>
|
<input type="submit" value="Statuer sur le semestre précédent"/>
|
||||||
<input type="hidden" name="formsemestre_id" value="%s"/>
|
<input type="hidden" name="formsemestre_id" value="{Se.prev_formsemestre.id}"/>
|
||||||
<input type="hidden" name="etudid" value="%s"/>
|
<input type="hidden" name="etudid" value="{etudid}"/>
|
||||||
<input type="hidden" name="desturl" value="formsemestre_validation_etud_form?etudid=%s&formsemestre_id=%s"/>
|
<input type="hidden" name="desturl" value="{
|
||||||
|
url_for("notes.formsemestre_validation_etud_form",
|
||||||
|
etudid=etudid, formsemestre_id=formsemestre_id, scodoc_dept=g.scodoc_dept
|
||||||
|
)}"/>
|
||||||
"""
|
"""
|
||||||
% (Se.prev["formsemestre_id"], etudid, etudid, formsemestre_id)
|
|
||||||
)
|
)
|
||||||
if sortcol:
|
if sortcol:
|
||||||
H.append('<input type="hidden" name="sortcol" value="%s"/>' % sortcol)
|
H.append(f"""<input type="hidden" name="sortcol" value="{sortcol}"/>""")
|
||||||
H.append("</form></div>")
|
H.append("</form></div>")
|
||||||
|
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
@ -405,7 +407,7 @@ def formsemestre_validation_etud(
|
|||||||
sortcol=None,
|
sortcol=None,
|
||||||
):
|
):
|
||||||
"""Enregistre validation"""
|
"""Enregistre validation"""
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = Identite.get_etud(etudid)
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
# retrouve la decision correspondant au code:
|
# retrouve la decision correspondant au code:
|
||||||
choices = Se.get_possible_choices(assiduite=True)
|
choices = Se.get_possible_choices(assiduite=True)
|
||||||
@ -438,7 +440,7 @@ def formsemestre_validation_etud_manu(
|
|||||||
"""Enregistre validation"""
|
"""Enregistre validation"""
|
||||||
if assidu:
|
if assidu:
|
||||||
assidu = True
|
assidu = True
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = Identite.get_etud(etudid)
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
if code_etat in Se.parcours.UNUSED_CODES:
|
if code_etat in Se.parcours.UNUSED_CODES:
|
||||||
raise ScoValueError("code decision invalide dans ce parcours")
|
raise ScoValueError("code decision invalide dans ce parcours")
|
||||||
@ -494,32 +496,35 @@ def decisions_possible_rows(Se, assiduite, subtitle="", trclass=""):
|
|||||||
choices = Se.get_possible_choices(assiduite=assiduite)
|
choices = Se.get_possible_choices(assiduite=assiduite)
|
||||||
if not choices:
|
if not choices:
|
||||||
return ""
|
return ""
|
||||||
TitlePrev = ""
|
prev_title = ""
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
if Se.prev["semestre_id"] >= 0:
|
if Se.prev_formsemestre.semestre_id >= 0:
|
||||||
TitlePrev = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.prev["semestre_id"])
|
prev_title = "%s%d" % (
|
||||||
|
Se.parcours.SESSION_ABBRV,
|
||||||
|
Se.prev_formsemestre.semestre_id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
TitlePrev = "Prec."
|
prev_title = "Prec."
|
||||||
|
|
||||||
if Se.sem["semestre_id"] >= 0:
|
if Se.cur_sem.semestre_id >= 0:
|
||||||
TitleCur = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.sem["semestre_id"])
|
cur_title = "%s%d" % (Se.parcours.SESSION_ABBRV, Se.cur_sem.semestre_id)
|
||||||
else:
|
else:
|
||||||
TitleCur = Se.parcours.SESSION_NAME
|
cur_title = Se.parcours.SESSION_NAME
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
'<tr class="%s titles"><th class="sfv_subtitle">%s</em></th>'
|
'<tr class="%s titles"><th class="sfv_subtitle">%s</em></th>'
|
||||||
% (trclass, subtitle)
|
% (trclass, subtitle)
|
||||||
]
|
]
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
H.append("<th>Code %s</th>" % TitlePrev)
|
H.append(f"<th>Code {prev_title}</th>")
|
||||||
H.append("<th>Code %s</th><th>Devenir</th></tr>" % TitleCur)
|
H.append(f"<th>Code {cur_title}</th><th>Devenir</th></tr>")
|
||||||
for ch in choices:
|
for ch in choices:
|
||||||
H.append(
|
H.append(
|
||||||
"""<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" onClick="document.getElementById('subut').disabled=false;">"""
|
"""<tr class="%s"><td title="règle %s"><input type="radio" name="codechoice" value="%s" onClick="document.getElementById('subut').disabled=false;">"""
|
||||||
% (trclass, ch.rule_id, ch.codechoice)
|
% (trclass, ch.rule_id, ch.codechoice)
|
||||||
)
|
)
|
||||||
H.append("%s </input></td>" % ch.explication)
|
H.append("%s </input></td>" % ch.explication)
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
H.append('<td class="centercell">%s</td>' % _dispcode(ch.new_code_prev))
|
H.append('<td class="centercell">%s</td>' % _dispcode(ch.new_code_prev))
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="centercell">%s</td><td>%s</td>'
|
'<td class="centercell">%s</td><td>%s</td>'
|
||||||
@ -535,7 +540,6 @@ def formsemestre_recap_parcours_table(
|
|||||||
etudid,
|
etudid,
|
||||||
with_links=False,
|
with_links=False,
|
||||||
with_all_columns=True,
|
with_all_columns=True,
|
||||||
a_url="",
|
|
||||||
sem_info=None,
|
sem_info=None,
|
||||||
show_details=False,
|
show_details=False,
|
||||||
):
|
):
|
||||||
@ -576,14 +580,14 @@ def formsemestre_recap_parcours_table(
|
|||||||
H.append("<th></th></tr>")
|
H.append("<th></th></tr>")
|
||||||
|
|
||||||
num_sem = 0
|
num_sem = 0
|
||||||
for sem in situation_etud_cursus.get_semestres():
|
for formsemestre in situation_etud_cursus.formsemestres:
|
||||||
is_prev = situation_etud_cursus.prev and (
|
is_prev = situation_etud_cursus.prev_formsemestre and (
|
||||||
situation_etud_cursus.prev["formsemestre_id"] == sem["formsemestre_id"]
|
situation_etud_cursus.prev_formsemestre.id == formsemestre.id
|
||||||
)
|
)
|
||||||
is_cur = situation_etud_cursus.formsemestre_id == sem["formsemestre_id"]
|
is_cur = situation_etud_cursus.formsemestre_id == formsemestre.id
|
||||||
num_sem += 1
|
num_sem += 1
|
||||||
|
|
||||||
dpv = sco_pv_dict.dict_pvjury(sem["formsemestre_id"], etudids=[etudid])
|
dpv = sco_pv_dict.dict_pvjury(formsemestre.id, etudids=[etudid])
|
||||||
pv = dpv["decisions"][0]
|
pv = dpv["decisions"][0]
|
||||||
decision_sem = pv["decision_sem"]
|
decision_sem = pv["decision_sem"]
|
||||||
decisions_ue = pv["decisions_ue"]
|
decisions_ue = pv["decisions_ue"]
|
||||||
@ -592,7 +596,6 @@ def formsemestre_recap_parcours_table(
|
|||||||
else:
|
else:
|
||||||
ass = ""
|
ass = ""
|
||||||
|
|
||||||
formsemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
if is_cur:
|
if is_cur:
|
||||||
type_sem = "*" # now unused
|
type_sem = "*" # now unused
|
||||||
@ -603,20 +606,24 @@ def formsemestre_recap_parcours_table(
|
|||||||
else:
|
else:
|
||||||
type_sem = ""
|
type_sem = ""
|
||||||
class_sem = "sem_autre"
|
class_sem = "sem_autre"
|
||||||
if sem["formation_code"] != situation_etud_cursus.formation.formation_code:
|
if (
|
||||||
|
formsemestre.formation.formation_code
|
||||||
|
!= situation_etud_cursus.formation.formation_code
|
||||||
|
):
|
||||||
class_sem += " sem_autre_formation"
|
class_sem += " sem_autre_formation"
|
||||||
if sem["bul_bgcolor"]:
|
bgcolor = (
|
||||||
bgcolor = sem["bul_bgcolor"]
|
formsemestre.bul_bgcolor
|
||||||
else:
|
if formsemestre.bul_bgcolor
|
||||||
bgcolor = "background-color: rgb(255,255,240)"
|
else "background-color: rgb(255,255,240)"
|
||||||
|
)
|
||||||
# 1ere ligne: titre sem, decision, acronymes UE
|
# 1ere ligne: titre sem, decision, acronymes UE
|
||||||
H.append('<tr class="%s rcp_l1 sem_%s">' % (class_sem, sem["formsemestre_id"]))
|
H.append('<tr class="%s rcp_l1 sem_%s">' % (class_sem, formsemestre.id))
|
||||||
if is_cur:
|
if is_cur:
|
||||||
pm = ""
|
pm = ""
|
||||||
elif is_prev:
|
elif is_prev:
|
||||||
pm = minuslink % sem["formsemestre_id"]
|
pm = minuslink % formsemestre.id
|
||||||
else:
|
else:
|
||||||
pm = plusminus % sem["formsemestre_id"]
|
pm = plusminus % formsemestre.id
|
||||||
|
|
||||||
inscr = formsemestre.etuds_inscriptions.get(etudid)
|
inscr = formsemestre.etuds_inscriptions.get(etudid)
|
||||||
parcours_name = ""
|
parcours_name = ""
|
||||||
@ -638,9 +645,12 @@ def formsemestre_recap_parcours_table(
|
|||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td>
|
<td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td>
|
||||||
<td class="datedebut">{sem['mois_debut']}</td>
|
<td class="datedebut">{formsemestre.mois_debut()}</td>
|
||||||
<td class="rcp_titre_sem"><a class="formsemestre_status_link"
|
<td class="rcp_titre_sem"><a class="formsemestre_status_link"
|
||||||
href="{a_url}formsemestre_bulletinetud?formsemestre_id={formsemestre.id}&etudid={etudid}"
|
href="{
|
||||||
|
url_for("notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id, etudid=etudid
|
||||||
|
)}"
|
||||||
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a>
|
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -675,7 +685,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
ues = [
|
ues = [
|
||||||
ue
|
ue
|
||||||
for ue in ues
|
for ue in ues
|
||||||
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue.id)
|
if etud_est_inscrit_ue(cnx, etudid, formsemestre.id, ue.id)
|
||||||
or etud_ue_status[ue.id]["is_capitalized"]
|
or etud_ue_status[ue.id]["is_capitalized"]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -697,7 +707,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
H.append("<td></td>")
|
H.append("<td></td>")
|
||||||
H.append("</tr>")
|
H.append("</tr>")
|
||||||
# 2eme ligne: notes
|
# 2eme ligne: notes
|
||||||
H.append(f"""<tr class="{class_sem} rcp_l2 sem_{sem["formsemestre_id"]}">""")
|
H.append(f"""<tr class="{class_sem} rcp_l2 sem_{formsemestre.id}">""")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<td class="rcp_type_sem"
|
f"""<td class="rcp_type_sem"
|
||||||
style="background-color:{bgcolor};"> </td>"""
|
style="background-color:{bgcolor};"> </td>"""
|
||||||
@ -706,21 +716,28 @@ def formsemestre_recap_parcours_table(
|
|||||||
default_sem_info = '<span class="fontred">[sem. précédent]</span>'
|
default_sem_info = '<span class="fontred">[sem. précédent]</span>'
|
||||||
else:
|
else:
|
||||||
default_sem_info = ""
|
default_sem_info = ""
|
||||||
if not sem["etat"]: # locked
|
if not formsemestre.etat: # locked
|
||||||
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
||||||
default_sem_info += lockicon
|
default_sem_info += lockicon
|
||||||
if sem["formation_code"] != situation_etud_cursus.formation.formation_code:
|
if (
|
||||||
default_sem_info += f"""Autre formation: {sem["formation_code"]}"""
|
formsemestre.formation.formation_code
|
||||||
|
!= situation_etud_cursus.formation.formation_code
|
||||||
|
):
|
||||||
|
default_sem_info += (
|
||||||
|
f"""Autre formation: {formsemestre.formation.formation_code}"""
|
||||||
|
)
|
||||||
H.append(
|
H.append(
|
||||||
'<td class="datefin">%s</td><td class="sem_info">%s</td>'
|
'<td class="datefin">%s</td><td class="sem_info">%s</td>'
|
||||||
% (sem["mois_fin"], sem_info.get(sem["formsemestre_id"], default_sem_info))
|
% (formsemestre.mois_fin(), sem_info.get(formsemestre.id, default_sem_info))
|
||||||
)
|
)
|
||||||
# Moy Gen (sous le code decision)
|
# Moy Gen (sous le code decision)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
||||||
)
|
)
|
||||||
# Absences (nb d'abs non just. dans ce semestre)
|
# Absences (nb d'abs non just. dans ce semestre)
|
||||||
nbabsnj = sco_assiduites.get_assiduites_count(etudid, sem)[0]
|
nbabsnj = sco_assiduites.formsemestre_get_assiduites_count(
|
||||||
|
etudid, formsemestre
|
||||||
|
)[0]
|
||||||
H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""")
|
H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""")
|
||||||
|
|
||||||
# UEs
|
# UEs
|
||||||
@ -767,26 +784,30 @@ def formsemestre_recap_parcours_table(
|
|||||||
H.append("<td></td>")
|
H.append("<td></td>")
|
||||||
if with_links:
|
if with_links:
|
||||||
H.append(
|
H.append(
|
||||||
'<td><a href="%sformsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s">modifier</a></td>'
|
f"""<td><a class="stdlink" href="{
|
||||||
% (a_url, sem["formsemestre_id"], etudid)
|
url_for("notes.formsemestre_validation_etud_form", scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id, etudid=etudid
|
||||||
|
)}">modifier</a></td>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append("</tr>")
|
H.append("</tr>")
|
||||||
# 3eme ligne: ECTS
|
# 3eme ligne: ECTS
|
||||||
if (
|
if (
|
||||||
sco_preferences.get_preference("bul_show_ects", sem["formsemestre_id"])
|
sco_preferences.get_preference("bul_show_ects", formsemestre.id)
|
||||||
or nt.parcours.ECTS_ONLY
|
or nt.parcours.ECTS_ONLY
|
||||||
):
|
):
|
||||||
etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels
|
etud_ects_infos = nt.get_etud_ects_pot(etudid) # ECTS potentiels
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr class="{class_sem} rcp_l2 sem_{sem["formsemestre_id"]}">
|
f"""<tr class="{class_sem} rcp_l2 sem_{formsemestre.id}">
|
||||||
<td class="rcp_type_sem" style="background-color:{bgcolor};"> </td>
|
<td class="rcp_type_sem" style="background-color:{bgcolor};"> </td>
|
||||||
<td></td>"""
|
<td></td>"""
|
||||||
)
|
)
|
||||||
# Total ECTS (affiché sous la moyenne générale)
|
# Total ECTS (affiché sous la moyenne générale)
|
||||||
H.append(
|
H.append(
|
||||||
f"""<td class="sem_ects_tit"><a title="crédit acquis">ECTS:</a></td>
|
f"""<td class="sem_ects_tit"><a title="crédit acquis">ECTS:</a></td>
|
||||||
<td class="sem_ects">{pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g}</td>
|
<td class="sem_ects">{
|
||||||
|
pv.get("sum_ects",0):2.2g} / {etud_ects_infos["ects_total"]:2.2g}
|
||||||
|
</td>
|
||||||
<td class="rcp_abs"></td>
|
<td class="rcp_abs"></td>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -865,7 +886,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
|||||||
# précédent n'est pas géré dans ScoDoc (code ADC_)
|
# précédent n'est pas géré dans ScoDoc (code ADC_)
|
||||||
# log(str(Se.sems))
|
# log(str(Se.sems))
|
||||||
for sem in Se.sems:
|
for sem in Se.sems:
|
||||||
if sem["can_compensate"]:
|
if sem["formsemestre_id"] in Se.can_compensate:
|
||||||
H.append(
|
H.append(
|
||||||
'<option value="%s_%s">Admis par compensation avec S%s (%s)</option>'
|
'<option value="%s_%s">Admis par compensation avec S%s (%s)</option>'
|
||||||
% (
|
% (
|
||||||
@ -882,7 +903,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
|||||||
H.append("</select></td></tr>")
|
H.append("</select></td></tr>")
|
||||||
|
|
||||||
# Choix code semestre precedent:
|
# Choix code semestre precedent:
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td>Code semestre précédent: </td><td><select name="new_code_prev"><option value="">Choisir une décision...</option>'
|
'<tr><td>Code semestre précédent: </td><td><select name="new_code_prev"><option value="">Choisir une décision...</option>'
|
||||||
)
|
)
|
||||||
@ -975,7 +996,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
conflicts = [] # liste des etudiants avec decision differente déjà saisie
|
conflicts = [] # liste des etudiants avec decision differente déjà saisie
|
||||||
with sco_cache.DeferredSemCacheManager():
|
with sco_cache.DeferredSemCacheManager():
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = Identite.get_etud(etudid)
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
{"etudid": etudid, "formsemestre_id": formsemestre_id}
|
{"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||||
@ -984,7 +1005,7 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
# Conditions pour validation automatique:
|
# Conditions pour validation automatique:
|
||||||
if ins["etat"] == scu.INSCRIT and (
|
if ins["etat"] == scu.INSCRIT and (
|
||||||
(
|
(
|
||||||
(not Se.prev)
|
(not Se.prev_formsemestre)
|
||||||
or (
|
or (
|
||||||
Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)
|
Se.prev_decision and Se.prev_decision["code"] in (ADM, ADC, ADJ)
|
||||||
)
|
)
|
||||||
@ -1055,8 +1076,8 @@ def do_formsemestre_validation_auto(formsemestre_id):
|
|||||||
f"""<li><a href="{
|
f"""<li><a href="{
|
||||||
url_for('notes.formsemestre_validation_etud_form',
|
url_for('notes.formsemestre_validation_etud_form',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id,
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id,
|
||||||
etudid=etud["etudid"], check=1)
|
etudid=etud.id, check=1)
|
||||||
}">{etud["nomprenom"]}</li>"""
|
}">{etud_d["nomprenom"]}</li>"""
|
||||||
)
|
)
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -54,11 +54,11 @@ class Jour:
|
|||||||
"""
|
"""
|
||||||
return self.date.isocalendar()[0:2] == datetime.date.today().isocalendar()[0:2]
|
return self.date.isocalendar()[0:2] == datetime.date.today().isocalendar()[0:2]
|
||||||
|
|
||||||
def get_date(self) -> str:
|
def get_date(self, fmt=scu.DATE_FMT) -> str:
|
||||||
"""
|
"""
|
||||||
Renvoie la date du jour au format "dd/mm/yyyy"
|
Renvoie la date du jour au format fmt ou "dd/mm/yyyy" par défaut
|
||||||
"""
|
"""
|
||||||
return self.date.strftime(scu.DATE_FMT)
|
return self.date.strftime(fmt)
|
||||||
|
|
||||||
def get_html(self):
|
def get_html(self):
|
||||||
"""
|
"""
|
||||||
@ -93,12 +93,22 @@ class Calendrier:
|
|||||||
Représente un calendrier
|
Représente un calendrier
|
||||||
Permet d'obtenir les informations sur les jours
|
Permet d'obtenir les informations sur les jours
|
||||||
et générer une représentation html
|
et générer une représentation html
|
||||||
|
|
||||||
|
highlight: str
|
||||||
|
-> ["jour", "semaine", "mois"]
|
||||||
|
permet de mettre en valeur lors du passage de la souris
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, date_debut: datetime.date, date_fin: datetime.date):
|
def __init__(
|
||||||
|
self,
|
||||||
|
date_debut: datetime.date,
|
||||||
|
date_fin: datetime.date,
|
||||||
|
highlight: str = None,
|
||||||
|
):
|
||||||
self.date_debut = date_debut
|
self.date_debut = date_debut
|
||||||
self.date_fin = date_fin
|
self.date_fin = date_fin
|
||||||
self.jours: dict[str, list[Jour]] = {}
|
self.jours: dict[str, list[Jour]] = {}
|
||||||
|
self.highlight: str = highlight
|
||||||
|
|
||||||
def _get_dates_between(self) -> list[datetime.date]:
|
def _get_dates_between(self) -> list[datetime.date]:
|
||||||
"""
|
"""
|
||||||
@ -130,11 +140,13 @@ class Calendrier:
|
|||||||
month = scu.MONTH_NAMES_ABBREV[date.month - 1]
|
month = scu.MONTH_NAMES_ABBREV[date.month - 1]
|
||||||
# Ajouter le jour à la liste correspondante au mois
|
# Ajouter le jour à la liste correspondante au mois
|
||||||
if month not in organized:
|
if month not in organized:
|
||||||
organized[month] = []
|
organized[month] = {} # semaine {22: []}
|
||||||
|
|
||||||
jour: Jour = self.instanciate_jour(date)
|
jour: Jour = self.instanciate_jour(date)
|
||||||
|
semaine = date.strftime("%G-W%V")
|
||||||
organized[month].append(jour)
|
if semaine not in organized[month]:
|
||||||
|
organized[month][semaine] = []
|
||||||
|
organized[month][semaine].append(jour)
|
||||||
|
|
||||||
self.jours = organized
|
self.jours = organized
|
||||||
|
|
||||||
@ -150,4 +162,53 @@ class Calendrier:
|
|||||||
get_html Renvoie le code html du calendrier
|
get_html Renvoie le code html du calendrier
|
||||||
"""
|
"""
|
||||||
self.organize_by_month()
|
self.organize_by_month()
|
||||||
return render_template("calendrier.j2", calendrier=self.jours)
|
return render_template(
|
||||||
|
"calendrier.j2", calendrier=self.jours, highlight=self.highlight
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class JourChoix(Jour):
|
||||||
|
"""
|
||||||
|
Représente un jour dans le calendrier pour choisir une date
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_html(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
class CalendrierChoix(Calendrier):
|
||||||
|
"""
|
||||||
|
Représente un calendrier pour choisir une date
|
||||||
|
"""
|
||||||
|
|
||||||
|
def instanciate_jour(self, date: datetime.date) -> Jour:
|
||||||
|
return JourChoix(date)
|
||||||
|
|
||||||
|
|
||||||
|
def calendrier_choix_date(
|
||||||
|
date_debut: datetime.date,
|
||||||
|
date_fin: datetime.date,
|
||||||
|
url: str,
|
||||||
|
mode: str = "jour",
|
||||||
|
titre: str = "Choisir une date",
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Permet d'afficher un calendrier pour choisir une date et renvoyer sur une url.
|
||||||
|
|
||||||
|
mode : str
|
||||||
|
- "jour" -> ajoutera "&day=yyyy-mm-dd" à l'url (ex: 2024-05-30)
|
||||||
|
- "semaine" -> ajoutera "&week=yyyy-Www" à l'url (ex : 2024-W22)
|
||||||
|
|
||||||
|
titre : str
|
||||||
|
- texte à afficher au dessus du calendrier
|
||||||
|
"""
|
||||||
|
|
||||||
|
calendrier: CalendrierChoix = CalendrierChoix(date_debut, date_fin, highlight=mode)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"choix_date.j2",
|
||||||
|
calendrier=calendrier.get_html(),
|
||||||
|
url=url,
|
||||||
|
titre=titre,
|
||||||
|
mode=mode,
|
||||||
|
)
|
||||||
|
@ -466,9 +466,9 @@ def etud_add_group_infos(
|
|||||||
etud['groupes'] = "TDB, Gr2, TPB1"
|
etud['groupes'] = "TDB, Gr2, TPB1"
|
||||||
etud['partitionsgroupes'] = "Groupes TD:TDB, Groupes TP:Gr2 (...)"
|
etud['partitionsgroupes'] = "Groupes TD:TDB, Groupes TP:Gr2 (...)"
|
||||||
"""
|
"""
|
||||||
etud[
|
etud["partitions"] = (
|
||||||
"partitions"
|
collections.OrderedDict()
|
||||||
] = collections.OrderedDict() # partition_id : group + partition_name
|
) # partition_id : group + partition_name
|
||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
etud["groupes"] = ""
|
etud["groupes"] = ""
|
||||||
return etud
|
return etud
|
||||||
@ -1409,21 +1409,17 @@ def groups_auto_repartition(partition: Partition):
|
|||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
|
||||||
|
|
||||||
def _get_prev_moy(etudid, formsemestre_id):
|
def _get_prev_moy(etudid: int, formsemestre_id: int) -> float | str:
|
||||||
"""Donne la derniere moyenne generale calculee pour cette étudiant,
|
"""Donne la derniere moyenne generale calculee pour cette étudiant,
|
||||||
ou 0 si on n'en trouve pas (nouvel inscrit,...).
|
ou 0 si on n'en trouve pas (nouvel inscrit,...).
|
||||||
"""
|
"""
|
||||||
info = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
etud = Identite.get_etud(etudid)
|
||||||
if not info:
|
|
||||||
raise ScoValueError("etudiant invalide: etudid=%s" % etudid)
|
|
||||||
etud = info[0]
|
|
||||||
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
prev_sem = db.session.get(FormSemestre, Se.prev["formsemestre_id"])
|
prev_sem = db.session.get(FormSemestre, Se.prev_formsemestre.id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem)
|
||||||
return nt.get_etud_moy_gen(etudid)
|
return nt.get_etud_moy_gen(etud.id)
|
||||||
else:
|
return 0.0
|
||||||
return 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
|
||||||
|
@ -27,10 +27,8 @@
|
|||||||
|
|
||||||
"""Exports groupes
|
"""Exports groupes
|
||||||
"""
|
"""
|
||||||
from flask import request
|
|
||||||
|
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import sco_excel
|
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
@ -83,14 +81,13 @@ def groups_export_annotations(group_ids, formsemestre_id=None, fmt="html"):
|
|||||||
"date_str": "Date",
|
"date_str": "Date",
|
||||||
"comment": "Annotation",
|
"comment": "Annotation",
|
||||||
},
|
},
|
||||||
origin="Généré par %s le " % sco_version.SCONAME
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
+ scu.timedate_human_repr()
|
|
||||||
+ "",
|
|
||||||
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
|
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
|
||||||
caption="Annotations",
|
caption="Annotations",
|
||||||
base_url=groups_infos.base_url,
|
base_url=groups_infos.base_url,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id="groups_export_annotations",
|
||||||
)
|
)
|
||||||
return table.make_page(fmt=fmt)
|
return table.make_page(fmt=fmt)
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Affichage étudiants d'un ou plusieurs groupes
|
"""Affichage étudiants d'un ou plusieurs groupes
|
||||||
sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
|
sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
|
||||||
@ -39,7 +39,7 @@ from flask import url_for, g, request
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre, Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_excel
|
from app.scodoc import sco_excel
|
||||||
@ -661,6 +661,7 @@ def groups_table(
|
|||||||
text_fields_separator=prefs["moodle_csv_separator"],
|
text_fields_separator=prefs["moodle_csv_separator"],
|
||||||
text_with_titles=prefs["moodle_csv_with_headerline"],
|
text_with_titles=prefs["moodle_csv_with_headerline"],
|
||||||
preferences=prefs,
|
preferences=prefs,
|
||||||
|
table_id="groups_table",
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
if fmt == "html":
|
if fmt == "html":
|
||||||
@ -861,21 +862,25 @@ def groups_table(
|
|||||||
# et ajoute infos inscription
|
# et ajoute infos inscription
|
||||||
for m in groups_infos.members:
|
for m in groups_infos.members:
|
||||||
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
||||||
|
# TODO utiliser Identite
|
||||||
|
etud = Identite.get_etud(m["etudid"])
|
||||||
m.update(etud_info)
|
m.update(etud_info)
|
||||||
sco_etud.etud_add_lycee_infos(etud_info)
|
sco_etud.etud_add_lycee_infos(etud_info)
|
||||||
# et ajoute le parcours
|
# et ajoute le parcours
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
Se = sco_cursus.get_situation_etud_cursus(
|
||||||
etud_info, groups_infos.formsemestre_id
|
etud, groups_infos.formsemestre_id
|
||||||
)
|
)
|
||||||
m["parcours"] = Se.get_cursus_descr()
|
m["parcours"] = Se.get_cursus_descr()
|
||||||
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
||||||
etud_info["etudid"], sems=etud_info["sems"]
|
etud.id, formsemestres=etud.get_formsemestres()
|
||||||
)
|
)
|
||||||
|
# TODO utiliser Identite:
|
||||||
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
||||||
title = "etudiants_%s" % groups_infos.groups_filename
|
title = f"etudiants_{groups_infos.groups_filename}"
|
||||||
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
|
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
|
||||||
filename = title
|
return scu.send_file(
|
||||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
xls, filename=title, suffix=scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ScoValueError("unsupported format")
|
raise ScoValueError("unsupported format")
|
||||||
|
|
||||||
@ -977,16 +982,16 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
if not authuser.has_permission(Permission.AbsChange):
|
if not authuser.has_permission(Permission.AbsChange):
|
||||||
return ""
|
return ""
|
||||||
return f"""
|
return f"""
|
||||||
<button onclick="window.location='{
|
<a class="stdlink" href="{
|
||||||
url_for(
|
url_for(
|
||||||
"assiduites.signal_assiduites_group",
|
"assiduites.signal_assiduites_group",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
jour=datetime.date.today().isoformat(),
|
day=datetime.date.today().isoformat(),
|
||||||
formsemestre_id=groups_infos.formsemestre_id,
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||||
)
|
)
|
||||||
}';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button>
|
}">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -997,16 +1002,16 @@ def form_choix_saisie_semaine(groups_infos):
|
|||||||
return ""
|
return ""
|
||||||
query_args = parse_qs(request.query_string)
|
query_args = parse_qs(request.query_string)
|
||||||
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
||||||
semaine = datetime.date.today().isocalendar().week
|
semaine = datetime.datetime.now().strftime("%G-W%V")
|
||||||
return f"""
|
return f"""
|
||||||
<button onclick="window.location='{url_for(
|
<a class="stdlink" href="{url_for(
|
||||||
"assiduites.signal_assiduites_diff",
|
"assiduites.signal_assiduites_hebdo",
|
||||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
semaine=semaine,
|
week=semaine,
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=groups_infos.formsemestre_id,
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
moduleimpl_id=moduleimpl_id
|
moduleimpl_id=moduleimpl_id
|
||||||
)}';">Saisie à la semaine</button>
|
)}">Saisie à la semaine (semaine {''.join(semaine[-2:])})</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -1028,10 +1033,9 @@ def export_groups_as_moodle_csv(formsemestre_id=None):
|
|||||||
moodle_sem_name = sem["session_id"]
|
moodle_sem_name = sem["session_id"]
|
||||||
|
|
||||||
columns_ids = ("email", "semestre_groupe")
|
columns_ids = ("email", "semestre_groupe")
|
||||||
T = []
|
rows = []
|
||||||
for partition_id in partitions_etud_groups:
|
for partition_id, members in partitions_etud_groups.items():
|
||||||
partition = sco_groups.get_partition(partition_id)
|
partition = sco_groups.get_partition(partition_id)
|
||||||
members = partitions_etud_groups[partition_id]
|
|
||||||
for etudid in members:
|
for etudid in members:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
group_name = members[etudid]["group_name"]
|
group_name = members[etudid]["group_name"]
|
||||||
@ -1040,16 +1044,17 @@ def export_groups_as_moodle_csv(formsemestre_id=None):
|
|||||||
elts.append(partition["partition_name"])
|
elts.append(partition["partition_name"])
|
||||||
if group_name:
|
if group_name:
|
||||||
elts.append(group_name)
|
elts.append(group_name)
|
||||||
T.append({"email": etud["email"], "semestre_groupe": "-".join(elts)})
|
rows.append({"email": etud["email"], "semestre_groupe": "-".join(elts)})
|
||||||
# Make table
|
# Make table
|
||||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
rows=T,
|
|
||||||
columns_ids=("email", "semestre_groupe"),
|
columns_ids=("email", "semestre_groupe"),
|
||||||
filename=moodle_sem_name + "-moodle",
|
filename=moodle_sem_name + "-moodle",
|
||||||
titles={x: x for x in columns_ids},
|
preferences=prefs,
|
||||||
|
rows=rows,
|
||||||
text_fields_separator=prefs["moodle_csv_separator"],
|
text_fields_separator=prefs["moodle_csv_separator"],
|
||||||
text_with_titles=prefs["moodle_csv_with_headerline"],
|
text_with_titles=prefs["moodle_csv_with_headerline"],
|
||||||
preferences=prefs,
|
table_id="export_groups_as_moodle_csv",
|
||||||
|
titles={x: x for x in columns_ids},
|
||||||
)
|
)
|
||||||
return tab.make_page(fmt="csv")
|
return tab.make_page(fmt="csv")
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
""" Importation des étudiants à partir de fichiers CSV
|
""" Importation des étudiants à partir de fichiers CSV
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import collections
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -64,6 +63,7 @@ import app.scodoc.sco_utils as scu
|
|||||||
FORMAT_FILE = "format_import_etudiants.txt"
|
FORMAT_FILE = "format_import_etudiants.txt"
|
||||||
|
|
||||||
# Champs modifiables via "Import données admission"
|
# Champs modifiables via "Import données admission"
|
||||||
|
# (nom/prénom modifiables en mode "avec etudid")
|
||||||
ADMISSION_MODIFIABLE_FIELDS = (
|
ADMISSION_MODIFIABLE_FIELDS = (
|
||||||
"code_nip",
|
"code_nip",
|
||||||
"code_ine",
|
"code_ine",
|
||||||
@ -132,19 +132,27 @@ def sco_import_format(with_codesemestre=True):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def sco_import_format_dict(with_codesemestre=True):
|
def sco_import_format_dict(with_codesemestre=True, use_etudid: bool = False):
|
||||||
"""Attribut: { 'type': , 'table', 'allow_nulls' , 'description' }"""
|
"""Attribut: { 'type': , 'table', 'allow_nulls' , 'description' }"""
|
||||||
fmt = sco_import_format(with_codesemestre=with_codesemestre)
|
fmt = sco_import_format(with_codesemestre=with_codesemestre)
|
||||||
R = collections.OrderedDict()
|
formats = {}
|
||||||
for l in fmt:
|
for l in fmt:
|
||||||
R[l[0]] = {
|
formats[l[0]] = {
|
||||||
"type": l[1],
|
"type": l[1],
|
||||||
"table": l[2],
|
"table": l[2],
|
||||||
"allow_nulls": l[3],
|
"allow_nulls": l[3],
|
||||||
"description": l[4],
|
"description": l[4],
|
||||||
"aliases": l[5],
|
"aliases": l[5],
|
||||||
}
|
}
|
||||||
return R
|
if use_etudid:
|
||||||
|
formats["etudid"] = {
|
||||||
|
"type": "int",
|
||||||
|
"table": "identite",
|
||||||
|
"allow_nulls": False,
|
||||||
|
"description": "",
|
||||||
|
"aliases": ["etudid", "id"],
|
||||||
|
}
|
||||||
|
return formats
|
||||||
|
|
||||||
|
|
||||||
def sco_import_generate_excel_sample(
|
def sco_import_generate_excel_sample(
|
||||||
@ -188,8 +196,7 @@ def sco_import_generate_excel_sample(
|
|||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
||||||
members = groups_infos.members
|
members = groups_infos.members
|
||||||
log(
|
log(
|
||||||
"sco_import_generate_excel_sample: group_ids=%s %d members"
|
f"sco_import_generate_excel_sample: group_ids={group_ids}, {len(members)} members"
|
||||||
% (group_ids, len(members))
|
|
||||||
)
|
)
|
||||||
titles = ["etudid"] + titles
|
titles = ["etudid"] + titles
|
||||||
titles_styles = [style] + titles_styles
|
titles_styles = [style] + titles_styles
|
||||||
@ -234,21 +241,26 @@ def students_import_excel(
|
|||||||
exclude_cols=["photo_filename"],
|
exclude_cols=["photo_filename"],
|
||||||
)
|
)
|
||||||
if return_html:
|
if return_html:
|
||||||
if formsemestre_id:
|
dest_url = (
|
||||||
dest = url_for(
|
url_for(
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
else:
|
if formsemestre_id
|
||||||
dest = url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
else url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
||||||
|
)
|
||||||
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for d in diag:
|
for d in diag:
|
||||||
H.append("<li>%s</li>" % d)
|
H.append(f"<li>{d}</li>")
|
||||||
H.append("</ul>")
|
H.append(
|
||||||
H.append("<p>Import terminé !</p>")
|
f"""
|
||||||
H.append('<p><a class="stdlink" href="%s">Continuer</a></p>' % dest)
|
</ul>)
|
||||||
|
<p>Import terminé !</p>
|
||||||
|
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
||||||
|
"""
|
||||||
|
)
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
@ -308,13 +320,13 @@ def scolars_import_excel_file(
|
|||||||
titleslist = []
|
titleslist = []
|
||||||
for t in fs:
|
for t in fs:
|
||||||
if t not in titles:
|
if t not in titles:
|
||||||
raise ScoValueError('Colonne invalide: "%s"' % t)
|
raise ScoValueError(f'Colonne invalide: "{t}"')
|
||||||
titleslist.append(t) #
|
titleslist.append(t) #
|
||||||
# ok, same titles
|
# ok, same titles
|
||||||
# Start inserting data, abort whole transaction in case of error
|
# Start inserting data, abort whole transaction in case of error
|
||||||
created_etudids = []
|
created_etudids = []
|
||||||
np_imported_homonyms = 0
|
np_imported_homonyms = 0
|
||||||
GroupIdInferers = {}
|
group_id_inferer = {}
|
||||||
try: # --- begin DB transaction
|
try: # --- begin DB transaction
|
||||||
linenum = 0
|
linenum = 0
|
||||||
for line in data[1:]:
|
for line in data[1:]:
|
||||||
@ -429,7 +441,7 @@ def scolars_import_excel_file(
|
|||||||
_import_one_student(
|
_import_one_student(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
values,
|
values,
|
||||||
GroupIdInferers,
|
group_id_inferer,
|
||||||
annee_courante,
|
annee_courante,
|
||||||
created_etudids,
|
created_etudids,
|
||||||
linenum,
|
linenum,
|
||||||
@ -496,13 +508,14 @@ def scolars_import_excel_file(
|
|||||||
|
|
||||||
|
|
||||||
def students_import_admission(
|
def students_import_admission(
|
||||||
csvfile, type_admission="", formsemestre_id=None, return_html=True
|
csvfile, type_admission="", formsemestre_id=None, return_html=True, use_etudid=False
|
||||||
):
|
) -> str:
|
||||||
"import donnees admission from Excel file (v2016)"
|
"import donnees admission from Excel file (v2016)"
|
||||||
diag = scolars_import_admission(
|
diag = scolars_import_admission(
|
||||||
csvfile,
|
csvfile,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
type_admission=type_admission,
|
type_admission=type_admission,
|
||||||
|
use_etudid=use_etudid,
|
||||||
)
|
)
|
||||||
if return_html:
|
if return_html:
|
||||||
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
||||||
@ -524,6 +537,7 @@ def students_import_admission(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _import_one_student(
|
def _import_one_student(
|
||||||
@ -599,13 +613,15 @@ def _is_new_ine(cnx, code_ine):
|
|||||||
|
|
||||||
|
|
||||||
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
||||||
def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
|
def scolars_import_admission(
|
||||||
|
datafile, formsemestre_id=None, type_admission=None, use_etudid=False
|
||||||
|
):
|
||||||
"""Importe données admission depuis un fichier Excel quelconque
|
"""Importe données admission depuis un fichier Excel quelconque
|
||||||
par exemple ceux utilisés avec APB
|
par exemple ceux utilisés avec APB, avec ou sans etudid
|
||||||
|
|
||||||
Cherche dans ce fichier les étudiants qui correspondent à des inscrits du
|
Cherche dans ce fichier les étudiants qui correspondent à des inscrits du
|
||||||
semestre formsemestre_id.
|
semestre formsemestre_id.
|
||||||
Le fichier n'a pas l'INE ni le NIP ni l'etudid, la correspondance se fait
|
Si le fichier n'a pas d'etudid (use_etudid faux), la correspondance se fait
|
||||||
via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux
|
via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux
|
||||||
étant ignorés).
|
étant ignorés).
|
||||||
|
|
||||||
@ -617,23 +633,24 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
dans le fichier importé) du champ type_admission.
|
dans le fichier importé) du champ type_admission.
|
||||||
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
||||||
|
|
||||||
TODO:
|
|
||||||
- choix onglet du classeur
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
|
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
|
||||||
|
diag: list[str] = []
|
||||||
members = sco_groups.get_group_members(
|
members = sco_groups.get_group_members(
|
||||||
sco_groups.get_default_group(formsemestre_id)
|
sco_groups.get_default_group(formsemestre_id)
|
||||||
)
|
)
|
||||||
etuds_by_nomprenom = {} # { nomprenom : etud }
|
etuds_by_nomprenom = {} # { nomprenom : etud }
|
||||||
diag = []
|
etuds_by_etudid = {} # { etudid : etud }
|
||||||
for m in members:
|
if use_etudid:
|
||||||
np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"]))
|
etuds_by_etudid = {m["etudid"]: m for m in members}
|
||||||
if np in etuds_by_nomprenom:
|
else:
|
||||||
msg = "Attention: hononymie pour %s %s" % (m["nom"], m["prenom"])
|
for m in members:
|
||||||
log(msg)
|
np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"]))
|
||||||
diag.append(msg)
|
if np in etuds_by_nomprenom:
|
||||||
etuds_by_nomprenom[np] = m
|
msg = f"""Attention: hononymie pour {m["nom"]} {m["prenom"]}"""
|
||||||
|
log(msg)
|
||||||
|
diag.append(msg)
|
||||||
|
etuds_by_nomprenom[np] = m
|
||||||
|
|
||||||
exceldata = datafile.read()
|
exceldata = datafile.read()
|
||||||
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
|
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
|
||||||
@ -644,19 +661,29 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
|
|
||||||
titles = data[0]
|
titles = data[0]
|
||||||
# idx -> ('field', convertor)
|
# idx -> ('field', convertor)
|
||||||
fields = adm_get_fields(titles, formsemestre_id)
|
fields = adm_get_fields(titles, formsemestre_id, use_etudid=use_etudid)
|
||||||
idx_nom = None
|
idx_nom = idx_prenom = idx_etudid = None
|
||||||
idx_prenom = None
|
|
||||||
for idx, field in fields.items():
|
for idx, field in fields.items():
|
||||||
if field[0] == "nom":
|
match field[0]:
|
||||||
idx_nom = idx
|
case "nom":
|
||||||
if field[0] == "prenom":
|
idx_nom = idx
|
||||||
idx_prenom = idx
|
case "prenom":
|
||||||
if (idx_nom is None) or (idx_prenom is None):
|
idx_prenom = idx
|
||||||
|
case "etudid":
|
||||||
|
idx_etudid = idx
|
||||||
|
|
||||||
|
if (not use_etudid and ((idx_nom is None) or (idx_prenom is None))) or (
|
||||||
|
use_etudid and idx_etudid is None
|
||||||
|
):
|
||||||
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
||||||
log("fields titles =" + ", ".join([fields[x][0] for x in fields]))
|
log("fields titles =" + ", ".join([x[0] for x in fields.values()]))
|
||||||
raise ScoFormatError(
|
raise ScoFormatError(
|
||||||
"scolars_import_admission: colonnes nom et prenom requises",
|
(
|
||||||
|
"""colonne etudid requise
|
||||||
|
(si l'option "Utiliser l'identifiant d'étudiant ScoDoc" est cochée)"""
|
||||||
|
if use_etudid
|
||||||
|
else "colonnes nom et prenom requises"
|
||||||
|
),
|
||||||
dest_url=url_for(
|
dest_url=url_for(
|
||||||
"scolar.form_students_import_infos_admissions",
|
"scolar.form_students_import_infos_admissions",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -665,18 +692,31 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
|
|||||||
)
|
)
|
||||||
|
|
||||||
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
||||||
|
if use_etudid:
|
||||||
|
modifiable_fields |= {"nom", "prenom"}
|
||||||
|
|
||||||
nline = 2 # la premiere ligne de donnees du fichier excel est 2
|
nline = 2 # la premiere ligne de donnees du fichier excel est 2
|
||||||
n_import = 0
|
n_import = 0
|
||||||
for line in data[1:]:
|
for line in data[1:]:
|
||||||
# Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
|
if use_etudid:
|
||||||
nom = adm_normalize_string(line[idx_nom])
|
try:
|
||||||
prenom = adm_normalize_string(line[idx_prenom])
|
etud = etuds_by_etudid.get(int(line[idx_etudid]))
|
||||||
if (nom, prenom) not in etuds_by_nomprenom:
|
except ValueError:
|
||||||
msg = f"""Étudiant <b>{line[idx_nom]} {line[idx_prenom]} inexistant</b>"""
|
etud = None
|
||||||
diag.append(msg)
|
if not etud:
|
||||||
|
msg = f"""Étudiant avec code etudid=<b>{line[idx_etudid]}</b> inexistant"""
|
||||||
|
diag.append(msg)
|
||||||
else:
|
else:
|
||||||
etud = etuds_by_nomprenom[(nom, prenom)]
|
# Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
|
||||||
|
nom = adm_normalize_string(line[idx_nom])
|
||||||
|
prenom = adm_normalize_string(line[idx_prenom])
|
||||||
|
etud = etuds_by_nomprenom.get((nom, prenom))
|
||||||
|
if not etud:
|
||||||
|
msg = (
|
||||||
|
f"""Étudiant <b>{line[idx_nom]} {line[idx_prenom]}</b> inexistant"""
|
||||||
|
)
|
||||||
|
diag.append(msg)
|
||||||
|
if etud:
|
||||||
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
|
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
|
||||||
# peuple les champs presents dans le tableau
|
# peuple les champs presents dans le tableau
|
||||||
args = {}
|
args = {}
|
||||||
@ -758,19 +798,19 @@ def adm_normalize_string(s):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def adm_get_fields(titles, formsemestre_id):
|
def adm_get_fields(titles, formsemestre_id: int, use_etudid: bool = False):
|
||||||
"""Cherche les colonnes importables dans les titres (ligne 1) du fichier excel
|
"""Cherche les colonnes importables dans les titres (ligne 1) du fichier excel
|
||||||
return: { idx : (field_name, convertor) }
|
return: { idx : (field_name, convertor) }
|
||||||
"""
|
"""
|
||||||
format_dict = sco_import_format_dict()
|
format_dict = sco_import_format_dict(use_etudid=use_etudid)
|
||||||
fields = {}
|
fields = {}
|
||||||
idx = 0
|
idx = 0
|
||||||
for title in titles:
|
for title in titles:
|
||||||
title_n = adm_normalize_string(title)
|
title_n = adm_normalize_string(title)
|
||||||
for k in format_dict:
|
for k, fmt in format_dict.items():
|
||||||
for v in format_dict[k]["aliases"]:
|
for v in fmt["aliases"]:
|
||||||
if adm_normalize_string(v) == title_n:
|
if adm_normalize_string(v) == title_n:
|
||||||
typ = format_dict[k]["type"]
|
typ = fmt["type"]
|
||||||
if typ == "real":
|
if typ == "real":
|
||||||
convertor = adm_convert_real
|
convertor = adm_convert_real
|
||||||
elif typ == "integer" or typ == "int":
|
elif typ == "integer" or typ == "int":
|
||||||
@ -834,11 +874,12 @@ def adm_table_description_format():
|
|||||||
columns_ids = ("attribute", "type", "writable", "description", "aliases_str")
|
columns_ids = ("attribute", "type", "writable", "description", "aliases_str")
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=list(Fmt.values()),
|
|
||||||
html_sortable=True,
|
|
||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
|
html_sortable=True,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
rows=list(Fmt.values()),
|
||||||
|
table_id="adm_table_description_format",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
@ -140,7 +140,7 @@ def read_users_excel_file(datafile, titles=TITLES) -> list[dict]:
|
|||||||
for line in data[1:]:
|
for line in data[1:]:
|
||||||
d = {}
|
d = {}
|
||||||
for i, field in enumerate(xls_titles):
|
for i, field in enumerate(xls_titles):
|
||||||
d[field] = line[i]
|
d[field] = (line[i] or "").strip()
|
||||||
users.append(d)
|
users.append(d)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
@ -747,10 +747,11 @@ def etuds_select_box_xls(src_cat):
|
|||||||
else:
|
else:
|
||||||
e["paiementinscription_str"] = "-"
|
e["paiementinscription_str"] = "-"
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
|
||||||
columns_ids=columns_ids,
|
|
||||||
rows=etuds,
|
|
||||||
caption="%(title)s. %(help)s" % src_cat["infos"],
|
caption="%(title)s. %(help)s" % src_cat["infos"],
|
||||||
|
columns_ids=columns_ids,
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
rows=etuds,
|
||||||
|
table_id="etuds_select_box_xls",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
return tab.excel() # tab.make_page(filename=src_cat["infos"]["filename"])
|
return tab.excel() # tab.make_page(filename=src_cat["infos"]["filename"])
|
||||||
|
@ -599,20 +599,21 @@ def _make_table_notes(
|
|||||||
)
|
)
|
||||||
# display
|
# display
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
|
||||||
columns_ids=columns_ids,
|
|
||||||
rows=rows,
|
|
||||||
html_sortable=True,
|
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
filename=filename,
|
|
||||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
||||||
caption=caption,
|
caption=caption,
|
||||||
html_next_section=html_next_section,
|
columns_ids=columns_ids,
|
||||||
page_title="Notes de " + formsemestre.titre_mois(),
|
filename=filename,
|
||||||
html_title=html_title,
|
|
||||||
pdf_title=pdf_title,
|
|
||||||
html_class="notes_evaluation",
|
html_class="notes_evaluation",
|
||||||
|
html_next_section=html_next_section,
|
||||||
|
html_sortable=True,
|
||||||
|
html_title=html_title,
|
||||||
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
|
page_title="Notes de " + formsemestre.titre_mois(),
|
||||||
|
pdf_title=pdf_title,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
||||||
|
rows=rows,
|
||||||
|
table_id="table-liste-notes",
|
||||||
|
titles=titles,
|
||||||
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
# html_generate_cells=False # la derniere ligne (moyennes) est incomplete
|
||||||
)
|
)
|
||||||
if fmt == "bordereau":
|
if fmt == "bordereau":
|
||||||
|
@ -180,6 +180,7 @@ def _table_etuds_lycees(etuds, group_lycees, title, preferences, no_links=False)
|
|||||||
html_class="table_leftalign table_listegroupe",
|
html_class="table_leftalign table_listegroupe",
|
||||||
bottom_titles=bottom_titles,
|
bottom_titles=bottom_titles,
|
||||||
preferences=preferences,
|
preferences=preferences,
|
||||||
|
table_id="table_etuds_lycees",
|
||||||
)
|
)
|
||||||
return tab, etuds_by_lycee
|
return tab, etuds_by_lycee
|
||||||
|
|
||||||
|
@ -30,9 +30,6 @@
|
|||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
from app import db
|
|
||||||
|
|
||||||
from app.models import Formation
|
|
||||||
from app.scodoc import scolog
|
from app.scodoc import scolog
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -56,7 +53,8 @@ _moduleimplEditor = ndb.EditableTable(
|
|||||||
|
|
||||||
def do_moduleimpl_create(args):
|
def do_moduleimpl_create(args):
|
||||||
"create a moduleimpl"
|
"create a moduleimpl"
|
||||||
# TODO remplacer par une methode de ModuleImpl qui appelle super().create_from_dict() puis invalide le formsemestre
|
# TODO remplacer par une methode de ModuleImpl qui appelle
|
||||||
|
# super().create_from_dict() puis invalide le formsemestre
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
r = _moduleimplEditor.create(cnx, args)
|
r = _moduleimplEditor.create(cnx, args)
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
|
@ -79,9 +79,9 @@ def moduleimpl_inscriptions_edit(
|
|||||||
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
module = modimpl.module
|
module = modimpl.module
|
||||||
formsemestre = modimpl.formsemestre
|
formsemestre = modimpl.formsemestre
|
||||||
# -- check lock
|
# -- check permission (and lock)
|
||||||
if not formsemestre.etat:
|
if not modimpl.can_change_inscriptions():
|
||||||
raise ScoValueError("opération impossible: semestre verrouille")
|
return # can_change_inscriptions raises exception
|
||||||
header = html_sco_header.sco_header(
|
header = html_sco_header.sco_header(
|
||||||
page_title="Inscription au module",
|
page_title="Inscription au module",
|
||||||
init_qtip=True,
|
init_qtip=True,
|
||||||
|
@ -64,19 +64,27 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
|||||||
can_edit_notes_ens = modimpl.can_edit_notes(current_user)
|
can_edit_notes_ens = modimpl.can_edit_notes(current_user)
|
||||||
|
|
||||||
if can_edit_notes and nbnotes != 0:
|
if can_edit_notes and nbnotes != 0:
|
||||||
sup_label = "Supprimer évaluation impossible (il y a des notes)"
|
sup_label = "Suppression évaluation impossible (il y a des notes)"
|
||||||
else:
|
else:
|
||||||
sup_label = "Supprimer évaluation"
|
sup_label = "Supprimer évaluation"
|
||||||
|
|
||||||
menu_eval = [
|
menu_eval = [
|
||||||
{
|
{
|
||||||
"title": "Saisir notes",
|
"title": "Saisir les notes",
|
||||||
"endpoint": "notes.saisie_notes",
|
"endpoint": "notes.saisie_notes",
|
||||||
"args": {
|
"args": {
|
||||||
"evaluation_id": evaluation_id,
|
"evaluation_id": evaluation_id,
|
||||||
},
|
},
|
||||||
"enabled": can_edit_notes_ens,
|
"enabled": can_edit_notes_ens,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Saisir par fichier tableur",
|
||||||
|
"id": "menu_saisie_tableur",
|
||||||
|
"endpoint": "notes.saisie_notes_tableur",
|
||||||
|
"args": {
|
||||||
|
"evaluation_id": evaluation.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Modifier évaluation",
|
"title": "Modifier évaluation",
|
||||||
"endpoint": "notes.evaluation_edit",
|
"endpoint": "notes.evaluation_edit",
|
||||||
@ -146,29 +154,48 @@ def moduleimpl_evaluation_menu(evaluation: Evaluation, nbnotes: int = 0) -> str:
|
|||||||
return htmlutils.make_menu("actions", menu_eval, alone=True)
|
return htmlutils.make_menu("actions", menu_eval, alone=True)
|
||||||
|
|
||||||
|
|
||||||
def _ue_coefs_html(coefs_lst) -> str:
|
def _ue_coefs_html(modimpl: ModuleImpl) -> str:
|
||||||
""" """
|
""" """
|
||||||
max_coef = max([x[1] for x in coefs_lst]) if coefs_lst else 1.0
|
coefs_lst = modimpl.module.ue_coefs_list()
|
||||||
H = """
|
max_coef = max(x[1] for x in coefs_lst) if coefs_lst else 1.0
|
||||||
|
H = f"""
|
||||||
<div id="modimpl_coefs">
|
<div id="modimpl_coefs">
|
||||||
<div>Coefficients vers les UE</div>
|
<div>Coefficients vers les UEs
|
||||||
"""
|
<span><a class="stdlink" href="{
|
||||||
if coefs_lst:
|
url_for(
|
||||||
H += (
|
"notes.edit_modules_ue_coefs",
|
||||||
f"""
|
scodoc_dept=g.scodoc_dept,
|
||||||
<div class="coefs_histo" style="--max:{max_coef}">
|
formation_id=modimpl.module.formation.id,
|
||||||
"""
|
semestre_idx=modimpl.formsemestre.semestre_id,
|
||||||
+ "\n".join(
|
|
||||||
[
|
|
||||||
f"""<div style="--coef:{coef};
|
|
||||||
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
|
||||||
"><div>{coef}</div>{ue.acronyme}</div>"""
|
|
||||||
for ue, coef in coefs_lst
|
|
||||||
if coef > 0
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
+ "</div>"
|
}">détail</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if coefs_lst:
|
||||||
|
H += _html_hinton_map(
|
||||||
|
colors=(uc[0].color for uc in coefs_lst),
|
||||||
|
max_val=max_coef,
|
||||||
|
size=36,
|
||||||
|
title=modimpl.module.get_ue_coefs_descr(),
|
||||||
|
values=(uc[1] for uc in coefs_lst),
|
||||||
)
|
)
|
||||||
|
# (
|
||||||
|
# f"""
|
||||||
|
# <div class="coefs_histo" style="--max:{max_coef}">
|
||||||
|
# """
|
||||||
|
# + "\n".join(
|
||||||
|
# [
|
||||||
|
# f"""<div style="--coef:{coef};
|
||||||
|
# {'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||||
|
# "><div>{coef}</div>{ue.acronyme}</div>"""
|
||||||
|
# for ue, coef in coefs_lst
|
||||||
|
# if coef > 0
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
# + "</div>"
|
||||||
|
# )
|
||||||
else:
|
else:
|
||||||
H += """<div class="missing_value">non définis</div>"""
|
H += """<div class="missing_value">non définis</div>"""
|
||||||
H += "</div>"
|
H += "</div>"
|
||||||
@ -177,9 +204,7 @@ def _ue_coefs_html(coefs_lst) -> str:
|
|||||||
|
|
||||||
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||||
"""Tableau de bord module (liste des evaluations etc)"""
|
"""Tableau de bord module (liste des evaluations etc)"""
|
||||||
if not isinstance(moduleimpl_id, int):
|
modimpl: ModuleImpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
||||||
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
|
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
|
||||||
g.current_moduleimpl_id = modimpl.id
|
g.current_moduleimpl_id = modimpl.id
|
||||||
module: Module = modimpl.module
|
module: Module = modimpl.module
|
||||||
formsemestre_id = modimpl.formsemestre_id
|
formsemestre_id = modimpl.formsemestre_id
|
||||||
@ -195,13 +220,22 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
Evaluation.date_debut.desc(),
|
Evaluation.date_debut.desc(),
|
||||||
).all()
|
).all()
|
||||||
nb_evaluations = len(evaluations)
|
nb_evaluations = len(evaluations)
|
||||||
max_poids = max(
|
# Le poids max pour chaque catégorie d'évaluation
|
||||||
[
|
max_poids_by_type: dict[int, float] = {}
|
||||||
max([p.poids for p in e.ue_poids] or [0]) * (e.coefficient or 0.0)
|
for eval_type in (
|
||||||
for e in evaluations
|
Evaluation.EVALUATION_NORMALE,
|
||||||
]
|
Evaluation.EVALUATION_RATTRAPAGE,
|
||||||
or [0]
|
Evaluation.EVALUATION_SESSION2,
|
||||||
)
|
Evaluation.EVALUATION_BONUS,
|
||||||
|
):
|
||||||
|
max_poids_by_type[eval_type] = max(
|
||||||
|
[
|
||||||
|
max([p.poids for p in e.ue_poids] or [0]) * (e.coefficient or 0.0)
|
||||||
|
for e in evaluations
|
||||||
|
if e.evaluation_type == eval_type
|
||||||
|
]
|
||||||
|
or [0.0]
|
||||||
|
)
|
||||||
#
|
#
|
||||||
sem_locked = not formsemestre.etat
|
sem_locked = not formsemestre.etat
|
||||||
can_edit_evals = (
|
can_edit_evals = (
|
||||||
@ -265,7 +299,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
H.append(scu.icontag("lock32_img", title="verrouillé"))
|
H.append(scu.icontag("lock32_img", title="verrouillé"))
|
||||||
H.append("""</td><td class="fichetitre2">""")
|
H.append("""</td><td class="fichetitre2">""")
|
||||||
if modimpl.module.is_apc():
|
if modimpl.module.is_apc():
|
||||||
H.append(_ue_coefs_html(modimpl.module.ue_coefs_list()))
|
H.append(_ue_coefs_html(modimpl))
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
f"""Coef. dans le semestre: {
|
f"""Coef. dans le semestre: {
|
||||||
@ -284,10 +318,13 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
H.append(
|
H.append(
|
||||||
f"""<tr><td class="fichetitre2">Inscrits: </td><td> {len(mod_inscrits)} étudiants"""
|
f"""<tr><td class="fichetitre2">Inscrits: </td><td> {len(mod_inscrits)} étudiants"""
|
||||||
)
|
)
|
||||||
if current_user.has_permission(Permission.EtudInscrit):
|
if modimpl.can_change_inscriptions(raise_exc=False):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a class="stdlink" style="margin-left:2em;"
|
f"""<a class="stdlink" style="margin-left:2em;"
|
||||||
href="moduleimpl_inscriptions_edit?moduleimpl_id={modimpl.id}">modifier</a>"""
|
href="{
|
||||||
|
url_for("notes.moduleimpl_inscriptions_edit",
|
||||||
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id
|
||||||
|
)}">modifier</a>"""
|
||||||
)
|
)
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
# Ligne: règle de calcul
|
# Ligne: règle de calcul
|
||||||
@ -318,35 +355,62 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
f"""
|
f"""
|
||||||
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
||||||
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
url_for("assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept)
|
||||||
}?group_ids={group_id}&jour={
|
}?group_ids={group_id}&day={
|
||||||
datetime.date.today().isoformat()
|
datetime.date.today().isoformat()
|
||||||
}&formsemestre_id={formsemestre.id}
|
}&formsemestre_id={formsemestre.id}
|
||||||
&moduleimpl_id={moduleimpl_id}
|
&moduleimpl_id={moduleimpl_id}
|
||||||
"
|
"
|
||||||
>Saisie Absences journée</a></span>
|
>Saisie Absences</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
current_week: str = datetime.datetime.now().strftime("%G-W%V")
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
<span class="moduleimpl_abs_link"><a class="stdlink" href="{
|
||||||
url_for(
|
url_for("assiduites.signal_assiduites_hebdo",
|
||||||
"assiduites.signal_assiduites_diff",
|
scodoc_dept=g.scodoc_dept,
|
||||||
scodoc_dept=g.scodoc_dept,
|
formsemestre_id=formsemestre.id,
|
||||||
group_ids=group_id,
|
group_ids=group_id,
|
||||||
formsemestre_id=formsemestre.id,
|
week=current_week,
|
||||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
moduleimpl_id=moduleimpl_id
|
||||||
)}"
|
)
|
||||||
>Saisie Absences Différée</a></span>
|
}
|
||||||
|
"
|
||||||
|
>Saisie Absences (Hebdo)</a></span>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append("</td></tr></table>")
|
H.append("</td></tr></table>")
|
||||||
#
|
#
|
||||||
if not modimpl.check_apc_conformity(nt):
|
if not modimpl.check_apc_conformity(nt):
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="warning conformite">Les poids des évaluations de ce module ne sont
|
"""<div class="warning conformite">Les poids des évaluations de ce
|
||||||
pas encore conformes au PN.
|
module ne permettent pas d'évaluer toutes les UEs (compétences)
|
||||||
Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.
|
prévues par les coefficients du programme.
|
||||||
|
<b>Ses notes ne peuvent pas être prises en compte dans les moyennes d'UE.</b>
|
||||||
|
Vérifiez les poids des évaluations.
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
if not modimpl.check_apc_conformity(
|
||||||
|
nt, evaluation_type=Evaluation.EVALUATION_SESSION2
|
||||||
|
):
|
||||||
|
H.append(
|
||||||
|
"""<div class="warning conformite">
|
||||||
|
Il y a des évaluations de <b>deuxième session</b>
|
||||||
|
mais leurs poids ne permettent pas d'évaluer toutes les UEs (compétences)
|
||||||
|
prévues par les coefficients du programme.
|
||||||
|
La deuxième session ne sera donc <b>pas prise en compte</b>.
|
||||||
|
Vérifiez les poids de ces évaluations.
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
if not modimpl.check_apc_conformity(
|
||||||
|
nt, evaluation_type=Evaluation.EVALUATION_RATTRAPAGE
|
||||||
|
):
|
||||||
|
H.append(
|
||||||
|
"""<div class="warning conformite">
|
||||||
|
Il y a des évaluations de <b>rattrapage</b>
|
||||||
|
mais leurs poids n'évaluent pas toutes les UEs (compétences)
|
||||||
|
prévues par les coefficients du programme.
|
||||||
|
Vérifiez les poids de ces évaluations.
|
||||||
</div>"""
|
</div>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -437,7 +501,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
eval_index=eval_index,
|
eval_index=eval_index,
|
||||||
nb_evals=nb_evaluations,
|
nb_evals=nb_evaluations,
|
||||||
is_apc=nt.is_apc,
|
is_apc=nt.is_apc,
|
||||||
max_poids=max_poids,
|
max_poids=max_poids_by_type.get(evaluation.evaluation_type, 10000.0),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
eval_index -= 1
|
eval_index -= 1
|
||||||
@ -756,27 +820,27 @@ def _ligne_evaluation(
|
|||||||
#
|
#
|
||||||
if etat["nb_notes"] == 0:
|
if etat["nb_notes"] == 0:
|
||||||
H.append(f"""<tr class="{tr_class}"><td></td>""")
|
H.append(f"""<tr class="{tr_class}"><td></td>""")
|
||||||
if modimpl.module.is_apc():
|
# if modimpl.module.is_apc():
|
||||||
H.append(
|
# H.append(
|
||||||
f"""<td colspan="8" class="eval_poids">{
|
# f"""<td colspan="8" class="eval_poids">{
|
||||||
evaluation.get_ue_poids_str()}</td>"""
|
# evaluation.get_ue_poids_str()}</td>"""
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
H.append('<td colspan="8"></td>')
|
# H.append('<td colspan="8"></td>')
|
||||||
H.append("""</tr>""")
|
H.append("""</tr>""")
|
||||||
else: # il y a deja des notes saisies
|
else: # il y a deja des notes saisies
|
||||||
gr_moyennes = etat["gr_moyennes"]
|
gr_moyennes = etat["gr_moyennes"]
|
||||||
first_group = True
|
# first_group = True
|
||||||
for gr_moyenne in gr_moyennes:
|
for gr_moyenne in gr_moyennes:
|
||||||
H.append(f"""<tr class="{tr_class}"><td> </td>""")
|
H.append(f"""<tr class="{tr_class}"><td> </td>""")
|
||||||
if first_group and modimpl.module.is_apc():
|
# if first_group and modimpl.module.is_apc():
|
||||||
H.append(
|
# H.append(
|
||||||
f"""<td class="eval_poids" colspan="4">{
|
# f"""<td class="eval_poids" colspan="4">{
|
||||||
evaluation.get_ue_poids_str()}</td>"""
|
# evaluation.get_ue_poids_str()}</td>"""
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
H.append("""<td colspan="4"></td>""")
|
H.append("""<td colspan="4"></td>""")
|
||||||
first_group = False
|
# first_group = False
|
||||||
if gr_moyenne["group_name"] is None:
|
if gr_moyenne["group_name"] is None:
|
||||||
name = "Tous" # tous
|
name = "Tous" # tous
|
||||||
else:
|
else:
|
||||||
@ -832,26 +896,47 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st
|
|||||||
ue_poids = evaluation.get_ue_poids_dict(sort=True) # { ue_id : poids }
|
ue_poids = evaluation.get_ue_poids_dict(sort=True) # { ue_id : poids }
|
||||||
if not ue_poids:
|
if not ue_poids:
|
||||||
return ""
|
return ""
|
||||||
if max_poids < scu.NOTES_PRECISION:
|
values = [poids * (evaluation.coefficient) for poids in ue_poids.values()]
|
||||||
|
colors = [db.session.get(UniteEns, ue_id).color for ue_id in ue_poids]
|
||||||
|
return _html_hinton_map(
|
||||||
|
classes=("evaluation_poids",),
|
||||||
|
colors=colors,
|
||||||
|
max_val=max_poids,
|
||||||
|
title=f"Poids de l'évaluation vers les UEs: {evaluation.get_ue_poids_str()}",
|
||||||
|
values=values,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _html_hinton_map(
|
||||||
|
classes=(),
|
||||||
|
colors=(),
|
||||||
|
max_val: float | None = None,
|
||||||
|
size=12,
|
||||||
|
title: str = "",
|
||||||
|
values=(),
|
||||||
|
) -> str:
|
||||||
|
"""Représente une liste de nombres sous forme de carrés"""
|
||||||
|
if max_val is None:
|
||||||
|
max_val = max(values)
|
||||||
|
if max_val < scu.NOTES_PRECISION:
|
||||||
return ""
|
return ""
|
||||||
H = (
|
return (
|
||||||
"""<div class="evaluation_poids">"""
|
f"""<div class="hinton_map {" ".join(classes)}"
|
||||||
|
style="--size:{size}px;"
|
||||||
|
title="{title}"
|
||||||
|
data-tooltip>"""
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
[
|
[
|
||||||
f"""<div title="poids vers {ue.acronyme}: {poids:g}">
|
f"""<div>
|
||||||
<div style="--size:{math.sqrt(poids*(evaluation.coefficient)/max_poids*144)}px;
|
<div style="--boxsize:{size*math.sqrt(value/max_val)}px;
|
||||||
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
{'background-color: ' + color + ';' if color else ''}
|
||||||
"></div>
|
"></div>
|
||||||
</div>"""
|
</div>"""
|
||||||
for ue, poids in (
|
for value, color in zip(values, colors)
|
||||||
(db.session.get(UniteEns, ue_id), poids)
|
|
||||||
for ue_id, poids in ue_poids.items()
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ "</div>"
|
+ "</div>"
|
||||||
)
|
)
|
||||||
return H
|
|
||||||
|
|
||||||
|
|
||||||
def _html_modimpl_etuds_attente(res: ResultatsSemestre, modimpl: ModuleImpl) -> str:
|
def _html_modimpl_etuds_attente(res: ResultatsSemestre, modimpl: ModuleImpl) -> str:
|
||||||
|
@ -264,14 +264,13 @@ def fiche_etud(etudid=None):
|
|||||||
sem_info[formsemestre.id] = grlink
|
sem_info[formsemestre.id] = grlink
|
||||||
|
|
||||||
if inscriptions:
|
if inscriptions:
|
||||||
Se = sco_cursus.get_situation_etud_cursus(info, info["last_formsemestre_id"])
|
Se = sco_cursus.get_situation_etud_cursus(etud, info["last_formsemestre_id"])
|
||||||
info["liste_inscriptions"] = formsemestre_recap_parcours_table(
|
info["liste_inscriptions"] = formsemestre_recap_parcours_table(
|
||||||
Se,
|
Se,
|
||||||
etudid,
|
etudid,
|
||||||
with_links=False,
|
with_links=False,
|
||||||
sem_info=sem_info,
|
sem_info=sem_info,
|
||||||
with_all_columns=False,
|
with_all_columns=False,
|
||||||
a_url="Notes/",
|
|
||||||
)
|
)
|
||||||
info["link_bul_pdf"] = (
|
info["link_bul_pdf"] = (
|
||||||
"""<span class="link_bul_pdf fontred">PDF interdits par l'admin.</span>"""
|
"""<span class="link_bul_pdf fontred">PDF interdits par l'admin.</span>"""
|
||||||
|
@ -247,6 +247,7 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
footer_template=DEFAULT_PDF_FOOTER_TEMPLATE,
|
footer_template=DEFAULT_PDF_FOOTER_TEMPLATE,
|
||||||
filigranne=None,
|
filigranne=None,
|
||||||
preferences=None, # dictionnary with preferences, required
|
preferences=None, # dictionnary with preferences, required
|
||||||
|
with_page_numbers=False,
|
||||||
):
|
):
|
||||||
"""Initialise our page template."""
|
"""Initialise our page template."""
|
||||||
# defered import (solve circular dependency ->sco_logo ->scodoc, ->sco_pdf
|
# defered import (solve circular dependency ->sco_logo ->scodoc, ->sco_pdf
|
||||||
@ -259,8 +260,9 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
self.pdfmeta_subject = subject
|
self.pdfmeta_subject = subject
|
||||||
self.server_name = server_name
|
self.server_name = server_name
|
||||||
self.filigranne = filigranne
|
self.filigranne = filigranne
|
||||||
self.page_number = 1
|
|
||||||
self.footer_template = footer_template
|
self.footer_template = footer_template
|
||||||
|
self.with_page_numbers = with_page_numbers
|
||||||
|
self.page_number = 1
|
||||||
if self.preferences:
|
if self.preferences:
|
||||||
self.with_page_background = self.preferences["bul_pdf_with_background"]
|
self.with_page_background = self.preferences["bul_pdf_with_background"]
|
||||||
else:
|
else:
|
||||||
@ -337,6 +339,7 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
|
|
||||||
def draw_footer(self, canv, content):
|
def draw_footer(self, canv, content):
|
||||||
"""Print the footer"""
|
"""Print the footer"""
|
||||||
|
# called 1/page
|
||||||
try:
|
try:
|
||||||
canv.setFont(
|
canv.setFont(
|
||||||
self.preferences["SCOLAR_FONT"],
|
self.preferences["SCOLAR_FONT"],
|
||||||
@ -351,8 +354,11 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
canv.drawString(
|
canv.drawString(
|
||||||
self.preferences["pdf_footer_x"] * mm,
|
self.preferences["pdf_footer_x"] * mm,
|
||||||
self.preferences["pdf_footer_y"] * mm,
|
self.preferences["pdf_footer_y"] * mm,
|
||||||
content,
|
content + " " + (self.preferences["pdf_footer_extra"] or ""),
|
||||||
)
|
)
|
||||||
|
if self.with_page_numbers:
|
||||||
|
canv.drawString(190.0 * mm, 6 * mm, f"Page {self.page_number}")
|
||||||
|
|
||||||
canv.restoreState()
|
canv.restoreState()
|
||||||
|
|
||||||
def footer_string(self) -> str:
|
def footer_string(self) -> str:
|
||||||
@ -382,18 +388,14 @@ class ScoDocPageTemplate(PageTemplate):
|
|||||||
filigranne = self.filigranne.get(doc.page, None)
|
filigranne = self.filigranne.get(doc.page, None)
|
||||||
if filigranne:
|
if filigranne:
|
||||||
canv.saveState()
|
canv.saveState()
|
||||||
canv.translate(9 * cm, 27.6 * cm)
|
canv.translate(10 * cm, 21.0 * cm)
|
||||||
canv.rotate(30)
|
canv.rotate(36)
|
||||||
canv.scale(4.5, 4.5)
|
canv.scale(7, 7)
|
||||||
canv.setFillColorRGB(1.0, 0.65, 0.65, alpha=0.6)
|
canv.setFillColorRGB(1.0, 0.65, 0.65, alpha=0.6)
|
||||||
canv.drawRightString(0, 0, SU(filigranne))
|
canv.drawCentredString(0, 0, SU(filigranne))
|
||||||
canv.restoreState()
|
canv.restoreState()
|
||||||
doc.filigranne = None
|
doc.filigranne = None
|
||||||
|
# Increment page number
|
||||||
def afterPage(self):
|
|
||||||
"""Called after all flowables have been drawn on a page.
|
|
||||||
Increment pageNum since the page has been completed.
|
|
||||||
"""
|
|
||||||
self.page_number += 1
|
self.page_number += 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,13 +96,16 @@ def photo_portal_url(code_nip: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_etud_photo_url(etudid, size="small"):
|
def get_etud_photo_url(etudid, size="small", seed=None):
|
||||||
|
"L'URL scodoc vers la photo de l'étudiant"
|
||||||
|
kwargs = {"seed": seed} if seed else {}
|
||||||
return (
|
return (
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.get_photo_image",
|
"scolar.get_photo_image",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
size=size,
|
size=size,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
if has_request_context()
|
if has_request_context()
|
||||||
else ""
|
else ""
|
||||||
@ -114,9 +117,11 @@ def etud_photo_url(etud: dict, size="small", fast=False) -> str:
|
|||||||
If ScoDoc doesn't have an image and a portal is configured, link to it.
|
If ScoDoc doesn't have an image and a portal is configured, link to it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
photo_url = get_etud_photo_url(etud["etudid"], size=size)
|
|
||||||
if fast:
|
if fast:
|
||||||
return photo_url
|
return get_etud_photo_url(etud["etudid"], size=size)
|
||||||
|
photo_url = get_etud_photo_url(
|
||||||
|
etud["etudid"], size=size, seed=hash(etud.get("photo_filename"))
|
||||||
|
)
|
||||||
path = photo_pathname(etud["photo_filename"], size=size)
|
path = photo_pathname(etud["photo_filename"], size=size)
|
||||||
if not path:
|
if not path:
|
||||||
# Portail ?
|
# Portail ?
|
||||||
@ -374,7 +379,15 @@ def copy_portal_photo_to_fs(etudid: int):
|
|||||||
portal_timeout = sco_preferences.get_preference("portal_timeout")
|
portal_timeout = sco_preferences.get_preference("portal_timeout")
|
||||||
error_message = None
|
error_message = None
|
||||||
try:
|
try:
|
||||||
r = requests.get(url, timeout=portal_timeout)
|
r = requests.get(
|
||||||
|
url,
|
||||||
|
timeout=portal_timeout,
|
||||||
|
params={
|
||||||
|
"nom": etud.nom or "",
|
||||||
|
"prenom": etud.prenom or "",
|
||||||
|
"civilite": etud.civilite,
|
||||||
|
},
|
||||||
|
)
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
error_message = "ConnectionError"
|
error_message = "ConnectionError"
|
||||||
except requests.Timeout:
|
except requests.Timeout:
|
||||||
|
@ -378,6 +378,7 @@ class PlacementRunner:
|
|||||||
preferences=sco_preferences.SemPreferences(
|
preferences=sco_preferences.SemPreferences(
|
||||||
self.moduleimpl_data["formsemestre_id"]
|
self.moduleimpl_data["formsemestre_id"]
|
||||||
),
|
),
|
||||||
|
table_id="placement_pdf",
|
||||||
)
|
)
|
||||||
return tab.make_page(fmt="pdf", with_html_headers=False)
|
return tab.make_page(fmt="pdf", with_html_headers=False)
|
||||||
|
|
||||||
|
@ -221,6 +221,7 @@ def formsemestre_poursuite_report(formsemestre_id, fmt="html"):
|
|||||||
html_class="table_leftalign table_listegroupe",
|
html_class="table_leftalign table_listegroupe",
|
||||||
pdf_link=False, # pas d'export pdf
|
pdf_link=False, # pas d'export pdf
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id="formsemestre_poursuite_report",
|
||||||
)
|
)
|
||||||
tab.filename = scu.make_filename("poursuite " + sem["titreannee"])
|
tab.filename = scu.make_filename("poursuite " + sem["titreannee"])
|
||||||
|
|
||||||
|
@ -110,6 +110,7 @@ get_base_preferences(formsemestre_id)
|
|||||||
Return base preferences for current scodoc_dept (instance BasePreferences)
|
Return base preferences for current scodoc_dept (instance BasePreferences)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import current_app, flash, g, request, url_for
|
from flask import current_app, flash, g, request, url_for
|
||||||
|
|
||||||
@ -592,7 +593,9 @@ class BasePreferences:
|
|||||||
PS: Au dela de %(abs_notify_abs_threshold)s, un email automatique est adressé toutes les %(abs_notify_abs_increment)s absences. Ces valeurs sont modifiables dans les préférences de ScoDoc.
|
PS: Au dela de %(abs_notify_abs_threshold)s, un email automatique est adressé toutes les %(abs_notify_abs_increment)s absences. Ces valeurs sont modifiables dans les préférences de ScoDoc.
|
||||||
""",
|
""",
|
||||||
"title": """Message notification e-mail""",
|
"title": """Message notification e-mail""",
|
||||||
"explanation": """Balises remplacées, voir la documentation""",
|
"explanation": """Balises remplacées, voir <a class="stdlink" target="_blank"
|
||||||
|
href="https://scodoc.org/GestionAbsences/#contenu-du-mail-de-notification"
|
||||||
|
>la documentation</a>""",
|
||||||
"input_type": "textarea",
|
"input_type": "textarea",
|
||||||
"rows": 15,
|
"rows": 15,
|
||||||
"cols": 64,
|
"cols": 64,
|
||||||
@ -622,18 +625,6 @@ class BasePreferences:
|
|||||||
"explanation": "Désactive la saisie et l'affichage des présences",
|
"explanation": "Désactive la saisie et l'affichage des présences",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"periode_defaut",
|
|
||||||
{
|
|
||||||
"initvalue": 2.0,
|
|
||||||
"size": 10,
|
|
||||||
"title": "Durée par défaut d'un créneau",
|
|
||||||
"type": "float",
|
|
||||||
"category": "assi",
|
|
||||||
"only_global": True,
|
|
||||||
"explanation": "Durée d'un créneau en heure. Utilisé dans les pages de saisie",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"nb_heures_par_jour",
|
"nb_heures_par_jour",
|
||||||
{
|
{
|
||||||
@ -963,6 +954,16 @@ class BasePreferences:
|
|||||||
"category": "pdf",
|
"category": "pdf",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"pdf_footer_extra",
|
||||||
|
{
|
||||||
|
"initvalue": "",
|
||||||
|
"title": "Texte à ajouter en pied de page",
|
||||||
|
"explanation": "sur tous les documents, par exemple vos coordonnées, ...",
|
||||||
|
"size": 78,
|
||||||
|
"category": "pdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"pdf_footer_x",
|
"pdf_footer_x",
|
||||||
{
|
{
|
||||||
|
@ -81,14 +81,11 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
nbabs = {}
|
nbabs = {}
|
||||||
nbabsjust = {}
|
nbabsjust = {}
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
etud.to_dict_scodoc7(), formsemestre_id
|
if Se.prev_formsemestre:
|
||||||
)
|
ntp: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||||
if Se.prev:
|
Se.prev_formsemestre
|
||||||
formsemestre_prev = FormSemestre.query.get_or_404(
|
|
||||||
Se.prev["formsemestre_id"]
|
|
||||||
)
|
)
|
||||||
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
|
|
||||||
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||||
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
||||||
ue_code_s = (
|
ue_code_s = (
|
||||||
@ -110,7 +107,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
||||||
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
||||||
|
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
try:
|
try:
|
||||||
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
@ -132,12 +129,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
# parcours:
|
# parcours:
|
||||||
parcours[etud.id] = Se.get_cursus_descr()
|
parcours[etud.id] = Se.get_cursus_descr()
|
||||||
# groupe principal (td)
|
# groupe principal (td)
|
||||||
groupestd[etud.id] = ""
|
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(main_partition_id, "")
|
||||||
for s in Se.etud["sems"]:
|
|
||||||
if s["formsemestre_id"] == formsemestre_id:
|
|
||||||
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
|
|
||||||
main_partition_id, ""
|
|
||||||
)
|
|
||||||
# absences:
|
# absences:
|
||||||
_, nbabsjust[etud.id], nbabs[etud.id] = sco_assiduites.get_assiduites_count(
|
_, nbabsjust[etud.id], nbabs[etud.id] = sco_assiduites.get_assiduites_count(
|
||||||
etud.id, sem
|
etud.id, sem
|
||||||
@ -340,295 +332,3 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
scu.XLSX_SUFFIX,
|
scu.XLSX_SUFFIX,
|
||||||
mime=scu.XLSX_MIMETYPE,
|
mime=scu.XLSX_MIMETYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def feuille_preparation_lille(formsemestre_id):
|
|
||||||
"""Feuille excel pour préparation des jurys classiques.
|
|
||||||
Non adaptée pour le BUT.
|
|
||||||
"""
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
|
|
||||||
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
|
||||||
main_partition_id = sco_groups.formsemestre_get_main_partition(formsemestre_id)[
|
|
||||||
"partition_id"
|
|
||||||
]
|
|
||||||
|
|
||||||
prev_moy_ue = scu.DictDefault(defaultvalue={}) # ue_code_s : { etudid : moy ue }
|
|
||||||
prev_ue_acro = {} # ue_code_s : acronyme (à afficher)
|
|
||||||
prev_moy = {} # moyennes gen sem prec
|
|
||||||
moy_ue = scu.DictDefault(defaultvalue={}) # ue_acro : moyennes { etudid : moy ue }
|
|
||||||
ue_acro = {} # ue_code_s : acronyme (à afficher)
|
|
||||||
moy = {} # moyennes gen
|
|
||||||
moy_inter = {} # moyenne gen. sur les 2 derniers semestres
|
|
||||||
code = {} # decision existantes s'il y en a
|
|
||||||
autorisations = {}
|
|
||||||
prev_code = {} # decisions sem prec
|
|
||||||
assidu = {}
|
|
||||||
parcours = {} # etudid : parcours, sous la forme S1, S2, S2, S3
|
|
||||||
groupestd = {} # etudid : nom groupe principal
|
|
||||||
nbabs = {}
|
|
||||||
nbabsjust = {}
|
|
||||||
for etud in etuds:
|
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
|
||||||
etud.to_dict_scodoc7(), formsemestre_id
|
|
||||||
)
|
|
||||||
if Se.prev:
|
|
||||||
formsemestre_prev = FormSemestre.query.get_or_404(
|
|
||||||
Se.prev["formsemestre_id"]
|
|
||||||
)
|
|
||||||
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
|
|
||||||
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
|
||||||
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
|
||||||
ue_code_s = (
|
|
||||||
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
|
|
||||||
) # code indentifiant l'UE
|
|
||||||
prev_moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
|
||||||
prev_ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
|
||||||
prev_moy[etud.id] = ntp.get_etud_moy_gen(etud.id)
|
|
||||||
prev_decision = ntp.get_etud_decision_sem(etud.id)
|
|
||||||
if prev_decision:
|
|
||||||
prev_code[etud.id] = prev_decision["code"]
|
|
||||||
if prev_decision["compense_formsemestre_id"]:
|
|
||||||
prev_code[etud.id] += "+" # indique qu'il a servi a compenser
|
|
||||||
|
|
||||||
moy[etud.id] = nt.get_etud_moy_gen(etud.id)
|
|
||||||
for ue in nt.get_ues_stat_dict(filter_sport=True):
|
|
||||||
ue_status = nt.get_etud_ue_status(etud.id, ue["ue_id"])
|
|
||||||
ue_code_s = f'{ue["ue_code"]}_{nt.sem["semestre_id"]}'
|
|
||||||
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
|
||||||
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
|
||||||
|
|
||||||
if Se.prev:
|
|
||||||
try:
|
|
||||||
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
decision = nt.get_etud_decision_sem(etud.id)
|
|
||||||
if decision:
|
|
||||||
code[etud.id] = decision["code"]
|
|
||||||
if decision["compense_formsemestre_id"]:
|
|
||||||
code[etud.id] += "+" # indique qu'il a servi a compenser
|
|
||||||
assidu[etud.id] = {False: "Non", True: "Oui"}.get(decision["assidu"], "")
|
|
||||||
|
|
||||||
autorisations_etud = ScolarAutorisationInscription.query.filter_by(
|
|
||||||
etudid=etud.id, origin_formsemestre_id=formsemestre_id
|
|
||||||
).all()
|
|
||||||
autorisations[etud.id] = ", ".join(
|
|
||||||
[f"S{x.semestre_id}" for x in autorisations_etud]
|
|
||||||
)
|
|
||||||
# parcours:
|
|
||||||
parcours[etud.id] = Se.get_parcours_descr()
|
|
||||||
# groupe principal (td)
|
|
||||||
groupestd[etud.id] = ""
|
|
||||||
for s in Se.etud["sems"]:
|
|
||||||
if s["formsemestre_id"] == formsemestre_id:
|
|
||||||
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
|
|
||||||
main_partition_id, ""
|
|
||||||
)
|
|
||||||
# absences:
|
|
||||||
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem)
|
|
||||||
nbabs[etud.id] = e_nbabs
|
|
||||||
nbabsjust[etud.id] = e_nbabs - e_nbabsjust
|
|
||||||
|
|
||||||
# Codes des UE "semestre précédent":
|
|
||||||
ue_prev_codes = list(prev_moy_ue.keys())
|
|
||||||
ue_prev_codes.sort(
|
|
||||||
key=lambda x, prev_ue_acro=prev_ue_acro: prev_ue_acro[ # pylint: disable=undefined-variable
|
|
||||||
x
|
|
||||||
]
|
|
||||||
)
|
|
||||||
# Codes des UE "semestre courant":
|
|
||||||
ue_codes = list(moy_ue.keys())
|
|
||||||
ue_codes.sort(
|
|
||||||
key=lambda x, ue_acro=ue_acro: ue_acro[x] # pylint: disable=undefined-variable
|
|
||||||
)
|
|
||||||
|
|
||||||
sid = sem["semestre_id"]
|
|
||||||
sn = sp = ""
|
|
||||||
if sid >= 0:
|
|
||||||
sn = f"S{sid}"
|
|
||||||
if prev_moy: # si qq chose dans precedent
|
|
||||||
sp = f"S{sid - 1}"
|
|
||||||
|
|
||||||
sheet = sco_excel.ScoExcelSheet(sheet_name=f"Prepa Jury {sn}")
|
|
||||||
# génération des styles
|
|
||||||
style_bold = sco_excel.excel_make_style(size=10, bold=True)
|
|
||||||
style_center = sco_excel.excel_make_style(halign="center")
|
|
||||||
style_boldcenter = sco_excel.excel_make_style(bold=True, halign="center")
|
|
||||||
style_moy = sco_excel.excel_make_style(
|
|
||||||
bold=True, halign="center", bgcolor=sco_excel.COLORS.LIGHT_YELLOW
|
|
||||||
)
|
|
||||||
style_note = sco_excel.excel_make_style(
|
|
||||||
halign="right", number_format=FORMAT_NUMBER_00
|
|
||||||
)
|
|
||||||
style_note_bold = sco_excel.excel_make_style(
|
|
||||||
halign="right", bold=True, number_format="General"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Première ligne
|
|
||||||
sheet.append_single_cell_row(
|
|
||||||
"Feuille préparation Jury %s" % scu.unescape_html(sem["titreannee"]), style_bold
|
|
||||||
)
|
|
||||||
sheet.append_blank_row()
|
|
||||||
|
|
||||||
# Ligne de titre
|
|
||||||
titles = ["Rang"]
|
|
||||||
if sco_preferences.get_preference("prepa_jury_nip"):
|
|
||||||
titles.append("NIP")
|
|
||||||
if sco_preferences.get_preference("prepa_jury_ine"):
|
|
||||||
titles.append("INE")
|
|
||||||
titles += [
|
|
||||||
"etudid",
|
|
||||||
"Civ.",
|
|
||||||
"Nom",
|
|
||||||
"Prénom",
|
|
||||||
"Naissance",
|
|
||||||
"Bac",
|
|
||||||
"Spe",
|
|
||||||
"Rg Adm",
|
|
||||||
"Parcours",
|
|
||||||
"Groupe",
|
|
||||||
]
|
|
||||||
if prev_moy: # si qq chose dans precedent
|
|
||||||
titles += [prev_ue_acro[x][1] for x in ue_prev_codes] + [
|
|
||||||
f"Moy {sp}",
|
|
||||||
f"Décision {sp}",
|
|
||||||
]
|
|
||||||
titles += [ue_acro[x][1] for x in ue_codes] + [f"Moy {sn}"]
|
|
||||||
if moy_inter:
|
|
||||||
titles += [f"Moy {sp}-{sn}"]
|
|
||||||
titles += ["Abs", "Abs Injust."]
|
|
||||||
if code:
|
|
||||||
titles.append("Proposit. {sn}")
|
|
||||||
if autorisations:
|
|
||||||
titles.append("Autorisations")
|
|
||||||
# titles.append('Assidu')
|
|
||||||
sheet.append_row(sheet.make_row(titles, style_boldcenter))
|
|
||||||
# if prev_moy:
|
|
||||||
# tit_prev_moy = "Moy " + sp
|
|
||||||
# # col_prev_moy = titles.index(tit_prev_moy)
|
|
||||||
# tit_moy = "Moy " + sn
|
|
||||||
# col_moy = titles.index(tit_moy)
|
|
||||||
# col_abs = titles.index("Abs")
|
|
||||||
|
|
||||||
def fmt(x):
|
|
||||||
"reduit les notes a deux chiffres"
|
|
||||||
x = scu.fmt_note(x, keep_numeric=False)
|
|
||||||
try:
|
|
||||||
return float(x)
|
|
||||||
except:
|
|
||||||
return x
|
|
||||||
|
|
||||||
i = 1 # numero etudiant
|
|
||||||
for etud in etuds:
|
|
||||||
cells = []
|
|
||||||
cells.append(sheet.make_cell(str(i)))
|
|
||||||
if sco_preferences.get_preference("prepa_jury_nip"):
|
|
||||||
cells.append(sheet.make_cell(etud.code_nip))
|
|
||||||
if sco_preferences.get_preference("prepa_jury_ine"):
|
|
||||||
cells.append(sheet.make_cell(etud.code_ine))
|
|
||||||
admission = etud.admission.first()
|
|
||||||
cells += sheet.make_row(
|
|
||||||
[
|
|
||||||
etud.id,
|
|
||||||
etud.civilite_str,
|
|
||||||
sco_etud.format_nom(etud.nom),
|
|
||||||
sco_etud.format_prenom(etud.prenom),
|
|
||||||
etud.date_naissance,
|
|
||||||
admission.bac,
|
|
||||||
admission.specialite,
|
|
||||||
admission.classement,
|
|
||||||
parcours[etud.id],
|
|
||||||
groupestd[etud.id],
|
|
||||||
]
|
|
||||||
)
|
|
||||||
co = len(cells)
|
|
||||||
if prev_moy:
|
|
||||||
for ue_acro in ue_prev_codes:
|
|
||||||
cells.append(
|
|
||||||
sheet.make_cell(
|
|
||||||
fmt(prev_moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
|
|
||||||
)
|
|
||||||
)
|
|
||||||
co += 1
|
|
||||||
cells.append(
|
|
||||||
sheet.make_cell(fmt(prev_moy.get(etud.id, "")), style_bold)
|
|
||||||
) # moy gen prev
|
|
||||||
cells.append(
|
|
||||||
sheet.make_cell(fmt(prev_code.get(etud.id, "")), style_moy)
|
|
||||||
) # decision prev
|
|
||||||
co += 2
|
|
||||||
|
|
||||||
for ue_acro in ue_codes:
|
|
||||||
cells.append(
|
|
||||||
sheet.make_cell(
|
|
||||||
fmt(moy_ue.get(ue_acro, {}).get(etud.id, "")), style_note
|
|
||||||
)
|
|
||||||
)
|
|
||||||
co += 1
|
|
||||||
cells.append(
|
|
||||||
sheet.make_cell(fmt(moy.get(etud.id, "")), style_note_bold)
|
|
||||||
) # moy gen
|
|
||||||
co += 1
|
|
||||||
if moy_inter:
|
|
||||||
cells.append(sheet.make_cell(fmt(moy_inter.get(etud.id, "")), style_note))
|
|
||||||
cells.append(sheet.make_cell(str(nbabs.get(etud.id, "")), style_center))
|
|
||||||
cells.append(sheet.make_cell(str(nbabsjust.get(etud.id, "")), style_center))
|
|
||||||
if code:
|
|
||||||
cells.append(sheet.make_cell(code.get(etud.id, ""), style_moy))
|
|
||||||
cells.append(sheet.make_cell(autorisations.get(etud.id, ""), style_moy))
|
|
||||||
# l.append(assidu.get(etud.id, ''))
|
|
||||||
sheet.append_row(cells)
|
|
||||||
i += 1
|
|
||||||
#
|
|
||||||
sheet.append_blank_row()
|
|
||||||
# Explications des codes
|
|
||||||
codes = list(sco_codes_parcours.CODES_EXPL.keys())
|
|
||||||
codes.sort()
|
|
||||||
sheet.append_single_cell_row("Explication des codes")
|
|
||||||
for code in codes:
|
|
||||||
sheet.append_row(
|
|
||||||
sheet.make_row(["", "", "", code, sco_codes_parcours.CODES_EXPL[code]])
|
|
||||||
)
|
|
||||||
sheet.append_row(
|
|
||||||
sheet.make_row(
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"ADM+",
|
|
||||||
"indique que le semestre a déjà servi à en compenser un autre",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# UE : Correspondances acronyme et titre complet
|
|
||||||
sheet.append_blank_row()
|
|
||||||
sheet.append_single_cell_row("Titre des UE")
|
|
||||||
if prev_moy:
|
|
||||||
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
|
||||||
sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
|
||||||
for ue in nt.get_ues_stat_dict(filter_sport=True):
|
|
||||||
sheet.append_row(sheet.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
|
||||||
#
|
|
||||||
sheet.append_blank_row()
|
|
||||||
sheet.append_single_cell_row(
|
|
||||||
"Préparé par %s le %s sur %s pour %s"
|
|
||||||
% (
|
|
||||||
sco_version.SCONAME,
|
|
||||||
time.strftime("%d/%m/%Y"),
|
|
||||||
request.url_root,
|
|
||||||
current_user,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
xls = sheet.generate()
|
|
||||||
flash("Feuille préparation jury générée")
|
|
||||||
return scu.send_file(
|
|
||||||
xls,
|
|
||||||
f"PrepaJury{sn}",
|
|
||||||
scu.XLSX_SUFFIX,
|
|
||||||
mime=scu.XLSX_MIMETYPE,
|
|
||||||
)
|
|
||||||
|
@ -56,7 +56,8 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
etuds: Identite = nt.get_inscrits(order_by="moy") # tri par moy gen
|
breakpoint()
|
||||||
|
etuds: list[Identite] = nt.get_inscrits(order_by="moy") # tri par moy gen
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
etud_groups = sco_groups.formsemestre_get_etud_groupnames(formsemestre_id)
|
||||||
@ -81,14 +82,11 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
nbabsjust = {}
|
nbabsjust = {}
|
||||||
breakpoint()
|
breakpoint()
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
etud.to_dict_scodoc7(), formsemestre_id
|
if Se.prev_formsemestre:
|
||||||
)
|
ntp: NotesTableCompat = res_sem.load_formsemestre_results(
|
||||||
if Se.prev:
|
Se.prev_formsemestre
|
||||||
formsemestre_prev = FormSemestre.query.get_or_404(
|
|
||||||
Se.prev["formsemestre_id"]
|
|
||||||
)
|
)
|
||||||
ntp: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre_prev)
|
|
||||||
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||||
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
ue_status = ntp.get_etud_ue_status(etud.id, ue["ue_id"])
|
||||||
ue_code_s = (
|
ue_code_s = (
|
||||||
@ -110,7 +108,7 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
moy_ue[ue_code_s][etud.id] = ue_status["moy"] if ue_status else ""
|
||||||
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
ue_acro[ue_code_s] = (ue["numero"], ue["acronyme"], ue["titre"])
|
||||||
|
|
||||||
if Se.prev:
|
if Se.prev_formsemestre:
|
||||||
try:
|
try:
|
||||||
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
moy_inter[etud.id] = (moy[etud.id] + prev_moy[etud.id]) / 2.0
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
@ -133,8 +131,8 @@ def feuille_preparation_lille(formsemestre_id):
|
|||||||
parcours[etud.id] = "sco_prepajury_lille:132" # Se.get_parcours_descr()
|
parcours[etud.id] = "sco_prepajury_lille:132" # Se.get_parcours_descr()
|
||||||
# groupe principal (td)
|
# groupe principal (td)
|
||||||
groupestd[etud.id] = ""
|
groupestd[etud.id] = ""
|
||||||
for s in Se.etud["sems"]:
|
for s in Se.etud.formsemestre_inscriptions:
|
||||||
if s["formsemestre_id"] == formsemestre_id:
|
if s.formsemestre_id == formsemestre_id:
|
||||||
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
|
groupestd[etud.id] = etud_groups.get(etud.id, {}).get(
|
||||||
main_partition_id, ""
|
main_partition_id, ""
|
||||||
)
|
)
|
||||||
|
@ -42,7 +42,6 @@ from app.models import (
|
|||||||
but_validations,
|
but_validations,
|
||||||
)
|
)
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_edit_ue
|
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_cursus
|
from app.scodoc import sco_cursus
|
||||||
@ -81,6 +80,7 @@ def dict_pvjury(
|
|||||||
},
|
},
|
||||||
'autorisations' : [ { 'semestre_id' : { ... } } ],
|
'autorisations' : [ { 'semestre_id' : { ... } } ],
|
||||||
'validation_parcours' : True si parcours validé (diplome obtenu)
|
'validation_parcours' : True si parcours validé (diplome obtenu)
|
||||||
|
'parcours' : 'S1, S2, S3, S4, A1',
|
||||||
'prev_code' : code (calculé slt si with_prev),
|
'prev_code' : code (calculé slt si with_prev),
|
||||||
'mention' : mention (en fct moy gen),
|
'mention' : mention (en fct moy gen),
|
||||||
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
|
'sum_ects' : total ECTS acquis dans ce semestre (incluant les UE capitalisées)
|
||||||
@ -107,10 +107,10 @@ def dict_pvjury(
|
|||||||
D = {} # même chose que decisions, mais { etudid : dec }
|
D = {} # même chose que decisions, mais { etudid : dec }
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
situation_etud = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id)
|
||||||
etud.to_dict_scodoc7(), formsemestre_id
|
semestre_non_terminal = (
|
||||||
|
semestre_non_terminal or situation_etud.semestre_non_terminal
|
||||||
)
|
)
|
||||||
semestre_non_terminal = semestre_non_terminal or Se.semestre_non_terminal
|
|
||||||
d = {}
|
d = {}
|
||||||
d["identite"] = nt.identdict[etudid]
|
d["identite"] = nt.identdict[etudid]
|
||||||
d["etat"] = nt.get_etud_etat(
|
d["etat"] = nt.get_etud_etat(
|
||||||
@ -120,9 +120,8 @@ def dict_pvjury(
|
|||||||
d["decisions_ue"] = nt.get_etud_decisions_ue(etudid)
|
d["decisions_ue"] = nt.get_etud_decisions_ue(etudid)
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
d.update(but_validations.dict_decision_jury(etud, formsemestre))
|
d.update(but_validations.dict_decision_jury(etud, formsemestre))
|
||||||
d["last_formsemestre_id"] = Se.get_semestres()[
|
# id du dernier semestre (chronologiquement) dans lequel il a été inscrit:
|
||||||
-1
|
d["last_formsemestre_id"] = situation_etud.get_semestres()[-1]
|
||||||
] # id du dernier semestre (chronologiquement) dans lequel il a été inscrit
|
|
||||||
|
|
||||||
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
|
ects_capitalises_by_ue_code = _comp_ects_capitalises_by_ue_code(nt, etudid)
|
||||||
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
|
d["sum_ects_capitalises"] = sum(ects_capitalises_by_ue_code.values())
|
||||||
@ -162,10 +161,13 @@ def dict_pvjury(
|
|||||||
d["autorisations"] = [a.to_dict() for a in autorisations]
|
d["autorisations"] = [a.to_dict() for a in autorisations]
|
||||||
d["autorisations_descr"] = descr_autorisations(autorisations)
|
d["autorisations_descr"] = descr_autorisations(autorisations)
|
||||||
|
|
||||||
d["validation_parcours"] = Se.parcours_validated()
|
d["validation_parcours"] = situation_etud.parcours_validated()
|
||||||
d["parcours"] = Se.get_cursus_descr(filter_futur=True)
|
d["parcours"] = situation_etud.get_cursus_descr(filter_futur=True)
|
||||||
|
d["parcours_in_cur_formation"] = situation_etud.get_cursus_descr(
|
||||||
|
filter_futur=True, filter_formation_code=True
|
||||||
|
)
|
||||||
if with_parcours_decisions:
|
if with_parcours_decisions:
|
||||||
d["parcours_decisions"] = Se.get_parcours_decisions()
|
d["parcours_decisions"] = situation_etud.get_parcours_decisions()
|
||||||
# Observations sur les compensations:
|
# Observations sur les compensations:
|
||||||
compensators = sco_cursus_dut.scolar_formsemestre_validation_list(
|
compensators = sco_cursus_dut.scolar_formsemestre_validation_list(
|
||||||
cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid}
|
cnx, args={"compense_formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||||
@ -206,19 +208,19 @@ def dict_pvjury(
|
|||||||
if not info:
|
if not info:
|
||||||
continue # should not occur
|
continue # should not occur
|
||||||
etud = info[0]
|
etud = info[0]
|
||||||
if Se.prev and Se.prev_decision:
|
if situation_etud.prev_formsemestre and situation_etud.prev_decision:
|
||||||
d["prev_decision_sem"] = Se.prev_decision
|
d["prev_decision_sem"] = situation_etud.prev_decision
|
||||||
d["prev_code"] = Se.prev_decision["code"]
|
d["prev_code"] = situation_etud.prev_decision["code"]
|
||||||
d["prev_code_descr"] = _descr_decision_sem(
|
d["prev_code_descr"] = _descr_decision_sem(
|
||||||
scu.INSCRIT, Se.prev_decision
|
scu.INSCRIT, situation_etud.prev_decision
|
||||||
)
|
)
|
||||||
d["prev"] = Se.prev
|
d["prev"] = situation_etud.prev_formsemestre.to_dict()
|
||||||
has_prev = True
|
has_prev = True
|
||||||
else:
|
else:
|
||||||
d["prev_decision_sem"] = None
|
d["prev_decision_sem"] = None
|
||||||
d["prev_code"] = ""
|
d["prev_code"] = ""
|
||||||
d["prev_code_descr"] = ""
|
d["prev_code_descr"] = ""
|
||||||
d["Se"] = Se
|
d["Se"] = situation_etud
|
||||||
|
|
||||||
decisions.append(d)
|
decisions.append(d)
|
||||||
D[etudid] = d
|
D[etudid] = d
|
||||||
|
@ -149,7 +149,7 @@ def pvjury_table(
|
|||||||
etudid=e["identite"]["etudid"],
|
etudid=e["identite"]["etudid"],
|
||||||
),
|
),
|
||||||
"_nomprenom_td_attrs": f"""id="{e['identite']['etudid']}" class="etudinfo" """,
|
"_nomprenom_td_attrs": f"""id="{e['identite']['etudid']}" class="etudinfo" """,
|
||||||
"parcours": e["parcours"],
|
"parcours": e["parcours_in_cur_formation"],
|
||||||
"decision": _descr_decision_sem_abbrev(e["etat"], e["decision_sem"]),
|
"decision": _descr_decision_sem_abbrev(e["etat"], e["decision_sem"]),
|
||||||
"ue_cap": e["decisions_ue_descr"],
|
"ue_cap": e["decisions_ue_descr"],
|
||||||
"validation_parcours_code": "ADM" if e["validation_parcours"] else "",
|
"validation_parcours_code": "ADM" if e["validation_parcours"] else "",
|
||||||
@ -252,6 +252,7 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id="formsemestre_pvjury",
|
||||||
)
|
)
|
||||||
if fmt != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(
|
return tab.make_page(
|
||||||
@ -312,6 +313,7 @@ def formsemestre_pvjury(formsemestre_id, fmt="html", publish=True):
|
|||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id="formsemestre_pvjury_counts",
|
||||||
).html()
|
).html()
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -257,7 +257,9 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
|
|||||||
else:
|
else:
|
||||||
params["autorisations_txt"] = ""
|
params["autorisations_txt"] = ""
|
||||||
|
|
||||||
if decision["decision_sem"] and situation_etud.parcours_validated():
|
if (
|
||||||
|
formsemestre.formation.is_apc() or decision["decision_sem"]
|
||||||
|
) and situation_etud.parcours_validated():
|
||||||
params["diplome_txt"] = (
|
params["diplome_txt"] = (
|
||||||
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
|
"""Vous avez donc obtenu le diplôme : <b>%(titre_formation)s</b>""" % params
|
||||||
)
|
)
|
||||||
@ -357,5 +359,8 @@ def add_apc_infos(formsemestre: FormSemestre, params: dict, decision: dict):
|
|||||||
params[
|
params[
|
||||||
"decision_ue_txt"
|
"decision_ue_txt"
|
||||||
] = f"""{params["decision_ue_txt"]}<br/>
|
] = f"""{params["decision_ue_txt"]}<br/>
|
||||||
<b>Niveaux de compétences:</b><br/> {decision.get("descr_decisions_rcue") or ""}
|
<b>Niveaux de compétences:</b>
|
||||||
|
<br/> - {
|
||||||
|
'<br/> - '.join( decision.get("descr_decisions_rcue_list", []) )
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
@ -236,6 +236,7 @@ def _results_by_category(
|
|||||||
html_col_width="4em",
|
html_col_width="4em",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id=f"results_by_category-{category_name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -247,8 +248,6 @@ def formsemestre_report(
|
|||||||
result="codedecision",
|
result="codedecision",
|
||||||
category_name="",
|
category_name="",
|
||||||
result_name="",
|
result_name="",
|
||||||
title="Statistiques",
|
|
||||||
only_primo=None,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Tableau sur résultats (result) par type de category bac
|
Tableau sur résultats (result) par type de category bac
|
||||||
@ -276,9 +275,6 @@ def formsemestre_report(
|
|||||||
f"Répartition des résultats par {category_name}, semestre {sem['titreannee']}"
|
f"Répartition des résultats par {category_name}, semestre {sem['titreannee']}"
|
||||||
)
|
)
|
||||||
tab.html_caption = f"Répartition des résultats par {category_name}."
|
tab.html_caption = f"Répartition des résultats par {category_name}."
|
||||||
tab.base_url = "%s?formsemestre_id=%s" % (request.base_url, formsemestre_id)
|
|
||||||
if only_primo:
|
|
||||||
tab.base_url += "&only_primo=on"
|
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
@ -325,8 +321,15 @@ def formsemestre_report_counts(
|
|||||||
category=category,
|
category=category,
|
||||||
result=result,
|
result=result,
|
||||||
category_name=category_name,
|
category_name=category_name,
|
||||||
title=title,
|
)
|
||||||
only_primo=only_primo,
|
tab.base_url = url_for(
|
||||||
|
"notes.formsemestre_report_counts",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
category=category,
|
||||||
|
only_primo=int(bool(only_primo)),
|
||||||
|
result=result,
|
||||||
|
group_ids=group_ids,
|
||||||
)
|
)
|
||||||
if len(formsemestre.inscriptions) == 0:
|
if len(formsemestre.inscriptions) == 0:
|
||||||
F = ["""<p><em>Aucun étudiant</em></p>"""]
|
F = ["""<p><em>Aucun étudiant</em></p>"""]
|
||||||
@ -350,6 +353,7 @@ def formsemestre_report_counts(
|
|||||||
"statut",
|
"statut",
|
||||||
"annee_admission",
|
"annee_admission",
|
||||||
"type_admission",
|
"type_admission",
|
||||||
|
"boursier",
|
||||||
"boursier_prec",
|
"boursier_prec",
|
||||||
]
|
]
|
||||||
if jury_but_mode:
|
if jury_but_mode:
|
||||||
@ -695,19 +699,18 @@ def table_suivi_cohorte(
|
|||||||
if statut:
|
if statut:
|
||||||
dbac += " statut: %s" % statut
|
dbac += " statut: %s" % statut
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
caption="Suivi cohorte " + pp + sem["titreannee"] + dbac,
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=L,
|
filename=scu.make_filename("cohorte " + sem["titreannee"]),
|
||||||
|
html_class="table_cohorte",
|
||||||
html_col_width="4em",
|
html_col_width="4em",
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
filename=scu.make_filename("cohorte " + sem["titreannee"]),
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
origin="Généré par %s le " % sco_version.SCONAME
|
|
||||||
+ scu.timedate_human_repr()
|
|
||||||
+ "",
|
|
||||||
caption="Suivi cohorte " + pp + sem["titreannee"] + dbac,
|
|
||||||
page_title="Suivi cohorte " + sem["titreannee"],
|
page_title="Suivi cohorte " + sem["titreannee"],
|
||||||
html_class="table_cohorte",
|
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
||||||
|
rows=L,
|
||||||
|
table_id="table_suivi_cohorte",
|
||||||
|
titles=titles,
|
||||||
)
|
)
|
||||||
# Explication: liste des semestres associés à chaque date
|
# Explication: liste des semestres associés à chaque date
|
||||||
if not P:
|
if not P:
|
||||||
@ -1304,6 +1307,7 @@ def table_suivi_cursus(formsemestre_id, only_primo=False, grouped_parcours=True)
|
|||||||
"code_cursus": len(etuds),
|
"code_cursus": len(etuds),
|
||||||
},
|
},
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
table_id="table_suivi_cursus",
|
||||||
)
|
)
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
@ -87,15 +87,16 @@ def formsemestre_but_indicateurs(formsemestre_id: int, fmt="html"):
|
|||||||
bacs.append("Total")
|
bacs.append("Total")
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles={bac: bac for bac in bacs},
|
|
||||||
columns_ids=["titre_indicateur"] + bacs,
|
|
||||||
rows=rows,
|
|
||||||
html_sortable=False,
|
|
||||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
|
||||||
filename=scu.make_filename(f"Indicateurs_BUT_{formsemestre.titre_annee()}"),
|
|
||||||
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
|
||||||
html_caption="Indicateurs BUT annuels.",
|
|
||||||
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
|
||||||
|
columns_ids=["titre_indicateur"] + bacs,
|
||||||
|
filename=scu.make_filename(f"Indicateurs_BUT_{formsemestre.titre_annee()}"),
|
||||||
|
html_caption="Indicateurs BUT annuels.",
|
||||||
|
html_sortable=False,
|
||||||
|
origin=f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}",
|
||||||
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
rows=rows,
|
||||||
|
titles={bac: bac for bac in bacs},
|
||||||
|
table_id="formsemestre_but_indicateurs",
|
||||||
)
|
)
|
||||||
title = "Indicateurs suivi annuel BUT"
|
title = "Indicateurs suivi annuel BUT"
|
||||||
t = tab.make_page(
|
t = tab.make_page(
|
||||||
|
@ -29,12 +29,15 @@
|
|||||||
|
|
||||||
Formulaire revu en juillet 2016
|
Formulaire revu en juillet 2016
|
||||||
"""
|
"""
|
||||||
|
import html
|
||||||
import time
|
import time
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for, request
|
from flask import g, url_for, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from flask_sqlalchemy.query import Query
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
@ -75,8 +78,6 @@ import app.scodoc.sco_utils as scu
|
|||||||
from app.scodoc.sco_utils import json_error
|
from app.scodoc.sco_utils import json_error
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
from flask_sqlalchemy.query import Query
|
|
||||||
|
|
||||||
|
|
||||||
def convert_note_from_string(
|
def convert_note_from_string(
|
||||||
note: str,
|
note: str,
|
||||||
@ -115,7 +116,7 @@ def convert_note_from_string(
|
|||||||
return note_value, invalid
|
return note_value, invalid
|
||||||
|
|
||||||
|
|
||||||
def _displayNote(val):
|
def _display_note(val):
|
||||||
"""Convert note from DB to viewable string.
|
"""Convert note from DB to viewable string.
|
||||||
Utilisé seulement pour I/O vers formulaires (sans perte de precision)
|
Utilisé seulement pour I/O vers formulaires (sans perte de precision)
|
||||||
(Utiliser fmt_note pour les affichages)
|
(Utiliser fmt_note pour les affichages)
|
||||||
@ -272,7 +273,7 @@ def do_evaluation_upload_xls():
|
|||||||
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||||||
raise InvalidNoteValue()
|
raise InvalidNoteValue()
|
||||||
else:
|
else:
|
||||||
etudids_changed, nb_suppress, etudids_with_decisions = notes_add(
|
etudids_changed, nb_suppress, etudids_with_decisions, messages = notes_add(
|
||||||
current_user, evaluation_id, valid_notes, comment
|
current_user, evaluation_id, valid_notes, comment
|
||||||
)
|
)
|
||||||
# news
|
# news
|
||||||
@ -292,9 +293,19 @@ def do_evaluation_upload_xls():
|
|||||||
max_frequency=30 * 60, # 30 minutes
|
max_frequency=30 * 60, # 30 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = f"""<p>{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes, {
|
msg = f"""<p>
|
||||||
len(absents)} absents, {nb_suppress} note supprimées)
|
{len(etudids_changed)} notes changées ({len(withoutnotes)} sans notes,
|
||||||
|
{len(absents)} absents, {nb_suppress} note supprimées)
|
||||||
</p>"""
|
</p>"""
|
||||||
|
if messages:
|
||||||
|
msg += f"""<div class="warning">Attention :
|
||||||
|
<ul>
|
||||||
|
<li>{
|
||||||
|
'</li><li>'.join(messages)
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>"""
|
||||||
if etudids_with_decisions:
|
if etudids_with_decisions:
|
||||||
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
msg += """<p class="warning">Important: il y avait déjà des décisions de jury
|
||||||
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
enregistrées, qui sont peut-être à revoir suite à cette modification !</p>
|
||||||
@ -322,7 +333,7 @@ def do_evaluation_set_etud_note(evaluation: Evaluation, etud: Identite, value) -
|
|||||||
# Convert and check value
|
# Convert and check value
|
||||||
L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
|
L, invalids, _, _, _ = _check_notes([(etud.id, value)], evaluation)
|
||||||
if len(invalids) == 0:
|
if len(invalids) == 0:
|
||||||
etudids_changed, _, _ = notes_add(
|
etudids_changed, _, _, _ = notes_add(
|
||||||
current_user, evaluation.id, L, "Initialisation notes"
|
current_user, evaluation.id, L, "Initialisation notes"
|
||||||
)
|
)
|
||||||
if len(etudids_changed) == 1:
|
if len(etudids_changed) == 1:
|
||||||
@ -398,7 +409,9 @@ def do_evaluation_set_missing(
|
|||||||
)
|
)
|
||||||
# ok
|
# ok
|
||||||
comment = "Initialisation notes manquantes"
|
comment = "Initialisation notes manquantes"
|
||||||
etudids_changed, _, _ = notes_add(current_user, evaluation_id, valid_notes, comment)
|
etudids_changed, _, _, _ = notes_add(
|
||||||
|
current_user, evaluation_id, valid_notes, comment
|
||||||
|
)
|
||||||
# news
|
# news
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
@ -456,7 +469,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
|
||||||
current_user, evaluation_id, notes, do_it=False, check_inscription=False
|
current_user, evaluation_id, notes, do_it=False, check_inscription=False
|
||||||
)
|
)
|
||||||
msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
|
msg = f"""<p>Confirmer la suppression des {nb_suppress} notes ?
|
||||||
@ -477,7 +490,7 @@ def evaluation_suppress_alln(evaluation_id, dialog_confirmed=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# modif
|
# modif
|
||||||
etudids_changed, nb_suppress, existing_decisions = notes_add(
|
etudids_changed, nb_suppress, existing_decisions, _ = notes_add(
|
||||||
current_user,
|
current_user,
|
||||||
evaluation_id,
|
evaluation_id,
|
||||||
notes,
|
notes,
|
||||||
@ -519,7 +532,7 @@ def notes_add(
|
|||||||
comment=None,
|
comment=None,
|
||||||
do_it=True,
|
do_it=True,
|
||||||
check_inscription=True,
|
check_inscription=True,
|
||||||
) -> tuple[list[int], int, list[int]]:
|
) -> tuple[list[int], int, list[int], list[str]]:
|
||||||
"""
|
"""
|
||||||
Insert or update notes
|
Insert or update notes
|
||||||
notes is a list of tuples (etudid,value)
|
notes is a list of tuples (etudid,value)
|
||||||
@ -528,30 +541,48 @@ def notes_add(
|
|||||||
Nota:
|
Nota:
|
||||||
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||||||
|
|
||||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
|
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision, messages)
|
||||||
|
|
||||||
|
messages = list de messages d'avertissement/information pour l'utilisateur
|
||||||
"""
|
"""
|
||||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||||
now = psycopg2.Timestamp(*time.localtime()[:6])
|
now = psycopg2.Timestamp(*time.localtime()[:6])
|
||||||
|
messages = []
|
||||||
# Vérifie inscription et valeur note
|
# Vérifie inscription au module (même DEM/DEF)
|
||||||
inscrits = {
|
etudids_inscrits_mod = {
|
||||||
x[0]
|
x[0]
|
||||||
for x in sco_groups.do_evaluation_listeetuds_groups(
|
for x in sco_groups.do_evaluation_listeetuds_groups(
|
||||||
evaluation_id, getallstudents=True, include_demdef=True
|
evaluation_id, getallstudents=True, include_demdef=True
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
# Les étudiants inscrits au semestre ni DEM ni DEF
|
# Les étudiants inscrits au semestre et ceux "actifs" (ni DEM ni DEF)
|
||||||
_, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
|
etudids_inscrits_sem, etudids_actifs = (
|
||||||
|
evaluation.moduleimpl.formsemestre.etudids_actifs()
|
||||||
|
)
|
||||||
for etudid, value in notes:
|
for etudid, value in notes:
|
||||||
if check_inscription and (
|
|
||||||
(etudid not in inscrits) or (etudid not in etudids_actifs)
|
if check_inscription:
|
||||||
):
|
msg_err, msg_warn = "", ""
|
||||||
log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
|
if etudid not in etudids_inscrits_sem:
|
||||||
raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
|
msg_err = "non inscrit au semestre"
|
||||||
|
elif etudid not in etudids_inscrits_mod:
|
||||||
|
msg_err = "non inscrit au module"
|
||||||
|
elif etudid not in etudids_actifs:
|
||||||
|
# DEM ou DEF
|
||||||
|
msg_warn = "démissionnaire ou défaillant (note enregistrée)"
|
||||||
|
if msg_err or msg_warn:
|
||||||
|
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||||
|
msg = f"étudiant {etud.nomprenom if etud else etudid} {msg_err or msg_warn}"
|
||||||
|
if msg_err:
|
||||||
|
log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
|
||||||
|
raise NoteProcessError(msg)
|
||||||
|
if msg_warn:
|
||||||
|
messages.append(msg)
|
||||||
if (value is not None) and not isinstance(value, float):
|
if (value is not None) and not isinstance(value, float):
|
||||||
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
||||||
|
etud = Identite.query.get(etudid) if isinstance(etudid, int) else None
|
||||||
raise NoteProcessError(
|
raise NoteProcessError(
|
||||||
f"etudiant {etudid}: valeur de note invalide ({value})"
|
f"etudiant {etud.nomprenom if etud else etudid}: valeur de note invalide ({value})"
|
||||||
)
|
)
|
||||||
# Recherche notes existantes
|
# Recherche notes existantes
|
||||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||||
@ -566,102 +597,20 @@ def notes_add(
|
|||||||
etudids_with_decision = []
|
etudids_with_decision = []
|
||||||
try:
|
try:
|
||||||
for etudid, value in notes:
|
for etudid, value in notes:
|
||||||
changed = False
|
changed, suppressed = _record_note(
|
||||||
if etudid not in notes_db:
|
cursor,
|
||||||
# nouvelle note
|
notes_db,
|
||||||
if value != scu.NOTES_SUPPRESS:
|
etudid,
|
||||||
if do_it:
|
evaluation_id,
|
||||||
args = {
|
value,
|
||||||
"etudid": etudid,
|
comment=comment,
|
||||||
"evaluation_id": evaluation_id,
|
user=user,
|
||||||
"value": value,
|
date=now,
|
||||||
"comment": comment,
|
do_it=do_it,
|
||||||
"uid": user.id,
|
)
|
||||||
"date": now,
|
if suppressed:
|
||||||
}
|
nb_suppress += 1
|
||||||
ndb.quote_dict(args)
|
|
||||||
# Note: le conflit ci-dessous peut arriver si un autre thread
|
|
||||||
# a modifié la base après qu'on ait lu notes_db
|
|
||||||
cursor.execute(
|
|
||||||
"""INSERT INTO notes_notes
|
|
||||||
(etudid, evaluation_id, value, comment, date, uid)
|
|
||||||
VALUES
|
|
||||||
(%(etudid)s,%(evaluation_id)s,%(value)s,
|
|
||||||
%(comment)s,%(date)s,%(uid)s)
|
|
||||||
ON CONFLICT ON CONSTRAINT notes_notes_etudid_evaluation_id_key
|
|
||||||
DO UPDATE SET etudid=%(etudid)s, evaluation_id=%(evaluation_id)s,
|
|
||||||
value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
|
||||||
""",
|
|
||||||
args,
|
|
||||||
)
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
# il y a deja une note
|
|
||||||
oldval = notes_db[etudid]["value"]
|
|
||||||
if type(value) != type(oldval):
|
|
||||||
changed = True
|
|
||||||
elif isinstance(value, float) and (
|
|
||||||
abs(value - oldval) > scu.NOTES_PRECISION
|
|
||||||
):
|
|
||||||
changed = True
|
|
||||||
elif value != oldval:
|
|
||||||
changed = True
|
|
||||||
if changed:
|
|
||||||
# recopie l'ancienne note dans notes_notes_log, puis update
|
|
||||||
if do_it:
|
|
||||||
cursor.execute(
|
|
||||||
"""INSERT INTO notes_notes_log
|
|
||||||
(etudid,evaluation_id,value,comment,date,uid)
|
|
||||||
SELECT etudid, evaluation_id, value, comment, date, uid
|
|
||||||
FROM notes_notes
|
|
||||||
WHERE etudid=%(etudid)s
|
|
||||||
and evaluation_id=%(evaluation_id)s
|
|
||||||
""",
|
|
||||||
{"etudid": etudid, "evaluation_id": evaluation_id},
|
|
||||||
)
|
|
||||||
args = {
|
|
||||||
"etudid": etudid,
|
|
||||||
"evaluation_id": evaluation_id,
|
|
||||||
"value": value,
|
|
||||||
"date": now,
|
|
||||||
"comment": comment,
|
|
||||||
"uid": user.id,
|
|
||||||
}
|
|
||||||
ndb.quote_dict(args)
|
|
||||||
if value != scu.NOTES_SUPPRESS:
|
|
||||||
if do_it:
|
|
||||||
cursor.execute(
|
|
||||||
"""UPDATE notes_notes
|
|
||||||
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
|
||||||
WHERE etudid = %(etudid)s
|
|
||||||
and evaluation_id = %(evaluation_id)s
|
|
||||||
""",
|
|
||||||
args,
|
|
||||||
)
|
|
||||||
else: # suppression ancienne note
|
|
||||||
if do_it:
|
|
||||||
log(
|
|
||||||
f"""notes_add, suppress, evaluation_id={evaluation_id}, etudid={
|
|
||||||
etudid}, oldval={oldval}"""
|
|
||||||
)
|
|
||||||
cursor.execute(
|
|
||||||
"""DELETE FROM notes_notes
|
|
||||||
WHERE etudid = %(etudid)s
|
|
||||||
AND evaluation_id = %(evaluation_id)s
|
|
||||||
""",
|
|
||||||
args,
|
|
||||||
)
|
|
||||||
# garde trace de la suppression dans l'historique:
|
|
||||||
args["value"] = scu.NOTES_SUPPRESS
|
|
||||||
cursor.execute(
|
|
||||||
"""INSERT INTO notes_notes_log
|
|
||||||
(etudid,evaluation_id,value,comment,date,uid)
|
|
||||||
VALUES
|
|
||||||
(%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
|
|
||||||
""",
|
|
||||||
args,
|
|
||||||
)
|
|
||||||
nb_suppress += 1
|
|
||||||
if changed:
|
if changed:
|
||||||
etudids_changed.append(etudid)
|
etudids_changed.append(etudid)
|
||||||
if res.etud_has_decision(etudid, include_rcues=False):
|
if res.etud_has_decision(etudid, include_rcues=False):
|
||||||
@ -678,7 +627,108 @@ def notes_add(
|
|||||||
cnx.commit()
|
cnx.commit()
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||||
sco_cache.EvaluationCache.delete(evaluation_id)
|
sco_cache.EvaluationCache.delete(evaluation_id)
|
||||||
return etudids_changed, nb_suppress, etudids_with_decision
|
return etudids_changed, nb_suppress, etudids_with_decision, messages
|
||||||
|
|
||||||
|
|
||||||
|
def _record_note(
|
||||||
|
cursor,
|
||||||
|
notes_db,
|
||||||
|
etudid: int,
|
||||||
|
evaluation_id: int,
|
||||||
|
value: float,
|
||||||
|
comment: str = "",
|
||||||
|
user: User = None,
|
||||||
|
date=None,
|
||||||
|
do_it=False,
|
||||||
|
):
|
||||||
|
"Enregistrement de la note en base"
|
||||||
|
changed = False
|
||||||
|
suppressed = False
|
||||||
|
args = {
|
||||||
|
"etudid": etudid,
|
||||||
|
"evaluation_id": evaluation_id,
|
||||||
|
"value": value,
|
||||||
|
# convention scodoc7 quote comment:
|
||||||
|
"comment": (html.escape(comment) if isinstance(comment, str) else comment),
|
||||||
|
"uid": user.id,
|
||||||
|
"date": date,
|
||||||
|
}
|
||||||
|
if etudid not in notes_db:
|
||||||
|
# nouvelle note
|
||||||
|
if value != scu.NOTES_SUPPRESS:
|
||||||
|
if do_it:
|
||||||
|
# Note: le conflit ci-dessous peut arriver si un autre thread
|
||||||
|
# a modifié la base après qu'on ait lu notes_db
|
||||||
|
cursor.execute(
|
||||||
|
"""INSERT INTO notes_notes
|
||||||
|
(etudid, evaluation_id, value, comment, date, uid)
|
||||||
|
VALUES
|
||||||
|
(%(etudid)s,%(evaluation_id)s,%(value)s,
|
||||||
|
%(comment)s,%(date)s,%(uid)s)
|
||||||
|
ON CONFLICT ON CONSTRAINT notes_notes_etudid_evaluation_id_key
|
||||||
|
DO UPDATE SET etudid=%(etudid)s, evaluation_id=%(evaluation_id)s,
|
||||||
|
value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
||||||
|
""",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
# il y a deja une note
|
||||||
|
oldval = notes_db[etudid]["value"]
|
||||||
|
if type(value) != type(oldval):
|
||||||
|
changed = True
|
||||||
|
elif isinstance(value, float) and (abs(value - oldval) > scu.NOTES_PRECISION):
|
||||||
|
changed = True
|
||||||
|
elif value != oldval:
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
# recopie l'ancienne note dans notes_notes_log, puis update
|
||||||
|
if do_it:
|
||||||
|
cursor.execute(
|
||||||
|
"""INSERT INTO notes_notes_log
|
||||||
|
(etudid,evaluation_id,value,comment,date,uid)
|
||||||
|
SELECT etudid, evaluation_id, value, comment, date, uid
|
||||||
|
FROM notes_notes
|
||||||
|
WHERE etudid=%(etudid)s
|
||||||
|
and evaluation_id=%(evaluation_id)s
|
||||||
|
""",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
if value != scu.NOTES_SUPPRESS:
|
||||||
|
if do_it:
|
||||||
|
cursor.execute(
|
||||||
|
"""UPDATE notes_notes
|
||||||
|
SET value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s
|
||||||
|
WHERE etudid = %(etudid)s
|
||||||
|
and evaluation_id = %(evaluation_id)s
|
||||||
|
""",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
else: # suppression ancienne note
|
||||||
|
if do_it:
|
||||||
|
log(
|
||||||
|
f"""notes_add, suppress, evaluation_id={evaluation_id}, etudid={
|
||||||
|
etudid}, oldval={oldval}"""
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"""DELETE FROM notes_notes
|
||||||
|
WHERE etudid = %(etudid)s
|
||||||
|
AND evaluation_id = %(evaluation_id)s
|
||||||
|
""",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
# garde trace de la suppression dans l'historique:
|
||||||
|
args["value"] = scu.NOTES_SUPPRESS
|
||||||
|
cursor.execute(
|
||||||
|
"""INSERT INTO notes_notes_log
|
||||||
|
(etudid,evaluation_id,value,comment,date,uid)
|
||||||
|
VALUES
|
||||||
|
(%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)
|
||||||
|
""",
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
suppressed = True
|
||||||
|
return changed, suppressed
|
||||||
|
|
||||||
|
|
||||||
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
def saisie_notes_tableur(evaluation_id, group_ids=()):
|
||||||
@ -703,7 +753,7 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
|||||||
)
|
)
|
||||||
|
|
||||||
page_title = "Saisie des notes" + (
|
page_title = "Saisie des notes" + (
|
||||||
f"""de {evaluation.description}""" if evaluation.description else ""
|
f""" de {evaluation.description}""" if evaluation.description else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
# Informations sur les groupes à afficher:
|
# Informations sur les groupes à afficher:
|
||||||
@ -797,9 +847,13 @@ def saisie_notes_tableur(evaluation_id, group_ids=()):
|
|||||||
}">
|
}">
|
||||||
Revenir au tableau de bord du module</a>
|
Revenir au tableau de bord du module</a>
|
||||||
|
|
||||||
|
<a class="stdlink" href="{url_for("notes.saisie_notes_tableur",
|
||||||
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
|
}">Charger un autre fichier de notes</a>
|
||||||
|
|
||||||
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
<a class="stdlink" href="{url_for("notes.saisie_notes",
|
||||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
}">Charger d'autres notes dans cette évaluation</a>
|
}">Formulaire de saisie des notes</a>
|
||||||
</p>"""
|
</p>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -1015,7 +1069,7 @@ def saisie_notes(evaluation_id: int, group_ids: list = None):
|
|||||||
"Autres opérations",
|
"Autres opérations",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"title": "Saisie par fichier tableur",
|
"title": "Saisir par fichier tableur",
|
||||||
"id": "menu_saisie_tableur",
|
"id": "menu_saisie_tableur",
|
||||||
"endpoint": "notes.saisie_notes_tableur",
|
"endpoint": "notes.saisie_notes_tableur",
|
||||||
"args": {
|
"args": {
|
||||||
@ -1126,7 +1180,7 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
|
|||||||
|
|
||||||
# Note actuelle de l'étudiant:
|
# Note actuelle de l'étudiant:
|
||||||
if etudid in notes_db:
|
if etudid in notes_db:
|
||||||
e["val"] = _displayNote(notes_db[etudid]["value"])
|
e["val"] = _display_note(notes_db[etudid]["value"])
|
||||||
comment = notes_db[etudid]["comment"]
|
comment = notes_db[etudid]["comment"]
|
||||||
if comment is None:
|
if comment is None:
|
||||||
comment = ""
|
comment = ""
|
||||||
@ -1368,7 +1422,7 @@ def save_notes(
|
|||||||
#
|
#
|
||||||
valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
|
valid_notes, _, _, _, _ = _check_notes(notes, evaluation)
|
||||||
if valid_notes:
|
if valid_notes:
|
||||||
etudids_changed, _, etudids_with_decision = notes_add(
|
etudids_changed, _, etudids_with_decision, messages = notes_add(
|
||||||
current_user, evaluation.id, valid_notes, comment=comment, do_it=True
|
current_user, evaluation.id, valid_notes, comment=comment, do_it=True
|
||||||
)
|
)
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
@ -1386,12 +1440,14 @@ def save_notes(
|
|||||||
etudid: get_note_history_menu(evaluation.id, etudid)
|
etudid: get_note_history_menu(evaluation.id, etudid)
|
||||||
for etudid in etudids_changed
|
for etudid in etudids_changed
|
||||||
},
|
},
|
||||||
|
"messages": messages,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
result = {
|
result = {
|
||||||
"etudids_changed": [],
|
"etudids_changed": [],
|
||||||
"etudids_with_decision": [],
|
"etudids_with_decision": [],
|
||||||
"history_menu": [],
|
"history_menu": [],
|
||||||
|
"messages": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -1420,7 +1476,7 @@ def get_note_history_menu(evaluation_id: int, etudid: int) -> str:
|
|||||||
first = True
|
first = True
|
||||||
for i in history:
|
for i in history:
|
||||||
jt = i["date"].strftime("le %d/%m/%Y à %H:%M") + " (%s)" % i["user_name"]
|
jt = i["date"].strftime("le %d/%m/%Y à %H:%M") + " (%s)" % i["user_name"]
|
||||||
dispnote = _displayNote(i["value"])
|
dispnote = _display_note(i["value"])
|
||||||
if first:
|
if first:
|
||||||
nv = "" # ne repete pas la valeur de la note courante
|
nv = "" # ne repete pas la valeur de la note courante
|
||||||
else:
|
else:
|
||||||
|
@ -378,11 +378,9 @@ class SemSet(dict):
|
|||||||
|
|
||||||
def html_diagnostic(self):
|
def html_diagnostic(self):
|
||||||
"""Affichage de la partie Effectifs et Liste des étudiants
|
"""Affichage de la partie Effectifs et Liste des étudiants
|
||||||
(actif seulement si un portail est configuré) XXX pourquoi ??
|
(actif seulement si un portail est configuré)
|
||||||
"""
|
"""
|
||||||
if sco_portal_apogee.has_portal():
|
return self.bilan.html_diagnostic()
|
||||||
return self.bilan.html_diagnostic()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def get_semsets_list():
|
def get_semsets_list():
|
||||||
@ -482,10 +480,9 @@ def semset_page(fmt="html"):
|
|||||||
# (remplacé par n liens vers chacun des semestres)
|
# (remplacé par n liens vers chacun des semestres)
|
||||||
# s['_semtitles_str_target'] = s['_export_link_target']
|
# s['_semtitles_str_target'] = s['_export_link_target']
|
||||||
# Experimental:
|
# Experimental:
|
||||||
s[
|
s["_title_td_attrs"] = (
|
||||||
"_title_td_attrs"
|
'class="inplace_edit" data-url="edit_semset_set_title" id="%s"'
|
||||||
] = 'class="inplace_edit" data-url="edit_semset_set_title" id="%s"' % (
|
% (s["semset_id"])
|
||||||
s["semset_id"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -513,6 +510,7 @@ def semset_page(fmt="html"):
|
|||||||
html_class="table_leftalign",
|
html_class="table_leftalign",
|
||||||
filename="semsets",
|
filename="semsets",
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
|
table_id="table-semsets",
|
||||||
)
|
)
|
||||||
if fmt != "html":
|
if fmt != "html":
|
||||||
return tab.make_page(fmt=fmt)
|
return tab.make_page(fmt=fmt)
|
||||||
|
@ -115,7 +115,8 @@ def formsemestre_synchro_etuds(
|
|||||||
url_for('notes.formsemestre_editwithmodules',
|
url_for('notes.formsemestre_editwithmodules',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
}">Modifier ce semestre</a>)
|
}">Modifier ce semestre</a>)
|
||||||
"""
|
""",
|
||||||
|
safe=True,
|
||||||
)
|
)
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
base_url = url_for(
|
base_url = url_for(
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user