diff --git a/app/api/jury.py b/app/api/jury.py
index f31d1a78..383396e5 100644
--- a/app/api/jury.py
+++ b/app/api/jury.py
@@ -5,7 +5,7 @@
##############################################################################
"""
- ScoDoc 9 API : jury
+ ScoDoc 9 API : jury WIP
"""
from flask import g, jsonify, request
@@ -17,8 +17,8 @@ from app.api import api_bp as bp, api_web_bp
from app.decorators import scodoc, permission_required
from app.scodoc.sco_exceptions import ScoException
from app.scodoc.sco_utils import json_error
-from app.but import jury_but_recap
-from app.models import FormSemestre, FormSemestreInscription, Identite
+from app.but import jury_but_results
+from app.models import FormSemestre
from app.scodoc.sco_permissions import Permission
@@ -33,7 +33,7 @@ def decisions_jury(formsemestre_id: int):
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
if formsemestre.formation.is_apc():
app.set_sco_dept(formsemestre.departement.acronym)
- rows = jury_but_recap.get_jury_but_results(formsemestre)
+ rows = jury_but_results.get_jury_but_results(formsemestre)
return jsonify(rows)
else:
raise ScoException("non implemente")
diff --git a/app/but/jury_but_results.py b/app/but/jury_but_results.py
new file mode 100644
index 00000000..79aa14df
--- /dev/null
+++ b/app/but/jury_but_results.py
@@ -0,0 +1,94 @@
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
+# See LICENSE
+##############################################################################
+
+"""Jury BUT et classiques: récupération des résults pour API
+"""
+
+import numpy as np
+
+from app.but import jury_but
+from app.models.etudiants import Identite
+from app.models.formsemestre import FormSemestre
+from app.scodoc import sco_pvjury
+
+
+def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
+ """Liste des résultats jury BUT sous forme de dict, pour API"""
+ if formsemestre.formation.referentiel_competence is None:
+ # pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
+ return []
+ dpv = sco_pvjury.dict_pvjury(formsemestre.id)
+ rows = []
+ for etudid in formsemestre.etuds_inscriptions:
+ rows.append(_get_jury_but_etud_result(formsemestre, dpv, etudid))
+ return rows
+
+
+def _get_jury_but_etud_result(
+ formsemestre: FormSemestre, dpv: dict, etudid: int
+) -> dict:
+ """Résultats de jury d'un étudiant sur un semestre pair de BUT"""
+ etud: Identite = Identite.query.get(etudid)
+ dec_etud = dpv["decisions_dict"][etudid]
+ if formsemestre.formation.is_apc():
+ deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
+ else:
+ deca = None
+ row = {
+ "etudid": etud.id,
+ "code_nip": etud.code_nip,
+ "code_ine": etud.code_ine,
+ "is_apc": dpv["is_apc"], # BUT ou classic ?
+ "etat": dec_etud["etat"], # I ou D ou DEF
+ "nb_competences": deca.nb_competences if deca else 0,
+ }
+ # --- Les RCUEs
+ rcue_list = []
+ if deca:
+ for rcue in deca.rcues_annee:
+ dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
+ if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
+ dec_ue1 = deca.decisions_ues[rcue.ue_1.id]
+ dec_ue2 = deca.decisions_ues[rcue.ue_2.id]
+ rcue_dict = {
+ "ue_1": {
+ "ue_id": rcue.ue_1.id,
+ "moy": None
+ if (dec_ue1.moy_ue is None or np.isnan(dec_ue1.moy_ue))
+ else dec_ue1.moy_ue,
+ "code": dec_ue1.code_valide,
+ },
+ "ue_2": {
+ "ue_id": rcue.ue_2.id,
+ "moy": None
+ if (dec_ue2.moy_ue is None or np.isnan(dec_ue2.moy_ue))
+ else dec_ue2.moy_ue,
+ "code": dec_ue2.code_valide,
+ },
+ "moy": rcue.moy_rcue,
+ "code": dec_rcue.code_valide,
+ }
+ rcue_list.append(rcue_dict)
+ row["rcues"] = rcue_list
+ # --- Les UEs
+ ue_list = []
+ if dec_etud["decisions_ue"]:
+ for ue_id, ue_dec in dec_etud["decisions_ue"].items():
+ ue_dict = {
+ "ue_id": ue_id,
+ "code": ue_dec["code"],
+ "ects": ue_dec["ects"],
+ }
+ ue_list.append(ue_dict)
+ row["ues"] = ue_list
+ # --- Le semestre (pour les formations classiques)
+ if dec_etud["decision_sem"]:
+ row["semestre"] = {"code": dec_etud["decision_sem"].get("code")}
+ else:
+ row["semestre"] = {} # APC, ...
+ # --- Autorisations
+ row["autorisations"] = dec_etud["autorisations"]
+ return row
diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py
index e323348d..416c985b 100644
--- a/app/scodoc/sco_pvjury.py
+++ b/app/scodoc/sco_pvjury.py
@@ -522,9 +522,9 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
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.but import jury_but_recap
+ from app.tables import jury_recap
- return jury_but_recap.formsemestre_saisie_jury_but(
+ return jury_recap.formsemestre_saisie_jury_but(
formsemestre, read_only=True, mode="recap"
)
# /XXX
diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py
index 7343ceb2..4c1a6340 100644
--- a/app/scodoc/sco_recapcomplet.py
+++ b/app/scodoc/sco_recapcomplet.py
@@ -54,7 +54,7 @@ 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.but.jury_but_recap import TableJury
+from app.tables.jury_recap import TableJury
def formsemestre_recapcomplet(
diff --git a/app/static/js/table_recap.js b/app/static/js/table_recap.js
index 9597f9db..0e2f9f10 100644
--- a/app/static/js/table_recap.js
+++ b/app/static/js/table_recap.js
@@ -1,6 +1,8 @@
// Tableau recap notes
$(function () {
$(function () {
+ if ($('table.table_recap').length == 0) { return; }
+
let hidden_colums = [
"etud_codes", "identite_detail",
"partition_aux", "partition_rangs", "admission",
diff --git a/app/but/jury_but_recap.py b/app/tables/jury_recap.py
similarity index 59%
rename from app/but/jury_but_recap.py
rename to app/tables/jury_recap.py
index 6ac6ba0a..ce962002 100644
--- a/app/but/jury_but_recap.py
+++ b/app/tables/jury_recap.py
@@ -230,6 +230,19 @@ class RowJury(RowRecap):
column_classes={"col_rcue"},
)
+ # # --- Les ECTS validés
+ # ects_valides = 0.0
+ # if deca.res_impair:
+ # ects_valides += deca.res_impair.get_etud_ects_valides(etudid)
+ # if deca.res_pair:
+ # ects_valides += deca.res_pair.get_etud_ects_valides(etudid)
+ # row.add_cell(
+ # "ects_annee",
+ # "ECTS",
+ # f"""{int(ects_valides)}""",
+ # "col_code_annee",
+ # )
+
def formsemestre_saisie_jury_but(
formsemestre: FormSemestre,
@@ -316,7 +329,7 @@ def formsemestre_saisie_jury_but(
{table_html}
-
+
"""
)
@@ -375,263 +388,3 @@ def formsemestre_saisie_jury_but(
"""
)
return "\n".join(H)
-
-
-def build_table_jury_but_html(
- filename: str, rows, titles, column_ids, selected_etudid: int = None, klass=""
-) -> str:
- """assemble la table html"""
- footer_rows = [] # inutilisé pour l'instant
- H = [
- f"""
"""
- ]
- # header
- H.append(
- f"""
-
- {scu.gen_row(column_ids, titles, "th")}
-
- """
- )
- # body
- H.append("")
- for row in rows:
- H.append(f"{scu.gen_row(column_ids, row, selected_etudid=selected_etudid)}\n")
- H.append("\n")
- # footer
- H.append("")
- idx_last = len(footer_rows) - 1
- for i, row in enumerate(footer_rows):
- H.append(f'{scu.gen_row(column_ids, row, "th" if i == idx_last else "td")}\n')
- H.append(
- """
-
-
-
- """
- )
- return "".join(H)
-
-
-class RowCollector:
- """Une ligne de la table"""
-
- def __init__(
- self,
- cells: dict = None,
- titles: dict = None,
- convert_values=True,
- column_classes: dict = None,
- ):
- self.titles = titles
- self.row = cells or {} # col_id : str
- self.column_classes = column_classes # col_id : str, css class
- self.idx = 0
- self.last_etud_cell_idx = 0
- if convert_values:
- self.fmt_note = scu.fmt_note
- else:
- self.fmt_note = lambda x: x
-
- def __setitem__(self, key, value):
- self.row[key] = value
-
- def __getitem__(self, key):
- return self.row[key]
-
- def get_row_dict(self):
- "La ligne, comme un dict"
- # create empty cells
- for col_id in self.titles:
- if col_id not in self.row:
- self.row[col_id] = ""
- klass = self.column_classes.get(col_id)
- if klass:
- self.row[f"_{col_id}_class"] = klass
- return self.row
-
- def add_cell(
- self,
- col_id: str,
- title: str,
- content: str,
- classes: str = "",
- idx: int = None,
- column_class="",
- ):
- """Add a row to our table. classes is a list of css class names"""
- self.idx = idx if idx is not None else self.idx
- self.row[col_id] = content
- if classes:
- self.row[f"_{col_id}_class"] = classes + f" c{self.idx}"
- if not col_id in self.titles:
- self.titles[col_id] = title
- self.titles[f"_{col_id}_col_order"] = self.idx
- if classes:
- self.titles[f"_{col_id}_class"] = classes
- self.column_classes[col_id] = column_class
- self.idx += 1
-
-
-def get_jury_but_table( # XXX A SUPPRIMER apres avoir recupéré les stats
- formsemestre2: FormSemestre, read_only: bool = False, mode="jury", with_links=True
-) -> tuple[list[dict], list[str], list[str], dict]:
- """Construit la table des résultats annuels pour le jury BUT
- => rows_dict, titles, column_ids, jury_stats
- où jury_stats est un dict donnant des comptages sur le jury.
- """
-
- # /////// XXX /////// XXX //////
- titles = {} # column_id : title
- jury_stats = {
- "nb_etuds": len(formsemestre2.etuds_inscriptions),
- "codes_annuels": collections.Counter(),
- }
- table = TableJury(res2, mode_jury=True)
- for etudid in formsemestre2.etuds_inscriptions:
- etud: Identite = Identite.query.get(etudid)
- deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre2)
- # XXX row = RowCollector(titles=titles, column_classes=column_classes)
- row = RowJury(table, etudid)
- table.add_row(row)
- row.add_etud(etud)
- # --- Nombre de niveaux
- row.add_nb_rcues_cell(deca)
- # --- Les RCUEs
- for rcue in deca.rcues_annee:
- dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
- if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
- row.add_ue_cells(deca.decisions_ues[rcue.ue_1.id])
- row.add_ue_cells(deca.decisions_ues[rcue.ue_2.id])
- row.add_rcue_cells(dec_rcue)
- # --- Les ECTS validés
- ects_valides = 0.0
- if deca.res_impair:
- ects_valides += deca.res_impair.get_etud_ects_valides(etudid)
- if deca.res_pair:
- ects_valides += deca.res_pair.get_etud_ects_valides(etudid)
- row.add_cell(
- "ects_annee",
- "ECTS",
- f"""{int(ects_valides)}""",
- "col_code_annee",
- )
- # --- Le code annuel existant
- row.add_cell(
- "code_annee",
- "Année",
- f"""{deca.code_valide or ''}""",
- "col_code_annee",
- )
- if deca.code_valide:
- jury_stats["codes_annuels"][deca.code_valide] += 1
- # --- Le lien de saisie
- if mode != "recap" and with_links:
- row.add_cell(
- "lien_saisie",
- "",
- f"""
-
- {"voir" if read_only else ("modif." if deca.code_valide else "saisie")}
- décision
- """
- if deca.inscription_etat == scu.INSCRIT
- else deca.inscription_etat,
- "col_lien_saisie_but",
- )
- rows.append(row)
- rows_dict = [row.get_row_dict() for row in rows]
- if len(rows_dict) > 0:
- col_idx = res2.recap_add_partitions(
- rows_dict, titles, col_idx=row.last_etud_cell_idx + 1
- )
- res2.recap_add_cursus(rows_dict, titles, col_idx=col_idx + 1)
- column_ids = [title for title in titles if not title.startswith("_")]
- column_ids.sort(key=lambda col_id: titles.get("_" + col_id + "_col_order", 1000))
- rows_dict.sort(key=lambda row: row["_nom_disp_order"])
- return rows_dict, titles, column_ids, jury_stats
-
-
-def get_jury_but_results(formsemestre: FormSemestre) -> list[dict]:
- """Liste des résultats jury BUT sous forme de dict, pour API"""
- if formsemestre.formation.referentiel_competence is None:
- # pas de ref. comp., donc pas de decisions de jury (ne lance pas d'exception)
- return []
- dpv = sco_pvjury.dict_pvjury(formsemestre.id)
- rows = []
- for etudid in formsemestre.etuds_inscriptions:
- rows.append(get_jury_but_etud_result(formsemestre, dpv, etudid))
- return rows
-
-
-def get_jury_but_etud_result(
- formsemestre: FormSemestre, dpv: dict, etudid: int
-) -> dict:
- """Résultats de jury d'un étudiant sur un semestre pair de BUT"""
- etud: Identite = Identite.query.get(etudid)
- dec_etud = dpv["decisions_dict"][etudid]
- if formsemestre.formation.is_apc():
- deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
- else:
- deca = None
- row = {
- "etudid": etud.id,
- "code_nip": etud.code_nip,
- "code_ine": etud.code_ine,
- "is_apc": dpv["is_apc"], # BUT ou classic ?
- "etat": dec_etud["etat"], # I ou D ou DEF
- "nb_competences": deca.nb_competences if deca else 0,
- }
- # --- Les RCUEs
- rcue_list = []
- if deca:
- for rcue in deca.rcues_annee:
- dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
- if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
- dec_ue1 = deca.decisions_ues[rcue.ue_1.id]
- dec_ue2 = deca.decisions_ues[rcue.ue_2.id]
- rcue_dict = {
- "ue_1": {
- "ue_id": rcue.ue_1.id,
- "moy": None
- if (dec_ue1.moy_ue is None or np.isnan(dec_ue1.moy_ue))
- else dec_ue1.moy_ue,
- "code": dec_ue1.code_valide,
- },
- "ue_2": {
- "ue_id": rcue.ue_2.id,
- "moy": None
- if (dec_ue2.moy_ue is None or np.isnan(dec_ue2.moy_ue))
- else dec_ue2.moy_ue,
- "code": dec_ue2.code_valide,
- },
- "moy": rcue.moy_rcue,
- "code": dec_rcue.code_valide,
- }
- rcue_list.append(rcue_dict)
- row["rcues"] = rcue_list
- # --- Les UEs
- ue_list = []
- if dec_etud["decisions_ue"]:
- for ue_id, ue_dec in dec_etud["decisions_ue"].items():
- ue_dict = {
- "ue_id": ue_id,
- "code": ue_dec["code"],
- "ects": ue_dec["ects"],
- }
- ue_list.append(ue_dict)
- row["ues"] = ue_list
- # --- Le semestre (pour les formations classiques)
- if dec_etud["decision_sem"]:
- row["semestre"] = {"code": dec_etud["decision_sem"].get("code")}
- else:
- row["semestre"] = {} # APC, ...
- # --- Autorisations
- row["autorisations"] = dec_etud["autorisations"]
- return row
diff --git a/app/tables/recap.py b/app/tables/recap.py
index 51e3ba42..7431523c 100644
--- a/app/tables/recap.py
+++ b/app/tables/recap.py
@@ -86,22 +86,23 @@ class TableRecap(tb.Table):
row.add_etud_cols()
row.add_moyennes_cols(ues_sans_bonus)
- self.add_partitions()
- self.add_cursus()
- self.add_admissions()
+ if res.formsemestre.etuds_inscriptions: # table non vide
+ self.add_partitions()
+ self.add_cursus()
+ self.add_admissions()
- # tri par rang croissant
- if not res.formsemestre.block_moyenne_generale:
- self.sort_rows(key=lambda row: row.rang_order)
- else:
- self.sort_rows(key=lambda row: row.nb_ues_validables, reverse=True)
+ # tri par rang croissant
+ if not res.formsemestre.block_moyenne_generale:
+ self.sort_rows(key=lambda row: row.rang_order)
+ else:
+ self.sort_rows(key=lambda row: row.nb_ues_validables, reverse=True)
- # Lignes footer (min, max, ects, apo, ...)
- self.add_bottom_rows(ues_sans_bonus)
+ # Lignes footer (min, max, ects, apo, ...)
+ self.add_bottom_rows(ues_sans_bonus)
- # Evaluations:
- if include_evaluations:
- self.add_evaluations()
+ # Evaluations:
+ if include_evaluations:
+ self.add_evaluations()
if finalize:
self.finalize()
diff --git a/app/views/notes.py b/app/views/notes.py
index 4ae34dd7..ce2f95b6 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -42,7 +42,7 @@ from flask_login import current_user
from app import db
from app import models
from app.auth.models import User
-from app.but import apc_edit_ue, jury_but_recap
+from app.but import apc_edit_ue
from app.but import jury_but, jury_but_validation_auto
from app.but.forms import jury_but_forms
from app.but import jury_but_pv
@@ -60,6 +60,7 @@ 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.tables import jury_recap
from app.views import notes_bp as bp
from app.decorators import (
@@ -2826,7 +2827,7 @@ def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = Non
raise ScoValueError(
"formsemestre_jury_but_recap: réservé aux semestres pairs de BUT"
)
- return jury_but_recap.formsemestre_saisie_jury_but(
+ return jury_recap.formsemestre_saisie_jury_but(
formsemestre, read_only=read_only, selected_etudid=selected_etudid, mode="recap"
)