forked from ScoDoc/DocScoDoc
779 lines
30 KiB
Python
779 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():
|
|
return """
|
|
<h4 id='effectifs'>Liste des étudiants <span id='compte'></span>
|
|
<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>
|
|
</h4>
|
|
"""
|
|
|
|
|
|
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" ({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=u"semset_description"><h4>Tableau des effectifs</h4>',
|
|
self._diagtable(),
|
|
self.display_tags(),
|
|
entete_liste_etudiant(),
|
|
self.table_effectifs(),
|
|
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 = "doFiltrage(%s, %s, '*', '*', '%s', '%s', '%s');" % (
|
|
self.all_rows_str,
|
|
self.all_cols_str,
|
|
comptage,
|
|
"",
|
|
"",
|
|
)
|
|
elif ind_row == ROW_CUMUL:
|
|
javascript = "doFiltrage(%s, %s, '*', '.%s', '%s', '%s', '%s');" % (
|
|
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 = "doFiltrage(%s, %s, '.%s', '*', '%s', '%s', '%s');" % (
|
|
self.all_rows_str,
|
|
self.all_cols_str,
|
|
ind_row,
|
|
" (%d étudiants)" % count,
|
|
json.dumps(self.titres[ind_row])[1:-1],
|
|
"",
|
|
)
|
|
else:
|
|
javascript = "doFiltrage(%s, %s, '.%s', '.%s', '%s', '%s', '%s');" % (
|
|
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 '<a href="#synthese" onclick="%s">%d</a>' % (javascript, count)
|
|
|
|
def _diagtable(self):
|
|
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] = "Pas d'é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(
|
|
'Code(s) nip) partagé(s) par <a href="#synthèse" onclick="%s">%d</a> étudiants<br>'
|
|
% (javascript, self.tag_count[NIP_NON_UNIQUE])
|
|
)
|
|
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
|