ScoDoc/app/pe/pe_avislatex.py

518 lines
19 KiB
Python
Raw Permalink Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
#
##############################################################################
##############################################################################
# Module "Avis de poursuite d'étude"
# conçu et développé par Cléo Baras (IUT de Grenoble)
##############################################################################
2021-02-01 23:54:46 +01:00
import os
2020-09-26 16:19:37 +02:00
import codecs
import re
2021-09-26 10:01:20 +02:00
from app.pe import pe_tagtable
from app.pe import pe_jurype
from app.pe import pe_tools
2020-09-26 16:19:37 +02:00
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
2021-08-29 19:57:32 +02:00
from app import log
from app.scodoc.gen_tables import GenTable, SeqGenTable
from app.scodoc import sco_preferences
from app.scodoc import sco_etud
2020-09-26 16:19:37 +02:00
DEBUG = False # Pour debug et repérage des prints à changer en Log
DONNEE_MANQUANTE = (
2021-09-26 09:52:55 +02:00
"" # Caractère de remplacement des données manquantes dans un avis PE
2020-09-26 16:19:37 +02:00
)
# ----------------------------------------------------------------------------------------
def get_code_latex_from_modele(fichier):
"""Lit le code latex à partir d'un modèle. Renvoie une chaine unicode.
2020-09-26 16:19:37 +02:00
Le fichier doit contenir le chemin relatif
vers le modele : attention pas de vérification du format d'encodage
Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8)
"""
2021-02-01 23:54:46 +01:00
fid_latex = codecs.open(fichier, "r", encoding=scu.SCO_ENCODING)
2020-09-26 16:19:37 +02:00
un_avis_latex = fid_latex.read()
fid_latex.close()
return un_avis_latex
# ----------------------------------------------------------------------------------------
def get_code_latex_from_scodoc_preference(formsemestre_id, champ="pe_avis_latex_tmpl"):
2020-09-26 16:19:37 +02:00
"""
Extrait le template (ou le tag d'annotation au regard du champ fourni) des préférences LaTeX
et s'assure qu'il est renvoyé au format unicode
"""
template_latex = sco_preferences.get_preference(champ, formsemestre_id)
2020-09-26 16:19:37 +02:00
2021-07-12 15:13:10 +02:00
return template_latex or ""
2020-09-26 16:19:37 +02:00
# ----------------------------------------------------------------------------------------
def get_tags_latex(code_latex):
"""Recherche tous les tags présents dans un code latex (ce code étant obtenu
à la lecture d'un modèle d'avis pe).
Ces tags sont répérés par les balises **, débutant et finissant le tag
et sont renvoyés sous la forme d'une liste.
2020-09-26 16:19:37 +02:00
result: liste de chaines unicode
"""
if code_latex:
# changé par EV: était r"([\*]{2}[a-zA-Z0-9:éèàâêëïôöù]+[\*]{2})"
res = re.findall(r"([\*]{2}[^\t\n\r\f\v\*]+[\*]{2})", code_latex)
2020-09-26 16:19:37 +02:00
return [tag[2:-2] for tag in res]
else:
return []
def comp_latex_parcourstimeline(etudiant, promo, taille=17):
"""Interprète un tag dans un avis latex **parcourstimeline**
et génère le code latex permettant de retracer le parcours d'un étudiant
sous la forme d'une frise temporelle.
Nota: modeles/parcourstimeline.tex doit avoir été inclu dans le préambule
result: chaine unicode (EV:)
"""
codelatexDebut = (
2021-09-26 09:52:55 +02:00
""""
2020-09-26 16:19:37 +02:00
\\begin{parcourstimeline}{**debut**}{**fin**}{**nbreSemestres**}{%d}
"""
% taille
)
2021-09-26 09:52:55 +02:00
modeleEvent = """
2020-09-26 16:19:37 +02:00
\\parcoursevent{**nosem**}{**nomsem**}{**descr**}
"""
2021-09-26 09:52:55 +02:00
codelatexFin = """
2020-09-26 16:19:37 +02:00
\\end{parcourstimeline}
"""
reslatex = codelatexDebut
2021-07-12 15:13:10 +02:00
reslatex = reslatex.replace("**debut**", etudiant["entree"])
reslatex = reslatex.replace("**fin**", str(etudiant["promo"]))
reslatex = reslatex.replace("**nbreSemestres**", str(etudiant["nbSemestres"]))
2020-09-26 16:19:37 +02:00
# Tri du parcours par ordre croissant : de la forme descr, nom sem date-date
parcours = etudiant["parcours"][::-1] # EV: XXX je ne comprend pas ce commentaire ?
for no_sem in range(etudiant["nbSemestres"]):
descr = modeleEvent
nom_semestre_dans_parcours = parcours[no_sem]["nom_semestre_dans_parcours"]
2021-09-26 09:52:55 +02:00
descr = descr.replace("**nosem**", str(no_sem + 1))
2020-09-26 16:19:37 +02:00
if no_sem % 2 == 0:
2021-09-26 09:52:55 +02:00
descr = descr.replace("**nomsem**", nom_semestre_dans_parcours)
descr = descr.replace("**descr**", "")
2020-09-26 16:19:37 +02:00
else:
2021-09-26 09:52:55 +02:00
descr = descr.replace("**nomsem**", "")
descr = descr.replace("**descr**", nom_semestre_dans_parcours)
2020-09-26 16:19:37 +02:00
reslatex += descr
reslatex += codelatexFin
return reslatex
# ----------------------------------------------------------------------------------------
def interprete_tag_latex(tag):
"""Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible
2020-09-26 16:19:37 +02:00
le résultat sous la forme d'un quadruplet.
"""
infotag = tag.split(":")
if len(infotag) == 4:
return (
infotag[0].upper(),
infotag[1].lower(),
infotag[2].lower(),
infotag[3].lower(),
)
else:
return (None, None, None, None)
# ----------------------------------------------------------------------------------------
def get_code_latex_avis_etudiant(
donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs
):
"""
Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses
2020-09-26 16:19:37 +02:00
donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un
fichier modele donné
result: chaine unicode
"""
if not donnees_etudiant or not un_avis_latex: # Cas d'un template vide
2021-09-26 09:52:55 +02:00
return annotationPE if annotationPE else ""
2020-09-26 16:19:37 +02:00
# Le template latex (corps + footer)
code = un_avis_latex + "\n\n" + footer_latex
# Recherche des tags dans le fichier
tags_latex = get_tags_latex(code)
if DEBUG:
2021-02-01 23:54:46 +01:00
log("Les tags" + str(tags_latex))
2020-09-26 16:19:37 +02:00
# Interprète et remplace chaque tags latex par les données numériques de l'étudiant (y compris les
# tags "macros" tels que parcourstimeline
for tag_latex in tags_latex:
# les tags numériques
valeur = DONNEE_MANQUANTE
if ":" in tag_latex:
(aggregat, groupe, tag_scodoc, champ) = interprete_tag_latex(tag_latex)
valeur = str_from_syntheseJury(
donnees_etudiant, aggregat, groupe, tag_scodoc, champ
)
# La macro parcourstimeline
2021-09-26 09:52:55 +02:00
elif tag_latex == "parcourstimeline":
2020-09-26 16:19:37 +02:00
valeur = comp_latex_parcourstimeline(
donnees_etudiant, donnees_etudiant["promo"]
)
# Le tag annotationPE
2021-09-26 09:52:55 +02:00
elif tag_latex == "annotation":
2020-09-26 16:19:37 +02:00
valeur = annotationPE
# Le tag bilanParTag
2021-09-26 09:52:55 +02:00
elif tag_latex == "bilanParTag":
2020-09-26 16:19:37 +02:00
valeur = get_bilanParTag(donnees_etudiant)
# Les tags "simples": par ex. nom, prenom, civilite, ...
2020-09-26 16:19:37 +02:00
else:
if tag_latex in donnees_etudiant:
2021-07-12 15:13:10 +02:00
valeur = donnees_etudiant[tag_latex]
2020-09-26 16:19:37 +02:00
elif tag_latex in prefs: # les champs **NomResponsablePE**, ...
2021-07-12 15:13:10 +02:00
valeur = pe_tools.escape_for_latex(prefs[tag_latex])
2020-09-26 16:19:37 +02:00
2021-07-09 17:47:06 +02:00
# Vérification des pb d'encodage (debug)
# assert isinstance(tag_latex, unicode)
# assert isinstance(valeur, unicode)
2020-09-26 16:19:37 +02:00
# Substitution
code = code.replace("**" + tag_latex + "**", valeur)
return code
# ----------------------------------------------------------------------------------------
def get_annotation_PE(etudid, tag_annotation_pe):
"""Renvoie l'annotation PE dans la liste de ces annotations ;
Cette annotation est reconnue par la présence d'un tag **PE**
2021-06-13 19:12:20 +02:00
(cf. .get_preferences -> pe_tag_annotation_avis_latex).
2020-09-26 16:19:37 +02:00
Result: chaine unicode
"""
if tag_annotation_pe:
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
annotations = sco_etud.etud_annotations_list(
2020-09-26 16:19:37 +02:00
cnx, args={"etudid": etudid}
) # Les annotations de l'étudiant
annotationsPE = []
exp = re.compile(r"^" + tag_annotation_pe)
for a in annotations:
2021-07-12 15:13:10 +02:00
commentaire = scu.unescape_html(a["comment"])
2020-09-26 16:19:37 +02:00
if exp.match(commentaire): # tag en début de commentaire ?
a["comment_u"] = commentaire # unicode, HTML non quoté
annotationsPE.append(
a
) # sauvegarde l'annotation si elle contient le tag
if annotationsPE: # Si des annotations existent, prend la plus récente
annotationPE = sorted(annotationsPE, key=lambda a: a["date"], reverse=True)[
0
]["comment_u"]
annotationPE = exp.sub(
2021-09-26 09:52:55 +02:00
"", annotationPE
2020-09-26 16:19:37 +02:00
) # Suppression du tag d'annotation PE
2021-09-26 09:52:55 +02:00
annotationPE = annotationPE.replace("\r", "") # Suppression des \r
2020-09-26 16:19:37 +02:00
annotationPE = annotationPE.replace(
2021-09-26 09:52:55 +02:00
"<br/>", "\n\n"
2020-09-26 16:19:37 +02:00
) # Interprète les retours chariots html
return annotationPE
2021-09-26 09:52:55 +02:00
return "" # pas d'annotations
2020-09-26 16:19:37 +02:00
# ----------------------------------------------------------------------------------------
def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ):
"""Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée,
une valeur indiquée par un champ ;
2020-09-26 16:19:37 +02:00
si champ est une liste, renvoie la liste des valeurs extraites.
Result: chaine unicode ou liste de chaines unicode
"""
if isinstance(champ, list):
return [
str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, chp)
for chp in champ
]
else: # champ = str à priori
valeur = DONNEE_MANQUANTE
if (
(aggregat in donnees_etudiant)
and (groupe in donnees_etudiant[aggregat])
and (tag_scodoc in donnees_etudiant[aggregat][groupe])
):
donnees_numeriques = donnees_etudiant[aggregat][groupe][tag_scodoc]
if champ == "rang":
2021-09-26 09:52:55 +02:00
valeur = "%s/%d" % (
2020-09-26 16:19:37 +02:00
donnees_numeriques[
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index("rang")
],
donnees_numeriques[
pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index(
"nbinscrits"
)
],
)
elif champ in pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS:
indice_champ = pe_tagtable.TableTag.FORMAT_DONNEES_ETUDIANTS.index(
champ
)
if (
len(donnees_numeriques) > indice_champ
and donnees_numeriques[indice_champ] != None
):
if isinstance(
donnees_numeriques[indice_champ], float
): # valeur numérique avec formattage unicode
2021-09-26 09:52:55 +02:00
valeur = "%2.2f" % donnees_numeriques[indice_champ]
2020-09-26 16:19:37 +02:00
else:
2021-09-26 09:52:55 +02:00
valeur = "%s" % donnees_numeriques[indice_champ]
2020-09-26 16:19:37 +02:00
return valeur
# ----------------------------------------------------------------------------------------
def get_bilanParTag(donnees_etudiant, groupe="groupe"):
"""Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans
2020-09-26 16:19:37 +02:00
les données étudiants, ses résultats.
result: chaine unicode
"""
entete = [
(
agg,
pe_jurype.JuryPE.PARCOURS[agg]["affichage_court"],
pe_jurype.JuryPE.PARCOURS[agg]["ordre"],
)
for agg in pe_jurype.JuryPE.PARCOURS
]
entete = sorted(entete, key=lambda t: t[2])
lignes = []
valeurs = {"note": [], "rang": []}
2021-02-01 23:54:46 +01:00
for (indice_aggregat, (aggregat, intitule, _)) in enumerate(entete):
2020-09-26 16:19:37 +02:00
# print("> " + aggregat)
# listeTags = jury.get_allTagForAggregat(aggregat) # les tags de l'aggrégat
listeTags = [
tag for tag in donnees_etudiant[aggregat][groupe].keys() if tag != "dut"
] #
for tag in listeTags:
if tag not in lignes:
lignes.append(tag)
valeurs["note"].append(
[""] * len(entete)
) # Ajout d'une ligne de données
valeurs["rang"].append(
[""] * len(entete)
) # Ajout d'une ligne de données
indice_tag = lignes.index(tag) # l'indice de ligne du tag
# print(" --- " + tag + "(" + str(indice_tag) + "," + str(indice_aggregat) + ")")
[note, rang] = str_from_syntheseJury(
donnees_etudiant, aggregat, groupe, tag, ["note", "rang"]
)
valeurs["note"][indice_tag][indice_aggregat] = "" + note + ""
valeurs["rang"][indice_tag][indice_aggregat] = (
("\\textit{" + rang + "}") if note else ""
) # rang masqué si pas de notes
2021-09-26 09:52:55 +02:00
code_latex = "\\begin{tabular}{|c|" + "|c" * (len(entete)) + "|}\n"
code_latex += "\\hline \n"
2020-09-26 16:19:37 +02:00
code_latex += (
2021-09-26 09:52:55 +02:00
" & "
2021-02-01 23:54:46 +01:00
+ " & ".join(["\\textbf{" + intitule + "}" for (agg, intitule, _) in entete])
2020-09-26 16:19:37 +02:00
+ " \\\\ \n"
)
2021-09-26 09:52:55 +02:00
code_latex += "\\hline"
code_latex += "\\hline \n"
2020-09-26 16:19:37 +02:00
for (i, ligne_val) in enumerate(valeurs["note"]):
titre = lignes[i] # règle le pb d'encodage
code_latex += "\\textbf{" + titre + "} & " + " & ".join(ligne_val) + "\\\\ \n"
2020-09-26 16:19:37 +02:00
code_latex += (
2021-09-26 09:52:55 +02:00
" & "
+ " & ".join(
["{\\scriptsize " + clsmt + "}" for clsmt in valeurs["rang"][i]]
2020-09-26 16:19:37 +02:00
)
2021-09-26 09:52:55 +02:00
+ "\\\\ \n"
2020-09-26 16:19:37 +02:00
)
2021-09-26 09:52:55 +02:00
code_latex += "\\hline \n"
code_latex += "\\end{tabular}"
2020-09-26 16:19:37 +02:00
return code_latex
# ----------------------------------------------------------------------------------------
def get_avis_poursuite_par_etudiant(
jury, etudid, template_latex, tag_annotation_pe, footer_latex, prefs
2020-09-26 16:19:37 +02:00
):
"""Renvoie un nom de fichier et le contenu de l'avis latex d'un étudiant dont l'etudid est fourni.
result: [ chaine unicode, chaine unicode ]
"""
if pe_tools.PE_DEBUG:
2021-08-10 17:12:10 +02:00
pe_tools.pe_print(jury.syntheseJury[etudid]["nom"] + " " + str(etudid))
2020-09-26 16:19:37 +02:00
2021-07-12 15:13:10 +02:00
civilite_str = jury.syntheseJury[etudid]["civilite_str"]
nom = jury.syntheseJury[etudid]["nom"].replace(" ", "-")
prenom = jury.syntheseJury[etudid]["prenom"].replace(" ", "-")
2020-09-26 16:19:37 +02:00
nom_fichier = scu.sanitize_filename(
"avis_poursuite_%s_%s_%s" % (nom, prenom, etudid)
)
2020-09-26 16:19:37 +02:00
if pe_tools.PE_DEBUG:
pe_tools.pe_print("fichier latex =" + nom_fichier, type(nom_fichier))
# Entete (commentaire)
contenu_latex = (
"%% ---- Etudiant: " + civilite_str + " " + nom + " " + prenom + "\n"
)
2020-09-26 16:19:37 +02:00
# les annnotations
annotationPE = get_annotation_PE(etudid, tag_annotation_pe=tag_annotation_pe)
2020-09-26 16:19:37 +02:00
if pe_tools.PE_DEBUG:
pe_tools.pe_print(annotationPE, type(annotationPE))
# le LaTeX
avis = get_code_latex_avis_etudiant(
jury.syntheseJury[etudid], template_latex, annotationPE, footer_latex, prefs
)
# if pe_tools.PE_DEBUG: pe_tools.pe_print(avis, type(avis))
contenu_latex += avis + "\n"
return [nom_fichier, contenu_latex]
def get_templates_from_distrib(template="avis"):
"""Récupère le template (soit un_avis.tex soit le footer.tex) à partir des fichiers mémorisés dans la distrib des avis pe (distrib local
ou par défaut et le renvoie"""
if template == "avis":
pe_local_tmpl = pe_tools.PE_LOCAL_AVIS_LATEX_TMPL
pe_default_tmpl = pe_tools.PE_DEFAULT_AVIS_LATEX_TMPL
elif template == "footer":
pe_local_tmpl = pe_tools.PE_LOCAL_FOOTER_TMPL
pe_default_tmpl = pe_tools.PE_DEFAULT_FOOTER_TMPL
if template in ["avis", "footer"]:
# pas de preference pour le template: utilise fichier du serveur
if os.path.exists(pe_local_tmpl):
template_latex = get_code_latex_from_modele(pe_local_tmpl)
2020-09-26 16:19:37 +02:00
else:
if os.path.exists(pe_default_tmpl):
template_latex = get_code_latex_from_modele(pe_default_tmpl)
2020-09-26 16:19:37 +02:00
else:
template_latex = "" # fallback: avis vides
return template_latex
# ----------------------------------------------------------------------------------------
def table_syntheseAnnotationPE(syntheseJury, tag_annotation_pe):
"""Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant"""
2020-09-26 16:19:37 +02:00
sT = SeqGenTable() # le fichier excel à générer
# Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom
donnees_tries = sorted(
[
(etudid, syntheseJury[etudid]["nom"] + " " + syntheseJury[etudid]["prenom"])
for etudid in syntheseJury.keys()
],
key=lambda c: c[1],
)
etudids = [e[0] for e in donnees_tries]
if not etudids: # Si pas d'étudiants
T = GenTable(
columns_ids=["pas d'étudiants"],
rows=[],
titles={"pas d'étudiants": "pas d'étudiants"},
html_sortable=True,
xls_sheet_name="dut",
)
sT.add_genTable("Annotation PE", T)
return sT
# Si des étudiants
maxParcours = max(
[syntheseJury[etudid]["nbSemestres"] for etudid in etudids]
) # le nombre de semestre le + grand
infos = ["civilite", "nom", "prenom", "age", "nbSemestres"]
2020-09-26 16:19:37 +02:00
entete = ["etudid"]
entete.extend(infos)
entete.extend(["P%d" % i for i in range(1, maxParcours + 1)]) # ajout du parcours
entete.append("Annotation PE")
columns_ids = entete # les id et les titres de colonnes sont ici identiques
titles = {i: i for i in columns_ids}
rows = []
for (
etudid
) in etudids: # parcours des étudiants par ordre alphabétique des nom+prénom
e = syntheseJury[etudid]
# Les info générales:
row = {
"etudid": etudid,
"civilite": e["civilite"],
2020-09-26 16:19:37 +02:00
"nom": e["nom"],
"prenom": e["prenom"],
"age": e["age"],
"nbSemestres": e["nbSemestres"],
}
# Les parcours: P1, P2, ...
n = 1
for p in e["parcours"]:
row["P%d" % n] = p["titreannee"]
n += 1
# L'annotation PE
annotationPE = get_annotation_PE(etudid, tag_annotation_pe=tag_annotation_pe)
2021-07-12 15:13:10 +02:00
row["Annotation PE"] = annotationPE if annotationPE else ""
2020-09-26 16:19:37 +02:00
rows.append(row)
T = GenTable(
columns_ids=columns_ids,
rows=rows,
titles=titles,
html_sortable=True,
xls_sheet_name="Annotation PE",
)
sT.add_genTable("Annotation PE", T)
return sT