diff --git a/app/auth/cas.py b/app/auth/cas.py index 4aac61b16..b9584f83a 100644 --- a/app/auth/cas.py +++ b/app/auth/cas.py @@ -30,7 +30,7 @@ def after_cas_login(): flask.session.get("CAS_USERNAME"), ) if cas_id is not None: - user: User = User.query.filter_by(cas_id=cas_id).first() + user: User = User.query.filter_by(cas_id=str(cas_id)).first() if user and user.active: if user.cas_allow_login: current_app.logger.info(f"CAS: login {user.user_name}") diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index eb71373f1..fc680dbc7 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -285,9 +285,9 @@ class BulletinBUT: eval_notes[etud.id], note_max=e.note_max, ), - "min": fmt_note(notes_ok.min()), - "max": fmt_note(notes_ok.max()), - "moy": fmt_note(notes_ok.mean()), + "min": fmt_note(notes_ok.min(), note_max=e.note_max), + "max": fmt_note(notes_ok.max(), note_max=e.note_max), + "moy": fmt_note(notes_ok.mean(), note_max=e.note_max), }, "poids": poids, "url": url_for( diff --git a/app/scodoc/sco_apogee_reader.py b/app/scodoc/sco_apogee_reader.py index abe8e2bf3..1e7984336 100644 --- a/app/scodoc/sco_apogee_reader.py +++ b/app/scodoc/sco_apogee_reader.py @@ -78,6 +78,7 @@ from chardet import detect as chardet_detect from app import log from app.scodoc.sco_exceptions import ScoFormatError +from app.scodoc import sco_preferences APO_PORTAL_ENCODING = ( "utf8" # encodage du fichier CSV Apogée (était 'ISO-8859-1' avant jul. 2016) @@ -384,9 +385,12 @@ col_ids={pprint.pformat(self.col_ids)} """write apo CSV header on f (beginning of CSV until columns titles just after XX-APO_VALEURS-XX line) """ + remove_typ_res = sco_preferences.get_preference("export_res_remove_typ_res") for section, data in self.sections_str.items(): - if section != "XX-APO_VALEURS-XX": - # XXX TODO ici on va filtrer XX-APO_TYP_RES-XX + # ne recopie pas la section résultats, et en option supprime APO_TYP_RES + if (section != "XX-APO_VALEURS-XX") and ( + section != "XX-APO_TYP_RES-XX" or not remove_typ_res + ): f.write(data) f.write("XX-APO_VALEURS-XX" + APO_NEWLINE) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index fce11e1c1..2f9624698 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -144,7 +144,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): """ from app.scodoc import sco_abs - if not version in scu.BULLETINS_VERSIONS: + if version not in scu.BULLETINS_VERSIONS: raise ValueError("invalid version code !") prefs = sco_preferences.SemPreferences(formsemestre_id) diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 20ca1f410..a93f4392e 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -388,10 +388,10 @@ def _list_modimpls( if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]: etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) if prefs["bul_show_minmax_eval"]: - eval_dict["min"] = scu.fmt_note(etat["mini"]) - eval_dict["max"] = scu.fmt_note(etat["maxi"]) + eval_dict["min"] = etat["mini"] # chaine, sur 20 + eval_dict["max"] = etat["maxi"] if prefs["bul_show_moypromo"]: - eval_dict["moy"] = scu.fmt_note(etat["moy"]) + eval_dict["moy"] = etat["moy"] mod_dict["evaluation"].append(eval_dict) diff --git a/app/scodoc/sco_bulletins_standard.py b/app/scodoc/sco_bulletins_standard.py index c7c51135c..4a6bb2772 100644 --- a/app/scodoc/sco_bulletins_standard.py +++ b/app/scodoc/sco_bulletins_standard.py @@ -69,6 +69,7 @@ from app.scodoc import sco_groups from app.scodoc import sco_evaluations from app.scodoc import gen_tables + # Important: Le nom de la classe ne doit pas changer (bien le choisir), # car il sera stocké en base de données (dans les préférences) class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): @@ -685,10 +686,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]: etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"]) if prefs["bul_show_minmax_eval"]: - t["min"] = scu.fmt_note(etat["mini"]) - t["max"] = scu.fmt_note(etat["maxi"]) + t["min"] = etat["mini"] + t["max"] = etat["maxi"] if prefs["bul_show_moypromo"]: - t["moy"] = scu.fmt_note(etat["moy"]) + t["moy"] = etat["moy"] P.append(t) nbeval += 1 return nbeval diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 4626509f4..ed34842ab 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -97,11 +97,22 @@ def ListMedian(L): # -------------------------------------------------------------------- -def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition=False): - """donne infos sur l'état de l'évaluation - { nb_inscrits, nb_notes, nb_abs, nb_neutre, nb_att, - moyenne, mediane, mini, maxi, - date_last_modif, gr_complets, gr_incomplets, evalcomplete } +def do_evaluation_etat( + evaluation_id: int, partition_id: int = None, select_first_partition=False +) -> dict: + """Donne infos sur l'état de l'évaluation. + Ancienne fonction, lente: préférer ModuleImplResults pour tout calcul. + { + nb_inscrits : inscrits au module + nb_notes + nb_abs, + nb_neutre, + nb_att, + moy, median, mini, maxi : # notes, en chaine, sur 20 + last_modif: datetime, + gr_complets, gr_incomplets, + evalcomplete + } evalcomplete est vrai si l'eval est complete (tous les inscrits à ce module ont des notes) evalattente est vrai s'il ne manque que des notes en attente @@ -137,7 +148,7 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= insmod = sco_moduleimpl.do_moduleimpl_inscription_list( moduleimpl_id=E["moduleimpl_id"] ) - insmodset = set([x["etudid"] for x in insmod]) + insmodset = {x["etudid"] for x in insmod} # retire de insem ceux qui ne sont pas inscrits au module ins = [i for i in insem if i["etudid"] in insmodset] @@ -155,14 +166,13 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= 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 + maxi_num = 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) + moy = scu.fmt_note(moy_num, E["note_max"]) + mini = scu.fmt_note(mini_num, E["note_max"]) + maxi = scu.fmt_note(maxi_num, E["note_max"]) # cherche date derniere modif note if len(etuds_notes_dict): t = [x["date"] for x in etuds_notes_dict.values()] @@ -226,28 +236,22 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= # Calcul moyenne dans chaque groupe de TD gr_moyennes = [] # group : {moy,median, nb_notes} - for group_id in GrNotes.keys(): - notes = GrNotes[group_id] + for group_id, notes in GrNotes.items(): gr_moy, gr_median, gr_mini, gr_maxi = notes_moyenne_median_mini_maxi(notes) gr_moyennes.append( { "group_id": group_id, "group_name": groups[group_id]["group_name"], - "gr_moy_num": gr_moy, - "gr_moy": scu.fmt_note(gr_moy), - "gr_median_num": gr_median, - "gr_median": scu.fmt_note(gr_median), - "gr_mini": scu.fmt_note(gr_mini), - "gr_maxi": scu.fmt_note(gr_maxi), - "gr_mini_num": gr_mini, - "gr_maxi_num": gr_maxi, + "gr_moy": scu.fmt_note(gr_moy, E["note_max"]), + "gr_median": scu.fmt_note(gr_median, E["note_max"]), + "gr_mini": scu.fmt_note(gr_mini, E["note_max"]), + "gr_maxi": scu.fmt_note(gr_maxi, E["note_max"]), "gr_nb_notes": len(notes), "gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]), } ) gr_moyennes.sort(key=operator.itemgetter("group_name")) - # retourne mapping return { "evaluation_id": evaluation_id, "nb_inscrits": nb_inscrits, @@ -256,14 +260,11 @@ def do_evaluation_etat(evaluation_id, partition_id=None, select_first_partition= "nb_abs": nb_abs, "nb_neutre": nb_neutre, "nb_att": nb_att, - "moy": moy, - "moy_num": moy_num, + "moy": moy, # chaine formattée, sur 20 "median": median, "mini": mini, - "mini_num": mini_num, "maxi": maxi, - "maxi_num": maxi_num, - "median_num": median_num, + "maxi_num": maxi_num, # note maximale, en nombre "last_modif": last_modif, "gr_incomplets": gr_incomplets, "gr_moyennes": gr_moyennes, @@ -283,18 +284,19 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True): [ { 'coefficient': 1.0, 'description': 'QCM et cas pratiques', - 'etat': {'evalattente': False, + 'etat': { + 'evalattente': False, 'evalcomplete': True, 'evaluation_id': 'GEAEVAL82883', 'gr_incomplets': [], - 'gr_moyennes': [{'gr_median': '12.00', - 'gr_median_num' : 12., - 'gr_moy': '11.88', - 'gr_moy_num' : 11.88, - 'gr_nb_att': 0, - 'gr_nb_notes': 166, - 'group_id': 'GEAG266762', - 'group_name': None}], + 'gr_moyennes': [{ + 'gr_median': '12.00', # sur 20 + 'gr_moy': '11.88', + 'gr_nb_att': 0, + 'gr_nb_notes': 166, + 'group_id': 'GEAG266762', + 'group_name': None + }], 'groups': {'GEAG266762': {'etudid': 'GEAEID80603', 'group_id': 'GEAG266762', 'group_name': None, @@ -362,7 +364,7 @@ def _eval_etat(evals): if last_modif is not None: dates.append(e["etat"]["last_modif"]) - if len(dates): + if dates: dates = scu.sort_dates(dates) last_modif = dates[-1] # date de derniere modif d'une note dans un module else: diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index eaa3d9584..e7f47b0b4 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -646,26 +646,33 @@ def formsemestre_description_table( ects_str = ue.ects ue_info = { "UE": ue.acronyme, + "Code": "", "ects": ects_str, "Module": ue.titre, "_css_row_class": "table_row_ue", - "_UE_td_attrs": f'style="background-color: {ue.color} !important;"' - if ue.color - else "", } if use_ue_coefs: ue_info["Coef."] = ue.coefficient ue_info["Coef._class"] = "ue_coef" - rows.append(ue_info) + if ue.color: + for k in list(ue_info.keys()): + if not k.startswith("_"): + ue_info[ + f"_{k}_td_attrs" + ] = f'style="background-color: {ue.color} !important;"' + if not formsemestre.formation.is_apc(): + # n'affiche la ligne UE qu'en formation classique + # car l'UE de rattachement n'a pas d'intérêt en BUT + rows.append(ue_info) mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list( moduleimpl_id=modimpl.id ) enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants) - l = { + row = { "UE": modimpl.module.ue.acronyme, - "_UE_td_attrs": ue_info["_UE_td_attrs"], + "_UE_td_attrs": ue_info.get("_UE_td_attrs", ""), "Code": modimpl.module.code or "", "Module": modimpl.module.abbrev or modimpl.module.titre, "_Module_class": "scotext", @@ -692,26 +699,32 @@ def formsemestre_description_table( sum_coef += modimpl.module.coefficient coef_dict = modimpl.module.get_ue_coef_dict() for ue in ues: - l[f"ue_{ue.id}"] = coef_dict.get(ue.id, 0.0) or "" + row[f"ue_{ue.id}"] = coef_dict.get(ue.id, 0.0) or "" if with_parcours: - l["parcours"] = ", ".join( + row["parcours"] = ", ".join( sorted([pa.code for pa in modimpl.module.parcours]) ) - rows.append(l) + rows.append(row) if with_evals: # Ajoute lignes pour evaluations evals = nt.get_mod_evaluation_etat_list(modimpl.id) evals.reverse() # ordre chronologique # Ajoute etat: + eval_rows = [] for eval_dict in evals: e = eval_dict.copy() + e["_description_target"] = url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=e["evaluation_id"], + ) e["_jour_order"] = e["jour"].isoformat() e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else "" - e["UE"] = l["UE"] - e["_UE_td_attrs"] = l["_UE_td_attrs"] - e["Code"] = l["Code"] + e["UE"] = row["UE"] + e["_UE_td_attrs"] = row["_UE_td_attrs"] + e["Code"] = row["Code"] e["_css_row_class"] = "evaluation" e["Module"] = "éval." # Cosmetic: conversions pour affichage @@ -734,8 +747,9 @@ def formsemestre_description_table( e[f"ue_{ue_id}"] = poids or "" e[f"_ue_{ue_id}_class"] = "poids" e[f"_ue_{ue_id}_help"] = "poids vers l'UE" + eval_rows.append(e) - rows += evals + rows += eval_rows sums = {"_css_row_class": "moyenne sortbottom", "ects": sum_ects, "Coef.": sum_coef} rows.append(sums) diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index eefe92756..ba938fecd 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -207,6 +207,7 @@ PREF_CATEGORIES = ( ("apc", {"title": "BUT et Approches par Compétences"}), ("abs", {"title": "Suivi des absences", "related": ("bul",)}), ("portal", {"title": "Liaison avec portail (Apogée, etc)"}), + ("apogee", {"title": "Exports Apogée"}), ( "pdf", { @@ -745,7 +746,7 @@ class BasePreferences(object): "explanation": "remplissage maquettes export Apogée", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", "only_global": True, }, ), @@ -757,7 +758,7 @@ class BasePreferences(object): "explanation": "remplissage maquettes export Apogée", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", "only_global": True, }, ), @@ -769,7 +770,7 @@ class BasePreferences(object): "explanation": "remplissage maquettes export Apogée", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", "only_global": True, }, ), @@ -781,7 +782,7 @@ class BasePreferences(object): "explanation": "remplissage maquettes export Apogée", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", "only_global": True, }, ), @@ -793,7 +794,7 @@ class BasePreferences(object): "explanation": "si coché, exporte exporte étudiants même si pas décision de jury saisie (sinon laisse vide)", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", "only_global": True, }, ), @@ -805,7 +806,19 @@ class BasePreferences(object): "explanation": "si coché, exporte exporte étudiants en attente de ratrapage comme ATT (sinon laisse vide)", "input_type": "boolcheckbox", "labels": ["non", "oui"], - "category": "portal", + "category": "apogee", + "only_global": True, + }, + ), + ( + "export_res_remove_typ_res", + { + "initvalue": 0, + "title": "Ne pas recopier la section APO_TYP_RES", + "explanation": "si coché, ne réécrit pas la section APO_TYP_RES (rarement utile, utiliser avec précaution)", + "input_type": "boolcheckbox", + "labels": ["non", "oui"], + "category": "apogee", "only_global": True, }, ), diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index 7a4f21d5a..72168b839 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -351,7 +351,7 @@ def check_modif_user( # Unicité du cas_id if cas_id: - cas_users = User.query.filter_by(cas_id=cas_id).all() + cas_users = User.query.filter_by(cas_id=str(cas_id)).all() if edit: if cas_users and ( len(cas_users) > 1 or cas_users[0].user_name != user_name diff --git a/tools/etc/scodoc9.service b/tools/etc/scodoc9.service index 8028442cd..5745ade30 100644 --- a/tools/etc/scodoc9.service +++ b/tools/etc/scodoc9.service @@ -19,7 +19,6 @@ After=network.target User=scodoc Group=scodoc WorkingDirectory=/opt/scodoc -#Environment=FLASK_ENV=production ExecStart=/opt/scodoc/venv/bin/gunicorn -b localhost:8000 -w 4 --timeout 600 scodoc:app Restart=always