# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 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 # ############################################################################## """Tableau recapitulatif des notes d'un semestre """ import time import datetime from xml.etree import ElementTree import app.scodoc.sco_utils as scu from app.scodoc.notes_log import log from app.scodoc import html_sco_header from app.scodoc import sco_bac from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_xml from app.scodoc import sco_bulletins, sco_excel from app.scodoc import sco_codes_parcours from app.scodoc import sco_cache from app.scodoc import sco_evaluations from app.scodoc import sco_formations from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre_status from app.scodoc import sco_groups from app.scodoc import sco_permissions from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences from app.scodoc import sco_etud from app.scodoc import sco_users from app.scodoc import sco_xml from app.scodoc.sco_codes_parcours import DEF, UE_SPORT def formsemestre_recapcomplet( context, formsemestre_id=None, modejury=False, # affiche lien saisie decision jury hidemodules=False, # cache colonnes notes modules hidebac=False, # cache colonne Bac tabformat="html", sortcol=None, xml_with_decisions=False, # XML avec decisions rank_partition_id=None, # si None, calcul rang global pref_override=True, # si vrai, les prefs ont la priorite sur le param hidebac force_publishing=True, # publie les XML/JSON meme si bulletins non publiés REQUEST=None, ): """Page récapitulant les notes d'un semestre. Grand tableau récapitulatif avec toutes les notes de modules pour tous les étudiants, les moyennes par UE et générale, trié par moyenne générale décroissante. """ sem = sco_formsemestre.get_formsemestre(formsemestre_id) F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0] parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) # traduit du DTML modejury = int(modejury) hidemodules = ( int(hidemodules) or parcours.UE_IS_MODULE ) # cache les colonnes des modules pref_override = int(pref_override) if pref_override: hidebac = int(sco_preferences.get_preference("recap_hidebac", formsemestre_id)) else: hidebac = int(hidebac) xml_with_decisions = int(xml_with_decisions) force_publishing = int(force_publishing) isFile = tabformat in ("csv", "xls", "xml", "xlsall", "json") H = [] if not isFile: H += [ html_sco_header.sco_header( page_title="Récapitulatif", no_side_bar=True, init_qtip=True, javascripts=["libjs/sorttable.js", "js/etud_info.js"], ), sco_formsemestre_status.formsemestre_status_head( context, formsemestre_id=formsemestre_id, REQUEST=REQUEST ), '
") H.append( """""" % formsemestre_id ) if sco_permissions_check.can_validate_sem(formsemestre_id): H.append("") if modejury: H.append( """Calcul automatique des décisions du jury
""" % (formsemestre_id,) ) else: H.append( """Saisie des décisions du jury""" % formsemestre_id ) H.append("") if sco_preferences.get_preference("use_ue_coefs", formsemestre_id): H.append( """utilise les coefficients d'UE pour calculer la moyenne générale.
""" ) H.append(html_sco_header.sco_footer()) return "".join(H) # HTML or binary data... def do_formsemestre_recapcomplet( context=None, REQUEST=None, formsemestre_id=None, format="html", # html, xml, xls, xlsall, json hidemodules=False, # ne pas montrer les modules (ignoré en XML) hidebac=False, # pas de colonne Bac (ignoré en XML) xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML) modejury=False, # saisie décisions jury sortcol=None, # indice colonne a trier dans table T xml_with_decisions=False, disable_etudlink=False, rank_partition_id=None, # si None, calcul rang global force_publishing=True, ): """Calcule et renvoie le tableau récapitulatif.""" data, filename, format = make_formsemestre_recapcomplet(**vars()) if format == "xml" or format == "html": return data elif format == "csv": return scu.sendCSVFile(REQUEST, data, filename) elif format[:3] == "xls": return sco_excel.send_excel_file(REQUEST, data, filename) elif format == "json": return scu.sendJSON(REQUEST, data) else: raise ValueError("unknown format %s" % format) def make_formsemestre_recapcomplet( context=None, REQUEST=None, formsemestre_id=None, format="html", # html, xml, xls, xlsall, json hidemodules=False, # ne pas montrer les modules (ignoré en XML) hidebac=False, # pas de colonne Bac (ignoré en XML) xml_nodate=False, # format XML sans dates (sert pour debug cache: comparaison de XML) modejury=False, # saisie décisions jury sortcol=None, # indice colonne a trier dans table T xml_with_decisions=False, disable_etudlink=False, rank_partition_id=None, # si None, calcul rang global force_publishing=True, # donne bulletins JSON/XML meme si non publiés ): """Grand tableau récapitulatif avec toutes les notes de modules pour tous les étudiants, les moyennes par UE et générale, trié par moyenne générale décroissante. """ civ_nom_prenom = False # 3 colonnes différentes ou une seule avec prénom abrégé ? if format == "xml": return _formsemestre_recapcomplet_xml( context, formsemestre_id, xml_nodate, xml_with_decisions=xml_with_decisions, force_publishing=force_publishing, ) elif format == "json": return _formsemestre_recapcomplet_json( context, formsemestre_id, xml_nodate=xml_nodate, xml_with_decisions=xml_with_decisions, force_publishing=force_publishing, ) if format[:3] == "xls": civ_nom_prenom = True # 3 cols: civilite, nom, prenom keep_numeric = True # pas de conversion des notes en strings else: keep_numeric = False if hidebac: admission_extra_cols = [] else: admission_extra_cols = [ "type_admission", "classement", "apb_groupe", "apb_classement_gr", ] sem = sco_formsemestre.do_formsemestre_list( args={"formsemestre_id": formsemestre_id} )[0] nt = sco_cache.NotesTableCache.get( formsemestre_id ) # > get_modimpls, get_ues, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_etud_decision_sem, modimpls = nt.get_modimpls() ues = nt.get_ues() # incluant le(s) UE de sport # partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups( formsemestre_id ) if rank_partition_id and format == "html": # Calcul rang sur une partition et non sur l'ensemble # seulement en format HTML (car colonnes rangs toujours presentes en xls) rank_partition = sco_groups.get_partition(rank_partition_id) rank_label = "Rg (%s)" % rank_partition["partition_name"] else: rank_partition = sco_groups.get_default_partition(formsemestre_id) rank_label = "Rg" T = nt.get_table_moyennes_triees() if not T: return "", "", format # Construit une liste de listes de chaines: le champs du tableau resultat (HTML ou CSV) F = [] h = [rank_label] if civ_nom_prenom: h += ["Civilité", "Nom", "Prénom"] else: h += ["Nom"] if not hidebac: h.append("Bac") # Si CSV ou XLS, indique tous les groupes if format[:3] == "xls" or format == "csv": for partition in partitions: h.append("%s" % partition["partition_name"]) else: h.append("Gr") h.append("Moy") # Ajoute rangs dans groupe seulement si CSV ou XLS if format[:3] == "xls" or format == "csv": for partition in partitions: h.append("rang_%s" % partition["partition_name"]) cod2mod = {} # code : moduleimpl mod_evals = {} # moduleimpl_id : liste de toutes les evals de ce module for ue in ues: if ue["type"] != UE_SPORT: h.append(ue["acronyme"]) else: # UE_SPORT: # n'affiche pas la moyenne d'UE dans ce cas # mais laisse col. vide si modules affichés (pour séparer les UE) if not hidemodules: h.append("") pass if not hidemodules and not ue["is_external"]: for modimpl in modimpls: if modimpl["module"]["ue_id"] == ue["ue_id"]: code = modimpl["module"]["code"] h.append(code) cod2mod[code] = modimpl # pour fabriquer le lien if format == "xlsall": evals = nt.get_mod_evaluation_etat_list( modimpl["moduleimpl_id"] ) mod_evals[modimpl["moduleimpl_id"]] = evals h += _list_notes_evals_titles(context, code, evals) h += admission_extra_cols h += ["code_nip", "etudid"] F.append(h) ue_index = [] # indices des moy UE dans l (pour appliquer style css) def fmtnum(val): # conversion en nombre pour cellules excel if keep_numeric: try: return float(val) except: return val else: return val # Compte les decisions de jury codes_nb = scu.DictDefault(defaultvalue=0) # is_dem = {} # etudid : bool for t in T: etudid = t[-1] dec = nt.get_etud_decision_sem(etudid) if dec: codes_nb[dec["code"]] += 1 etud_etat = nt.get_etud_etat(etudid) if etud_etat == "D": gr_name = "Dém." is_dem[etudid] = True elif etud_etat == DEF: gr_name = "Déf." is_dem[etudid] = False else: group = sco_groups.get_etud_main_group(etudid, sem) gr_name = group["group_name"] or "" is_dem[etudid] = False if rank_partition_id: rang_gr, _, rank_gr_name = sco_bulletins.get_etud_rangs_groups( context, etudid, formsemestre_id, partitions, partitions_etud_groups, nt ) if rank_gr_name[rank_partition_id]: rank = "%s %s" % ( rank_gr_name[rank_partition_id], rang_gr[rank_partition_id], ) else: rank = "" else: rank = nt.get_etud_rang(etudid) e = nt.identdict[etudid] if civ_nom_prenom: sco_etud.format_etud_ident(e) l = [rank, e["civilite_str"], e["nom_disp"], e["prenom"]] # civ, nom prenom else: l = [rank, nt.get_nom_short(etudid)] # rang, nom, if not hidebac: bac = sco_bac.Baccalaureat(e["bac"], e["specialite"]) l.append(bac.abbrev()) if format[:3] == "xls" or format == "csv": # tous les groupes for partition in partitions: group = partitions_etud_groups[partition["partition_id"]].get( etudid, None ) if group: l.append(group["group_name"]) else: l.append("") else: l.append(gr_name) # groupe l.append(fmtnum(scu.fmt_note(t[0], keep_numeric=keep_numeric))) # moy_gen # Ajoute rangs dans groupes seulement si CSV ou XLS if format[:3] == "xls" or format == "csv": rang_gr, _, gr_name = sco_bulletins.get_etud_rangs_groups( context, etudid, formsemestre_id, partitions, partitions_etud_groups, nt ) for partition in partitions: l.append(rang_gr[partition["partition_id"]]) i = 0 for ue in ues: i += 1 if ue["type"] != UE_SPORT: l.append( fmtnum(scu.fmt_note(t[i], keep_numeric=keep_numeric)) ) # moyenne etud dans ue else: # UE_SPORT: # n'affiche pas la moyenne d'UE dans ce cas if not hidemodules: l.append("") ue_index.append(len(l) - 1) if not hidemodules and not ue["is_external"]: j = 0 for modimpl in modimpls: if modimpl["module"]["ue_id"] == ue["ue_id"]: l.append( fmtnum( scu.fmt_note( t[j + len(ues) + 1], keep_numeric=keep_numeric ) ) ) # moyenne etud dans module if format == "xlsall": l += _list_notes_evals( context, mod_evals[modimpl["moduleimpl_id"]], etudid ) j += 1 if not hidebac: for k in admission_extra_cols: l.append(e[k]) l.append( nt.identdict[etudid]["code_nip"] or "" ) # avant-derniere colonne = code_nip l.append(etudid) # derniere colonne = etudid F.append(l) # Dernière ligne: moyennes, min et max des UEs et modules if not hidemodules: # moy/min/max dans chaque module mods_stats = {} # moduleimpl_id : stats for modimpl in modimpls: mods_stats[modimpl["moduleimpl_id"]] = nt.get_mod_stats( modimpl["moduleimpl_id"] ) def add_bottom_stat(key, title, corner_value=""): l = ["", title] if civ_nom_prenom: l += ["", ""] if not hidebac: l.append("") if format[:3] == "xls" or format == "csv": l += [""] * len(partitions) else: l += [""] l.append(corner_value) if format[:3] == "xls" or format == "csv": for _ in partitions: l += [""] # rangs dans les groupes for ue in ues: if ue["type"] != UE_SPORT: if key == "nb_valid_evals": l.append("") elif key == "coef": if sco_preferences.get_preference("use_ue_coefs", formsemestre_id): l.append("%2.3f" % ue["coefficient"]) else: l.append("") else: if key == "ects": if keep_numeric: l.append(ue[key]) else: l.append(str(ue[key])) else: l.append(scu.fmt_note(ue[key], keep_numeric=keep_numeric)) else: # UE_SPORT: # n'affiche pas la moyenne d'UE dans ce cas if not hidemodules: l.append("") ue_index.append(len(l) - 1) if not hidemodules and not ue["is_external"]: for modimpl in modimpls: if modimpl["module"]["ue_id"] == ue["ue_id"]: if key == "coef": coef = modimpl["module"]["coefficient"] if format[:3] != "xls": coef = str(coef) l.append(coef) elif key == "ects": l.append("") # ECTS module ? else: val = mods_stats[modimpl["moduleimpl_id"]][key] if key == "nb_valid_evals": if ( format[:3] != "xls" ): # garde val numerique pour excel val = str(val) else: # moyenne du module val = scu.fmt_note(val, keep_numeric=keep_numeric) l.append(val) if format == "xlsall": l += _list_notes_evals_stats( context, mod_evals[modimpl["moduleimpl_id"]], key ) if modejury: l.append("") # case vide sur ligne "Moyennes" l += [""] * len(admission_extra_cols) # infos admission vides ici F.append(l + ["", ""]) # ajoute cellules code_nip et etudid inutilisees ici add_bottom_stat( "min", "Min", corner_value=scu.fmt_note(nt.moy_min, keep_numeric=keep_numeric) ) add_bottom_stat( "max", "Max", corner_value=scu.fmt_note(nt.moy_max, keep_numeric=keep_numeric) ) add_bottom_stat( "moy", "Moyennes", corner_value=scu.fmt_note(nt.moy_moy, keep_numeric=keep_numeric), ) add_bottom_stat("coef", "Coef") add_bottom_stat("nb_valid_evals", "Nb évals") add_bottom_stat("ects", "ECTS") # Generation table au format demandé if format == "html": # Table format HTML H = [ """%s | ' % ( cls, mod["moduleimpl_id"], mod["module"]["titre"], sco_users.user_info(mod["responsable_id"])["nomcomplet"], F[0][i], ) else: cells += '%s | ' % (cls, F[0][i]) if modejury: cells += 'Décision | ' ligne_titres = cells + "||||
%s | ' % nsn[0] # rang cells += '%s | ' % el # nom etud (lien) if not hidebac: cells += '%s | ' % nsn[2] # bac idx_col_gr = 3 else: idx_col_gr = 2 cells += '%s | ' % nsn[idx_col_gr] # group name # Style si moyenne generale < barre idx_col_moy = idx_col_gr + 1 cssclass = "recap_col_moy" try: if float(nsn[idx_col_moy]) < ( nt.parcours.BARRE_MOY - scu.NOTES_TOLERANCE ): cssclass = "recap_col_moy_inf" except: pass cells += '%s | ' % (cssclass, nsn[idx_col_moy]) ue_number = 0 for i in range(idx_col_moy + 1, len(nsn)): if i in ue_index: cssclass = "recap_col_ue" # grise si moy UE < barre ue = ues[ue_number] ue_number += 1 if (ir < (nblines - 4)) or (ir == nblines - 3): try: if float(nsn[i]) < nt.parcours.get_barre_ue( ue["type"] ): # NOTES_BARRE_UE cssclass = "recap_col_ue_inf" elif float(nsn[i]) >= nt.parcours.NOTES_BARRE_VALID_UE: cssclass = "recap_col_ue_val" except: pass else: cssclass = "recap_col" if ( ir == nblines - 3 ): # si moyenne generale module < barre ue, surligne: try: if float(nsn[i]) < nt.parcours.get_barre_ue(ue["type"]): cssclass = "recap_col_moy_inf" except: pass cells += '%s | ' % (cssclass, nsn[i]) if modejury and etudid: decision_sem = nt.get_etud_decision_sem(etudid) if is_dem[etudid]: code = "DEM" act = "" elif decision_sem: code = decision_sem["code"] act = "(modifier)" else: code = "" act = "saisir" cells += '%s' % code if act: # cells += ' %s' % (formsemestre_id, etudid, act) cells += ( """ %s""" % (formsemestre_id, etudid, act) ) cells += " | " H.append(cells + "
%s | %d |