forked from ScoDoc/ScoDoc
Refactoring et uniformisation tables jury/recap.
This commit is contained in:
parent
7739530383
commit
107341efab
@ -13,6 +13,7 @@
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
|
||||
from flask_login import current_user
|
||||
import flask_sqlalchemy
|
||||
from flask import flash, g
|
||||
from sqlalchemy import and_, or_
|
||||
@ -20,6 +21,7 @@ from sqlalchemy.sql import text
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
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.but_refcomp import (
|
||||
ApcAnneeParcours,
|
||||
@ -535,10 +537,32 @@ class FormSemestre(db.Model):
|
||||
else:
|
||||
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"
|
||||
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:
|
||||
"""L'année de début de l'année scolaire.
|
||||
Par exemple, 2022 si le semestre va de septembre 2022 à février 2023."""
|
||||
|
@ -251,7 +251,7 @@ def sco_header(
|
||||
#gtrcontent {{
|
||||
margin-left: {params["margin_left"]};
|
||||
height: 100%%;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
}}
|
||||
</style>
|
||||
"""
|
||||
|
@ -47,7 +47,7 @@
|
||||
qui est une description (humaine, format libre) de l'archive.
|
||||
|
||||
"""
|
||||
import chardet
|
||||
from typing import Union
|
||||
import datetime
|
||||
import glob
|
||||
import json
|
||||
@ -56,10 +56,11 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from typing import Union
|
||||
|
||||
import chardet
|
||||
|
||||
import flask
|
||||
from flask import g, request
|
||||
from flask import flash, g, request, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
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.models import FormSemestre
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
)
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoPermissionDenied
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_formsemestre
|
||||
@ -319,7 +318,7 @@ def do_formsemestre_archive(
|
||||
"""
|
||||
from app.scodoc.sco_recapcomplet import (
|
||||
gen_formsemestre_recapcomplet_excel,
|
||||
gen_formsemestre_recapcomplet_html,
|
||||
gen_formsemestre_recapcomplet_html_table,
|
||||
gen_formsemestre_recapcomplet_json,
|
||||
)
|
||||
|
||||
@ -343,7 +342,7 @@ def do_formsemestre_archive(
|
||||
if 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)
|
||||
table_html = gen_formsemestre_recapcomplet_html(
|
||||
table_html, _ = gen_formsemestre_recapcomplet_html_table(
|
||||
formsemestre, res, include_evaluations=True
|
||||
)
|
||||
if table_html:
|
||||
@ -421,8 +420,15 @@ def formsemestre_archive(formsemestre_id, group_ids=[]):
|
||||
"""Make and store new archive for this formsemestre.
|
||||
(all students or only selected groups)
|
||||
"""
|
||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
||||
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
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)
|
||||
if not group_ids:
|
||||
@ -584,26 +590,38 @@ def formsemestre_list_archives(formsemestre_id):
|
||||
|
||||
def formsemestre_get_archived_file(formsemestre_id, archive_name, filename):
|
||||
"""Send file to client."""
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
sem_archive_id = formsemestre_id
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
sem_archive_id = formsemestre.id
|
||||
return PVArchive.get_archived_file(sem_archive_id, archive_name, filename)
|
||||
|
||||
|
||||
def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=False):
|
||||
"""Delete an archive"""
|
||||
if not sco_permissions_check.can_edit_pv(formsemestre_id):
|
||||
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
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_archive_id = formsemestre_id
|
||||
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:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Confirmer la suppression de l'archive du %s ?</h2>
|
||||
<p>La suppression sera définitive.</p>"""
|
||||
% PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M"),
|
||||
f"""<h2>Confirmer la suppression de l'archive du {
|
||||
PVArchive.get_archive_date(archive_id).strftime("%d/%m/%Y %H:%M")
|
||||
} ?</h2>
|
||||
<p>La suppression sera définitive.</p>
|
||||
""",
|
||||
dest_url="",
|
||||
cancel_url=dest_url,
|
||||
parameters={
|
||||
@ -613,4 +631,5 @@ def formsemestre_delete_archive(formsemestre_id, archive_name, dialog_confirmed=
|
||||
)
|
||||
|
||||
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,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
"enabled": formsemestre.can_edit_jury(),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer note d'une UE externe",
|
||||
@ -1217,7 +1217,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id)
|
||||
"enabled": formsemestre.can_edit_jury()
|
||||
and not formsemestre.formation.is_apc(),
|
||||
},
|
||||
{
|
||||
@ -1227,7 +1227,7 @@ def make_menu_autres_operations(
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
"enabled": formsemestre.can_edit_jury(),
|
||||
},
|
||||
{
|
||||
"title": "Éditer PV jury",
|
||||
|
@ -27,22 +27,21 @@
|
||||
|
||||
"""Exception handling
|
||||
"""
|
||||
from flask_login import current_user
|
||||
|
||||
# --- Exceptions
|
||||
MSGPERMDENIED = "l'utilisateur %s n'a pas le droit d'effectuer cette operation"
|
||||
|
||||
|
||||
class ScoException(Exception):
|
||||
pass
|
||||
"super classe de toutes les exceptions ScoDoc."
|
||||
|
||||
|
||||
class InvalidNoteValue(ScoException):
|
||||
pass
|
||||
"Valeur note invalide. Usage interne saisie note."
|
||||
|
||||
|
||||
class ScoValueError(ScoException):
|
||||
"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):
|
||||
super().__init__(msg)
|
||||
self.dest_url = dest_url
|
||||
@ -53,7 +52,9 @@ class ScoPermissionDenied(ScoValueError):
|
||||
|
||||
def __init__(self, msg=None, dest_url=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)
|
||||
|
||||
|
||||
|
@ -431,17 +431,18 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
|
||||
},
|
||||
{
|
||||
"title": "Saisie des décisions du jury",
|
||||
"endpoint": "notes.formsemestre_saisie_jury",
|
||||
"endpoint": "notes.formsemestre_recapcomplet",
|
||||
"args": {
|
||||
"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",
|
||||
"endpoint": "notes.formsemestre_archive",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": sco_permissions_check.can_edit_pv(formsemestre_id),
|
||||
"enabled": formsemestre.can_edit_pv(),
|
||||
},
|
||||
{
|
||||
"title": "Documents archivés",
|
||||
|
@ -72,9 +72,9 @@ def formsemestre_validation_etud_form(
|
||||
etudid=None, # one of etudid or etud_index is required
|
||||
etud_index=None,
|
||||
check=0, # opt: si true, propose juste une relecture du parcours
|
||||
desturl=None,
|
||||
dest_url=None,
|
||||
sortcol=None,
|
||||
readonly=True,
|
||||
read_only=True,
|
||||
):
|
||||
"""Formulaire de validation des décisions de jury"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.filter_by(
|
||||
@ -111,7 +111,7 @@ def formsemestre_validation_etud_form(
|
||||
etud_index_prev = etud_index - 1
|
||||
if etud_index_prev < 0:
|
||||
etud_index_prev = None
|
||||
if readonly:
|
||||
if read_only:
|
||||
check = True
|
||||
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
@ -216,13 +216,13 @@ def formsemestre_validation_etud_form(
|
||||
|
||||
H.append(
|
||||
formsemestre_recap_parcours_table(
|
||||
Se, etudid, with_links=(check and not readonly)
|
||||
Se, etudid, with_links=(check and not read_only)
|
||||
)
|
||||
)
|
||||
if check:
|
||||
if not desturl:
|
||||
desturl = url_tableau
|
||||
H.append(f'<ul><li><a href="{desturl}">Continuer</a></li></ul>')
|
||||
if not dest_url:
|
||||
dest_url = url_tableau
|
||||
H.append(f'<ul><li><a href="{dest_url}">Continuer</a></li></ul>')
|
||||
|
||||
return "\n".join(H + footer)
|
||||
|
||||
@ -342,8 +342,8 @@ def formsemestre_validation_etud_form(
|
||||
<input type="hidden" name="formsemestre_id" value="%s"/>"""
|
||||
% (etudid, formsemestre_id)
|
||||
)
|
||||
if desturl:
|
||||
H.append('<input type="hidden" name="desturl" value="%s"/>' % desturl)
|
||||
if dest_url:
|
||||
H.append('<input type="hidden" name="desturl" value="%s"/>' % dest_url)
|
||||
if sortcol:
|
||||
H.append('<input type="hidden" name="sortcol" value="%s"/>' % sortcol)
|
||||
|
||||
|
@ -55,12 +55,8 @@ _SCO_PERMISSIONS = (
|
||||
),
|
||||
# 27 à 39 ... réservé pour "entreprises"
|
||||
(1 << 40, "ScoEtudChangePhoto", "Modifier la photo d'un étudiant"),
|
||||
# Api scodoc9
|
||||
# XXX à revoir
|
||||
(1 << 40, "ScoAssiduiteChange", "Modifier les assiduités"),
|
||||
(1 << 41, "ScoJustifChange", "Modifier les justificatifs"),
|
||||
# (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)
|
||||
|
||||
|
||||
def can_validate_sem(formsemestre_id):
|
||||
"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):
|
||||
def is_chef_or_diretud(sem): # remplacé par formsemestre.est_chef_or_diretud
|
||||
"Vrai si utilisateur est admin, chef dept ou responsable du semestre"
|
||||
if (
|
||||
current_user.has_permission(Permission.ScoImplement)
|
||||
|
@ -1243,7 +1243,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": 0,
|
||||
"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",
|
||||
"category": "bul",
|
||||
"labels": ["non", "oui"],
|
||||
|
@ -516,18 +516,18 @@ def pvjury_table(
|
||||
|
||||
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
||||
"""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)
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
if format == "html" and is_apc and formsemestre.semestre_id % 2 == 0:
|
||||
from app.tables import jury_recap
|
||||
|
||||
return jury_recap.formsemestre_saisie_jury_but(
|
||||
formsemestre, read_only=True, mode="recap"
|
||||
if format == "html" and is_apc:
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_recapcomplet",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
mode_jury=1,
|
||||
)
|
||||
)
|
||||
# /XXX
|
||||
|
||||
footer = html_sco_header.sco_footer()
|
||||
|
||||
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 import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_status
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_preferences
|
||||
from app.tables.recap import TableRecap
|
||||
from app.tables.jury_recap import TableJury
|
||||
@ -95,17 +94,26 @@ def formsemestre_recapcomplet(
|
||||
mode_jury = int(mode_jury)
|
||||
xml_with_decisions = int(xml_with_decisions)
|
||||
force_publishing = int(force_publishing)
|
||||
|
||||
data = _do_formsemestre_recapcomplet(
|
||||
formsemestre_id,
|
||||
format=tabformat,
|
||||
mode_jury=mode_jury,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
selected_etudid=selected_etudid,
|
||||
filename = scu.sanitize_filename(
|
||||
f"""recap-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
||||
)
|
||||
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 = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"{formsemestre.sem_modalite()}: "
|
||||
@ -131,64 +139,90 @@ def formsemestre_recapcomplet(
|
||||
H.append(
|
||||
'<select name="tabformat" onchange="document.f.submit()" class="noprint">'
|
||||
)
|
||||
for (format, label) in (
|
||||
for (fmt, label) in (
|
||||
("html", "Tableau"),
|
||||
("evals", "Avec toutes les évaluations"),
|
||||
("xlsx", "Excel (non formaté)"),
|
||||
("xlsall", "Excel avec évaluations"),
|
||||
("xml", "Bulletins XML (obsolète)"),
|
||||
("json", "Bulletins JSON"),
|
||||
):
|
||||
if format == tabformat:
|
||||
if fmt == tabformat:
|
||||
selected = " selected"
|
||||
else:
|
||||
selected = ""
|
||||
H.append(f'<option value="{format}"{selected}>{label}</option>')
|
||||
H.append("</select>")
|
||||
|
||||
H.append(f'<option value="{fmt}"{selected}>{label}</option>')
|
||||
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',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
}">ici avoir le classeur papier</a>)
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
H.append(data)
|
||||
|
||||
H.append(table_html) # La table
|
||||
|
||||
if len(formsemestre.inscriptions) > 0:
|
||||
H.append("</form>")
|
||||
H.append("""<div class="links_under_recap"><ul>""")
|
||||
H.append(
|
||||
f"""<p><a class="stdlink" href="{url_for('notes.formsemestre_pvjury',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||
}">Voir les décisions du jury</a></p>"""
|
||||
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
|
||||
}">Décisions du jury</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
if sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||
H.append("<p>")
|
||||
if formsemestre.can_edit_jury():
|
||||
if mode_jury:
|
||||
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)
|
||||
}">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)
|
||||
}">Effacer <em>toutes</em> les décisions de jury du semestre</a>
|
||||
<p>
|
||||
</p>
|
||||
}">Effacer <em>toutes</em> les décisions de jury (BUT) du semestre</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
else:
|
||||
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>")
|
||||
H.append("</ul></div>")
|
||||
|
||||
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):
|
||||
H.append(
|
||||
"""
|
||||
<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())
|
||||
# HTML or binary data ?
|
||||
if len(H) > 1:
|
||||
@ -199,62 +233,69 @@ def formsemestre_recapcomplet(
|
||||
return H
|
||||
|
||||
|
||||
def _do_formsemestre_recapcomplet(
|
||||
formsemestre_id=None,
|
||||
format="html", # html, xml, xls, xlsall, json
|
||||
xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML)
|
||||
def _formsemestre_recapcomplet_to_html(
|
||||
formsemestre: FormSemestre,
|
||||
tabformat="html", # "html" or "evals"
|
||||
filename: str = "",
|
||||
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,
|
||||
force_publishing=True,
|
||||
selected_etudid=None,
|
||||
):
|
||||
"""Calcule et renvoie le tableau récapitulatif."""
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
|
||||
filename = scu.sanitize_filename(
|
||||
f"""recap-{formsemestre.titre_num()}-{time.strftime("%Y-%m-%d")}"""
|
||||
)
|
||||
|
||||
if format == "html" or format == "evals":
|
||||
if tabformat.startswith("xls"):
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
data = gen_formsemestre_recapcomplet_html(
|
||||
formsemestre,
|
||||
res,
|
||||
include_evaluations=(format == "evals"),
|
||||
mode_jury=mode_jury,
|
||||
filename=filename,
|
||||
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"
|
||||
include_evaluations = tabformat in {
|
||||
"xlsall",
|
||||
"csv ",
|
||||
} # csv not supported anymore
|
||||
if tabformat != "csv":
|
||||
tabformat = "xlsx"
|
||||
data, filename = gen_formsemestre_recapcomplet_excel(
|
||||
res,
|
||||
include_evaluations=include_evaluations,
|
||||
filename=filename,
|
||||
)
|
||||
return scu.send_file(data, filename=filename, mime=scu.get_mime_suffix(format))
|
||||
elif format == "xml":
|
||||
elif tabformat == "xml":
|
||||
data = gen_formsemestre_recapcomplet_xml(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
xml_nodate,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
)
|
||||
return scu.send_file(data, filename=filename, suffix=scu.XML_SUFFIX)
|
||||
elif format == "json":
|
||||
elif tabformat == "json":
|
||||
data = gen_formsemestre_recapcomplet_json(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
xml_nodate=xml_nodate,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
)
|
||||
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(
|
||||
@ -368,22 +409,26 @@ def formsemestres_bulletins(annee_scolaire):
|
||||
return scu.sendJSON(js_list)
|
||||
|
||||
|
||||
def gen_formsemestre_recapcomplet_html(
|
||||
def gen_formsemestre_recapcomplet_html_table(
|
||||
formsemestre: FormSemestre,
|
||||
res: NotesTableCompat,
|
||||
include_evaluations=False,
|
||||
mode_jury=False,
|
||||
filename="",
|
||||
selected_etudid=None,
|
||||
):
|
||||
) -> tuple[str, TableRecap]:
|
||||
"""Construit table recap pour le BUT
|
||||
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
|
||||
data est une chaine, le <div>...</div> incluant le tableau.
|
||||
Return: html (str), table (None sauf en mode jury ou si pas cachée)
|
||||
|
||||
html est une chaine, le <div>...</div> incluant le tableau.
|
||||
"""
|
||||
table = None
|
||||
table_html = None
|
||||
if not (mode_jury or selected_etudid):
|
||||
if include_evaluations:
|
||||
@ -392,7 +437,7 @@ def gen_formsemestre_recapcomplet_html(
|
||||
table_html = sco_cache.TableRecapCache.get(formsemestre.id)
|
||||
# en mode jury ne cache pas la table html
|
||||
if mode_jury or (table_html is None):
|
||||
table_html = _gen_formsemestre_recapcomplet_html(
|
||||
table = _gen_formsemestre_recapcomplet_table(
|
||||
formsemestre,
|
||||
res,
|
||||
include_evaluations,
|
||||
@ -400,48 +445,37 @@ def gen_formsemestre_recapcomplet_html(
|
||||
filename,
|
||||
selected_etudid=selected_etudid,
|
||||
)
|
||||
table_html = table.html()
|
||||
if not mode_jury:
|
||||
if include_evaluations:
|
||||
sco_cache.TableRecapWithEvalsCache.set(formsemestre.id, table_html)
|
||||
else:
|
||||
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,
|
||||
res: ResultatsSemestre,
|
||||
include_evaluations=False,
|
||||
mode_jury=False,
|
||||
filename: str = "",
|
||||
selected_etudid=None,
|
||||
) -> str:
|
||||
"""Génère le html"""
|
||||
) -> TableRecap:
|
||||
"""Construit la table récap."""
|
||||
table_class = TableJury if mode_jury else TableRecap
|
||||
table = table_class(
|
||||
res,
|
||||
convert_values=True,
|
||||
include_evaluations=include_evaluations,
|
||||
mode_jury=mode_jury,
|
||||
read_only=not formsemestre.can_edit_jury(),
|
||||
)
|
||||
|
||||
table.data["filename"] = filename
|
||||
table.select_row(selected_etudid)
|
||||
return f"""
|
||||
<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>
|
||||
"""
|
||||
return table
|
||||
|
||||
|
||||
def gen_formsemestre_recapcomplet_excel(
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
* DataTables style for ScoDoc gen_tables
|
||||
* generated using https://datatables.net/manual/styling/theme-creator
|
||||
* and customized by hand
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -374,9 +375,11 @@ table.dataTable td {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter {
|
||||
float: right;
|
||||
text-align: right;
|
||||
.dataTables_wrapper div.dataTables_filter {
|
||||
float: left;
|
||||
text-align: left;
|
||||
margin-left: 64px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input {
|
||||
|
@ -35,7 +35,7 @@ h3 {
|
||||
}
|
||||
|
||||
div#gtrcontent {
|
||||
margin-bottom: 4ex;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.gtrcontent {
|
||||
@ -4015,8 +4015,14 @@ div.table_recap {
|
||||
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 {
|
||||
width: auto;
|
||||
margin-left: 0px;
|
||||
/* font-family: Consolas, monaco, monospace; */
|
||||
}
|
||||
|
||||
@ -4344,6 +4350,9 @@ div.table_jury_but_links {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
div.links_under_recap ul li {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ------------- Tableau stats jury BUT -------- */
|
||||
table.jury_stats_codes {
|
||||
|
@ -8,11 +8,6 @@ $(function () {
|
||||
"partition_aux", "partition_rangs", "admission",
|
||||
"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:
|
||||
|
||||
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) {
|
||||
localStorage.clear();
|
||||
console.log("cleared localStorage");
|
||||
@ -124,7 +119,7 @@ $(function () {
|
||||
// table jury: avec ou sans codes enregistrés
|
||||
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,
|
||||
});
|
||||
} 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 ---------
|
||||
try {
|
||||
|
@ -114,7 +114,7 @@ class TableJury(TableRecap):
|
||||
"jury_link",
|
||||
"",
|
||||
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",
|
||||
classes=["fontred"] if a_saisir else [],
|
||||
target=url_for(
|
||||
@ -250,149 +250,3 @@ class RowJury(RowRecap):
|
||||
# f"""{int(ects_valides)}""",
|
||||
# "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,
|
||||
row_class=None,
|
||||
finalize=True,
|
||||
read_only: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
self.rows: list["RowRecap"] = [] # juste pour que VSCode nous aide sur .rows
|
||||
@ -61,7 +62,7 @@ class TableRecap(tb.Table):
|
||||
self.res = res
|
||||
self.include_evaluations = include_evaluations
|
||||
self.mode_jury = mode_jury
|
||||
|
||||
self.read_only = read_only # utilisé seulement dans sous-classes
|
||||
parcours = res.formsemestre.formation.get_parcours()
|
||||
self.barre_moy = parcours.BARRE_MOY - scu.NOTES_TOLERANCE
|
||||
self.barre_valid_ue = parcours.NOTES_BARRE_VALID_UE
|
||||
@ -103,7 +104,7 @@ class TableRecap(tb.Table):
|
||||
self.add_cursus()
|
||||
self.add_admissions()
|
||||
|
||||
# tri par rang croissant
|
||||
# Tri par rang croissant
|
||||
if not res.formsemestre.block_moyenne_generale:
|
||||
self.sort_rows(key=lambda row: row.rang_order)
|
||||
else:
|
||||
@ -361,6 +362,7 @@ class TableRecap(tb.Table):
|
||||
pour tous les étudiants de la table.
|
||||
Les colonnes ont la classe css "evaluation"
|
||||
"""
|
||||
self.group_titles["eval"] = "Évaluations"
|
||||
# nouvelle ligne pour description évaluations:
|
||||
row_descr_eval = tb.BottomRow(
|
||||
self,
|
||||
@ -382,7 +384,7 @@ class TableRecap(tb.Table):
|
||||
for e in evals:
|
||||
col_id = f"eval_{e.id}"
|
||||
title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}'
|
||||
col_classes = ["evaluation"]
|
||||
col_classes = []
|
||||
if first_eval:
|
||||
col_classes.append("first")
|
||||
elif first_eval_of_mod:
|
||||
@ -408,13 +410,15 @@ class TableRecap(tb.Table):
|
||||
"EXC": "exc",
|
||||
}.get(content, "")
|
||||
]
|
||||
row.add_cell(col_id, title, content, "", classes=classes)
|
||||
row.add_cell(
|
||||
col_id, title, content, group="eval", classes=classes
|
||||
)
|
||||
else:
|
||||
row.add_cell(
|
||||
col_id,
|
||||
title,
|
||||
"ni",
|
||||
"",
|
||||
group="eval",
|
||||
classes=col_classes + ["non_inscrit"],
|
||||
)
|
||||
|
||||
@ -505,6 +509,24 @@ class TableRecap(tb.Table):
|
||||
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):
|
||||
"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.modules import Module
|
||||
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.views import notes_bp as bp
|
||||
|
||||
@ -2257,8 +2257,8 @@ def formsemestre_validation_etud_form(
|
||||
sortcol=None,
|
||||
):
|
||||
"Formulaire choix jury pour un étudiant"
|
||||
readonly = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
read_only = not formsemestre.can_edit_jury()
|
||||
if formsemestre.formation.is_apc():
|
||||
return redirect(
|
||||
url_for(
|
||||
@ -2273,8 +2273,8 @@ def formsemestre_validation_etud_form(
|
||||
etudid=etudid,
|
||||
etud_index=etud_index,
|
||||
check=check,
|
||||
readonly=readonly,
|
||||
desturl=desturl,
|
||||
read_only=read_only,
|
||||
dest_url=desturl,
|
||||
sortcol=sortcol,
|
||||
)
|
||||
|
||||
@ -2291,10 +2291,14 @@ def formsemestre_validation_etud(
|
||||
sortcol=None,
|
||||
):
|
||||
"Enregistre choix jury pour un étudiant"
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
||||
return sco_formsemestre_validation.formsemestre_validation_etud(
|
||||
@ -2321,10 +2325,14 @@ def formsemestre_validation_etud_manu(
|
||||
sortcol=None,
|
||||
):
|
||||
"Enregistre choix jury pour un étudiant"
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
||||
return sco_formsemestre_validation.formsemestre_validation_etud_manu(
|
||||
@ -2364,7 +2372,7 @@ def formsemestre_validation_but(
|
||||
etudid = int(etudid)
|
||||
except ValueError:
|
||||
abort(404, "invalid etudid")
|
||||
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
|
||||
read_only = not formsemestre.can_edit_jury()
|
||||
|
||||
# --- Navigation
|
||||
prev_lnk = (
|
||||
@ -2391,9 +2399,13 @@ def formsemestre_validation_but(
|
||||
{prev_lnk}
|
||||
</div>
|
||||
<div class="back_list">
|
||||
<a href="{url_for(
|
||||
"notes.formsemestre_saisie_jury", scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id, selected_etudid=etud.id
|
||||
<a href="{
|
||||
url_for(
|
||||
"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>
|
||||
</div>
|
||||
<div class="next">
|
||||
@ -2583,15 +2595,16 @@ def formsemestre_validation_but(
|
||||
@permission_required(Permission.ScoView)
|
||||
def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
||||
"Saisie automatique des décisions de jury BUT"
|
||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||
return scu.confirm_dialog(
|
||||
message=f"<p>Opération non autorisée pour {current_user}</h2>",
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
||||
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)")
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_saisie_jury",
|
||||
"notes.formsemestre_recapcomplet",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
mode_jury=1,
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
@ -2621,11 +2635,16 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
||||
@scodoc7func
|
||||
def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
|
||||
"Form. saisie UE validée hors ScoDoc"
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
||||
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
|
||||
formsemestre_id, etudid
|
||||
)
|
||||
@ -2645,11 +2664,16 @@ sco_publish(
|
||||
@scodoc7func
|
||||
def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
|
||||
"Form. edition UE semestre extérieur"
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
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(
|
||||
formsemestre_id, etudid
|
||||
)
|
||||
@ -2668,11 +2692,16 @@ sco_publish(
|
||||
@scodoc7func
|
||||
def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
||||
"""Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
|
||||
return sco_formsemestre_validation.etud_ue_suppress_validation(
|
||||
etudid, formsemestre_id, ue_id
|
||||
)
|
||||
@ -2684,14 +2713,18 @@ def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
|
||||
@scodoc7func
|
||||
def formsemestre_validation_auto(formsemestre_id):
|
||||
"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(
|
||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
||||
).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():
|
||||
return redirect(
|
||||
url_for(
|
||||
@ -2709,10 +2742,14 @@ def formsemestre_validation_auto(formsemestre_id):
|
||||
@scodoc7func
|
||||
def do_formsemestre_validation_auto(formsemestre_id):
|
||||
"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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
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)
|
||||
@ -2726,13 +2763,16 @@ def formsemestre_validation_suppress_etud(
|
||||
formsemestre_id, etudid, dialog_confirmed=False
|
||||
):
|
||||
"""Suppression des décisions de jury pour un étudiant."""
|
||||
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.get_or_404(formsemestre_id)
|
||||
if not formsemestre.can_edit_jury():
|
||||
raise ScoPermissionDenied(
|
||||
dest_url=url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if formsemestre.formation.is_apc():
|
||||
next_url = url_for(
|
||||
"scolar.ficheEtud",
|
||||
@ -2800,15 +2840,8 @@ sco_publish("/pvjury_table_but", jury_but_pv.pvjury_table_but, Permission.ScoVie
|
||||
@scodoc7func
|
||||
def formsemestre_saisie_jury(formsemestre_id: int, selected_etudid: int = None):
|
||||
"""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
|
||||
"""
|
||||
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(
|
||||
url_for(
|
||||
"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(
|
||||
"/formsemestre_jury_but_erase/<int:formsemestre_id>",
|
||||
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.
|
||||
"""
|
||||
only_one_sem = int(request.args.get("only_one_sem") or False)
|
||||
if not sco_permissions_check.can_validate_sem(formsemestre_id):
|
||||
raise ScoValueError("opération non autorisée")
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
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 not formsemestre.formation.is_apc():
|
||||
raise ScoValueError("semestre non BUT")
|
||||
if etudid is None:
|
||||
etud = None
|
||||
etuds = formsemestre.get_inscrits(include_demdef=True)
|
||||
dest_url = url_for(
|
||||
"notes.formsemestre_saisie_jury",
|
||||
"notes.formsemestre_recapcomplet",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
mode_jury=1,
|
||||
)
|
||||
else:
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
|
Loading…
Reference in New Issue
Block a user