1
0
forked from ScoDoc/ScoDoc
ScoDoc/app/scodoc/sco_etape_bilan.py
ivan 2ffcc503cb Apogée:
- corrige affichage état (filtres/comptes)
- affichage et modification période semset (+ API).
- export résultat annuel BUT.
2023-04-16 05:32:21 +02:00

784 lines
30 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""
# Outil de comparaison Apogée/ScoDoc (J.-M. Place, Jan 2020)
## fonctionalités
Le menu 'synchronisation avec Apogée' ne permet pas de traiter facilement les cas
où un même code étape est implementé dans des formsemestres différents.
On ajoute à la page de description des ensembles de semestres
une section permettant de faire le point sur les cas particuliers.
Cette section est composée de deux parties:
* Une partie effectif où figurent le nombre d'étudiants selon une répartition par
semestre (en ligne) et par code étape (en colonne). On ajoute également des
colonnes/lignes correspondant à des anomalies (étudiant sans code étape, sans
semestre, avec deux semestres, sans NIP, etc.).
* Une seconde partie présente la liste des étudiants. Il est possible qu'un
même nom figure deux fois dans la liste (si on a pas pu faire la correspondance
entre une inscription Apogée et un étudiant d'un semestre, par exemple).
L'activation d'un des nombres du tableau 'effectifs' restreint l'affichage de
la liste aux étudiants qui contribuent à ce nombre.
## Réalisation
Les modifications logicielles portent sur:
### La création d'une classe sco_etape_bilan.py
Cette classe compile la totalité des données:
** Liste des semestres
** Listes des étapes
** Liste des étudiants
** constitution des listes d'anomalies
Cette classe explore la suite semestres du semset.
Pour chaque semestre, elle recense les étudiants du semestre et
les codes étapes concernés, puis tous les codes étapes (toujours
en important les étudiants de l'étape via le portail).
Enfin on dispatch chaque étudiant dans une case - soit ordinaire, soit
correspondant à une anomalie.
### Modification de sco_etape_apogee_view.py
Pour insertion de l'affichage ajouté.
### Modification de sco_semset.py
Affichage proprement dit.
### Modification de sco_formsemestre.py
Modification/ajout de la méthode sem_in_semestre_scolaire pour permettre
l'inscription de semestres décalés (S1 en septembre, ...).
Le filtrage s'effectue sur la date et non plus sur la parité du semestre (1-3/2-4).
"""
import json
from flask import url_for, g
from app.scodoc.sco_portal_apogee import get_inscrits_etape
from app import log
from app.scodoc.sco_utils import annee_scolaire_debut
from app.scodoc.gen_tables import GenTable
COL_PREFIX = "COL_"
# Les indicatifs sont des marqueurs de classe CSS insérés dans la table étudiant
# et utilisés par le javascript pour permettre un filtrage de la liste étudiants
# sur un 'cas' considéré
# indicatifs
COL_CUMUL = "C9"
ROW_CUMUL = "R9"
# Constante d'anomalie
PAS_DE_NIP = "C1"
PAS_D_ETAPE = "C2"
PLUSIEURS_ETAPES = "C3"
PAS_DE_SEMESTRE = "R4"
PLUSIEURS_SEMESTRES = "R5"
NIP_NON_UNIQUE = "U"
FLAG = {
PAS_DE_NIP: "A",
PAS_D_ETAPE: "B",
PLUSIEURS_ETAPES: "C",
PAS_DE_SEMESTRE: "D",
PLUSIEURS_SEMESTRES: "E",
NIP_NON_UNIQUE: "U",
}
class DataEtudiant(object):
"""
Structure de donnée des informations pour un étudiant
"""
def __init__(self, nip="", etudid=""):
self.nip = nip
self.etudid = etudid
self.data_apogee = None
self.data_scodoc = None
self.etapes = set() # l'ensemble des étapes où il est inscrit
self.semestres = set() # l'ensemble des formsemestre_id où il est inscrit
self.tags = set() # les anomalies relevées
self.ind_row = "-" # là où il compte dans les effectifs (ligne et colonne)
self.ind_col = "-"
def add_etape(self, etape):
self.etapes.add(etape)
def add_semestre(self, formsemestre_id: int):
self.semestres.add(formsemestre_id)
def set_apogee(self, data_apogee):
self.data_apogee = data_apogee
def set_scodoc(self, data_scodoc):
self.data_scodoc = data_scodoc
def add_tag(self, tag):
self.tags.add(tag)
def set_ind_row(self, indicatif):
self.ind_row = indicatif
def set_ind_col(self, indicatif):
self.ind_col = indicatif
def get_identity(self):
"""
Calcul le nom/prénom de l'étudiant (données ScoDoc en priorité, sinon données Apogée)
:return: L'identité calculée
"""
if self.data_scodoc is not None:
return self.data_scodoc["nom"] + self.data_scodoc["prenom"]
else:
return self.data_apogee["nom"] + self.data_apogee["prenom"]
def help():
return """
<div id="export_help" class="pas_help"> <span>Explications sur les tableaux des effectifs et liste des
étudiants</span>
<div> <p>Le tableau des effectifs présente le nombre d'étudiants selon deux critères:</p>
<ul>
<li>En colonne le statut de l'étudiant par rapport à Apogée:
<ul>
<li><span class="libelle">Hors Apogée</span>
<span class="anomalie">(anomalie A</span>): Le NIP de l'étudiant n'est pas connu d'apogée ou
l'étudiant n'a pas de NIP</li>
<li><span class="libelle">Pas d'étape</span> <span class="anomalie">(anomalie B</span>): Le NIP de
l'étudiant ne correspond à aucune des étapes connues pour cet ensemble de semestre. Il est
possible qu'il soit inscrit ailleurs (dans une autre ensemble de semestres, un autre département,
une autre composante de l'université) ou en mobilité internationale.</li>
<li><span class="libelle">Plusieurs étapes</span> <span class="anomalie">(anomalie C)</span>:
Les étudiants inscrits dans plusieurs étapes apogée de l'ensemble de semestres</li>
<li>Un des codes étapes connus (la liste des codes étapes connus est l'union des codes étapes
déclarés pour chaque semestre particpant</li>
<li><span class="libelle">Total semestre</span>: cumul des effectifs de la ligne</li>
</ul>
</li>
<li>En ligne le statut de l'étudiant par rapport à ScoDoc:
<ul>
<li>Inscription dans un des semestres de l'ensemble</li>
<li><span class="libelle">Hors semestre</span> <span class="anomalie">(anomalie D)</span>:
L'étudiant, bien qu'enregistré par apogée dans un des codes étapes connus, ne figure dans aucun
des semestres de l'ensemble. On y trouve par exemple les étudiants régulièrement inscrits
mais non présents à la rentrée (donc non enregistrés dans ScoDoc) <p>Note: On ne considère
ici que les semestres de l'ensemble (l'inscription de l'étudiant dans un semestre étranger à
l'ensemble actuel n'est pas vérifiée).</p> </li>
<li><span class="libelle">Plusieurs semestres</span> <span class="anomalie">(anomalie E)</span>:
L'étudiant est enregistré dans plusieurs semestres de l'ensemble.</li>
<li><span class="libelle">Total</span>: cumul des effectifs de la colonne</li>
</ul>
</li>
<li>(<span class="anomalie">anomalie U</span>) On présente également les cas où un même NIP est affecté
à deux dossiers différents (Un dossier d'apogée et un dossier de ScoDoc). Un tel cas compte pour
deux unités dans le tableau des effcetifs et engendre 2 lignes distinctes dans la liste des étudiants</li>
</ul>
</div>
</div> """
def entete_liste_etudiant() -> str:
return """
<ul>
<li id="sans_filtre">Pas de filtrage: Cliquez sur un des nombres du tableau ci-dessus pour
n'afficher que les étudiants correspondants</li>
<li id="filtre_row" style="display:none"></li>
<li id="filtre_col" style="display:none"></li>
</ul>
"""
class EtapeBilan:
"""
Structure de donnée représentation l'état global de la comparaison ScoDoc/Apogée
"""
def __init__(self):
self.semestres = {}
"Dictionnaire des formsemestres du semset (formsemestre_id -> semestre)"
self.etapes = [] # Liste des étapes apogées du semset (clé_apogée)
# pour les descriptions qui suivent:
# cle_etu = nip si non vide, sinon etudid
# data_etu = { nip, etudid, data_apogee, data_scodoc }
self.etudiants = {}
"cle_etu -> data_etu"
self.keys_etu = {}
"nip -> [ etudid* ]"
self.etu_semestre = {}
"semestre -> { key_etu }"
self.etu_etapes = {}
"etape -> { key_etu }"
self.repartition = {}
"(ind_row, ind_col) -> nombre d étudiants"
self.tag_count = {}
"nombre d'anomalies détectées (par type d'anomalie)"
# on collectionne les indicatifs trouvés pour n'afficher que les indicatifs 'utiles'
self.indicatifs = {}
self.top_row = 0
self.top_col = 0
self.all_rows_ind = [PAS_DE_SEMESTRE, PLUSIEURS_SEMESTRES]
self.all_cols_ind = [PAS_DE_NIP, PAS_D_ETAPE, PLUSIEURS_ETAPES]
self.all_rows_str = None
self.all_cols_str = None
self.titres = {
PAS_DE_NIP: "PAS_DE_NIP",
PAS_D_ETAPE: "PAS_D_ETAPE",
PLUSIEURS_ETAPES: "PLUSIEURS_ETAPES",
PAS_DE_SEMESTRE: "PAS_DE_SEMESTRE",
PLUSIEURS_SEMESTRES: "PLUSIEURS_SEMESTRES",
NIP_NON_UNIQUE: "NIP_NON_UNIQUE",
}
def inc_tag_count(self, tag):
if tag not in self.tag_count:
self.tag_count[tag] = 0
self.tag_count[tag] += 1
def set_indicatif(self, item, as_row):
"""item = semestre ou key_etape"""
if as_row:
indicatif = "R" + chr(self.top_row + 97)
self.all_rows_ind.append(indicatif)
self.top_row += 1
else:
indicatif = "C" + chr(self.top_col + 97)
self.all_cols_ind.append(indicatif)
self.top_col += 1
self.indicatifs[item] = indicatif
if self.top_row > 26:
log("Dépassement (plus de 26 semestres dans la table diagnostic")
if self.top_col > 26:
log("Dépassement (plus de 26 étapes dans la table diagnostic")
def add_sem(self, sem: dict):
"""
Prise en compte d'un semestre dans le bilan.
* ajoute le semestre et les étudiants du semestre
* ajoute les étapes du semestre et (via portail) les étudiants pour ces codes étapes
:param semestre: Le semestre à prendre en compte
:return: None
"""
self.semestres[sem["formsemestre_id"]] = sem
# if anneeapogee == None: # année d'inscription par défaut
annee_apogee = str(
annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])
)
self.set_indicatif(sem["formsemestre_id"], True)
for etape in sem["etapes"]:
self.add_etape(etape.etape_vdi, annee_apogee)
def add_etape(self, etape_str, annee_apogee):
"""
Prise en compte d'une étape apogée
:param etape_str: La clé de l'étape à prendre en compte
:param anneeapogee: l'année de l'étape à prendre en compte
:return: None
"""
if etape_str != "":
key_etape = etape_to_key(annee_apogee, etape_str)
if key_etape not in self.etapes:
self.etapes.append(key_etape)
self.set_indicatif(
key_etape, False
) # ajout de la colonne/indicatif supplémentaire
def compute_key_etu(self, nip, etudid):
"""
Calcul de la clé étudiant:
* Le nip si il existe
* sinon l'identifiant ScoDoc
Tient à jour le dictionnaire key_etu (référentiel des étudiants)
La problèmatique est de gérer toutes les anomalies possibles:
- étudiant sans nip,
- plusieurs étudiants avec le même nip,
- etc.
:param nip: le nip de l'étudiant
:param etudid: l'identifiant ScoDoc
:return: L'identifiant unique de l'étudiant
"""
if nip not in self.keys_etu:
self.keys_etu[nip] = []
if etudid not in self.keys_etu[nip]:
if etudid is None:
if len(self.keys_etu[nip]) == 1:
etudid = self.keys_etu[nip][0]
else: # nip non trouvé ou utilisé par plusieurs étudiants
self.keys_etu[nip].append(None)
else:
self.keys_etu[nip].append(etudid)
return nip, etudid
def register_etud_apogee(self, etud, etape):
"""
Enregistrement des données de l'étudiant par rapport à apogée.
L'étudiant peut avoir été déjà enregistré auparavant (par exemple connu par son semestre)
Dans ce cas, on ne met à jour que son association à l'étape apogée
:param etud: les données étudiant
:param etape: l'étape apogée
:return:
"""
nip = etud["nip"]
key_etu = self.compute_key_etu(nip, None)
if key_etu not in self.etudiants:
data = DataEtudiant(nip)
data.set_apogee(etud)
data.add_etape(etape)
self.etudiants[key_etu] = data
else:
self.etudiants[key_etu].set_apogee(etud)
self.etudiants[key_etu].add_etape(etape)
return key_etu
def register_etud_scodoc(self, etud: dict, sem: dict):
"""
Enregistrement de l'étudiant par rapport à son semestre
:param etud: Les données de l'étudiant
:param semestre: Le semestre où il est à enregistrer
:return: la clé unique pour cet étudiant
"""
nip = etud["code_nip"]
etudid = etud["etudid"]
key_etu = self.compute_key_etu(nip, etudid)
if key_etu not in self.etudiants:
data = DataEtudiant(nip, etudid)
data.set_scodoc(etud)
data.add_semestre(sem)
self.etudiants[key_etu] = data
else:
self.etudiants[key_etu].add_semestre(sem)
return key_etu
def load_listes(self):
"""
Inventaire complet des étudiants:
* Pour tous les semestres d'abord
* Puis pour toutes les étapes
:return: None
"""
for formsemestre_id, sem in self.semestres.items():
etuds = sem["etuds"]
self.etu_semestre[formsemestre_id] = set()
for etud in etuds:
key_etu = self.register_etud_scodoc(etud, formsemestre_id)
self.etu_semestre[formsemestre_id].add(key_etu)
for key_etape in self.etapes:
anneeapogee, etapestr = key_to_values(key_etape)
self.etu_etapes[key_etape] = set()
for etud in get_inscrits_etape(etapestr, anneeapogee):
key_etu = self.register_etud_apogee(etud, key_etape)
self.etu_etapes[key_etape].add(key_etu)
def dispatch(self):
"""
Réparti l'ensemble des étudiants selon les lignes (semestres) et les colonnes (étapes).
:return: None
"""
# Initialisation des cumuls
self.repartition[ROW_CUMUL, COL_CUMUL] = 0
self.repartition[PAS_DE_SEMESTRE, COL_CUMUL] = 0
self.repartition[PLUSIEURS_SEMESTRES, COL_CUMUL] = 0
self.repartition[ROW_CUMUL, PAS_DE_NIP] = 0
self.repartition[ROW_CUMUL, PAS_D_ETAPE] = 0
self.repartition[ROW_CUMUL, PLUSIEURS_ETAPES] = 0
for semestre in self.semestres:
self.repartition[self.indicatifs[semestre], COL_CUMUL] = 0
for key_etape in self.etapes:
self.repartition[ROW_CUMUL, self.indicatifs[key_etape]] = 0
# recherche des nip identiques
for nip, keys_etu_nip in self.keys_etu.items():
if nip != "":
nbnips = len(keys_etu_nip)
if nbnips > 1:
for i, etudid in enumerate(keys_etu_nip):
data_etu = self.etudiants[nip, etudid]
data_etu.add_tag(NIP_NON_UNIQUE)
data_etu.nip = data_etu.nip + f"&nbsp;({i+1}/{nbnips})"
self.inc_tag_count(NIP_NON_UNIQUE)
for nip, keys_etu_nip in self.keys_etu.items():
for etudid in keys_etu_nip:
key_etu = (nip, etudid)
data_etu = self.etudiants[key_etu]
ind_col = "-"
ind_row = "-"
# calcul de la colonne
if len(data_etu.etapes) == 1:
ind_col = self.indicatifs[list(data_etu.etapes)[0]]
elif nip == "":
data_etu.add_tag(FLAG[PAS_DE_NIP])
ind_col = PAS_DE_NIP
elif len(data_etu.etapes) == 0:
self.etudiants[key_etu].add_tag(FLAG[PAS_D_ETAPE])
ind_col = PAS_D_ETAPE
if len(data_etu.etapes) > 1:
data_etu.add_tag(FLAG[PLUSIEURS_ETAPES])
ind_col = PLUSIEURS_ETAPES
if len(data_etu.semestres) == 1:
ind_row = self.indicatifs[list(data_etu.semestres)[0]]
elif len(data_etu.semestres) > 1:
data_etu.add_tag(FLAG[PLUSIEURS_SEMESTRES])
ind_row = PLUSIEURS_SEMESTRES
elif len(data_etu.semestres) < 1:
self.etudiants[key_etu].add_tag(FLAG[PAS_DE_SEMESTRE])
ind_row = PAS_DE_SEMESTRE
data_etu.set_ind_col(ind_col)
data_etu.set_ind_row(ind_row)
self._inc_count(ind_row, ind_col)
self.inc_tag_count(ind_row)
self.inc_tag_count(ind_col)
def html_diagnostic(self):
"""
affichage de l'html
:return: Le code html à afficher
"""
self.load_listes() # chargement des données
self.dispatch() # analyse et répartition
# calcul de la liste des colonnes et des lignes de la table des effectifs
self.all_rows_str = "'" + ",".join(["." + r for r in self.all_rows_ind]) + "'"
self.all_cols_str = "'" + ",".join(["." + c for c in self.all_cols_ind]) + "'"
H = [
"""<div id="synthese" class="semset_description">
<details open="true">
<summary><b>Tableau des effectifs</b>
</summary>
""",
self._diagtable(),
"""</details>""",
self.display_tags(),
"""<details open="true">
<summary><b id="effectifs">Liste des étudiants <span id="compte"></span></b>
</summary>
""",
entete_liste_etudiant(),
self.table_effectifs(),
"""</details>""",
help(),
]
return "\n".join(H)
def _inc_count(self, ind_row, ind_col):
if (ind_row, ind_col) not in self.repartition:
self.repartition[ind_row, ind_col] = 0
self.repartition[ind_row, ind_col] += 1
self.repartition[ROW_CUMUL, ind_col] += 1
self.repartition[ind_row, COL_CUMUL] += 1
self.repartition[ROW_CUMUL, COL_CUMUL] += 1
def _get_count(self, ind_row, ind_col):
if (ind_row, ind_col) in self.repartition:
count = self.repartition[ind_row, ind_col]
if count > 1:
comptage = f"({count} étudiants)"
else:
comptage = "(1 étudiant)"
else:
count = 0
return ""
# Ajoute l'appel à la routine javascript de filtrage (apo_semset_maq_status.js
# signature:
# function show_css(elt, all_rows, all_cols, row, col, precision)
# elt: le lien cliqué
# all_rows: la liste de toutes les lignes existantes dans le tableau répartition
# (exemple: ".Rb,.R1,.R2,.R3")
# all_cols: la liste de toutes les colonnes existantes dans le tableau répartition
# (exemple: ".Ca,.C1,.C2,.C3")
# row: la ligne sélectionnée (sélecteur css) (expl: ".R1")
# ; '*' si pas de sélection sur la ligne
# col: la (les) colonnes sélectionnées (sélecteur css) (exple: ".C2")
# ; '*' si pas de sélection sur colonne
# precision: ajout sur le titre (en général, le nombre d'étudiant)
# filtre_row: explicitation du filtre ligne éventuelle
# filtre_col: explicitation du filtre colonne évnetuelle
if ind_row == ROW_CUMUL and ind_col == COL_CUMUL:
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
'*', '*',
'{comptage}',
'', ''
);"""
elif ind_row == ROW_CUMUL:
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
'*', '.{ind_col}',
'{comptage}', '',
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
);"""
elif ind_col == COL_CUMUL:
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
'.{ind_row}', '*',
' ({count} étudiants)',
'{json.dumps(self.titres[ind_row])[1:-1]}', ''
);"""
else:
javascript = f"""doFiltrage({self.all_rows_str}, {self.all_cols_str},
'.{ind_row}', '.{ind_col}',
'{comptage}',
'{json.dumps(self.titres[ind_row])[1:-1]}',
'{json.dumps(self.titres[ind_col].replace("<br>", " / "))[1:-1]}'
);"""
return f"""<a href="#synthese" class="stdlink" onclick="{javascript}">{count}</a>"""
def _diagtable(self) -> str:
"""Table avec les semestres et les effectifs"""
H = []
liste_semestres = sorted(self.semestres.keys())
liste_etapes = []
for key_etape in self.etapes:
liste_etapes.append(key_etape)
liste_etapes.sort(key=lambda key: etape_to_col(key_etape))
col_ids = []
if PAS_DE_NIP in self.tag_count:
col_ids.append(PAS_DE_NIP)
if PAS_D_ETAPE in self.tag_count:
col_ids.append(PAS_D_ETAPE)
if PLUSIEURS_ETAPES in self.tag_count:
col_ids.append(PLUSIEURS_ETAPES)
self.titres["row_title"] = "Semestre"
self.titres[PAS_DE_NIP] = "Hors Apogée (" + FLAG[PAS_DE_NIP] + ")"
self.titres[PAS_D_ETAPE] = "Sans étape (" + FLAG[PAS_D_ETAPE] + ")"
self.titres[PLUSIEURS_ETAPES] = (
"Plusieurs etapes (" + FLAG[PLUSIEURS_ETAPES] + ")"
)
for key_etape in liste_etapes:
col_id = self.indicatifs[key_etape]
col_ids.append(col_id)
self.titres[col_id] = "%s<br>%s" % key_to_values(key_etape)
col_ids.append(COL_CUMUL)
self.titres[COL_CUMUL] = "Total<br>semestre"
rows = []
for semestre in liste_semestres:
ind_row = self.indicatifs[semestre]
self.titres[ind_row] = (
"%(titre_num)s (%(formsemestre_id)s)" % self.semestres[semestre]
)
row = {
"row_title": self.link_semestre(semestre),
PAS_DE_NIP: self._get_count(ind_row, PAS_DE_NIP),
PAS_D_ETAPE: self._get_count(ind_row, PAS_D_ETAPE),
PLUSIEURS_ETAPES: self._get_count(ind_row, PLUSIEURS_ETAPES),
COL_CUMUL: self._get_count(ind_row, COL_CUMUL),
"_css_row_class": ind_row,
}
for key_etape in liste_etapes:
ind_col = self.indicatifs[key_etape]
row[ind_col] = self._get_count(ind_row, ind_col)
rows.append(row)
if PAS_DE_SEMESTRE in self.tag_count:
row = {
"row_title": "Hors semestres (" + FLAG[PAS_DE_SEMESTRE] + ")",
PAS_DE_NIP: "",
PAS_D_ETAPE: "",
PLUSIEURS_ETAPES: "",
COL_CUMUL: self._get_count(PAS_DE_SEMESTRE, COL_CUMUL),
"_css_row_class": PAS_DE_SEMESTRE,
}
for key_etape in liste_etapes:
ind_col = self.indicatifs[key_etape]
row[ind_col] = self._get_count(PAS_DE_SEMESTRE, ind_col)
rows.append(row)
if PLUSIEURS_SEMESTRES in self.tag_count:
row = {
"row_title": "Plusieurs semestres (" + FLAG[PLUSIEURS_SEMESTRES] + ")",
PAS_DE_NIP: "",
PAS_D_ETAPE: "",
PLUSIEURS_ETAPES: "",
COL_CUMUL: self._get_count(PLUSIEURS_SEMESTRES, COL_CUMUL),
"_css_row_class": PLUSIEURS_SEMESTRES,
}
for key_etape in liste_etapes:
ind_col = self.indicatifs[key_etape]
row[ind_col] = self._get_count(PLUSIEURS_SEMESTRES, ind_col)
rows.append(row)
row = {
"row_title": "Total",
PAS_DE_NIP: self._get_count(ROW_CUMUL, PAS_DE_NIP),
PAS_D_ETAPE: self._get_count(ROW_CUMUL, PAS_D_ETAPE),
PLUSIEURS_ETAPES: self._get_count(ROW_CUMUL, PLUSIEURS_ETAPES),
COL_CUMUL: self._get_count(ROW_CUMUL, COL_CUMUL),
"_css_row_class": COL_CUMUL,
}
for key_etape in liste_etapes:
ind_col = self.indicatifs[key_etape]
row[ind_col] = self._get_count(ROW_CUMUL, ind_col)
rows.append(row)
H.append(
GenTable(
rows,
col_ids,
self.titres,
html_class="repartition",
html_with_td_classes=True,
).gen(format="html")
)
return "\n".join(H)
def display_tags(self):
H = []
if NIP_NON_UNIQUE in self.tag_count:
H.append("<h4>Anomalies</h4>")
javascript = "show_tag(%s, %s, '%s');" % (
self.all_rows_str,
self.all_cols_str,
NIP_NON_UNIQUE,
)
H.append(
f"""Code(s) nip) partagé(s) par
<a href="#synthèse" class="stdlink"
onclick="{javascript}">{self.tag_count[NIP_NON_UNIQUE]}</a>
étudiants<br>
"""
)
return "\n".join(H)
@staticmethod
def link_etu(etudid, nom):
return '<a class="stdlink" href="%s">%s</a>' % (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
nom,
)
def link_semestre(self, semestre, short=False):
if short:
return (
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%('
"formsemestre_id)s</a> " % self.semestres[semestre]
)
else:
return (
'<a class="stdlink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s'
" %(mois_debut)s - %(mois_fin)s)</a>" % self.semestres[semestre]
)
def table_effectifs(self):
H = []
col_ids = ["tag", "etudiant", "prenom", "nip", "semestre", "apogee", "annee"]
titles = {
"tag": "Etat",
"etudiant": "Nom",
"prenom": "Prenom",
"nip": "code nip",
"semestre": "semestre",
"annee": "année",
"apogee": "etape",
}
rows = []
for data_etu in sorted(
list(self.etudiants.values()), key=lambda etu: etu.get_identity()
):
nip = data_etu.nip
etudid = data_etu.etudid
if data_etu.data_scodoc is None:
nom = data_etu.data_apogee["nom"]
prenom = data_etu.data_apogee["prenom"]
link = nom
else:
nom = data_etu.data_scodoc["nom"]
prenom = data_etu.data_scodoc["prenom"]
link = self.link_etu(etudid, nom)
tag = ", ".join([tag for tag in sorted(data_etu.tags)])
semestre = "<br>".join(
[self.link_semestre(sem, True) for sem in data_etu.semestres]
)
annees = "<br>".join([etape[0] for etape in data_etu.etapes])
etapes = "<br>".join([etape[1] for etape in data_etu.etapes])
classe = data_etu.ind_row + data_etu.ind_col
if NIP_NON_UNIQUE in data_etu.tags:
classe += " " + NIP_NON_UNIQUE
row = {
"tag": tag,
"etudiant": link,
"prenom": prenom.capitalize(),
"nip": nip,
"semestre": semestre,
"annee": annees,
"apogee": etapes,
"_css_row_class": classe,
}
rows.append(row)
H.append(
GenTable(
rows,
col_ids,
titles,
table_id="detail",
html_class="table_leftalign",
html_sortable=True,
).gen(format="html")
)
return "\n".join(H)
def etape_to_key(anneeapogee, etapestr):
return anneeapogee, etapestr
def key_to_values(key_etape):
return key_etape
def etape_to_col(key_etape):
return "%s@%s" % key_etape