diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 79423197f..cd1c59a5d 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -149,9 +149,9 @@ class ResultatsSemestreBUT: """dict synthèse résultats des modules indiqués, avec évaluations de chacun.""" d = {} - etud_idx = self.etud_index[etud.id] + # etud_idx = self.etud_index[etud.id] for mi in modimpls: - mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id) + # mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id) # # moyennes indicatives (moyennes de moyennes d'UE) # try: # moyennes_etuds = np.nan_to_num( diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index f71a9e798..40616cbab 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -111,13 +111,14 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> tuple: N'utilise pas de cache ScoDoc. """ - # L'index du dataframe est la liste des étudiants inscrits au semestre, sans les démissionnaires - etudids = { + # L'index du dataframe est la liste des étudiants inscrits au semestre, + # sans les démissionnaires + etudids = [ e.etudid for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits( include_dem=False ) - } + ] evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all() # --- Calcul nombre d'inscrits pour détermnier si évaluation "complete": if evaluations: @@ -128,7 +129,8 @@ def df_load_modimpl_notes(moduleimpl_id: int) -> tuple: nb_inscrits_module = len(inscrits_module) else: nb_inscrits_module = 0 - evals_notes = pd.DataFrame(index=etudids, dtype=float) # empty df with all students + # empty df with all students: + evals_notes = pd.DataFrame(index=etudids, dtype=float) evaluations_completes = [] for evaluation in evaluations: eval_df = pd.read_sql_query( diff --git a/app/models/evaluations.py b/app/models/evaluations.py index 9a237c5c9..e0733fb77 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -76,11 +76,11 @@ class Evaluation(db.Model): db.session.add(copy) return copy - def set_ue_poids(self, ue, poids: float): + def set_ue_poids(self, ue, poids: float) -> None: """Set poids évaluation vers cette UE""" self.update_ue_poids_dict({ue.id: poids}) - def set_ue_poids_dict(self, ue_poids_dict: dict): + def set_ue_poids_dict(self, ue_poids_dict: dict) -> None: """set poids vers les UE (remplace existants) ue_poids_dict = { ue_id : poids } """ @@ -91,16 +91,23 @@ class Evaluation(db.Model): self.ue_poids = L self.moduleimpl.invalidate_evaluations_poids() # inval cache - def update_ue_poids_dict(self, ue_poids_dict: dict): + def update_ue_poids_dict(self, ue_poids_dict: dict) -> None: """update poids vers UE (ajoute aux existants)""" current = self.get_ue_poids_dict() current.update(ue_poids_dict) self.set_ue_poids_dict(current) - def get_ue_poids_dict(self): + def get_ue_poids_dict(self) -> dict: """returns { ue_id : poids }""" return {p.ue.id: p.poids for p in self.ue_poids} + def get_ue_poids_str(self) -> str: + """string describing poids, for excel cells and pdfs + Note: si les poids ne sont pas initialisés (poids par défaut), + ils ne sont pas affichés. + """ + return ", ".join([f"{p.ue.acronyme}: {p.poids}" for p in self.ue_poids]) + class EvaluationUEPoids(db.Model): """Poids des évaluations (BUT) diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 2b5ec2c4e..81df14d6a 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -33,6 +33,7 @@ import json from app.but import bulletin_but from app.models.formsemestre import FormSemestre +from app.models.etudiants import Identite import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -86,6 +87,7 @@ def formsemestre_bulletinetud_published_dict( from app.scodoc import sco_bulletins formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + etud = Identite.query.get(etudid) sem = sco_formsemestre.get_formsemestre(formsemestre_id) if formsemestre.formation.is_apc(): @@ -139,6 +141,11 @@ def formsemestre_bulletinetud_published_dict( if not published: return d # stop ! + etat_inscription = etud.etat_inscription(formsemestre.id) + if etat_inscription != scu.INSCRIT: + d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True)) + return d + # Groupes: partitions = sco_groups.get_partitions_list(formsemestre_id, with_default=False) partitions_etud_groups = {} # { partition_id : { etudid : group } } diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index cd5dcb7e3..cdc83a042 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -104,7 +104,10 @@ def do_ue_create(args): # check duplicates ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]}) if ues: - raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"]) + raise ScoValueError( + f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé ! + (chaque UE doit avoir un acronyme unique dans la formation)""" + ) # create ue_id = _ueEditor.create(cnx, args) @@ -471,7 +474,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list raise ScoValueError("invalid formation_id") parcours = formation.get_parcours() is_apc = parcours.APC_SAE - locked = sco_formations.formation_has_locked_sems(formation_id) + locked = formation.has_locked_sems() if semestre_idx == "all": semestre_idx = None else: @@ -541,7 +544,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list if locked: H.append( f"""
Cette formation est verrouillée car -{len(locked)} semestres verrouillés s'y réferent. +des semestres verrouillés s'y réferent. Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module), vous devez:
@@ -1146,7 +1149,10 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False): new_acro = args["acronyme"] ues = ue_list({"formation_id": ue["formation_id"], "acronyme": new_acro}) if ues and ues[0]["ue_id"] != ue_id: - raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"]) + raise ScoValueError( + f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé ! + (chaque UE doit avoir un acronyme unique dans la formation)""" + ) # On ne peut pas supprimer le code UE: if "ue_code" in args and not args["ue_code"]: diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index ab6cdf6fb..7d6bb82b1 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -109,30 +109,10 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= nb_inscrits = len( sco_groups.do_evaluation_listeetuds_groups(evaluation_id, getallstudents=True) ) - NotesDB = sco_evaluation_db.do_evaluation_get_all_notes( + etuds_notes_dict = sco_evaluation_db.do_evaluation_get_all_notes( evaluation_id - ) # { etudid : value } - notes = [x["value"] for x in NotesDB.values()] - nb_abs = len([x for x in notes if x is None]) - nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE]) - nb_att = len([x for x in notes if x == scu.NOTES_ATTENTE]) - moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes) - if moy_num is None: - median, moy = "", "" - median_num, moy_num = None, None - mini, maxi = "", "" - mini_num, maxi_num = None, None - else: - median = scu.fmt_note(median_num) - moy = scu.fmt_note(moy_num) - mini = scu.fmt_note(mini_num) - maxi = scu.fmt_note(maxi_num) - # cherche date derniere modif note - if len(NotesDB): - t = [x["date"] for x in NotesDB.values()] - last_modif = max(t) - else: - last_modif = None + ) # { etudid : note } + # ---- Liste des groupes complets et incomplets E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] @@ -163,8 +143,32 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= # Nombre de notes valides d'étudiants inscrits au module # (car il peut y avoir des notes d'étudiants désinscrits depuis l'évaluation) - nb_notes = len(insmodset.intersection(NotesDB)) - nb_notes_total = len(NotesDB) + etudids_avec_note = insmodset.intersection(etuds_notes_dict) + nb_notes = len(etudids_avec_note) + # toutes saisies, y compris chez des non-inscrits: + nb_notes_total = len(etuds_notes_dict) + + notes = [etuds_notes_dict[etudid]["value"] for etudid in etudids_avec_note] + nb_abs = len([x for x in notes if x is None]) + nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE]) + nb_att = len([x for x in notes if x == scu.NOTES_ATTENTE]) + moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes) + if moy_num is None: + median, moy = "", "" + median_num, moy_num = None, None + mini, maxi = "", "" + mini_num, maxi_num = None, None + else: + median = scu.fmt_note(median_num) + moy = scu.fmt_note(moy_num) + mini = scu.fmt_note(mini_num) + maxi = scu.fmt_note(maxi_num) + # cherche date derniere modif note + if len(etuds_notes_dict): + t = [x["date"] for x in etuds_notes_dict.values()] + last_modif = max(t) + else: + last_modif = None # On considere une note "manquante" lorsqu'elle n'existe pas # ou qu'elle est en attente (ATT) @@ -181,8 +185,8 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= groups[group["group_id"]] = group # isMissing = False - if i["etudid"] in NotesDB: - val = NotesDB[i["etudid"]]["value"] + if i["etudid"] in etuds_notes_dict: + val = etuds_notes_dict[i["etudid"]]["value"] if val == scu.NOTES_ATTENTE: isMissing = True TotalNbAtt += 1 diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index b2eaef981..6bff40c51 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -230,9 +230,10 @@ def formation_import_xml(doc: str, import_tags=True): ue_id = sco_edit_ue.do_ue_create(ue_info[1]) if xml_ue_id: ues_old2new[xml_ue_id] = ue_id - ue_reference = int(ue_info[1].get("reference")) + # élément optionnel présent dans les exports BUT: + ue_reference = ue_info[1].get("reference") if ue_reference: - ue_reference_to_id[ue_reference] = ue_id + ue_reference_to_id[int(ue_reference)] = ue_id # -- create matieres for mat_info in ue_info[2]: assert mat_info[0] == "matiere" diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index 20ffa7a90..30cbec83d 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -66,7 +66,9 @@ def do_formsemestre_inscription_list(*args, **kw): def do_formsemestre_inscription_listinscrits(formsemestre_id): - """Liste les inscrits (état I) à ce semestre et cache le résultat""" + """Liste les inscrits (état I) à ce semestre et cache le résultat. + Result: [ { "etudid":, "formsemestre_id": , "etat": , "etape": }] + """ r = sco_cache.SemInscriptionsCache.get(formsemestre_id) if r is None: # retreive list diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index bff2283c7..290b03403 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -1210,6 +1210,7 @@ def formsemestre_tableau_modules( ) if mod.module_type in ( + None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs ModuleType.STANDARD, ModuleType.RESSOURCE, ModuleType.SAE, @@ -1249,7 +1250,7 @@ def formsemestre_tableau_modules( % (modimpl["moduleimpl_id"], nb_malus_notes) ) else: - raise ValueError("Invalid module_type") # a bug + raise ValueError(f"Invalid module_type {mod.module_type}") # a bug H.append("") return "\n".join(H) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index cfd331968..da46270d6 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -35,7 +35,6 @@ Optimisation possible: """ import collections import operator -import re import time from xml.etree import ElementTree @@ -45,6 +44,7 @@ import flask from flask import g, request from flask import url_for, make_response +from app import db from app.models.groups import Partition import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py index fd4181ea9..09e5480f7 100644 --- a/app/scodoc/sco_liste_notes.py +++ b/app/scodoc/sco_liste_notes.py @@ -32,6 +32,7 @@ import flask from flask import url_for, g, request from app import models +from app.models.evaluations import Evaluation from app.models.moduleimpls import ModuleImpl import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb @@ -53,35 +54,34 @@ from app.scodoc.gen_tables import GenTable from app.scodoc.htmlutils import histogram_notes -def do_evaluation_listenotes(): +def do_evaluation_listenotes( + evaluation_id=None, moduleimpl_id=None, format="html" +) -> str: """ - Affichage des notes d'une évaluation - - args: evaluation_id ou moduleimpl_id - (si moduleimpl_id, affiche toutes les évaluations du module) + Affichage des notes d'une évaluation (si evaluation_id) + ou de toutes les évaluations d'un module (si moduleimpl_id) """ mode = None - vals = scu.get_request_args() - if "evaluation_id" in vals: - evaluation_id = int(vals["evaluation_id"]) - mode = "eval" - evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id}) - if "moduleimpl_id" in vals and vals["moduleimpl_id"]: - moduleimpl_id = int(vals["moduleimpl_id"]) + if moduleimpl_id: mode = "module" evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) - if not mode: + elif evaluation_id: + mode = "eval" + evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id}) + else: raise ValueError("missing argument: evaluation or module") if not evals: return "Aucune évaluation !
" - format = vals.get("format", "html") E = evals[0] # il y a au moins une evaluation + modimpl = ModuleImpl.query.get(E["moduleimpl_id"]) # description de l'evaluation if mode == "eval": H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)] + page_title = f"Notes {E['description'] or modimpl.module.code}" else: H = [] + page_title = f"Notes {modimpl.module.code}" # groupes groups = sco_groups.do_evaluation_listegroupes( E["evaluation_id"], include_default=True @@ -187,7 +187,7 @@ def do_evaluation_listenotes(): is_submitted=True, # toujours "soumis" (démarre avec liste complète) ) if tf[0] == 0: - return "\n".join(H) + "\n" + tf[1] + return "\n".join(H) + "\n" + tf[1], page_title elif tf[0] == -1: return flask.redirect( "%s/Notes/moduleimpl_status?moduleimpl_id=%s" @@ -198,16 +198,19 @@ def do_evaluation_listenotes(): note_sur_20 = tf[2]["note_sur_20"] hide_groups = tf[2]["hide_groups"] with_emails = tf[2]["with_emails"] - return _make_table_notes( - tf[1], - evals, - format=format, - note_sur_20=note_sur_20, - anonymous_listing=anonymous_listing, - group_ids=tf[2]["group_ids"], - hide_groups=hide_groups, - with_emails=with_emails, - mode=mode, + return ( + _make_table_notes( + tf[1], + evals, + format=format, + note_sur_20=note_sur_20, + anonymous_listing=anonymous_listing, + group_ids=tf[2]["group_ids"], + hide_groups=hide_groups, + with_emails=with_emails, + mode=mode, + ), + page_title, ) @@ -393,6 +396,7 @@ def _make_table_notes( key_mgr, note_sur_20, keep_numeric, + format=format, ) columns_ids.append(e["evaluation_id"]) # @@ -406,7 +410,7 @@ def _make_table_notes( # Si module, ajoute la (les) "moyenne(s) du module: if mode == "module": if len(evals) > 1: - # Moyenne de l'étudant dans le module + # Moyenne de l'étudiant dans le module # Affichée même en APC à titre indicatif _add_moymod_column( sem["formsemestre_id"], @@ -635,6 +639,7 @@ def _add_eval_columns( K, note_sur_20, keep_numeric, + format="html", ): """Add eval e""" nb_notes = 0 @@ -643,6 +648,7 @@ def _add_eval_columns( sum_notes = 0 notes = [] # liste des notes numeriques, pour calcul histogramme uniquement evaluation_id = e["evaluation_id"] + e_o = Evaluation.query.get(evaluation_id) # XXX en attendant ré-écriture NotesDB = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) for row in rows: etudid = row["etudid"] @@ -704,9 +710,12 @@ def _add_eval_columns( row_coefs[evaluation_id] = "coef. %s" % e["coefficient"] if is_apc: - row_poids[evaluation_id] = _mini_table_eval_ue_poids( - evaluation_id, evals_poids, ues - ) + if format == "html": + row_poids[evaluation_id] = _mini_table_eval_ue_poids( + evaluation_id, evals_poids, ues + ) + else: + row_poids[evaluation_id] = e_o.get_ue_poids_str() if note_sur_20: nmax = 20.0 else: diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 921fbd113..c9f5aaa28 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -31,9 +31,10 @@ import time from flask import g, url_for from flask_login import current_user -from app.auth.models import User +from app.auth.models import User from app.models import ModuleImpl +from app.models.evaluations import Evaluation import app.scodoc.sco_utils as scu from app.scodoc.sco_permissions import Permission @@ -391,8 +392,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): ) H.append("""""" % tr_class_1) @@ -586,19 +586,35 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): H.append(""" | ||||||||||||||||||
""" % tr_class) - H.append(""" | ||||||||||||||||||
""" % tr_class) + if modimpl.module.is_apc(): + H.append( + f""" | { + evaluation.get_ue_poids_str()} | """ + ) + else: + H.append('') + H.append(""" | ||||||||||||||||
""") + H.append(""" | """) + if first_group and modimpl.module.is_apc(): + H.append( + f""" | { + evaluation.get_ue_poids_str()} | """ + ) + else: + H.append("""""") + first_group = False if gr_moyenne["group_name"] is None: name = "Tous" # tous else: name = "Groupe %s" % gr_moyenne["group_name"] H.append( - """ | %s | """ % name + """ | %s | """ % name ) if gr_moyenne["gr_nb_notes"] > 0: H.append("%(gr_moy)s" % gr_moyenne) @@ -637,6 +653,7 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): H.append("""""") H.append("") H.append(""" |