forked from ScoDoc/ScoDoc
Refactoring et uniformisation tables jury/recap.
This commit is contained in:
parent
fa911907ad
commit
4db6ee368a
@ -13,6 +13,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
import flask_sqlalchemy
|
import flask_sqlalchemy
|
||||||
from flask import flash, g
|
from flask import flash, g
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_, or_
|
||||||
@ -20,6 +21,7 @@ from sqlalchemy.sql import text
|
|||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
from app.auth.models import User
|
||||||
from app.models import APO_CODE_STR_LEN, CODE_STR_LEN, SHORT_STR_LEN
|
from app.models import APO_CODE_STR_LEN, CODE_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
ApcAnneeParcours,
|
ApcAnneeParcours,
|
||||||
@ -535,10 +537,32 @@ class FormSemestre(db.Model):
|
|||||||
else:
|
else:
|
||||||
return ", ".join([u.get_nomcomplet() for u in self.responsables])
|
return ", ".join([u.get_nomcomplet() for u in self.responsables])
|
||||||
|
|
||||||
def est_responsable(self, user):
|
def est_responsable(self, user: User):
|
||||||
"True si l'user est l'un des responsables du semestre"
|
"True si l'user est l'un des responsables du semestre"
|
||||||
return user.id in [u.id for u in self.responsables]
|
return user.id in [u.id for u in self.responsables]
|
||||||
|
|
||||||
|
def est_chef_or_diretud(self, user: User = None):
|
||||||
|
"Vrai si utilisateur (par def. current) est admin, chef dept ou responsable du semestre"
|
||||||
|
user = user or current_user
|
||||||
|
return user.has_permission(Permission.ScoImplement) or self.est_responsable(
|
||||||
|
user
|
||||||
|
)
|
||||||
|
|
||||||
|
def can_edit_jury(self, user: User = None):
|
||||||
|
"""Vrai si utilisateur (par def. current) peut saisir decision de jury
|
||||||
|
dans ce semestre: vérifie permission et verrouillage.
|
||||||
|
"""
|
||||||
|
user = user or current_user
|
||||||
|
return self.etat and self.est_chef_or_diretud(user)
|
||||||
|
|
||||||
|
def can_edit_pv(self, user: User = None):
|
||||||
|
"Vrai si utilisateur (par def. current) peut editer un PV de jury de ce semestre"
|
||||||
|
user = user or current_user
|
||||||
|
# Autorise les secrétariats, repérés via la permission ScoEtudChangeAdr
|
||||||
|
return self.est_chef_or_diretud(user) or user.has_permission(
|
||||||
|
Permission.ScoEtudChangeAdr
|
||||||
|
)
|
||||||
|
|
||||||
def annee_scolaire(self) -> int:
|
def annee_scolaire(self) -> int:
|
||||||
"""L'année de début de l'année scolaire.
|
"""L'année de début de l'année scolaire.
|
||||||
Par exemple, 2022 si le semestre va de septembre 2022 à février 2023."""
|
Par exemple, 2022 si le semestre va de septembre 2022 à février 2023."""
|
||||||
|
@ -251,7 +251,7 @@ def sco_header(
|
|||||||
#gtrcontent {{
|
#gtrcontent {{
|
||||||
margin-left: {params["margin_left"]};
|
margin-left: {params["margin_left"]};
|
||||||
height: 100%%;
|
height: 100%%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 16px;
|
||||||
}}
|
}}
|
||||||
</style>
|
</style>
|
||||||
"""
|
"""
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
qui est une description (humaine, format libre) de l'archive.
|
qui est une description (humaine, format libre) de l'archive.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import chardet
|
from typing import Union
|
||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
@ -56,10 +56,11 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
from typing import Union
|
|
||||||
|
import chardet
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import g, request
|
from flask import flash, g, request, url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -70,9 +71,7 @@ from app.comp import res_sem
|
|||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Departement, FormSemestre
|
from app.models import Departement, FormSemestre
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied
|
||||||
AccessDenied,
|
|
||||||
)
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_bulletins_pdf
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
@ -314,7 +313,7 @@ def do_formsemestre_archive(
|
|||||||
"""
|
"""
|
||||||
from app.scodoc.sco_recapcomplet import (
|
from app.scodoc.sco_recapcomplet import (
|
||||||
gen_formsemestre_recapcomplet_excel,
|
gen_formsemestre_recapcomplet_excel,
|
||||||
gen_formsemestre_recapcomplet_html,
|
gen_formsemestre_recapcomplet_html_table,
|
||||||
gen_formsemestre_recapcomplet_json,
|
gen_formsemestre_recapcomplet_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -338,7 +337,7 @@ def do_formsemestre_archive(
|
|||||||
if data:
|
if data:
|
||||||
PVArchive.store(archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, data)
|
PVArchive.store(archive_id, "Tableau_moyennes" + scu.XLSX_SUFFIX, data)
|
||||||
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)
|
# Tableau recap notes en HTML (pour tous les etudiants, n'utilise pas les groupes)
|
||||||
table_html = gen_formsemestre_recapcomplet_html(
|
table_html, _ = gen_formsemestre_recapcomplet_html_table(
|
||||||
formsemestre, res, include_evaluations=True
|
formsemestre, res, include_evaluations=True
|
||||||
)
|
)
|
||||||
if table_html:
|
if table_html:
|
||||||
@ -416,8 +415,15 @@ def formsemestre_archive(formsemestre_id, group_ids=[]):
|
|||||||
"""Make and store new archive for this formsemestre.
|
"""Make and store new archive for this formsemestre.
|
||||||
(all students or only selected groups)
|
(all students or only selected groups)
|
||||||
"""
|
"""
|
||||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
if not formsemestre.can_edit_pv():
|
||||||
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
@ -579,26 +585,38 @@ def formsemestre_list_archives(formsemestre_id):
|
|||||||
|
|
||||||
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
||||||
"""Send file to client."""
|
"""Send file to client."""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre.id
|
||||||
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
||||||
"""Delete an archive"""
|
"""Delete an archive"""
|
||||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
if not formsemestre.can_edit_pv():
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
sem_archive_id = formsemestre_id
|
sem_archive_id = formsemestre_id
|
||||||
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
archive_id = PVArchive.get_id_from_name(sem_archive_id, archive_name)
|
||||||
|
|
||||||
dest_url = "formsemestre_list_archives?formsemestre_id=%s" % (formsemestre_id)
|
dest_url = url_for(
|
||||||
|
"notes.formsemestre_list_archives",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Confirmer la suppression de l'archive du %s ?</h2>
|
f"""<h2>Confirmer la suppression de l'archive du {
|
||||||
<p>La suppression sera définitive.</p>"""
|
PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
|
||||||
% PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
} ?</h2>
|
||||||
|
<p>La suppression sera définitive.</p>
|
||||||
|
""",
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url=dest_url,
|
cancel_url=dest_url,
|
||||||
parameters={
|
parameters={
|
||||||
@ -608,4 +626,5 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
|||||||
)
|
)
|
||||||
|
|
||||||
PVArchive.delete_archive(archive_id)
|
PVArchive.delete_archive(archive_id)
|
||||||
return flask.redirect(dest_url + "&head_message=Archive%20supprimée")
|
flash("Archive supprimée")
|
||||||
|
return flask.redirect(dest_url)
|
||||||
|
@ -1208,7 +1208,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
"enabled": formsemestre.can_edit_jury(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Enregistrer note d'une UE externe",
|
"title": "Enregistrer note d'une UE externe",
|
||||||
@ -1217,7 +1217,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id)
|
"enabled": formsemestre.can_edit_jury()
|
||||||
and not formsemestre.formation.is_apc(),
|
and not formsemestre.formation.is_apc(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1227,7 +1227,7 @@ def make_menu_autres_operations(
|
|||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etudid": etud.id,
|
"etudid": etud.id,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
"enabled": formsemestre.can_edit_jury(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Éditer PV jury",
|
"title": "Éditer PV jury",
|
||||||
|
@ -27,22 +27,21 @@
|
|||||||
|
|
||||||
"""Exception handling
|
"""Exception handling
|
||||||
"""
|
"""
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
# --- Exceptions
|
# --- Exceptions
|
||||||
MSGPERMDENIED = "l'utilisateur %s n'a pas le droit d'effectuer cette operation"
|
|
||||||
|
|
||||||
|
|
||||||
class ScoException(Exception):
|
class ScoException(Exception):
|
||||||
pass
|
"super classe de toutes les exceptions ScoDoc."
|
||||||
|
|
||||||
|
|
||||||
class InvalidNoteValue(ScoException):
|
class InvalidNoteValue(ScoException):
|
||||||
pass
|
"Valeur note invalide. Usage interne saisie note."
|
||||||
|
|
||||||
|
|
||||||
class ScoValueError(ScoException):
|
class ScoValueError(ScoException):
|
||||||
"Exception avec page d'erreur utilisateur, et qui stoque dest_url"
|
"Exception avec page d'erreur utilisateur, et qui stoque dest_url"
|
||||||
|
# mal nommée: super classe de toutes les exceptions avec page
|
||||||
|
# d'erreur gentille.
|
||||||
def __init__(self, msg, dest_url=None):
|
def __init__(self, msg, dest_url=None):
|
||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
self.dest_url = dest_url
|
self.dest_url = dest_url
|
||||||
@ -53,7 +52,9 @@ class ScoPermissionDenied(ScoValueError):
|
|||||||
|
|
||||||
def __init__(self, msg=None, dest_url=None):
|
def __init__(self, msg=None, dest_url=None):
|
||||||
if msg is None:
|
if msg is None:
|
||||||
msg = "Opération non autorisée !"
|
msg = f"""Opération non autorisée pour {
|
||||||
|
current_user.get_nomcomplet() if current_user else "?"
|
||||||
|
}. Pas la permission, ou objet verrouillé."""
|
||||||
super().__init__(msg, dest_url=dest_url)
|
super().__init__(msg, dest_url=dest_url)
|
||||||
|
|
||||||
|
|
||||||
|
@ -431,17 +431,18 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Saisie des décisions du jury",
|
"title": "Saisie des décisions du jury",
|
||||||
"endpoint": "notes.formsemestre_saisie_jury",
|
"endpoint": "notes.formsemestre_recapcomplet",
|
||||||
"args": {
|
"args": {
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
|
"mode_jury": 1,
|
||||||
},
|
},
|
||||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
"enabled": formsemestre.can_edit_jury(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Éditer les PV et archiver les résultats",
|
"title": "Éditer les PV et archiver les résultats",
|
||||||
"endpoint": "notes.formsemestre_archive",
|
"endpoint": "notes.formsemestre_archive",
|
||||||
"args": {"formsemestre_id": formsemestre_id},
|
"args": {"formsemestre_id": formsemestre_id},
|
||||||
"enabled": sco_permissions_check.can_edit_pv(formsemestre_id),
|
"enabled": formsemestre.can_edit_pv(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Documents archivés",
|
"title": "Documents archivés",
|
||||||
|
@ -72,9 +72,9 @@ def formsemestre_validation_etud_form(
|
|||||||
etudid=None, # one of etudid or etud_index is required
|
etudid=None, # one of etudid or etud_index is required
|
||||||
etud_index=None,
|
etud_index=None,
|
||||||
check=0, # opt: si true, propose juste une relecture du parcours
|
check=0, # opt: si true, propose juste une relecture du parcours
|
||||||
desturl=None,
|
dest_url=None,
|
||||||
sortcol=None,
|
sortcol=None,
|
||||||
readonly=True,
|
read_only=True,
|
||||||
):
|
):
|
||||||
"""Formulaire de validation des décisions de jury"""
|
"""Formulaire de validation des décisions de jury"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||||
@ -111,7 +111,7 @@ def formsemestre_validation_etud_form(
|
|||||||
etud_index_prev = etud_index - 1
|
etud_index_prev = etud_index - 1
|
||||||
if etud_index_prev < 0:
|
if etud_index_prev < 0:
|
||||||
etud_index_prev = None
|
etud_index_prev = None
|
||||||
if readonly:
|
if read_only:
|
||||||
check = True
|
check = True
|
||||||
|
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
@ -216,13 +216,13 @@ def formsemestre_validation_etud_form(
|
|||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
formsemestre_recap_parcours_table(
|
formsemestre_recap_parcours_table(
|
||||||
Se, etudid, with_links=(check and not readonly)
|
Se, etudid, with_links=(check and not read_only)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if check:
|
if check:
|
||||||
if not desturl:
|
if not dest_url:
|
||||||
desturl = url_tableau
|
dest_url = url_tableau
|
||||||
H.append(f'<ul><li><a href="{desturl}">Continuer</a></li></ul>')
|
H.append(f'<ul><li><a href="{dest_url}">Continuer</a></li></ul>')
|
||||||
|
|
||||||
return "\n".join(H + footer)
|
return "\n".join(H + footer)
|
||||||
|
|
||||||
@ -342,8 +342,8 @@ def formsemestre_validation_etud_form(
|
|||||||
<input type="hidden" name="formsemestre_id" value="%s"/>"""
|
<input type="hidden" name="formsemestre_id" value="%s"/>"""
|
||||||
% (etudid, formsemestre_id)
|
% (etudid, formsemestre_id)
|
||||||
)
|
)
|
||||||
if desturl:
|
if dest_url:
|
||||||
H.append('<input type="hidden" name="desturl" value="%s"/>' % desturl)
|
H.append('<input type="hidden" name="desturl" value="%s"/>' % dest_url)
|
||||||
if sortcol:
|
if sortcol:
|
||||||
H.append('<input type="hidden" name="sortcol" value="%s"/>' % sortcol)
|
H.append('<input type="hidden" name="sortcol" value="%s"/>' % sortcol)
|
||||||
|
|
||||||
|
@ -55,10 +55,6 @@ _SCO_PERMISSIONS = (
|
|||||||
),
|
),
|
||||||
# 27 à 39 ... réservé pour "entreprises"
|
# 27 à 39 ... réservé pour "entreprises"
|
||||||
(1 << 40, "ScoEtudChangePhoto", "Modifier la photo d'un étudiant"),
|
(1 << 40, "ScoEtudChangePhoto", "Modifier la photo d'un étudiant"),
|
||||||
# Api scodoc9
|
|
||||||
# XXX à revoir
|
|
||||||
# (1 << 42, "APIEditAllNotes", "API: Modifier toutes les notes"),
|
|
||||||
# (1 << 43, "APIAbsChange", "API: Saisir des absences"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,30 +101,7 @@ def can_edit_suivi():
|
|||||||
return current_user.has_permission(Permission.ScoEtudChangeAdr)
|
return current_user.has_permission(Permission.ScoEtudChangeAdr)
|
||||||
|
|
||||||
|
|
||||||
def can_validate_sem(formsemestre_id):
|
def is_chef_or_diretud(sem): # remplacé par formsemestre.est_chef_or_diretud
|
||||||
"Vrai si utilisateur peut saisir decision de jury dans ce semestre"
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
if not sem["etat"]:
|
|
||||||
return False # semestre verrouillé
|
|
||||||
|
|
||||||
return is_chef_or_diretud(sem)
|
|
||||||
|
|
||||||
|
|
||||||
def can_edit_pv(formsemestre_id):
|
|
||||||
"Vrai si utilisateur peut editer un PV de jury de ce semestre"
|
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
if is_chef_or_diretud(sem):
|
|
||||||
return True
|
|
||||||
# Autorise les secrétariats, repérés via la permission ScoEtudChangeAdr
|
|
||||||
# (ceci nous évite d'ajouter une permission Zope aux installations existantes)
|
|
||||||
return current_user.has_permission(Permission.ScoEtudChangeAdr)
|
|
||||||
|
|
||||||
|
|
||||||
def is_chef_or_diretud(sem):
|
|
||||||
"Vrai si utilisateur est admin, chef dept ou responsable du semestre"
|
"Vrai si utilisateur est admin, chef dept ou responsable du semestre"
|
||||||
if (
|
if (
|
||||||
current_user.has_permission(Permission.ScoImplement)
|
current_user.has_permission(Permission.ScoImplement)
|
||||||
|
@ -1243,7 +1243,7 @@ class BasePreferences(object):
|
|||||||
{
|
{
|
||||||
"initvalue": 0,
|
"initvalue": 0,
|
||||||
"title": "Afficher toutes les évaluations sur les bulletins",
|
"title": "Afficher toutes les évaluations sur les bulletins",
|
||||||
"explanation": "y compris incomplètes ou futures (déconseillé, risque de publier des notes non définitives)",
|
"explanation": "y compris incomplètes ou futures (déconseillé, risque de publier des notes non définitives; n'affecte pas le calcul des moyennes)",
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"category": "bul",
|
"category": "bul",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
|
@ -516,18 +516,18 @@ def pvjury_table(
|
|||||||
|
|
||||||
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
||||||
"""Page récapitulant les décisions de jury"""
|
"""Page récapitulant les décisions de jury"""
|
||||||
|
|
||||||
# Bretelle provisoire pour BUT 9.3.0
|
|
||||||
# XXX TODO
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
is_apc = formsemestre.formation.is_apc()
|
is_apc = formsemestre.formation.is_apc()
|
||||||
if format == "html" and is_apc and formsemestre.semestre_id % 2 == 0:
|
if format == "html" and is_apc:
|
||||||
from app.tables import jury_recap
|
return redirect(
|
||||||
|
url_for(
|
||||||
return jury_recap.formsemestre_saisie_jury_but(
|
"notes.formsemestre_recapcomplet",
|
||||||
formsemestre, read_only=True, mode="recap"
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
mode_jury=1,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# /XXX
|
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
|
|
||||||
dpv = dict_pvjury(formsemestre_id, with_prev=True)
|
dpv = dict_pvjury(formsemestre_id, with_prev=True)
|
||||||
|
@ -51,7 +51,6 @@ from app.scodoc import sco_evaluations
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_formsemestre_status
|
from app.scodoc import sco_formsemestre_status
|
||||||
from app.scodoc import sco_permissions_check
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.tables.recap import TableRecap
|
from app.tables.recap import TableRecap
|
||||||
from app.tables.jury_recap import TableJury
|
from app.tables.jury_recap import TableJury
|
||||||
@ -95,17 +94,26 @@ def formsemestre_recapcomplet(
|
|||||||
mode_jury = int(mode_jury)
|
mode_jury = int(mode_jury)
|
||||||
xml_with_decisions = int(xml_with_decisions)
|
xml_with_decisions = int(xml_with_decisions)
|
||||||
force_publishing = int(force_publishing)
|
force_publishing = int(force_publishing)
|
||||||
|
filename = scu.sanitize_filename(
|
||||||
data = _do_formsemestre_recapcomplet(
|
f"""recap-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
||||||
formsemestre_id,
|
|
||||||
format=tabformat,
|
|
||||||
mode_jury=mode_jury,
|
|
||||||
xml_with_decisions=xml_with_decisions,
|
|
||||||
force_publishing=force_publishing,
|
|
||||||
selected_etudid=selected_etudid,
|
|
||||||
)
|
)
|
||||||
if is_file:
|
if is_file:
|
||||||
return data
|
return _formsemestre_recapcomplet_to_file(
|
||||||
|
formsemestre,
|
||||||
|
tabformat=tabformat,
|
||||||
|
filename=filename,
|
||||||
|
xml_with_decisions=xml_with_decisions,
|
||||||
|
force_publishing=force_publishing,
|
||||||
|
)
|
||||||
|
|
||||||
|
table_html, table = _formsemestre_recapcomplet_to_html(
|
||||||
|
formsemestre,
|
||||||
|
filename=filename,
|
||||||
|
mode_jury=mode_jury,
|
||||||
|
tabformat=tabformat,
|
||||||
|
selected_etudid=selected_etudid,
|
||||||
|
)
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title=f"{formsemestre.sem_modalite()}: "
|
page_title=f"{formsemestre.sem_modalite()}: "
|
||||||
@ -131,64 +139,90 @@ def formsemestre_recapcomplet(
|
|||||||
H.append(
|
H.append(
|
||||||
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
|
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
|
||||||
)
|
)
|
||||||
for (format, label) in (
|
for (fmt, label) in (
|
||||||
("html", "Tableau"),
|
("html", "Tableau"),
|
||||||
("evals", "Avec toutes les évaluations"),
|
("evals", "Avec toutes les évaluations"),
|
||||||
("xlsx", "Excel (non formaté)"),
|
("xlsx", "Excel (non formaté)"),
|
||||||
("xlsall", "Excel avec évaluations"),
|
("xlsall", "Excel avec évaluations"),
|
||||||
("xml", "Bulletins XML (obsolète)"),
|
|
||||||
("json", "Bulletins JSON"),
|
("json", "Bulletins JSON"),
|
||||||
):
|
):
|
||||||
if format == tabformat:
|
if fmt == tabformat:
|
||||||
selected = " selected"
|
selected = " selected"
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
H.append(f'<option value="{format}"{selected}>{label}</option>')
|
H.append(f'<option value="{fmt}"{selected}>{label}</option>')
|
||||||
H.append("</select>")
|
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f""" (cliquer sur un nom pour afficher son bulletin ou <a class="stdlink"
|
f"""
|
||||||
|
</select> (cliquer sur un nom pour afficher son bulletin ou
|
||||||
|
<a class="stdlink"
|
||||||
href="{url_for('notes.formsemestre_bulletins_pdf',
|
href="{url_for('notes.formsemestre_bulletins_pdf',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
}">ici avoir le classeur papier</a>)
|
}">ici avoir le classeur papier</a>)
|
||||||
|
</form>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
H.append(data)
|
|
||||||
|
H.append(table_html) # La table
|
||||||
|
|
||||||
if len(formsemestre.inscriptions) > 0:
|
if len(formsemestre.inscriptions) > 0:
|
||||||
H.append("</form>")
|
H.append("""<div class="links_under_recap"><ul>""")
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p><a class="stdlink" href="{url_for('notes.formsemestre_pvjury',
|
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
|
||||||
}">Voir les décisions du jury</a></p>"""
|
}">Décisions du jury</a>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
if sco_permissions_check.can_validate_sem(formsemestre_id):
|
if formsemestre.can_edit_jury():
|
||||||
H.append("<p>")
|
|
||||||
if mode_jury:
|
if mode_jury:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p><a class="stdlink" href="{url_for('notes.formsemestre_validation_auto',
|
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_validation_auto',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
}">Calcul automatique des décisions du jury</a>
|
}">Calcul automatique des décisions du jury</a>
|
||||||
</p><a class="stdlink" href="{url_for('notes.formsemestre_jury_but_erase',
|
</li>
|
||||||
|
<li><a class="stdlink" href="{url_for('notes.formsemestre_jury_but_erase',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, only_one_sem=1)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, only_one_sem=1)
|
||||||
}">Effacer <em>toutes</em> les décisions de jury du semestre</a>
|
}">Effacer <em>toutes</em> les décisions de jury (BUT) du semestre</a>
|
||||||
<p>
|
</li>
|
||||||
</p>
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
else:
|
H.append("</ul></div>")
|
||||||
H.append(
|
|
||||||
f"""<a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
|
|
||||||
}">Saisie des décisions du jury</a>"""
|
|
||||||
)
|
|
||||||
H.append("</p>")
|
|
||||||
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
|
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
"""
|
||||||
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if mode_jury and table and sum(table.freq_codes_annuels.values()) > 0:
|
||||||
|
H.append(
|
||||||
|
f"""
|
||||||
|
<div class="jury_stats">
|
||||||
|
<div>Nb d'étudiants avec décision annuelle:
|
||||||
|
{sum(table.freq_codes_annuels.values())} / {len(table)}
|
||||||
|
</div>
|
||||||
|
<div><b>Codes annuels octroyés:</b></div>
|
||||||
|
<table class="jury_stats_codes">
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
for code in sorted(table.freq_codes_annuels.keys()):
|
||||||
|
H.append(
|
||||||
|
f"""<tr>
|
||||||
|
<td>{code}</td>
|
||||||
|
<td style="text-align:right">{table.freq_codes_annuels[code]}</td>
|
||||||
|
<td style="text-align:right">{
|
||||||
|
(100*table.freq_codes_annuels[code] / len(table)):2.1f}%
|
||||||
|
</td>
|
||||||
|
</tr>"""
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
"""
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
# HTML or binary data ?
|
# HTML or binary data ?
|
||||||
if len(H) > 1:
|
if len(H) > 1:
|
||||||
@ -199,62 +233,69 @@ def formsemestre_recapcomplet(
|
|||||||
return H
|
return H
|
||||||
|
|
||||||
|
|
||||||
def _do_formsemestre_recapcomplet(
|
def _formsemestre_recapcomplet_to_html(
|
||||||
formsemestre_id=None,
|
formsemestre: FormSemestre,
|
||||||
format="html", # html, xml, xls, xlsall, json
|
tabformat="html", # "html" or "evals"
|
||||||
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
|
filename: str = "",
|
||||||
mode_jury=False, # saisie décisions jury
|
mode_jury=False, # saisie décisions jury
|
||||||
|
selected_etudid=None,
|
||||||
|
) -> tuple[str, TableRecap]:
|
||||||
|
"""Le tableau recap en html"""
|
||||||
|
if tabformat not in ("html", "evals"):
|
||||||
|
raise ScoValueError("invalid table format")
|
||||||
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
table_html, table = gen_formsemestre_recapcomplet_html_table(
|
||||||
|
formsemestre,
|
||||||
|
res,
|
||||||
|
include_evaluations=(tabformat == "evals"),
|
||||||
|
mode_jury=mode_jury,
|
||||||
|
filename=filename,
|
||||||
|
selected_etudid=selected_etudid,
|
||||||
|
)
|
||||||
|
return table_html, table
|
||||||
|
|
||||||
|
|
||||||
|
def _formsemestre_recapcomplet_to_file(
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
tabformat: str = "json", # xml, xls, xlsall, json
|
||||||
|
filename: str = "",
|
||||||
|
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
|
||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
force_publishing=True,
|
force_publishing=True,
|
||||||
selected_etudid=None,
|
|
||||||
):
|
):
|
||||||
"""Calcule et renvoie le tableau récapitulatif."""
|
"""Calcule et renvoie le tableau récapitulatif."""
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
if tabformat.startswith("xls"):
|
||||||
|
|
||||||
filename = scu.sanitize_filename(
|
|
||||||
f"""recap-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
|
||||||
)
|
|
||||||
|
|
||||||
if format == "html" or format == "evals":
|
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
data = gen_formsemestre_recapcomplet_html(
|
include_evaluations = tabformat in {
|
||||||
formsemestre,
|
"xlsall",
|
||||||
res,
|
"csv ",
|
||||||
include_evaluations=(format == "evals"),
|
} # csv not supported anymore
|
||||||
mode_jury=mode_jury,
|
if tabformat != "csv":
|
||||||
filename=filename,
|
tabformat = "xlsx"
|
||||||
selected_etudid=selected_etudid,
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
elif format.startswith("xls"):
|
|
||||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
include_evaluations = format in {"xlsall", "csv "} # csv not supported anymore
|
|
||||||
if format != "csv":
|
|
||||||
format = "xlsx"
|
|
||||||
data, filename = gen_formsemestre_recapcomplet_excel(
|
data, filename = gen_formsemestre_recapcomplet_excel(
|
||||||
res,
|
res,
|
||||||
include_evaluations=include_evaluations,
|
include_evaluations=include_evaluations,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
)
|
)
|
||||||
return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format))
|
return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format))
|
||||||
elif format == "xml":
|
elif tabformat == "xml":
|
||||||
data = gen_formsemestre_recapcomplet_xml(
|
data = gen_formsemestre_recapcomplet_xml(
|
||||||
formsemestre_id,
|
formsemestre.id,
|
||||||
xml_nodate,
|
xml_nodate,
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
)
|
)
|
||||||
return scu.send_file(data, filename=filename, suffix=scu.XML_SUFFIX)
|
return scu.send_file(data, filename=filename, suffix=scu.XML_SUFFIX)
|
||||||
elif format == "json":
|
elif tabformat == "json":
|
||||||
data = gen_formsemestre_recapcomplet_json(
|
data = gen_formsemestre_recapcomplet_json(
|
||||||
formsemestre_id,
|
formsemestre.id,
|
||||||
xml_nodate=xml_nodate,
|
xml_nodate=xml_nodate,
|
||||||
xml_with_decisions=xml_with_decisions,
|
xml_with_decisions=xml_with_decisions,
|
||||||
force_publishing=force_publishing,
|
force_publishing=force_publishing,
|
||||||
)
|
)
|
||||||
return scu.sendJSON(data, filename=filename)
|
return scu.sendJSON(data, filename=filename)
|
||||||
|
|
||||||
raise ScoValueError(f"Format demandé invalide: {format}")
|
raise ScoValueError(f"Format demandé invalide: {tabformat}")
|
||||||
|
|
||||||
|
|
||||||
def gen_formsemestre_recapcomplet_xml(
|
def gen_formsemestre_recapcomplet_xml(
|
||||||
@ -368,22 +409,26 @@ def formsemestres_bulletins(annee_scolaire):
|
|||||||
return scu.sendJSON(js_list)
|
return scu.sendJSON(js_list)
|
||||||
|
|
||||||
|
|
||||||
def gen_formsemestre_recapcomplet_html(
|
def gen_formsemestre_recapcomplet_html_table(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
res: NotesTableCompat,
|
res: NotesTableCompat,
|
||||||
include_evaluations=False,
|
include_evaluations=False,
|
||||||
mode_jury=False,
|
mode_jury=False,
|
||||||
filename="",
|
filename="",
|
||||||
selected_etudid=None,
|
selected_etudid=None,
|
||||||
):
|
) -> tuple[str, TableRecap]:
|
||||||
"""Construit table recap pour le BUT
|
"""Construit table recap pour le BUT
|
||||||
Cache le résultat pour le semestre (sauf en mode jury).
|
Cache le résultat pour le semestre (sauf en mode jury).
|
||||||
|
Note: on cache le HTML et non l'objet Table.
|
||||||
|
|
||||||
Si mode_jury, cache colonnes modules et affiche un lien vers la saisie de la décision de jury
|
Si mode_jury, occultera colonnes modules (en js)
|
||||||
|
et affiche un lien vers la saisie de la décision de jury
|
||||||
|
|
||||||
Return: data, filename
|
Return: html (str), table (None sauf en mode jury ou si pas cachée)
|
||||||
data est une chaine, le <div>...</div> incluant le tableau.
|
|
||||||
|
html est une chaine, le <div>...</div> incluant le tableau.
|
||||||
"""
|
"""
|
||||||
|
table = None
|
||||||
table_html = None
|
table_html = None
|
||||||
if not (mode_jury or selected_etudid):
|
if not (mode_jury or selected_etudid):
|
||||||
if include_evaluations:
|
if include_evaluations:
|
||||||
@ -392,7 +437,7 @@ def gen_formsemestre_recapcomplet_html(
|
|||||||
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
||||||
# en mode jury ne cache pas la table html
|
# en mode jury ne cache pas la table html
|
||||||
if mode_jury or (table_html is None):
|
if mode_jury or (table_html is None):
|
||||||
table_html = _gen_formsemestre_recapcomplet_html(
|
table = _gen_formsemestre_recapcomplet_table(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
res,
|
res,
|
||||||
include_evaluations,
|
include_evaluations,
|
||||||
@ -400,48 +445,37 @@ def gen_formsemestre_recapcomplet_html(
|
|||||||
filename,
|
filename,
|
||||||
selected_etudid=selected_etudid,
|
selected_etudid=selected_etudid,
|
||||||
)
|
)
|
||||||
|
table_html = table.html()
|
||||||
if not mode_jury:
|
if not mode_jury:
|
||||||
if include_evaluations:
|
if include_evaluations:
|
||||||
sco_cache.TableRecapWithEvalsCache.set(formsemestre.id, table_html)
|
sco_cache.TableRecapWithEvalsCache.set(formsemestre.id, table_html)
|
||||||
else:
|
else:
|
||||||
sco_cache.TableRecapCache.set(formsemestre.id, table_html)
|
sco_cache.TableRecapCache.set(formsemestre.id, table_html)
|
||||||
|
|
||||||
return table_html
|
return table_html, table
|
||||||
|
|
||||||
|
|
||||||
def _gen_formsemestre_recapcomplet_html(
|
def _gen_formsemestre_recapcomplet_table(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
res: ResultatsSemestre,
|
res: ResultatsSemestre,
|
||||||
include_evaluations=False,
|
include_evaluations=False,
|
||||||
mode_jury=False,
|
mode_jury=False,
|
||||||
filename: str = "",
|
filename: str = "",
|
||||||
selected_etudid=None,
|
selected_etudid=None,
|
||||||
) -> str:
|
) -> TableRecap:
|
||||||
"""Génère le html"""
|
"""Construit la table récap."""
|
||||||
table_class = TableJury if mode_jury else TableRecap
|
table_class = TableJury if mode_jury else TableRecap
|
||||||
table = table_class(
|
table = table_class(
|
||||||
res,
|
res,
|
||||||
convert_values=True,
|
convert_values=True,
|
||||||
include_evaluations=include_evaluations,
|
include_evaluations=include_evaluations,
|
||||||
mode_jury=mode_jury,
|
mode_jury=mode_jury,
|
||||||
|
read_only=not formsemestre.can_edit_jury(),
|
||||||
)
|
)
|
||||||
|
|
||||||
table.data["filename"] = filename
|
table.data["filename"] = filename
|
||||||
table.select_row(selected_etudid)
|
table.select_row(selected_etudid)
|
||||||
return f"""
|
return table
|
||||||
<div class="table_recap">
|
|
||||||
{
|
|
||||||
'<div class="message">aucun étudiant !</div>'
|
|
||||||
if table.is_empty()
|
|
||||||
else table.html(
|
|
||||||
extra_classes=[
|
|
||||||
'table_recap',
|
|
||||||
'apc' if formsemestre.formation.is_apc() else 'classic',
|
|
||||||
'jury' if mode_jury else ''
|
|
||||||
])
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def gen_formsemestre_recapcomplet_excel(
|
def gen_formsemestre_recapcomplet_excel(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* DataTables style for ScoDoc gen_tables
|
* DataTables style for ScoDoc gen_tables
|
||||||
* generated using https://datatables.net/manual/styling/theme-creator
|
* generated using https://datatables.net/manual/styling/theme-creator
|
||||||
|
* and customized by hand
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -374,9 +375,11 @@ table.dataTable td {
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataTables_wrapper .dataTables_filter {
|
.dataTables_wrapper div.dataTables_filter {
|
||||||
float: right;
|
float: left;
|
||||||
text-align: right;
|
text-align: left;
|
||||||
|
margin-left: 64px;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataTables_wrapper .dataTables_filter input {
|
.dataTables_wrapper .dataTables_filter input {
|
||||||
|
@ -35,7 +35,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div#gtrcontent {
|
div#gtrcontent {
|
||||||
margin-bottom: 4ex;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gtrcontent {
|
.gtrcontent {
|
||||||
@ -4015,8 +4015,14 @@ div.table_recap {
|
|||||||
background: linear-gradient(to bottom, rgb(51, 255, 0) 0%, lightgray 100%);
|
background: linear-gradient(to bottom, rgb(51, 255, 0) 0%, lightgray 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Non supproté par les navigateurs (en Fev. 2023)
|
||||||
|
.table_recap button:has(span a.clearreaload) {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
div.table_recap table.table_recap {
|
div.table_recap table.table_recap {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
margin-left: 0px;
|
||||||
/* font-family: Consolas, monaco, monospace; */
|
/* font-family: Consolas, monaco, monospace; */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4344,6 +4350,9 @@ div.table_jury_but_links {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.links_under_recap ul li {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------- Tableau stats jury BUT -------- */
|
/* ------------- Tableau stats jury BUT -------- */
|
||||||
table.jury_stats_codes {
|
table.jury_stats_codes {
|
||||||
|
@ -8,11 +8,6 @@ $(function () {
|
|||||||
"partition_aux", "partition_rangs", "admission",
|
"partition_aux", "partition_rangs", "admission",
|
||||||
"col_empty"
|
"col_empty"
|
||||||
];
|
];
|
||||||
let mode_jury_but_bilan = $('table.table_recap').hasClass("table_jury_but_bilan");
|
|
||||||
if (mode_jury_but_bilan) {
|
|
||||||
// table bilan décisions: cache les notes
|
|
||||||
hidden_colums = hidden_colums.concat(["col_lien_saisie_but"]);
|
|
||||||
}
|
|
||||||
// Etat (tri des colonnes) de la table:
|
// Etat (tri des colonnes) de la table:
|
||||||
|
|
||||||
const url = new URL(document.URL);
|
const url = new URL(document.URL);
|
||||||
@ -99,7 +94,7 @@ $(function () {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '<a title="Rétablir l\'affichage par défaut">➗</a>',
|
text: '<a title="Rétablir l\'affichage par défaut" class="clearreload">🔄</a>',
|
||||||
action: function (e, dt, node, config) {
|
action: function (e, dt, node, config) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
console.log("cleared localStorage");
|
console.log("cleared localStorage");
|
||||||
@ -124,7 +119,7 @@ $(function () {
|
|||||||
// table jury: avec ou sans codes enregistrés
|
// table jury: avec ou sans codes enregistrés
|
||||||
buttons.push(
|
buttons.push(
|
||||||
{
|
{
|
||||||
text: '<span data-group="recorded_code">Code jurys</span>',
|
text: '<span data-group="recorded_code">Codes jury</span>',
|
||||||
action: toggle_col_but_visibility,
|
action: toggle_col_but_visibility,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -165,6 +160,15 @@ $(function () {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Boutons évaluations (si présentes)
|
||||||
|
if ($('table.table_recap').hasClass("with_evaluations")) {
|
||||||
|
buttons.push(
|
||||||
|
{
|
||||||
|
text: '<span data-group="eval">Évaluations</span>',
|
||||||
|
action: toggle_col_but_visibility,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------- LA TABLE ---------
|
// ------------- LA TABLE ---------
|
||||||
try {
|
try {
|
||||||
|
@ -114,7 +114,7 @@ class TableJury(TableRecap):
|
|||||||
"jury_link",
|
"jury_link",
|
||||||
"",
|
"",
|
||||||
f"""{("➨ saisir" if a_saisir else "modifier")
|
f"""{("➨ saisir" if a_saisir else "modifier")
|
||||||
if res.formsemestre.etat else "voir"} décisions""",
|
if not self.read_only else "voir"} décisions""",
|
||||||
group="col_jury_link",
|
group="col_jury_link",
|
||||||
classes=["fontred"] if a_saisir else [],
|
classes=["fontred"] if a_saisir else [],
|
||||||
target=url_for(
|
target=url_for(
|
||||||
@ -250,149 +250,3 @@ class RowJury(RowRecap):
|
|||||||
# f"""{int(ects_valides)}""",
|
# f"""{int(ects_valides)}""",
|
||||||
# "col_code_annee",
|
# "col_code_annee",
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_saisie_jury_but(
|
|
||||||
formsemestre: FormSemestre,
|
|
||||||
read_only: bool = False,
|
|
||||||
selected_etudid: int = None,
|
|
||||||
mode="jury",
|
|
||||||
) -> str:
|
|
||||||
"""formsemestre est un semestre PAIR
|
|
||||||
Si readonly, ne montre pas le lien "saisir la décision"
|
|
||||||
|
|
||||||
=> page html complète
|
|
||||||
|
|
||||||
Si mode == "recap", table recap des codes, sans liens de saisie.
|
|
||||||
"""
|
|
||||||
# pour chaque etud de res2 trié
|
|
||||||
# S1: UE1, ..., UEn
|
|
||||||
# S2: UE1, ..., UEn
|
|
||||||
#
|
|
||||||
# UE1_s1, UE1_s2, moy_rcue, UE2... , Nbrcue_validables, Nbrcue<8, passage_de_droit, valide_moitie_rcue
|
|
||||||
#
|
|
||||||
# Pour chaque etud de res2 trié
|
|
||||||
# DecisionsProposeesAnnee(etud, formsemestre2)
|
|
||||||
# Pour le 1er etud, faire un check_ues_ready_jury(self) -> page d'erreur
|
|
||||||
# -> rcue .ue_1, .ue_2 -> stroe moy ues, rcue.moy_rcue, etc
|
|
||||||
|
|
||||||
if formsemestre.formation.referentiel_competence is None:
|
|
||||||
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
|
||||||
|
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
table = TableJury(
|
|
||||||
res,
|
|
||||||
convert_values=True,
|
|
||||||
mode_jury=True,
|
|
||||||
read_only=read_only,
|
|
||||||
classes=[
|
|
||||||
"table_jury_but_bilan" if mode == "recap" else "",
|
|
||||||
"table_recap",
|
|
||||||
"apc",
|
|
||||||
"jury table_jury_but",
|
|
||||||
],
|
|
||||||
selected_row_id=selected_etudid,
|
|
||||||
)
|
|
||||||
if table.is_empty():
|
|
||||||
return (
|
|
||||||
'<div class="table_recap"><div class="message">aucun étudiant !</div></div>'
|
|
||||||
)
|
|
||||||
table.data["filename"] = scu.sanitize_filename(
|
|
||||||
f"""jury-but-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
|
||||||
)
|
|
||||||
table_html = table.html()
|
|
||||||
H = [
|
|
||||||
html_sco_header.sco_header(
|
|
||||||
page_title=f"{formsemestre.sem_modalite()}: jury BUT",
|
|
||||||
no_side_bar=True,
|
|
||||||
init_qtip=True,
|
|
||||||
javascripts=["js/etud_info.js", "js/table_recap.js"],
|
|
||||||
),
|
|
||||||
sco_formsemestre_status.formsemestre_status_head(
|
|
||||||
formsemestre_id=formsemestre.id
|
|
||||||
),
|
|
||||||
]
|
|
||||||
if mode == "recap":
|
|
||||||
H.append(
|
|
||||||
f"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>
|
|
||||||
<div class="table_jury_but_links">
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{url_for(
|
|
||||||
"notes.pvjury_table_but",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
||||||
}" class="stdlink">Tableau PV de jury</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="{url_for(
|
|
||||||
"notes.formsemestre_lettres_individuelles",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
||||||
}" class="stdlink">Courriers individuels (classeur pdf)</a>
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<div class="table_recap">
|
|
||||||
{table_html}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table_jury_but_links">
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
if (mode == "recap") and not read_only:
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<p><a class="stdlink" href="{url_for(
|
|
||||||
"notes.formsemestre_saisie_jury",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
||||||
}">Saisie des décisions du jury</a>
|
|
||||||
</p>"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<p><a class="stdlink" href="{url_for(
|
|
||||||
"notes.formsemestre_validation_auto_but",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
||||||
}">Calcul automatique des décisions du jury</a>
|
|
||||||
</p>
|
|
||||||
<p><a class="stdlink" href="{url_for(
|
|
||||||
"notes.formsemestre_jury_but_recap",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
|
||||||
}">Tableau récapitulatif des décisions du jury</a>
|
|
||||||
</p>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="jury_stats">
|
|
||||||
<div>Nb d'étudiants avec décision annuelle:
|
|
||||||
{sum(table.freq_codes_annuels.values())} / {len(table)}
|
|
||||||
</div>
|
|
||||||
<div><b>Codes annuels octroyés:</b></div>
|
|
||||||
<table class="jury_stats_codes">
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
for code in sorted(table.freq_codes_annuels.keys()):
|
|
||||||
H.append(
|
|
||||||
f"""<tr>
|
|
||||||
<td>{code}</td>
|
|
||||||
<td style="text-align:right">{table.freq_codes_annuels[code]}</td>
|
|
||||||
<td style="text-align:right">{
|
|
||||||
(100*table.freq_codes_annuels[code] / len(table)):2.1f}%
|
|
||||||
</td>
|
|
||||||
</tr>"""
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{html_sco_header.sco_footer()}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
return "\n".join(H)
|
|
||||||
|
@ -54,6 +54,7 @@ class TableRecap(tb.Table):
|
|||||||
mode_jury=False,
|
mode_jury=False,
|
||||||
row_class=None,
|
row_class=None,
|
||||||
finalize=True,
|
finalize=True,
|
||||||
|
read_only: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.rows: list["RowRecap"] = [] # juste pour que VSCode nous aide sur .rows
|
self.rows: list["RowRecap"] = [] # juste pour que VSCode nous aide sur .rows
|
||||||
@ -61,7 +62,7 @@ class TableRecap(tb.Table):
|
|||||||
self.res = res
|
self.res = res
|
||||||
self.include_evaluations = include_evaluations
|
self.include_evaluations = include_evaluations
|
||||||
self.mode_jury = mode_jury
|
self.mode_jury = mode_jury
|
||||||
|
self.read_only = read_only # utilisé seulement dans sous-classes
|
||||||
parcours = res.formsemestre.formation.get_parcours()
|
parcours = res.formsemestre.formation.get_parcours()
|
||||||
self.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE
|
self.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE
|
||||||
self.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE
|
self.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE
|
||||||
@ -103,7 +104,7 @@ class TableRecap(tb.Table):
|
|||||||
self.add_cursus()
|
self.add_cursus()
|
||||||
self.add_admissions()
|
self.add_admissions()
|
||||||
|
|
||||||
# tri par rang croissant
|
# Tri par rang croissant
|
||||||
if not res.formsemestre.block_moyenne_generale:
|
if not res.formsemestre.block_moyenne_generale:
|
||||||
self.sort_rows(key=lambda row: row.rang_order)
|
self.sort_rows(key=lambda row: row.rang_order)
|
||||||
else:
|
else:
|
||||||
@ -361,6 +362,7 @@ class TableRecap(tb.Table):
|
|||||||
pour tous les étudiants de la table.
|
pour tous les étudiants de la table.
|
||||||
Les colonnes ont la classe css "evaluation"
|
Les colonnes ont la classe css "evaluation"
|
||||||
"""
|
"""
|
||||||
|
self.group_titles["eval"] = "Évaluations"
|
||||||
# nouvelle ligne pour description évaluations:
|
# nouvelle ligne pour description évaluations:
|
||||||
row_descr_eval = tb.BottomRow(
|
row_descr_eval = tb.BottomRow(
|
||||||
self,
|
self,
|
||||||
@ -382,7 +384,7 @@ class TableRecap(tb.Table):
|
|||||||
for e in evals:
|
for e in evals:
|
||||||
col_id = f"eval_{e.id}"
|
col_id = f"eval_{e.id}"
|
||||||
title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}'
|
title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}'
|
||||||
col_classes = ["evaluation"]
|
col_classes = []
|
||||||
if first_eval:
|
if first_eval:
|
||||||
col_classes.append("first")
|
col_classes.append("first")
|
||||||
elif first_eval_of_mod:
|
elif first_eval_of_mod:
|
||||||
@ -408,13 +410,15 @@ class TableRecap(tb.Table):
|
|||||||
"EXC": "exc",
|
"EXC": "exc",
|
||||||
}.get(content, "")
|
}.get(content, "")
|
||||||
]
|
]
|
||||||
row.add_cell(col_id, title, content, "", classes=classes)
|
row.add_cell(
|
||||||
|
col_id, title, content, group="eval", classes=classes
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
row.add_cell(
|
row.add_cell(
|
||||||
col_id,
|
col_id,
|
||||||
title,
|
title,
|
||||||
"ni",
|
"ni",
|
||||||
"",
|
group="eval",
|
||||||
classes=col_classes + ["non_inscrit"],
|
classes=col_classes + ["non_inscrit"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -505,6 +509,24 @@ class TableRecap(tb.Table):
|
|||||||
group="cursus",
|
group="cursus",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def html(self, extra_classes: list[str] = None) -> str:
|
||||||
|
"""HTML: pour les tables recap, un div au contenu variable"""
|
||||||
|
return f"""
|
||||||
|
<div class="table_recap">
|
||||||
|
{
|
||||||
|
'<div class="message">aucun étudiant !</div>'
|
||||||
|
if self.is_empty()
|
||||||
|
else super().html(
|
||||||
|
extra_classes=[
|
||||||
|
"table_recap",
|
||||||
|
"apc" if self.res.formsemestre.formation.is_apc() else "classic",
|
||||||
|
"jury" if self.mode_jury else "",
|
||||||
|
"with_evaluations" if self.include_evaluations else "",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class RowRecap(tb.Row):
|
class RowRecap(tb.Row):
|
||||||
"Ligne de la table recap, pour un étudiant"
|
"Ligne de la table recap, pour un étudiant"
|
||||||
|
@ -59,7 +59,7 @@ from app.models.formsemestre import FormSemestreUEComputationExpr
|
|||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.modules import Module
|
from app.models.modules import Module
|
||||||
from app.models.ues import DispenseUE, UniteEns
|
from app.models.ues import DispenseUE, UniteEns
|
||||||
from app.scodoc.sco_exceptions import ScoFormationConflict
|
from app.scodoc.sco_exceptions import ScoFormationConflict, ScoPermissionDenied
|
||||||
from app.tables import jury_recap
|
from app.tables import jury_recap
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
|
|
||||||
@ -2257,8 +2257,8 @@ def formsemestre_validation_etud_form(
|
|||||||
sortcol=None,
|
sortcol=None,
|
||||||
):
|
):
|
||||||
"Formulaire choix jury pour un étudiant"
|
"Formulaire choix jury pour un étudiant"
|
||||||
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
read_only = not formsemestre.can_edit_jury()
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -2273,8 +2273,8 @@ def formsemestre_validation_etud_form(
|
|||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
etud_index=etud_index,
|
etud_index=etud_index,
|
||||||
check=check,
|
check=check,
|
||||||
readonly=readonly,
|
read_only=read_only,
|
||||||
desturl=desturl,
|
dest_url=desturl,
|
||||||
sortcol=sortcol,
|
sortcol=sortcol,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2291,10 +2291,14 @@ def formsemestre_validation_etud(
|
|||||||
sortcol=None,
|
sortcol=None,
|
||||||
):
|
):
|
||||||
"Enregistre choix jury pour un étudiant"
|
"Enregistre choix jury pour un étudiant"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_validation.formsemestre_validation_etud(
|
return sco_formsemestre_validation.formsemestre_validation_etud(
|
||||||
@ -2321,10 +2325,14 @@ def formsemestre_validation_etud_manu(
|
|||||||
sortcol=None,
|
sortcol=None,
|
||||||
):
|
):
|
||||||
"Enregistre choix jury pour un étudiant"
|
"Enregistre choix jury pour un étudiant"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_validation.formsemestre_validation_etud_manu(
|
return sco_formsemestre_validation.formsemestre_validation_etud_manu(
|
||||||
@ -2364,7 +2372,7 @@ def formsemestre_validation_but(
|
|||||||
etudid = int(etudid)
|
etudid = int(etudid)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
abort(404, "invalid etudid")
|
abort(404, "invalid etudid")
|
||||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
read_only = not formsemestre.can_edit_jury()
|
||||||
|
|
||||||
# --- Navigation
|
# --- Navigation
|
||||||
prev_lnk = (
|
prev_lnk = (
|
||||||
@ -2391,9 +2399,13 @@ def formsemestre_validation_but(
|
|||||||
{prev_lnk}
|
{prev_lnk}
|
||||||
</div>
|
</div>
|
||||||
<div class="back_list">
|
<div class="back_list">
|
||||||
<a href="{url_for(
|
<a href="{
|
||||||
"notes.formsemestre_saisie_jury", scodoc_dept=g.scodoc_dept,
|
url_for(
|
||||||
formsemestre_id=formsemestre_id, selected_etudid=etud.id
|
"notes.formsemestre_recapcomplet",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
mode_jury=1,
|
||||||
|
selected_etudid=etud.id
|
||||||
)}" class="stdlink">retour à la liste</a>
|
)}" class="stdlink">retour à la liste</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="next">
|
<div class="next">
|
||||||
@ -2583,15 +2595,16 @@ def formsemestre_validation_but(
|
|||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
||||||
"Saisie automatique des décisions de jury BUT"
|
"Saisie automatique des décisions de jury BUT"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message=f"<p>Opération non autorisée pour {current_user}</h2>",
|
raise ScoPermissionDenied(
|
||||||
dest_url=url_for(
|
dest_url=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,
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@ -2602,9 +2615,10 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||||||
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_saisie_jury",
|
"notes.formsemestre_recapcomplet",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
|
mode_jury=1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -2621,11 +2635,16 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
|
def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
|
||||||
"Form. saisie UE validée hors ScoDoc"
|
"Form. saisie UE validée hors ScoDoc"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
|
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
|
||||||
formsemestre_id, etudid
|
formsemestre_id, etudid
|
||||||
)
|
)
|
||||||
@ -2645,11 +2664,16 @@ sco_publish(
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
|
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
|
||||||
"Form. edition UE semestre extérieur"
|
"Form. edition UE semestre extérieur"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_exterieurs.formsemestre_ext_edit_ue_validations(
|
return sco_formsemestre_exterieurs.formsemestre_ext_edit_ue_validations(
|
||||||
formsemestre_id, etudid
|
formsemestre_id, etudid
|
||||||
)
|
)
|
||||||
@ -2668,11 +2692,16 @@ sco_publish(
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
||||||
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
|
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_validation.etud_ue_suppress_validation(
|
return sco_formsemestre_validation.etud_ue_suppress_validation(
|
||||||
etudid, formsemestre_id, ue_id
|
etudid, formsemestre_id, ue_id
|
||||||
)
|
)
|
||||||
@ -2684,14 +2713,18 @@ def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_validation_auto(formsemestre_id):
|
def formsemestre_validation_auto(formsemestre_id):
|
||||||
"Formulaire saisie automatisee des decisions d'un semestre"
|
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
|
||||||
return scu.confirm_dialog(
|
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
|
||||||
dest_url=scu.ScoURL(),
|
|
||||||
)
|
|
||||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
|
if not formsemestre.can_edit_jury():
|
||||||
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -2709,10 +2742,14 @@ def formsemestre_validation_auto(formsemestre_id):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def do_formsemestre_validation_auto(formsemestre_id):
|
def do_formsemestre_validation_auto(formsemestre_id):
|
||||||
"Formulaire saisie automatisee des decisions d'un semestre"
|
"Formulaire saisie automatisee des decisions d'un semestre"
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return sco_formsemestre_validation.do_formsemestre_validation_auto(formsemestre_id)
|
return sco_formsemestre_validation.do_formsemestre_validation_auto(formsemestre_id)
|
||||||
@ -2726,13 +2763,16 @@ def formsemestre_validation_suppress_etud(
|
|||||||
formsemestre_id, etudid, dialog_confirmed=False
|
formsemestre_id, etudid, dialog_confirmed=False
|
||||||
):
|
):
|
||||||
"""Suppression des décisions de jury pour un étudiant."""
|
"""Suppression des décisions de jury pour un étudiant."""
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
return scu.confirm_dialog(
|
if not formsemestre.can_edit_jury():
|
||||||
message="<p>Opération non autorisée pour %s</h2>" % current_user,
|
raise ScoPermissionDenied(
|
||||||
dest_url=scu.ScoURL(),
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
etud = Identite.query.get_or_404(etudid)
|
etud = Identite.query.get_or_404(etudid)
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
next_url = url_for(
|
next_url = url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.ficheEtud",
|
||||||
@ -2800,15 +2840,8 @@ sco_publish("/pvjury_table_but", jury_but_pv.pvjury_table_but, Permission.ScoVie
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
||||||
"""Page de saisie: liste des étudiants et lien vers page jury
|
"""Page de saisie: liste des étudiants et lien vers page jury
|
||||||
en semestres pairs de BUT, table spécifique avec l'année
|
|
||||||
sinon, redirect vers page recap en mode jury
|
sinon, redirect vers page recap en mode jury
|
||||||
"""
|
"""
|
||||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
||||||
if formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0:
|
|
||||||
return jury_recap.formsemestre_saisie_jury_but(
|
|
||||||
formsemestre, read_only, selected_etudid=selected_etudid
|
|
||||||
)
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_recapcomplet",
|
"notes.formsemestre_recapcomplet",
|
||||||
@ -2819,23 +2852,6 @@ def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre_jury_but_recap")
|
|
||||||
@scodoc
|
|
||||||
@permission_required(Permission.ScoView)
|
|
||||||
@scodoc7func
|
|
||||||
def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = None):
|
|
||||||
"""Tableau affichage des codes"""
|
|
||||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
|
||||||
if not (formsemestre.formation.is_apc() and formsemestre.semestre_id % 2 == 0):
|
|
||||||
raise ScoValueError(
|
|
||||||
"formsemestre_jury_but_recap: réservé aux semestres pairs de BUT"
|
|
||||||
)
|
|
||||||
return jury_recap.formsemestre_saisie_jury_but(
|
|
||||||
formsemestre, read_only=read_only, selected_etudid=selected_etudid, mode="recap"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"/formsemestre_jury_but_erase/<int:formsemestre_id>",
|
"/formsemestre_jury_but_erase/<int:formsemestre_id>",
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
@ -2855,18 +2871,25 @@ def formsemestre_jury_but_erase(
|
|||||||
Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits.
|
Si l'étudiant n'est pas spécifié, efface les décisions de tous les inscrits.
|
||||||
"""
|
"""
|
||||||
only_one_sem = int(request.args.get("only_one_sem") or False)
|
only_one_sem = int(request.args.get("only_one_sem") or False)
|
||||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
raise ScoValueError("opération non autorisée")
|
if not formsemestre.can_edit_jury():
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
raise ScoPermissionDenied(
|
||||||
|
dest_url=url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
if not formsemestre.formation.is_apc():
|
if not formsemestre.formation.is_apc():
|
||||||
raise ScoValueError("semestre non BUT")
|
raise ScoValueError("semestre non BUT")
|
||||||
if etudid is None:
|
if etudid is None:
|
||||||
etud = None
|
etud = None
|
||||||
etuds = formsemestre.get_inscrits(include_demdef=True)
|
etuds = formsemestre.get_inscrits(include_demdef=True)
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"notes.formsemestre_saisie_jury",
|
"notes.formsemestre_recapcomplet",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
|
mode_jury=1,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
etud: Identite = Identite.query.get_or_404(etudid)
|
etud: Identite = Identite.query.get_or_404(etudid)
|
||||||
|
Loading…
Reference in New Issue
Block a user