From 3d513bb1695121841da20d0d3faf63d436f99115 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 25 Aug 2023 17:58:57 +0200 Subject: [PATCH] =?UTF-8?q?Modification=20codage=20dates=20=C3=A9valuation?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/but/bulletin_but.py | 2 +- app/but/bulletin_but_xml_compat.py | 8 +- app/comp/moy_mod.py | 2 +- app/comp/res_common.py | 15 +- app/comp/res_compat.py | 42 ++- app/models/evaluations.py | 273 +++++++++++------- app/models/formsemestre.py | 16 + app/scodoc/sco_abs.py | 4 +- app/scodoc/sco_abs_views.py | 4 +- app/scodoc/sco_bulletins.py | 148 +++++----- app/scodoc/sco_bulletins_json.py | 76 ++--- app/scodoc/sco_bulletins_xml.py | 55 ++-- app/scodoc/sco_evaluation_check_abs.py | 127 ++++---- app/scodoc/sco_evaluation_db.py | 32 +- app/scodoc/sco_evaluation_edit.py | 53 ++-- app/scodoc/sco_evaluation_recap.py | 4 +- app/scodoc/sco_evaluations.py | 237 ++++++++------- app/scodoc/sco_excel.py | 3 +- app/scodoc/sco_formsemestre_edit.py | 6 +- app/scodoc/sco_formsemestre_status.py | 12 +- app/scodoc/sco_liste_notes.py | 24 +- app/scodoc/sco_moduleimpl_status.py | 13 +- app/scodoc/sco_placement.py | 4 +- app/scodoc/sco_saisie_notes.py | 12 +- app/scodoc/sco_undo_notes.py | 14 +- app/scodoc/sco_utils.py | 2 + app/tables/recap.py | 4 +- app/views/notes.py | 2 +- tests/api/setup_test_api.py | 72 +++++ tests/api/test_api_etudiants.py | 19 +- tests/api/test_api_formsemestre.py | 21 +- tests/api/tools_test_api.py | 4 +- tests/unit/sco_fake_gen.py | 17 +- tests/unit/setup.py | 4 +- tests/unit/test_abs_demijournee.py | 4 +- tests/unit/test_bulletin.py | 2 +- tests/unit/test_but_modules.py | 5 +- tests/unit/test_caches.py | 30 +- tests/unit/test_notes_modules.py | 83 ++++-- tests/unit/test_notes_rattrapage.py | 7 +- tests/unit/test_sco_basic.py | 12 +- tests/unit/yaml_setup.py | 2 +- .../fakedatabase/create_test_api_database.py | 3 +- 43 files changed, 844 insertions(+), 635 deletions(-) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index b9261c4fb2..2e556908fc 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -297,7 +297,7 @@ class BulletinBUT: ) if has_request_context() else "na", - # deprecated + # deprecated (supprimer avant #sco9.7) "date": e.date_debut.isoformat() if e.date_debut else None, "heure_debut": e.date_debut.time().isoformat("minutes") if e.date_debut diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py index faf932e6f5..45668ac59f 100644 --- a/app/but/bulletin_but_xml_compat.py +++ b/app/but/bulletin_but_xml_compat.py @@ -218,12 +218,8 @@ def bulletin_but_xml_compat( jour=e.date_debut.isoformat() if e.date_debut else "", - heure_debut=e.date_debut.time().isoformat("minutes") - if e.date_debut - else "", - heure_fin=e.date_fin.time().isoformat("minutes") - if e.date_fin - else "", + heure_debut=e.heure_debut(), + heure_fin=e.heure_fin(), ) x_mod.append(x_eval) try: diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index 11dd05f62b..4180c35e35 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -250,7 +250,7 @@ class ModuleImplResults: ).reshape(-1, 1) # was _list_notes_evals_titles - def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list: + def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list[Evaluation]: "Liste des évaluations complètes" return [ e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id] diff --git a/app/comp/res_common.py b/app/comp/res_common.py index 03b91e0cc3..175e38df8b 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -80,8 +80,8 @@ class ResultatsSemestre(ResultatsCache): self.moy_gen_rangs_by_group = None # virtual self.modimpl_inscr_df: pd.DataFrame = None "Inscriptions: row etudid, col modimlpl_id" - self.modimpls_results: ModuleImplResults = None - "Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }" + self.modimpls_results: dict[int, ModuleImplResults] = None + "Résultats de chaque modimpl (classique ou BUT)" self.etud_coef_ue_df = None """coefs d'UE effectifs pour chaque étudiant (pour form. classiques)""" self.modimpl_coefs_df: pd.DataFrame = None @@ -192,6 +192,17 @@ class ResultatsSemestre(ResultatsCache): *[mr.etudids_attente for mr in self.modimpls_results.values()] ) + # # Etat des évaluations + # # (se substitue à do_evaluation_etat, sans les moyennes par groupes) + # def get_evaluations_etats(evaluation_id: int) -> dict: + # """Renvoie dict avec les clés: + # last_modif + # nb_evals_completes + # nb_evals_en_cours + # nb_evals_vides + # attente + # """ + # --- JURY... def get_formsemestre_validations(self) -> ValidationsSemestre: """Load validations if not already stored, set attribute and return value""" diff --git a/app/comp/res_compat.py b/app/comp/res_compat.py index 88fcd095f3..fe41163091 100644 --- a/app/comp/res_compat.py +++ b/app/comp/res_compat.py @@ -16,7 +16,13 @@ from app import db, log from app.comp import moy_sem from app.comp.aux_stats import StatsMoyenne from app.comp.res_common import ResultatsSemestre -from app.models import Identite, FormSemestre, ModuleImpl, ScolarAutorisationInscription +from app.models import ( + Evaluation, + Identite, + FormSemestre, + ModuleImpl, + ScolarAutorisationInscription, +) from app.scodoc.codes_cursus import UE_SPORT, DEF from app.scodoc import sco_utils as scu @@ -389,7 +395,7 @@ class NotesTableCompat(ResultatsSemestre): "ects_total": ects_total, } - def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]: + def get_modimpl_evaluations_completes(self, moduleimpl_id: int) -> list[Evaluation]: """Liste d'informations (compat NotesTable) sur évaluations completes de ce module. Évaluation "complete" ssi toutes notes saisies ou en attente. @@ -398,34 +404,24 @@ class NotesTableCompat(ResultatsSemestre): modimpl_results = self.modimpls_results.get(moduleimpl_id) if not modimpl_results: return [] # safeguard - evals_results = [] + evaluations = [] for e in modimpl.evaluations: if modimpl_results.evaluations_completes_dict.get(e.id, False): - d = e.to_dict() - d["heure_debut"] = e.heure_debut # datetime.time - d["heure_fin"] = e.heure_fin - d["jour"] = e.jour # datetime - d["notes"] = { - etud.id: { - "etudid": etud.id, - "value": modimpl_results.evals_notes[e.id][etud.id], - } - for etud in self.etuds - } - d["etat"] = { - "evalattente": modimpl_results.evaluations_etat[e.id].nb_attente, - } - evals_results.append(d) + evaluations.append(e) elif e.id not in modimpl_results.evaluations_completes_dict: # ne devrait pas arriver ? XXX log( - f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?" + f"Warning: 220213 get_modimpl_evaluations_completes {e.id} not in mod {moduleimpl_id} ?" ) - return evals_results + return evaluations + + def get_evaluations_etats(self) -> list[dict]: + """Liste de toutes les évaluations du semestre + [ {...evaluation et son etat...} ]""" + # TODO: à moderniser (voir dans ResultatsSemestre) + # utilisé par + # do_evaluation_etat_in_sem - def get_evaluations_etats(self): - """[ {...evaluation et son etat...} ]""" - # TODO: à moderniser from app.scodoc import sco_evaluations if not hasattr(self, "_evaluations_etats"): diff --git a/app/models/evaluations.py b/app/models/evaluations.py index deb4bc263e..249900802e 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -18,8 +18,8 @@ from app.models.ues import UniteEns from app.scodoc import sco_cache from app.scodoc.sco_exceptions import AccessDenied, ScoValueError -import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu +from app.scodoc.sco_xml import quote_xml_attr MAX_EVALUATION_DURATION = datetime.timedelta(days=365) NOON = datetime.time(12, 00) @@ -46,6 +46,7 @@ class Evaluation(db.Model): visibulletin = db.Column( db.Boolean, nullable=False, default=True, server_default="true" ) + "visible sur les bulletins version intermédiaire" publish_incomplete = db.Column( db.Boolean, nullable=False, default=False, server_default="false" ) @@ -67,9 +68,8 @@ class Evaluation(db.Model): def create( cls, moduleimpl: ModuleImpl = None, - jour=None, - heure_debut=None, - heure_fin=None, + date_debut: datetime.datetime = None, + date_fin: datetime.datetime = None, description=None, note_max=None, coefficient=None, @@ -124,7 +124,7 @@ class Evaluation(db.Model): next_eval = None t = date_debut for e in evaluations: - if e.date_debut > t: + if e.date_debut and e.date_debut > t: next_eval = e break if next_eval: @@ -140,26 +140,26 @@ class Evaluation(db.Model): def to_dict(self) -> dict: "Représentation dict (riche, compat ScoDoc 7)" - e = dict(self.__dict__) - e.pop("_sa_instance_state", None) + e_dict = dict(self.__dict__) + e_dict.pop("_sa_instance_state", None) # ScoDoc7 output_formators - e["evaluation_id"] = self.id - e["date_debut"] = e.date_debut.isoformat() if e.date_debut else None - e["date_fin"] = e.date_debut.isoformat() if e.date_fin else None - e["numero"] = ndb.int_null_is_zero(e["numero"]) - e["poids"] = self.get_ue_poids_dict() # { ue_id : poids } + e_dict["evaluation_id"] = self.id + e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None + e_dict["date_fin"] = self.date_debut.isoformat() if self.date_fin else None + e_dict["numero"] = self.numero or 0 + e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids } # Deprecated - e["jour"] = e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "" + e_dict["jour"] = self.date_debut.strftime("%d/%m/%Y") if self.date_debut else "" - return evaluation_enrich_dict(e) + return evaluation_enrich_dict(self, e_dict) def to_dict_api(self) -> dict: "Représentation dict pour API JSON" return { "coefficient": self.coefficient, - "date_debut": self.date_debut.isoformat(), - "date_fin": self.date_fin.isoformat(), + "date_debut": self.date_debut.isoformat() if self.date_debut else "", + "date_fin": self.date_fin.isoformat() if self.date_fin else "", "description": self.description, "evaluation_type": self.evaluation_type, "id": self.id, @@ -168,14 +168,36 @@ class Evaluation(db.Model): "numero": self.numero, "poids": self.get_ue_poids_dict(), "publish_incomplete": self.publish_incomplete, - "visi_bulletin": self.visibulletin, + "visibulletin": self.visibulletin, + # Deprecated (supprimer avant #sco9.7) + "date": self.date_debut.date().isoformat() if self.date_debut else "", + "heure_debut": self.date_debut.time().isoformat() + if self.date_debut + else "", + "heure_fin": self.date_fin.time().isoformat() if self.date_fin else "", } + def to_dict_bul(self) -> dict: + "dict pour les bulletins json" + # c'est la version API avec quelques champs legacy en plus + e_dict = self.to_dict_api() + # Pour les bulletins (json ou xml), quote toujours la description + e_dict["description"] = quote_xml_attr(self.description or "") + # deprecated fields: + e_dict["evaluation_id"] = self.id + e_dict["jour"] = e_dict["date_debut"] # chaine iso + e_dict["heure_debut"] = ( + self.date_debut.time().isoformat() if self.date_debut else "" + ) + e_dict["heure_fin"] = self.date_fin.time().isoformat() if self.date_fin else "" + + return e_dict + def from_dict(self, data): """Set evaluation attributes from given dict values.""" check_convert_evaluation_args(self.moduleimpl, data) if data.get("numero") is None: - data["numero"] = Evaluation.get_max_numero() + 1 + data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1 for k in self.__dict__.keys(): if k != "_sa_instance_state" and k != "id" and k in data: setattr(self, k, data[k]) @@ -217,28 +239,64 @@ class Evaluation(db.Model): db.session.commit() def descr_heure(self) -> str: - "Description de la plage horaire pour affichages" - if self.heure_debut and ( - not self.heure_fin or self.heure_fin == self.heure_debut - ): - return f"""à {self.heure_debut.strftime("%Hh%M")}""" - elif self.heure_debut and self.heure_fin: - return f"""de {self.heure_debut.strftime("%Hh%M")} à {self.heure_fin.strftime("%Hh%M")}""" + "Description de la plage horaire pour affichages ('de 13h00 à 14h00')" + if self.date_debut and (not self.date_fin or self.date_fin == self.date_debut): + return f"""à {self.date_debut.strftime("%Hh%M")}""" + elif self.date_debut and self.date_fin: + return f"""de {self.date_debut.strftime("%Hh%M") + } à {self.date_fin.strftime("%Hh%M")}""" else: return "" def descr_duree(self) -> str: - "Description de la durée pour affichages" - if self.heure_debut is None and self.heure_fin is None: + "Description de la durée pour affichages ('3h' ou '2h30')" + if self.date_debut is None or self.date_fin is None: return "" - debut = self.heure_debut or DEFAULT_EVALUATION_TIME - fin = self.heure_fin or DEFAULT_EVALUATION_TIME - d = (fin.hour * 60 + fin.minute) - (debut.hour * 60 + debut.minute) - duree = f"{d//60}h" - if d % 60: - duree += f"{d%60:02d}" + minutes = (self.date_fin - self.date_debut).seconds // 60 + duree = f"{minutes // 60}h" + minutes = minutes % 60 + if minutes != 0: + duree += f"{minutes:02d}" return duree + def descr_date(self) -> str: + """Description de la date pour affichages + 'sans date' + 'le 21/9/2021 à 13h' + 'le 21/9/2021 de 13h à 14h30' + 'du 21/9/2021 à 13h30 au 23/9/2021 à 15h' + """ + if self.date_debut is None: + return "sans date" + + def _h(dt: datetime.datetime) -> str: + if dt.minute: + return dt.strftime("%Hh%M") + return f"{dt.hour}h" + + if self.date_fin is None: + return ( + f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}" + ) + if self.date_debut.date() == self.date_fin.date(): # même jour + if self.date_debut.time() == self.date_fin.time(): + return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}" + return f"""le {self.date_debut.strftime('%d/%m/%Y')} de { + _h(self.date_debut())} à {_h(self.date_fin())}""" + # évaluation sur plus d'une journée + return f"""du {self.date_debut.strftime('%d/%m/%Y')} à { + _h(self.date_debut())} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin())}""" + + def heure_debut(self) -> str: + """L'heure de début (sans la date), en ISO. + Chaine vide si non renseignée.""" + return self.date_debut.time().isoformat("minutes") if self.date_debut else "" + + def heure_fin(self) -> str: + """L'heure de fin (sans la date), en ISO. + Chaine vide si non renseignée.""" + return self.date_fin.time().isoformat("minutes") if self.date_fin else "" + def clone(self, not_copying=()): """Clone, not copying the given attrs Attention: la copie n'a pas d'id avant le prochain commit @@ -381,41 +439,28 @@ class EvaluationUEPoids(db.Model): return f"" -# Fonction héritée de ScoDoc7 à refactorer -def evaluation_enrich_dict(e: dict): +# Fonction héritée de ScoDoc7 +def evaluation_enrich_dict(e: Evaluation, e_dict: dict): """add or convert some fields in an evaluation dict""" # For ScoDoc7 compat - heure_debut_dt = e["date_debut"].time() - heure_fin_dt = e["date_fin"].time() - e["heure_debut"] = heure_debut_dt.strftime("%Hh%M") - e["heure_fin"] = heure_fin_dt.strftime("%Hh%M") - e["jour_iso"] = e["date_debut"].isoformat() # XXX - heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] - d = _time_duration_HhM(heure_debut, heure_fin) - if d is not None: - m = d % 60 - e["duree"] = "%dh" % (d / 60) - if m != 0: - e["duree"] += "%02d" % m - else: - e["duree"] = "" - if heure_debut and (not heure_fin or heure_fin == heure_debut): - e["descrheure"] = " à " + heure_debut - elif heure_debut and heure_fin: - e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin) - else: - e["descrheure"] = "" + e_dict["heure_debut"] = e.date_debut.strftime("%Hh%M") if e.date_debut else "" + e_dict["heure_fin"] = e.date_fin.strftime("%Hh%M") if e.date_fin else "" + e_dict["jour_iso"] = e.date_debut.isoformat() if e.date_debut else "" + # Calcule durée en minutes + e_dict["descrheure"] = e.descr_heure() + e_dict["descrduree"] = e.descr_duree() # matin, apresmidi: utile pour se referer aux absences: - - if e["jour"] and heure_debut_dt < datetime.time(12, 00): - e["matin"] = 1 + # note août 2023: si l'évaluation s'étend sur plusieurs jours, + # cet indicateur n'a pas grand sens + if e.date_debut and e.date_debut.time() < datetime.time(12, 00): + e_dict["matin"] = 1 else: - e["matin"] = 0 - if e["jour"] and heure_fin_dt > datetime.time(12, 00): - e["apresmidi"] = 1 + e_dict["matin"] = 0 + if e.date_fin and e.date_fin.time() > datetime.time(12, 00): + e_dict["apresmidi"] = 1 else: - e["apresmidi"] = 0 - return e + e_dict["apresmidi"] = 0 + return e_dict def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict): @@ -426,71 +471,87 @@ def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict): May raise ScoValueError. """ # --- description - description = data.get("description", "") - if len(description) > scu.MAX_TEXT_LEN: + data["description"] = data.get("description", "") or "" + if len(data["description"]) > scu.MAX_TEXT_LEN: raise ScoValueError("description too large") + # --- evaluation_type try: data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0) if not data["evaluation_type"] in VALID_EVALUATION_TYPES: - raise ScoValueError("Invalid evaluation_type value") - except ValueError: - raise ScoValueError("Invalid evaluation_type value") + raise ScoValueError("invalid evaluation_type value") + except ValueError as exc: + raise ScoValueError("invalid evaluation_type value") from exc # --- note_max (bareme) note_max = data.get("note_max", 20.0) or 20.0 try: note_max = float(note_max) - except ValueError: - raise ScoValueError("Invalid note_max value") + except ValueError as exc: + raise ScoValueError("invalid note_max value") from exc if note_max < 0: - raise ScoValueError("Invalid note_max value (must be positive or null)") + raise ScoValueError("invalid note_max value (must be positive or null)") data["note_max"] = note_max # --- coefficient coef = data.get("coefficient", 1.0) or 1.0 try: coef = float(coef) - except ValueError: - raise ScoValueError("Invalid coefficient value") + except ValueError as exc: + raise ScoValueError("invalid coefficient value") from exc if coef < 0: - raise ScoValueError("Invalid coefficient value (must be positive or null)") + raise ScoValueError("invalid coefficient value (must be positive or null)") data["coefficient"] = coef - # --- jour (date de l'évaluation) - jour = data.get("jour", None) - if jour and not isinstance(jour, datetime.date): - if date_format == "dmy": - y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")] - jour = datetime.date(y, m, d) - else: # ISO - jour = datetime.date.fromisoformat(jour) - formsemestre = moduleimpl.formsemestre - if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut): + # --- date de l'évaluation + formsemestre = moduleimpl.formsemestre + date_debut = data.get("date_debut", None) + if date_debut: + if isinstance(date_debut, str): + data["date_debut"] = datetime.datetime.fromisoformat(date_debut) + if data["date_debut"].tzinfo is None: + data["date_debut"] = scu.TIME_ZONE.localize(data["date_debut"]) + if not ( + formsemestre.date_debut + <= data["date_debut"].date() + <= formsemestre.date_fin + ): raise ScoValueError( - f"""La date de l'évaluation ({jour.strftime("%d/%m/%Y")}) n'est pas dans le semestre !""", + f"""La date de début de l'évaluation ({ + data["date_debut"].strftime("%d/%m/%Y") + }) n'est pas dans le semestre !""", dest_url="javascript:history.back();", ) - data["jour"] = jour - # --- heures - heure_debut = data.get("heure_debut", None) - if heure_debut and not isinstance(heure_debut, datetime.time): - if date_format == "dmy": - data["heure_debut"] = heure_to_time(heure_debut) - else: # ISO - data["heure_debut"] = datetime.time.fromisoformat(heure_debut) - heure_fin = data.get("heure_fin", None) - if heure_fin and not isinstance(heure_fin, datetime.time): - if date_format == "dmy": - data["heure_fin"] = heure_to_time(heure_fin) - else: # ISO - data["heure_fin"] = datetime.time.fromisoformat(heure_fin) - if jour and ((not heure_debut) or (not heure_fin)): - raise ScoValueError("Les heures doivent être précisées") - if heure_debut and heure_fin: - duration = ((data["heure_fin"].hour * 60) + data["heure_fin"].minute) - ( - (data["heure_debut"].hour * 60) + data["heure_debut"].minute - ) - if duration < 0 or duration > 60 * 12: + date_fin = data.get("date_fin", None) + if date_fin: + if isinstance(date_fin, str): + data["date_fin"] = datetime.datetime.fromisoformat(date_fin) + if data["date_fin"].tzinfo is None: + data["date_fin"] = scu.TIME_ZONE.localize(data["date_fin"]) + if not ( + formsemestre.date_debut <= data["date_fin"].date() <= formsemestre.date_fin + ): + raise ScoValueError( + f"""La date de fin de l'évaluation ({ + data["date_fin"].strftime("%d/%m/%Y") + }) n'est pas dans le semestre !""", + dest_url="javascript:history.back();", + ) + if date_debut and date_fin: + duration = data["date_fin"] - data["date_debut"] + if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION: raise ScoValueError("Heures de l'évaluation incohérentes !") + # # --- heures + # heure_debut = data.get("heure_debut", None) + # if heure_debut and not isinstance(heure_debut, datetime.time): + # if date_format == "dmy": + # data["heure_debut"] = heure_to_time(heure_debut) + # else: # ISO + # data["heure_debut"] = datetime.time.fromisoformat(heure_debut) + # heure_fin = data.get("heure_fin", None) + # if heure_fin and not isinstance(heure_fin, datetime.time): + # if date_format == "dmy": + # data["heure_fin"] = heure_to_time(heure_fin) + # else: # ISO + # data["heure_fin"] = datetime.time.fromisoformat(heure_fin) def heure_to_time(heure: str) -> datetime.time: diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index e4690a06a2..677bfb1789 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -30,6 +30,7 @@ from app.models.but_refcomp import ( ) from app.models.config import ScoDocSiteConfig from app.models.etudiants import Identite +from app.models.evaluations import Evaluation from app.models.formations import Formation from app.models.groups import GroupDescr, Partition from app.models.moduleimpls import ModuleImpl, ModuleImplInscription @@ -350,6 +351,21 @@ class FormSemestre(db.Model): _cache[key] = ues return ues + def get_evaluations(self) -> list[Evaluation]: + "Liste de toutes les évaluations du semestre, triées par module/numero" + return ( + Evaluation.query.join(ModuleImpl) + .filter_by(formsemestre_id=self.id) + .join(Module) + .order_by( + Module.numero, + Module.code, + Evaluation.numero, + Evaluation.date_debut.desc(), + ) + .all() + ) + @cached_property def modimpls_sorted(self) -> list[ModuleImpl]: """Liste des modimpls du semestre (y compris bonus) diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py index 8a04cac276..8ef9de03ac 100755 --- a/app/scodoc/sco_abs.py +++ b/app/scodoc/sco_abs.py @@ -509,7 +509,7 @@ def _get_abs_description(a, cursor=None): return "" -def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None): +def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None) -> list[dict]: """Liste des absences et/ou justificatifs ce jour. is_abs: None (peu importe), True, False is_just: idem @@ -535,7 +535,7 @@ WHERE A.jour = %(date)s return A -def list_abs_non_just_jour(date, am=True, pm=True): +def list_abs_non_just_jour(date, am=True, pm=True) -> list[dict]: "Liste des absences non justifiees ce jour" cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py index 4af828c393..c6f99a2b90 100644 --- a/app/scodoc/sco_abs_views.py +++ b/app/scodoc/sco_abs_views.py @@ -972,12 +972,12 @@ def _tables_abs_etud( FROM notes_evaluation eval, notes_moduleimpl_inscription mi, notes_moduleimpl m - WHERE eval.jour = %(jour)s + WHERE DATE(eval.date_debut) = %(date_debut)s and eval.moduleimpl_id = m.id and mi.moduleimpl_id = m.id and mi.etudid = %(etudid)s """, - {"jour": a["jour"].strftime("%Y-%m-%d"), "etudid": etudid}, + {"date_debut": a["jour"], "etudid": etudid}, ) a["evals"] = cursor.dictfetchall() cursor.execute( diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 1596a11fb8..cdd8eb7611 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -47,6 +47,7 @@ from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_compat import NotesTableCompat from app.models import ( ApcParcours, + Evaluation, Formation, FormSemestre, Identite, @@ -482,6 +483,7 @@ def _ue_mod_bulletin( mods = [] # result ue_attente = False # true si une eval en attente dans cette UE for modimpl in ue_modimpls: + modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"]) mod_attente = False mod = modimpl.copy() mod_moy = nt.get_etud_mod_moy( @@ -531,10 +533,13 @@ def _ue_mod_bulletin( scu.fmt_coef(modimpl["module"]["coefficient"]), sco_users.user_info(modimpl["responsable_id"])["nomcomplet"], ) - link_mod = ( - '' - % (modimpl["moduleimpl_id"], mod["mod_descr_txt"]) - ) + link_mod = f"""""" + if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id): mod["code"] = modimpl["module"]["code"] mod["code_html"] = link_mod + (mod["code"] or "") + "" @@ -561,91 +566,88 @@ def _ue_mod_bulletin( mod["code_txt"] = "" mod["code_html"] = "" # Evaluations: notes de chaque eval - evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"]) + evaluations_completes = nt.get_modimpl_evaluations_completes( + modimpl["moduleimpl_id"] + ) + # On liste séparément les éval. complètes ou non mod["evaluations"] = [] - for e in evals: - e = e.copy() - if e["visibulletin"] or version == "long": - # affiche "bonus" quand les points de malus sont négatifs - if is_malus: - val = e["notes"].get(etudid, {"value": "NP"})[ - "value" - ] # NA si etud demissionnaire - if val == "NP" or val > 0: - e["name"] = "Points de malus sur cette UE" - else: - e["name"] = "Points de bonus sur cette UE" + mod["evaluations_incompletes"] = [] + complete_eval_ids = {e.id for e in evaluations_completes} + all_evals: list[Evaluation] = Evaluation.query.filter_by( + moduleimpl_id=modimpl["moduleimpl_id"] + ).order_by(Evaluation.numero, Evaluation.date_debut) + # (plus ancienne d'abord) + for e in all_evals: + if not e.visibulletin and version != "long": + continue + is_complete = e.id in complete_eval_ids + e_dict = e.to_dict_bul() + # Note à l'évaluation: + val = modimpl_results.evals_notes[e.id].get(etudid, "NP") + # Affiche "bonus" quand les points de malus sont négatifs + if is_malus: + if val == "NP": + e_dict["name"] = "Points de bonus/malus sur cette UE" + elif val > 0: + e_dict["name"] = "Points de malus sur cette UE" else: - e["name"] = e["description"] or f"le {e['jour']}" - e["target_html"] = url_for( + e_dict["name"] = "Points de bonus sur cette UE" + else: + e_dict[ + "name" + ] = f"""{e.description or ""} { + e.descr_date() + if e.date_debut and not is_complete + else ""}""" + e_dict["target_html"] = url_for( "notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, - evaluation_id=e["evaluation_id"], + evaluation_id=e.id, format="html", tf_submitted=1, ) - e[ + e_dict[ "name_html" ] = f"""{e['name']}""" - val = e["notes"].get(etudid, {"value": "NP"})["value"] - # val est NP si etud demissionnaire - if val == "NP": - e["note_txt"] = "nd" - e["note_html"] = 'nd' - e["coef_txt"] = scu.fmt_coef(e["coefficient"]) - else: - # (-0.15) s'affiche "bonus de 0.15" - if is_malus: - val = abs(val) - e["note_txt"] = scu.fmt_note(val, note_max=e["note_max"]) - e["note_html"] = e["note_txt"] - if is_malus: - e["coef_txt"] = "" + e_dict['target_html']}">{e_dict['name']}""" + if is_complete: # évaluation complète + # val est NP si etud demissionnaire + if val == "NP": + e_dict["note_txt"] = "nd" + e_dict["note_html"] = 'nd' + e_dict["coef_txt"] = scu.fmt_coef(e["coefficient"]) else: - e["coef_txt"] = scu.fmt_coef(e["coefficient"]) - if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE: - e["coef_txt"] = "rat." - elif e["evaluation_type"] == scu.EVALUATION_SESSION2: - e["coef_txt"] = "Ses. 2" - if e["etat"]["evalattente"]: + # (-0.15) s'affiche "bonus de 0.15" + if is_malus: + val = abs(val) + e_dict["note_txt"] = e_dict["note_html"] = scu.fmt_note( + val, note_max=e.note_max + ) + else: # évaluation incomplète: pas de note + e_dict["note_txt"] = e_dict["note_html"] = "" + + if is_malus: + e_dict["coef_txt"] = "" + else: + e_dict["coef_txt"] = scu.fmt_coef(e.coefficient) + if e.evaluation_type == scu.EVALUATION_RATTRAPAGE: + e_dict["coef_txt"] = "rat." + elif e.evaluation_type == scu.EVALUATION_SESSION2: + e_dict["coef_txt"] = "Ses. 2" + + if modimpl_results.evaluations_etat[e.id].nb_attente: mod_attente = True # une eval en attente dans ce module + if ((not is_malus) or (val != "NP")) and ( - ( - e["evaluation_type"] == scu.EVALUATION_NORMALE - or not np.isnan(val) - ) + (e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val)) ): # ne liste pas les eval malus sans notes # ni les rattrapages et sessions 2 si pas de note - mod["evaluations"].append(e) + if e.id in complete_eval_ids: + mod["evaluations"].append(e_dict) + else: + mod["evaluations_incompletes"].append(e_dict) - # Evaluations incomplètes ou futures: - mod["evaluations_incompletes"] = [] - if sco_preferences.get_preference("bul_show_all_evals", formsemestre_id): - complete_eval_ids = set([e["evaluation_id"] for e in evals]) - all_evals = sco_evaluation_db.do_evaluation_list( - args={"moduleimpl_id": modimpl["moduleimpl_id"]} - ) - all_evals.reverse() # plus ancienne d'abord - for e in all_evals: - if e["evaluation_id"] not in complete_eval_ids: - e = e.copy() - mod["evaluations_incompletes"].append(e) - e["name"] = (e["description"] or "") + " (%s)" % e["jour"] - e["target_html"] = url_for( - "notes.evaluation_listenotes", - scodoc_dept=g.scodoc_dept, - evaluation_id=e["evaluation_id"], - tf_submitted=1, - format="html", - ) - e["name_html"] = '%s' % ( - e["target_html"], - e["name"], - ) - e["note_txt"] = e["note_html"] = "" - e["coef_txt"] = scu.fmt_coef(e["coefficient"]) # Classement if ( bul_show_mod_rangs diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index 53849f045c..9bb83358b7 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import but_validations -from app.models import Evaluation, Matiere, ModuleImpl, UniteEns +from app.models import Evaluation, Matiere, UniteEns from app.models.etudiants import Identite from app.models.formsemestre import FormSemestre @@ -46,7 +46,6 @@ import app.scodoc.notesdb as ndb from app.scodoc import sco_assiduites from app.scodoc import sco_edit_ue from app.scodoc import sco_evaluations -from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_photos @@ -333,6 +332,7 @@ def _list_modimpls( mod_moy = scu.fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)) if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit continue + modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"]) mod = modimpl["module"] # if mod['ects'] is None: # ects = '' @@ -363,66 +363,42 @@ def _list_modimpls( mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1]) # --- notes de chaque eval: - evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"]) + evaluations_completes = nt.get_modimpl_evaluations_completes( + modimpl["moduleimpl_id"] + ) mod_dict["evaluation"] = [] if version != "short": - for e in evals: - if e["visibulletin"] or version == "long": - val = e["notes"].get(etudid, {"value": "NP"})["value"] + for e in evaluations_completes: + if e.visibulletin or version == "long": + # Note à l'évaluation: + val = modimpl_results.evals_notes[e.id].get(etudid, "NP") # nb: val est NA si etud démissionnaire - val = scu.fmt_note(val, note_max=e["note_max"]) - eval_dict = dict( - jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True), - heure_debut=ndb.TimetoISO8601( - e["heure_debut"], null_is_empty=True - ), - heure_fin=ndb.TimetoISO8601(e["heure_fin"], null_is_empty=True), - coefficient=e["coefficient"], - evaluation_type=e["evaluation_type"], - # CM : ajout pour permettre de faire le lien sur - # les bulletins en ligne avec l'évaluation: - evaluation_id=e["evaluation_id"], - description=quote_xml_attr(e["description"]), - note=val, - ) - 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"] = etat["mini"] # chaine, sur 20 - eval_dict["max"] = etat["maxi"] - if prefs["bul_show_moypromo"]: - eval_dict["moy"] = etat["moy"] + e_dict = e.to_dict_bul() + e_dict["note"] = scu.fmt_note(val, note_max=e.note_max) - mod_dict["evaluation"].append(eval_dict) + if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]: + # XXX à revoir pour utiliser modimplresult + etat = sco_evaluations.do_evaluation_etat(e.id) + if prefs["bul_show_minmax_eval"]: + e_dict["min"] = etat["mini"] # chaine, sur 20 + e_dict["max"] = etat["maxi"] + if prefs["bul_show_moypromo"]: + e_dict["moy"] = etat["moy"] + + mod_dict["evaluation"].append(e_dict) # Evaluations incomplètes ou futures: - complete_eval_ids = set([e["evaluation_id"] for e in evals]) + complete_eval_ids = {e.id for e in evaluations_completes} if prefs["bul_show_all_evals"]: - evaluations = Evaluation.query.filter_by( + evaluations: list[Evaluation] = Evaluation.query.filter_by( moduleimpl_id=modimpl["moduleimpl_id"] ).order_by(Evaluation.date_debut) # plus ancienne d'abord for e in evaluations: if e.id not in complete_eval_ids: - mod_dict["evaluation"].append( - dict( - date_debut=e.date_debut.isoformat() - if e.date_debut - else None, - date_fin=e.date_fin.isoformat() if e.date_fin else None, - coefficient=e.coefficient, - description=quote_xml_attr(e.description or ""), - incomplete="1", - # Deprecated: - jour=e.date_debut.isoformat() if e.date_debut else "", - heure_debut=ndb.TimetoISO8601( - e["heure_debut"], null_is_empty=True - ), - heure_fin=ndb.TimetoISO8601( - e["heure_fin"], null_is_empty=True - ), - ) - ) + e_dict = e.to_dict_bul() + e_dict["incomplete"] = 1 + mod_dict["evaluation"].append(e_dict) modules_dict.append(mod_dict) return modules_dict diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py index ca32ec46ac..4d96009b93 100644 --- a/app/scodoc/sco_bulletins_xml.py +++ b/app/scodoc/sco_bulletins_xml.py @@ -242,6 +242,7 @@ def make_xml_formsemestre_bulletinetud( # Liste les modules de l'UE ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]] for modimpl in ue_modimpls: + modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"]) mod_moy = scu.fmt_note( nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid) ) @@ -290,33 +291,24 @@ def make_xml_formsemestre_bulletinetud( ) ) # --- notes de chaque eval: - evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"]) + evaluations_completes = nt.get_modimpl_evaluations_completes( + modimpl["moduleimpl_id"] + ) if version != "short": - for e in evals: - if e["visibulletin"] or version == "long": - x_eval = Element( - "evaluation", - jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True), - heure_debut=ndb.TimetoISO8601( - e["heure_debut"], null_is_empty=True - ), - heure_fin=ndb.TimetoISO8601( - e["heure_fin"], null_is_empty=True - ), - coefficient=str(e["coefficient"]), - evaluation_type=str(e["evaluation_type"]), - description=quote_xml_attr(e["description"]), - # notes envoyées sur 20, ceci juste pour garder trace: - note_max_origin=str(e["note_max"]), - ) + for e in evaluations_completes: + if e.visibulletin or version == "long": + # pour xml, tout convertir en chaines + e_dict = {k: str(v) for k, v in e.to_dict_bul().items()} + # notes envoyées sur 20, ceci juste pour garder trace: + e_dict["note_max_origin"] = str(e.note_max) + x_eval = Element("evaluation", **e_dict) x_mod.append(x_eval) - val = e["notes"].get(etudid, {"value": "NP"})[ - "value" - ] # NA si etud demissionnaire - val = scu.fmt_note(val, note_max=e["note_max"]) + # Note à l'évaluation: + val = modimpl_results.evals_notes[e.id].get(etudid, "NP") + val = scu.fmt_note(val, note_max=e.note_max) x_eval.append(Element("note", value=val)) # Evaluations incomplètes ou futures: - complete_eval_ids = set([e["evaluation_id"] for e in evals]) + complete_eval_ids = {e.id for e in evaluations_completes} if sco_preferences.get_preference( "bul_show_all_evals", formsemestre_id ): @@ -325,21 +317,8 @@ def make_xml_formsemestre_bulletinetud( ).order_by(Evaluation.date_debut) for e in evaluations: if e.id not in complete_eval_ids: - x_eval = Element( - "evaluation", - jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True), - heure_debut=ndb.TimetoISO8601( - e["heure_debut"], null_is_empty=True - ), - heure_fin=ndb.TimetoISO8601( - e["heure_fin"], null_is_empty=True - ), - coefficient=str(e["coefficient"]), - description=quote_xml_attr(e["description"]), - incomplete="1", - # notes envoyées sur 20, ceci juste pour garder trace: - note_max_origin=str(e["note_max"] or ""), - ) + e_dict = e.to_dict_bul() + x_eval = Element("evaluation", **e_dict) x_mod.append(x_eval) # UE capitalisee (listee seulement si meilleure que l'UE courante) if ue_status["is_capitalized"]: diff --git a/app/scodoc/sco_evaluation_check_abs.py b/app/scodoc/sco_evaluation_check_abs.py index d34137bc0b..87853cc943 100644 --- a/app/scodoc/sco_evaluation_check_abs.py +++ b/app/scodoc/sco_evaluation_check_abs.py @@ -29,8 +29,9 @@ """ from flask import url_for, g +from app import db +from app.models import Evaluation, Identite import app.scodoc.sco_utils as scu -import app.scodoc.notesdb as ndb from app.scodoc import html_sco_header from app.scodoc import sco_abs from app.scodoc import sco_etud @@ -40,25 +41,8 @@ from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl -# matin et/ou après-midi ? -def _eval_demijournee(E): - "1 si matin, 0 si apres midi, 2 si toute la journee" - am, pm = False, False - if E["heure_debut"] < "13:00": - am = True - if E["heure_fin"] > "13:00": - pm = True - if am and pm: - demijournee = 2 - elif am: - demijournee = 1 - else: - demijournee = 0 - pm = True - return am, pm, demijournee - -def evaluation_check_absences(evaluation_id): +def evaluation_check_absences(evaluation: Evaluation): """Vérifie les absences au moment de cette évaluation. Cas incohérents que l'on peut rencontrer pour chaque étudiant: note et absent @@ -66,51 +50,58 @@ def evaluation_check_absences(evaluation_id): ABS et absent justifié EXC et pas noté absent EXC et pas justifie - Ramene 3 listes d'etudid + Ramene 5 listes d'etudid """ - E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0] - if not E["jour"]: + if not evaluation.date_debut: return [], [], [], [], [] # evaluation sans date - am, pm, demijournee = _eval_demijournee(E) + am, pm = evaluation.is_matin(), evaluation.is_apresmidi() # Liste les absences à ce moment: - A = sco_abs.list_abs_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm) - As = set([x["etudid"] for x in A]) # ensemble des etudiants absents - NJ = sco_abs.list_abs_non_just_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm) - NJs = set([x["etudid"] for x in NJ]) # ensemble des etudiants absents non justifies - Just = sco_abs.list_abs_jour( - ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True + absences = sco_abs.list_abs_jour(evaluation.date_debut, am=am, pm=pm) + abs_etudids = set([x["etudid"] for x in absences]) # ensemble des etudiants absents + abs_non_just = sco_abs.list_abs_non_just_jour( + evaluation.date_debut.date(), am=am, pm=pm ) - Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif + abs_nj_etudids = set( + [x["etudid"] for x in abs_non_just] + ) # ensemble des etudiants absents non justifies + justifs = sco_abs.list_abs_jour( + evaluation.date_debut.date(), am=am, pm=pm, is_abs=None, is_just=True + ) + just_etudids = set( + [x["etudid"] for x in justifs] + ) # ensemble des etudiants avec justif # Les notes: - notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) + notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id) ValButAbs = [] # une note mais noté absent AbsNonSignalee = [] # note ABS mais pas noté absent ExcNonSignalee = [] # note EXC mais pas noté absent ExcNonJust = [] # note EXC mais absent non justifie AbsButExc = [] # note ABS mais justifié for etudid, _ in sco_groups.do_evaluation_listeetuds_groups( - evaluation_id, getallstudents=True + evaluation.id, getallstudents=True ): if etudid in notes_db: val = notes_db[etudid]["value"] if ( - val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE - ) and etudid in As: + val is not None + and val != scu.NOTES_NEUTRALISE + and val != scu.NOTES_ATTENTE + ) and etudid in abs_etudids: # note valide et absent ValButAbs.append(etudid) - if val is None and not etudid in As: + if val is None and not etudid in abs_etudids: # absent mais pas signale comme tel AbsNonSignalee.append(etudid) - if val == scu.NOTES_NEUTRALISE and not etudid in As: + if val == scu.NOTES_NEUTRALISE and not etudid in abs_etudids: # Neutralisé mais pas signale absent ExcNonSignalee.append(etudid) - if val == scu.NOTES_NEUTRALISE and etudid in NJs: + if val == scu.NOTES_NEUTRALISE and etudid in abs_nj_etudids: # EXC mais pas justifié ExcNonJust.append(etudid) - if val is None and etudid in Justs: + if val is None and etudid in just_etudids: # ABS mais justificatif AbsButExc.append(etudid) @@ -119,9 +110,16 @@ def evaluation_check_absences(evaluation_id): def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True): """Affiche état vérification absences d'une évaluation""" - - E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0] - am, pm, demijournee = _eval_demijournee(E) + evaluation: Evaluation = db.session.get(Evaluation, evaluation_id) + am, pm = evaluation.is_matin(), evaluation.is_apresmidi() + # 1 si matin, 0 si apres midi, 2 si toute la journee: + match am, pm: + case False, True: + demijournee = 0 + case True, False: + demijournee = 1 + case _: + demijournee = 2 ( ValButAbs, @@ -129,19 +127,23 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True ExcNonSignalee, ExcNonJust, AbsButExc, - ) = evaluation_check_absences(evaluation_id) + ) = evaluation_check_absences(evaluation) if with_header: H = [ html_sco_header.html_sem_header("Vérification absences à l'évaluation"), - sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), - """

Vérification de la cohérence entre les notes saisies et les absences signalées.

""", + sco_evaluations.evaluation_describe(evaluation_id=evaluation.id), + """

Vérification de la cohérence entre les notes saisies + et les absences signalées.

""", ] else: # pas de header, mais un titre H = [ - """

%s du %s """ - % (E["description"], E["jour"]) + f"""

{ + evaluation.description or "évaluation" + } du { + evaluation.date_debut.strftime("%d/%m/%Y") if evaluation.date_debut else "" + } """ ] if ( not ValButAbs @@ -157,26 +159,27 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True if not etudids and show_ok: H.append("
  • aucun
  • ") for etudid in etudids: - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] + etud: Identite = db.session.get(Identite, etudid) H.append( - '
  • ' - % url_for( - "scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] - ) - + "%(nomprenom)s" % etud + f"""
  • {etud.nomprenom}""" ) if linkabs: - H.append( - f"""signaler cette absence""" + moduleimpl_id=evaluation.moduleimpl_id, + ) + H.append( + f"""signaler cette absence""" ) H.append("
  • ") H.append("") @@ -231,7 +234,7 @@ def formsemestre_check_absences_html(formsemestre_id): # Modules, dans l'ordre Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) for M in Mlist: - evals = sco_evaluation_db.do_evaluation_list( + evals = sco_evaluation_db.get_evaluation_dict( {"moduleimpl_id": M["moduleimpl_id"]} ) if evals: diff --git a/app/scodoc/sco_evaluation_db.py b/app/scodoc/sco_evaluation_db.py index f0af65eac0..d7b7b7ed1c 100644 --- a/app/scodoc/sco_evaluation_db.py +++ b/app/scodoc/sco_evaluation_db.py @@ -37,14 +37,13 @@ from flask_login import current_user from app import db, log from app.models import Evaluation, ModuleImpl, ScolarNews -from app.models.evaluations import evaluation_enrich_dict, check_convert_evaluation_args +from app.models.evaluations import check_convert_evaluation_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc import sco_cache from app.scodoc import sco_moduleimpl -from app.scodoc import sco_permissions_check _evaluationEditor = ndb.EditableTable( @@ -53,9 +52,8 @@ _evaluationEditor = ndb.EditableTable( ( "evaluation_id", "moduleimpl_id", - "jour", - "heure_debut", - "heure_fin", + "date_debut", + "date_fin", "description", "note_max", "coefficient", @@ -64,15 +62,11 @@ _evaluationEditor = ndb.EditableTable( "evaluation_type", "numero", ), - sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord + sortkey="numero, date_debut desc", # plus recente d'abord output_formators={ - "jour": ndb.DateISOtoDMY, "numero": ndb.int_null_is_zero, }, input_formators={ - "jour": ndb.DateDMYtoISO, - "heure_debut": ndb.TimetoISO8601, # converti par evaluation_enrich_dict - "heure_fin": ndb.TimetoISO8601, # converti par evaluation_enrich_dict "visibulletin": bool, "publish_incomplete": bool, "evaluation_type": int, @@ -80,8 +74,9 @@ _evaluationEditor = ndb.EditableTable( ) -def do_evaluation_list(args, sortkey=None): - """List evaluations, sorted by numero (or most recent date first). +def get_evaluation_dict(args: dict) -> list[dict]: + """Liste evaluations, triées numero (or most recent date first). + Fonction de transition pour ancien code ScoDoc7. Ajoute les champs: 'duree' : '2h30' @@ -89,13 +84,8 @@ def do_evaluation_list(args, sortkey=None): 'apresmidi' : 1 (termine après 12:00) ou 0 'descrheure' : ' de 15h00 à 16h30' """ - cnx = ndb.GetDBConnexion() - evals = _evaluationEditor.list(cnx, args, sortkey=sortkey) # calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi - for e in evals: - evaluation_enrich_dict(e) - - return evals + return [e.to_dict() for e in Evaluation.query.filter_by(**args)] def do_evaluation_list_in_formsemestre(formsemestre_id): @@ -103,7 +93,7 @@ def do_evaluation_list_in_formsemestre(formsemestre_id): mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) evals = [] for modimpl in mods: - evals += do_evaluation_list(args={"moduleimpl_id": modimpl["moduleimpl_id"]}) + evals += get_evaluation_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]}) return evals @@ -219,12 +209,12 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1): Evaluation.moduleimpl_evaluation_renumber( evaluation.moduleimpl, only_if_unumbered=True ) - e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0] + e = get_evaluation_dict(args={"evaluation_id": evaluation_id})[0] after = int(after) # 0: deplace avant, 1 deplace apres if after not in (0, 1): raise ValueError('invalid value for "after"') - mod_evals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]}) + mod_evals = get_evaluation_dict({"moduleimpl_id": e["moduleimpl_id"]}) if len(mod_evals) > 1: idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id) neigh = None # object to swap with diff --git a/app/scodoc/sco_evaluation_edit.py b/app/scodoc/sco_evaluation_edit.py index f40a30e945..f1b21ca796 100644 --- a/app/scodoc/sco_evaluation_edit.py +++ b/app/scodoc/sco_evaluation_edit.py @@ -27,7 +27,7 @@ """Formulaire ajout/édition d'une évaluation """ - +import datetime import time import flask @@ -38,6 +38,7 @@ from flask import request from app import db from app.models import Evaluation, FormSemestre, ModuleImpl +from app.models.evaluations import heure_to_time import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType @@ -47,7 +48,6 @@ from app.scodoc import html_sco_header from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_moduleimpl -from app.scodoc import sco_permissions_check from app.scodoc import sco_preferences @@ -139,15 +139,8 @@ def evaluation_create_form( heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)] # - initvalues["visibulletin"] = initvalues.get("visibulletin", True) - if initvalues["visibulletin"]: - initvalues["visibulletinlist"] = ["X"] - else: - initvalues["visibulletinlist"] = [] initvalues["coefficient"] = initvalues.get("coefficient", 1.0) vals = scu.get_request_args() - if vals.get("tf_submitted", False) and "visibulletinlist" not in vals: - vals["visibulletinlist"] = [] # ue_coef_dict = modimpl.module.get_ue_coef_dict() if is_apc: # BUT: poids vers les UE @@ -236,11 +229,9 @@ def evaluation_create_form( }, ), ( - "visibulletinlist", + "visibulletin", { - "input_type": "checkbox", - "allowed_values": ["X"], - "labels": [""], + "input_type": "boolcheckbox", "title": "Visible sur bulletins", "explanation": "(pour les bulletins en version intermédiaire)", }, @@ -349,15 +340,37 @@ def evaluation_create_form( return flask.redirect(dest_url) else: # form submission - if tf[2]["visibulletinlist"]: - tf[2]["visibulletin"] = True + args = tf[2] + # modifie le codage des dates + # (nb: ce formulaire ne permet de créer que des évaluation sur la même journée) + if args.get("jour"): + try: + date_debut = datetime.datetime.strptime(args["jour"], "%d/%m/%Y") + except ValueError as exc: + raise ScoValueError("Date (j/m/a) invalide") from exc else: - tf[2]["visibulletin"] = False + date_debut = None + args.pop("jour", None) + if args.get("heure_debut"): + try: + heure_debut = heure_to_time(args["heure_debut"]) + except ValueError as exc: + raise ScoValueError("Heure début invalide") from exc + args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut) + args.pop("heure_debut", None) + if args.get("heure_fin"): + try: + heure_fin = heure_to_time(args["heure_fin"]) + except ValueError as exc: + raise ScoValueError("Heure fin invalide") from exc + args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin) + args.pop("heure_fin", None) + # if edit: - sco_evaluation_db.do_evaluation_edit(tf[2]) + evaluation.from_dict(args) else: # création d'une evaluation - evaluation = Evaluation.create(moduleimpl=modimpl, **tf[2]) + evaluation = Evaluation.create(moduleimpl=modimpl, **args) db.session.add(evaluation) db.session.commit() evaluation_id = evaluation.id @@ -366,6 +379,6 @@ def evaluation_create_form( evaluation = db.session.get(Evaluation, evaluation_id) for ue in sem_ues: evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"]) - db.session.add(evaluation) - db.session.commit() + db.session.add(evaluation) + db.session.commit() return flask.redirect(dest_url) diff --git a/app/scodoc/sco_evaluation_recap.py b/app/scodoc/sco_evaluation_recap.py index c55843ca67..a5896ceb50 100644 --- a/app/scodoc/sco_evaluation_recap.py +++ b/app/scodoc/sco_evaluation_recap.py @@ -126,8 +126,8 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]: evaluation_id=evaluation_id, ), "_titre_target_attrs": 'class="discretelink"', - "date": e.jour.strftime("%d/%m/%Y") if e.jour else "", - "_date_order": e.jour.isoformat() if e.jour else "", + "date": e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "", + "_date_order": e.date_debut.isoformat() if e.date_debut else "", "complete": "oui" if eval_etat.is_complete else "non", "_complete_target": "#", "_complete_target_attrs": 'class="bull_link" title="prise en compte dans les moyennes"' diff --git a/app/scodoc/sco_evaluations.py b/app/scodoc/sco_evaluations.py index 12440d1126..4b14a6328e 100644 --- a/app/scodoc/sco_evaluations.py +++ b/app/scodoc/sco_evaluations.py @@ -101,6 +101,14 @@ def do_evaluation_etat( ) -> dict: """Donne infos sur l'état de l'évaluation. Ancienne fonction, lente: préférer ModuleImplResults pour tout calcul. + XXX utilisée par de très nombreuses fonctions, dont + - _eval_etat 1/1/1 + 'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900 'moduleimpl_id': 'GEAMIP80490', 'note_max': 20.0, 'numero': 0, @@ -323,11 +332,11 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True): 'visibulletin': 1} ] """ - req = """SELECT E.id AS evaluation_id, E.* + req = """SELECT E.id AS evaluation_id, E.* FROM notes_evaluation E, notes_moduleimpl MI WHERE MI.formsemestre_id = %(formsemestre_id)s and MI.id = E.moduleimpl_id - ORDER BY MI.id, numero desc, jour desc, heure_debut DESC + ORDER BY MI.id, numero desc, date_debut desc """ cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) @@ -335,9 +344,9 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True): res = cursor.dictfetchall() # etat de chaque evaluation: for r in res: - r["jour"] = r["jour"] or datetime.date(1900, 1, 1) # pour les comparaisons if with_etat: r["etat"] = do_evaluation_etat(r["evaluation_id"]) + r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1) return res @@ -379,7 +388,20 @@ def _eval_etat(evals): def do_evaluation_etat_in_sem(formsemestre_id): """-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides, - date derniere modif, attente""" + date derniere modif, attente + + XXX utilisé par + - formsemestre_status_head + - gen_formsemestre_recapcomplet_xml + - gen_formsemestre_recapcomplet_json + + "nb_evals_completes" + "nb_evals_en_cours" + "nb_evals_vides" + "date_derniere_note" + "last_modif" + "attente" + """ formsemestre = FormSemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) evals = nt.get_evaluations_etats() @@ -403,88 +425,97 @@ def formsemestre_evaluations_cal(formsemestre_id): formsemestre = FormSemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - evals = nt.get_evaluations_etats() - nb_evals = len(evals) + evaluations = formsemestre.get_evaluations() # TODO + nb_evals = len(evaluations) color_incomplete = "#FF6060" color_complete = "#A0FFA0" color_futur = "#70E0FF" - today = time.strftime("%Y-%m-%d") - - year = formsemestre.date_debut.year - if formsemestre.date_debut.month < 8: - year -= 1 # calendrier septembre a septembre + year = formsemestre.annee_scolaire() events = {} # (day, halfday) : event - for e in evals: - etat = e["etat"] - if not e["jour"]: - continue - day = e["jour"].strftime("%Y-%m-%d") - mod = sco_moduleimpl.moduleimpl_withmodule_list( - moduleimpl_id=e["moduleimpl_id"] - )[0] - txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval" - if e["heure_debut"]: - debut = e["heure_debut"].strftime("%Hh%M") + for e in evaluations: + if e.date_debut is None: + continue # éval. sans date + txt = e.moduleimpl.module.code or e.moduleimpl.module.abbrev or "éval." + if e.date_debut == e.date_fin: + heure_debut_txt, heure_fin_txt = "?", "?" else: - debut = "?" - if e["heure_fin"]: - fin = e["heure_fin"].strftime("%Hh%M") - else: - fin = "?" - description = "%s, de %s à %s" % (mod["module"]["titre"], debut, fin) - if etat["evalcomplete"]: + heure_debut_txt = e.date_debut.strftime("%Hh%M") if e.date_debut else "?" + heure_fin_txt = e.date_fin.strftime("%Hh%M") if e.date_fin else "?" + + description = f"""{ + e.moduleimpl.module.titre + }, de {heure_debut_txt} à {heure_fin_txt}""" + + # Etat (notes completes) de l'évaluation: + modimpl_result = nt.modimpls_results[e.moduleimpl.id] + if modimpl_result.evaluations_etat[e.id].is_complete: color = color_complete else: color = color_incomplete - if day > today: + if e.date_debut > datetime.datetime.now(scu.TIME_ZONE): color = color_futur - href = "moduleimpl_status?moduleimpl_id=%s" % e["moduleimpl_id"] - # if e['heure_debut'].hour < 12: - # halfday = True - # else: - # halfday = False - if not day in events: - # events[(day,halfday)] = [day, txt, color, href, halfday, description, mod] - events[day] = [day, txt, color, href, description, mod] + href = url_for( + "notes.moduleimpl_status", + scodoc_dept=g.scodoc_dept, + moduleimpl_id=e.moduleimpl_id, + ) + day = e.date_debut.date().isoformat() # yyyy-mm-dd + event = events.get(day) + if not event: + events[day] = [day, txt, color, href, description, e.moduleimpl] else: - e = events[day] - if e[-1]["moduleimpl_id"] != mod["moduleimpl_id"]: + if event[-1].id != e.moduleimpl.id: # plusieurs evals de modules differents a la meme date - e[1] += ", " + txt - e[4] += ", " + description - if not etat["evalcomplete"]: - e[2] = color_incomplete - if day > today: - e[2] = color_futur + event[1] += ", " + txt + event[4] += ", " + description + if color == color_incomplete: + event[2] = color_incomplete + if color == color_futur: + event[2] = color_futur - CalHTML = sco_abs.YearTable( + cal_html = sco_abs.YearTable( year, events=list(events.values()), halfday=False, pad_width=None ) - H = [ + return f""" + { html_sco_header.html_sem_header( "Evaluations du semestre", cssstyles=["css/calabs.css"], - ), - '
    ', - CalHTML, - "
    ", - "

    soit %s évaluations planifiées;" % nb_evals, - """

    • en rouge les évaluations passées auxquelles il manque des notes
    • -
    • en vert les évaluations déjà notées
    • -
    • en bleu les évaluations futures

    """ - % (color_incomplete, color_complete, color_futur), - """

    voir les délais de correction

    - """ - % (formsemestre_id,), - html_sco_header.sco_footer(), - ] - return "\n".join(H) + ) + } +
    + { cal_html } +
    +

    soit {nb_evals} évaluations planifiées; +

    +
      +
    • en rouge + les évaluations passées auxquelles il manque des notes +
    • +
    • en vert + les évaluations déjà notées +
    • +
    • en bleu + les évaluations futures +
    • +
    +

    voir les délais de correction +

    + { html_sco_header.sco_footer() } + """ -def evaluation_date_first_completion(evaluation_id): +def evaluation_date_first_completion(evaluation_id) -> datetime.datetime: """Première date à laquelle l'évaluation a été complète ou None si actuellement incomplète """ @@ -496,7 +527,7 @@ def evaluation_date_first_completion(evaluation_id): # Il faut considerer les inscriptions au semestre # (pour avoir l'etat et le groupe) et aussi les inscriptions # au module (pour gerer les modules optionnels correctement) - # E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0] + # E = get_evaluation_dict({"id":evaluation_id})[0] # M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] # formsemestre_id = M["formsemestre_id"] # insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id) @@ -536,40 +567,44 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"): N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus. """ formsemestre = FormSemestre.get_formsemestre(formsemestre_id) - nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - - evals = nt.get_evaluations_etats() - T = [] - for e in evals: - M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0] - Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] - if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or ( - Mod["module_type"] == ModuleType.MALUS + evaluations = formsemestre.get_evaluations() + rows = [] + for e in evaluations: + if (e.evaluation_type != scu.EVALUATION_NORMALE) or ( + e.moduleimpl.module.module_type == ModuleType.MALUS ): continue - e["date_first_complete"] = evaluation_date_first_completion(e["evaluation_id"]) - if e["date_first_complete"]: - e["delai_correction"] = (e["date_first_complete"].date() - e["jour"]).days + date_first_complete = evaluation_date_first_completion(e.id) + if date_first_complete and e.date_fin: + delai_correction = (date_first_complete.date() - e.date_fin).days else: - e["delai_correction"] = None + delai_correction = None - e["module_code"] = Mod["code"] - e["_module_code_target"] = url_for( - "notes.moduleimpl_status", - scodoc_dept=g.scodoc_dept, - moduleimpl_id=M["moduleimpl_id"], + rows.append( + { + "date_first_complete": date_first_complete, + "delai_correction": delai_correction, + "jour": e.date_debut.strftime("%d/%m/%Y") + if e.date_debut + else "sans date", + "_jour_target": url_for( + "notes.evaluation_listenotes", + scodoc_dept=g.scodoc_dept, + evaluation_id=e["evaluation_id"], + ), + "module_code": e.moduleimpl.module.code, + "_module_code_target": url_for( + "notes.moduleimpl_status", + scodoc_dept=g.scodoc_dept, + moduleimpl_id=e.moduleimpl.id, + ), + "module_titre": e.moduleimpl.module.abbrev or e.moduleimpl.module.titre, + "responsable_id": e.moduleimpl.responsable_id, + "responsable_nomplogin": sco_users.user_info( + e.moduleimpl.responsable_id + )["nomplogin"], + } ) - e["module_titre"] = Mod["titre"] - e["responsable_id"] = M["responsable_id"] - e["responsable_nomplogin"] = sco_users.user_info(M["responsable_id"])[ - "nomplogin" - ] - e["_jour_target"] = url_for( - "notes.evaluation_listenotes", - scodoc_dept=g.scodoc_dept, - evaluation_id=e["evaluation_id"], - ) - T.append(e) columns_ids = ( "module_code", @@ -592,16 +627,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"): tab = GenTable( titles=titles, columns_ids=columns_ids, - rows=T, + rows=rows, html_class="table_leftalign table_coldate", html_sortable=True, html_title="

    Correction des évaluations du semestre

    ", caption="Correction des évaluations du semestre", preferences=sco_preferences.SemPreferences(formsemestre_id), base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id), - origin="Généré par %s le " % sco_version.SCONAME - + scu.timedate_human_repr() - + "", + origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""", filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()), ) return tab.make_page(format=format) @@ -612,7 +645,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True): """HTML description of evaluation, for page headers edit_in_place: allow in-place editing when permitted (not implemented) """ - E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0] + E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0] moduleimpl_id = E["moduleimpl_id"] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0] diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py index 9229513797..c15f24d4a1 100644 --- a/app/scodoc/sco_excel.py +++ b/app/scodoc/sco_excel.py @@ -512,8 +512,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line # description evaluation ws.append_single_cell_row(scu.unescape_html(description), style_titres) ws.append_single_cell_row( - "Evaluation du %s (coef. %g)" - % (evaluation.jour or "sans date", evaluation.coefficient or 0.0), + f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})", style, ) # ligne blanche diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 41347f2d66..7b1ceb41a9 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -1245,7 +1245,9 @@ def do_formsemestre_clone( moduleimpl_id=mod_orig["moduleimpl_id"] ): # copie en enlevant la date - new_eval = e.clone(not_copying=("jour", "moduleimpl_id")) + new_eval = e.clone( + not_copying=("date_debut", "date_fin", "moduleimpl_id") + ) new_eval.moduleimpl_id = mid # Copie les poids APC de l'évaluation new_eval.set_ue_poids_dict(e.get_ue_poids_dict()) @@ -1443,7 +1445,7 @@ def do_formsemestre_delete(formsemestre_id): mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) for mod in mods: # evaluations - evals = sco_evaluation_db.do_evaluation_list( + evals = sco_evaluation_db.get_evaluation_dict( args={"moduleimpl_id": mod["moduleimpl_id"]} ) for e in evals: diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 00dfaa0c60..2e572ea309 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -498,7 +498,7 @@ def retreive_formsemestre_from_request() -> int: modimpl = modimpl[0] formsemestre_id = modimpl["formsemestre_id"] elif "evaluation_id" in args: - E = sco_evaluation_db.do_evaluation_list( + E = sco_evaluation_db.get_evaluation_dict( {"evaluation_id": args["evaluation_id"]} ) if not E: @@ -620,7 +620,7 @@ def formsemestre_description_table( columns_ids += ["Inscrits", "Responsable", "Enseignants"] if with_evals: columns_ids += [ - "jour", + "date_evaluation", "description", "coefficient", "evalcomplete_str", @@ -630,7 +630,7 @@ def formsemestre_description_table( titles = {title: title for title in columns_ids} titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues}) titles["ects"] = "ECTS" - titles["jour"] = "Évaluation" + titles["date_evaluation"] = "Évaluation" titles["description"] = "" titles["coefficient"] = "Coef. éval." titles["evalcomplete_str"] = "Complète" @@ -738,8 +738,10 @@ def formsemestre_description_table( 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["_date_evaluation_order"] = e["jour"].isoformat() + e["date_evaluation"] = ( + e["jour"].strftime("%d/%m/%Y") if e["jour"] else "" + ) e["UE"] = row["UE"] e["_UE_td_attrs"] = row["_UE_td_attrs"] e["Code"] = row["Code"] diff --git a/app/scodoc/sco_liste_notes.py b/app/scodoc/sco_liste_notes.py index 9be70378cc..00818e54d1 100644 --- a/app/scodoc/sco_liste_notes.py +++ b/app/scodoc/sco_liste_notes.py @@ -69,38 +69,44 @@ def do_evaluation_listenotes( mode = None if moduleimpl_id: mode = "module" - evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) + evals = sco_evaluation_db.get_evaluation_dict({"moduleimpl_id": moduleimpl_id}) elif evaluation_id: mode = "eval" - evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id}) + evals = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id}) else: raise ValueError("missing argument: evaluation or module") if not evals: return "

    Aucune évaluation !

    ", "ScoDoc" - E = evals[0] # il y a au moins une evaluation - modimpl = db.session.get(ModuleImpl, E["moduleimpl_id"]) + eval_dict = evals[0] # il y a au moins une evaluation + modimpl = db.session.get(ModuleImpl, eval_dict["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}" + page_title = f"Notes {eval_dict['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 + eval_dict["evaluation_id"], include_default=True ) grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons grnams = [str(g["group_id"]) for g in groups] # noms des checkbox if len(evals) > 1: descr = [ - ("moduleimpl_id", {"default": E["moduleimpl_id"], "input_type": "hidden"}) + ( + "moduleimpl_id", + {"default": eval_dict["moduleimpl_id"], "input_type": "hidden"}, + ) ] else: descr = [ - ("evaluation_id", {"default": E["evaluation_id"], "input_type": "hidden"}) + ( + "evaluation_id", + {"default": eval_dict["evaluation_id"], "input_type": "hidden"}, + ) ] if len(grnams) > 1: descr += [ @@ -199,7 +205,7 @@ def do_evaluation_listenotes( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, - moduleimpl_id=E["moduleimpl_id"], + moduleimpl_id=eval_dict["moduleimpl_id"], ) ), "", diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 3f167cf029..af8a90e87a 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -61,7 +61,7 @@ from app.tables import list_etuds # menu evaluation dans moduleimpl def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str: "Menu avec actions sur une evaluation" - E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0] + E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0] modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] group_id = sco_groups.get_default_group(modimpl["formsemestre_id"]) @@ -203,11 +203,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): ) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - # Evaluations, la plus RECENTE en tête + # Evaluations, par numéros ou la plus RECENTE en tête evaluations = modimpl.evaluations.order_by( Evaluation.numero.desc(), - Evaluation.jour.desc(), - Evaluation.heure_debut.desc(), + Evaluation.date_debut.desc(), ).all() nb_evaluations = len(evaluations) max_poids = max( @@ -571,10 +570,8 @@ def _ligne_evaluation( # visualisation des poids (Hinton map) H.append(_evaluation_poids_html(evaluation, max_poids)) H.append("""
    """) - if evaluation.jour: - H.append( - f"""Le {evaluation.jour.strftime("%d/%m/%Y")} {evaluation.descr_heure()}""" - ) + if evaluation.date_debut: + H.append(evaluation.descr_date()) else: H.append( f""" int: + ) -> dict: args = locals() del args["self"] moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id) @@ -322,7 +321,9 @@ class ScoFake(object): evaluation: Evaluation = Evaluation.create(moduleimpl=moduleimpl, **args) db.session.add(evaluation) db.session.commit() - return evaluation.id + eval_dict = evaluation.to_dict() + eval_dict["id"] = evaluation.id + return eval_dict @logging_meth def create_note( @@ -414,7 +415,7 @@ class ScoFake(object): for e_idx in range(1, nb_evaluations_per_module + 1): e = self.create_evaluation( moduleimpl_id=moduleimpl_id, - jour=date_debut, + date_debut=datetime.datetime.strptime(date_debut, "%d/%m/%Y"), description="evaluation test %s" % e_idx, coefficient=1.0, ) @@ -435,7 +436,7 @@ class ScoFake(object): for e in eval_list: for idx, etud in enumerate(etuds): self.create_note( - evaluation_id=e["id"], + evaluation_id=e["evaluation_id"], etudid=etud["etudid"], note=notes[idx % len(notes)], ) diff --git a/tests/unit/setup.py b/tests/unit/setup.py index 0930a414a7..a3756d9f93 100644 --- a/tests/unit/setup.py +++ b/tests/unit/setup.py @@ -1,9 +1,9 @@ """ Quelques fonctions d'initialisation pour tests unitaires """ +import datetime from app import db, models - import app.scodoc.sco_utils as scu from app.scodoc import codes_cursus @@ -133,7 +133,7 @@ def build_modules_with_evaluations( for _ in range(nb_evals_per_modimpl): e = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2021", + date_debut=datetime.datetime(2021, 1, 1), description="evaluation 1", coefficient=0, ) diff --git a/tests/unit/test_abs_demijournee.py b/tests/unit/test_abs_demijournee.py index a4783a0f7e..0a54a7ec5d 100644 --- a/tests/unit/test_abs_demijournee.py +++ b/tests/unit/test_abs_demijournee.py @@ -5,7 +5,7 @@ Créer et justifier des absences en utilisant le parametre demijournee """ # test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021 - +import datetime import json from tests.unit import sco_fake_gen @@ -163,7 +163,7 @@ def test_abs_basic(test_client): # --- Création d'une évaluation e = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="22/01/2021", + date_debut=datetime.datetime(2021, 1, 22), description="evaluation test", coefficient=1.0, ) diff --git a/tests/unit/test_bulletin.py b/tests/unit/test_bulletin.py index 68a050d79b..e92d872919 100644 --- a/tests/unit/test_bulletin.py +++ b/tests/unit/test_bulletin.py @@ -22,7 +22,7 @@ from tests.unit import test_sco_basic DEPT = TestConfig.DEPT_TEST -def test_bulletin(test_client): +def test_bulletin_data_classic(test_client): """Vérifications sur les bulletins de notes""" G = sco_fake_gen.ScoFake(verbose=False) app.set_sco_dept(DEPT) diff --git a/tests/unit/test_but_modules.py b/tests/unit/test_but_modules.py index 66b818fbca..fd1bc4306d 100644 --- a/tests/unit/test_but_modules.py +++ b/tests/unit/test_but_modules.py @@ -2,6 +2,7 @@ Test modèles évaluations avec poids BUT et calcul moyennes modules """ +import datetime import numpy as np import pandas as pd @@ -46,7 +47,7 @@ def test_evaluation_poids(test_client): ) _e1 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2021", + date_debut=datetime.datetime(2021, 1, 1), description="evaluation 1", coefficient=0, ) @@ -218,7 +219,7 @@ def test_module_moy(test_client): # Crée une deuxième évaluation dans le même moduleimpl: evaluation2_id = G.create_evaluation( moduleimpl_id=evaluation1.moduleimpl_id, - jour="02/01/2021", + date_debut=datetime.datetime(2021, 1, 2), description="evaluation 2", coefficient=coef_e2, )["evaluation_id"] diff --git a/tests/unit/test_caches.py b/tests/unit/test_caches.py index 14b9a0c066..3a4882d778 100644 --- a/tests/unit/test_caches.py +++ b/tests/unit/test_caches.py @@ -15,12 +15,11 @@ import app from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat -from app.models import FormSemestre +from app.models import Evaluation, FormSemestre, ModuleImpl from app.scodoc import sco_cache from app.scodoc import sco_evaluations from app.scodoc import sco_evaluation_db from app.scodoc import sco_formsemestre -from app.scodoc import notesdb as ndb from config import TestConfig from tests.unit.test_sco_basic import run_sco_basic @@ -58,23 +57,24 @@ def test_cache_evaluations(test_client): # prépare le département avec quelques semestres: run_sco_basic() # - sems = sco_formsemestre.do_formsemestre_list() - assert len(sems) - sem_evals = [] - for sem in sems: - sem_evals = sco_evaluations.do_evaluation_list_in_sem( - sem["formsemestre_id"], with_etat=False + formsemestres = FormSemestre.query + assert formsemestres.count() + evaluation = None + for formsemestre in formsemestres: + evaluation: Evaluation = ( + Evaluation.query.join(ModuleImpl) + .filter_by(formsemestre_id=formsemestre.id) + .first() ) - if sem_evals: + if evaluation is not None: break - if not sem_evals: + if evaluation is None: raise Exception("no evaluations") # - evaluation_id = sem_evals[0]["evaluation_id"] - eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) + eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id) # should have been be cached, except if empty if eval_notes: - assert sco_cache.EvaluationCache.get(evaluation_id) - sco_cache.invalidate_formsemestre(sem["formsemestre_id"]) + assert sco_cache.EvaluationCache.get(evaluation.id) + sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id) # should have been erased from cache: - assert not sco_cache.EvaluationCache.get(evaluation_id) + assert not sco_cache.EvaluationCache.get(evaluation.id) diff --git a/tests/unit/test_notes_modules.py b/tests/unit/test_notes_modules.py index 55dad99994..1542eaecda 100644 --- a/tests/unit/test_notes_modules.py +++ b/tests/unit/test_notes_modules.py @@ -2,6 +2,7 @@ Vérif moyennes de modules des bulletins et aussi moyennes modules et UE internes (via nt) """ +import datetime import numpy as np from flask import g from config import TestConfig @@ -93,13 +94,13 @@ def test_notes_modules(test_client): coef_2 = 2.0 e1 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation 1", coefficient=coef_1, ) e2 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation 2", coefficient=coef_2, ) @@ -107,16 +108,16 @@ def test_notes_modules(test_client): note_1 = 12.0 note_2 = 13.0 _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=note_1 + evaluation_id=e1["evaluation_id"], etudid=etuds[0]["etudid"], note=note_1 ) _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etuds[0]["etudid"], note=note_2 + evaluation_id=e2["evaluation_id"], etudid=etuds[0]["etudid"], note=note_2 ) _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=note_1 / 2 + evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=note_1 / 2 ) _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=note_2 / 3 + evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=note_2 / 3 ) b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] @@ -138,16 +139,24 @@ def test_notes_modules(test_client): ) # Absence à une évaluation - _, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs - _, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=note_2) + _, _, _ = G.create_note( + evaluation_id=e1["evaluation_id"], etudid=etudid, note=None + ) # abs + _, _, _ = G.create_note( + evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2 + ) b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] ) note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2) assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th) # Absences aux deux évaluations - _, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs - _, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=None) # abs + _, _, _ = G.create_note( + evaluation_id=e1["evaluation_id"], etudid=etudid, note=None + ) # abs + _, _, _ = G.create_note( + evaluation_id=e2["evaluation_id"], etudid=etudid, note=None + ) # abs b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] ) @@ -162,9 +171,11 @@ def test_notes_modules(test_client): ) # Note excusée EXC <-> scu.NOTES_NEUTRALISE - _, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1) _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE + evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1 + ) + _, _, _ = G.create_note( + evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE ) # EXC b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] @@ -179,9 +190,11 @@ def test_notes_modules(test_client): expected_moy_ue=note_1, ) # Note en attente ATT <-> scu.NOTES_ATTENTE - _, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1) _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE + evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1 + ) + _, _, _ = G.create_note( + evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE ) # ATT b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] @@ -197,10 +210,10 @@ def test_notes_modules(test_client): ) # Neutralisation (EXC) des 2 évals _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE + evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE ) # EXC _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE + evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE ) # EXC b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] @@ -216,10 +229,10 @@ def test_notes_modules(test_client): ) # Attente (ATT) sur les 2 evals _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_ATTENTE + evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE ) # ATT _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE + evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE ) # ATT b = sco_bulletins.formsemestre_bulletinetud_dict( sem["formsemestre_id"], etud["etudid"] @@ -277,7 +290,7 @@ def test_notes_modules(test_client): {"etudid": etudid, "moduleimpl_id": moduleimpl_id}, formsemestre_id=formsemestre_id, ) - _, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=12.5) + _, _, _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) mod_stats = nt.get_mod_stats(moduleimpl_id) @@ -301,11 +314,13 @@ def test_notes_modules(test_client): # Note dans module 2: e_m2 = G.create_evaluation( moduleimpl_id=moduleimpl_id2, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation mod 2", coefficient=1.0, ) - _, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=19.5) + _, _, _ = G.create_note( + evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5 + ) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) ue_status = nt.get_etud_ue_status(etudid, ue_id) @@ -314,20 +329,22 @@ def test_notes_modules(test_client): # 2 modules, notes EXC dans le premier, note valide n dans le second # la moyenne de l'UE doit être n _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE + evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE ) # EXC _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE + evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE ) # EXC - _, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=12.5) _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=11.0 + evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5 ) _, _, _ = G.create_note( - evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=11.0 + evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0 ) _, _, _ = G.create_note( - evaluation_id=e_m2["id"], etudid=etuds[1]["etudid"], note=11.0 + evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0 + ) + _, _, _ = G.create_note( + evaluation_id=e_m2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0 ) b = sco_bulletins.formsemestre_bulletinetud_dict( @@ -385,16 +402,20 @@ def test_notes_modules_att_dem(test_client): coef_1 = 1.0 e1 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation 1", coefficient=coef_1, ) # Attente (ATT) sur les 2 evals _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=scu.NOTES_ATTENTE + evaluation_id=e1["evaluation_id"], + etudid=etuds[0]["etudid"], + note=scu.NOTES_ATTENTE, ) # ATT _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=scu.NOTES_ATTENTE + evaluation_id=e1["evaluation_id"], + etudid=etuds[1]["etudid"], + note=scu.NOTES_ATTENTE, ) # ATT # Démission du premier étudiant sco_formsemestre_inscriptions.do_formsemestre_demission( @@ -435,7 +456,7 @@ def test_notes_modules_att_dem(test_client): # Saisie note ABS pour le deuxième etud _, _, _ = G.create_note( - evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=None + evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=None ) nt = check_nt( etuds[1]["etudid"], diff --git a/tests/unit/test_notes_rattrapage.py b/tests/unit/test_notes_rattrapage.py index 853824929e..45433886fd 100644 --- a/tests/unit/test_notes_rattrapage.py +++ b/tests/unit/test_notes_rattrapage.py @@ -1,5 +1,6 @@ """Test calculs rattrapages """ +import datetime import app from app import db @@ -58,14 +59,14 @@ def test_notes_rattrapage(test_client): # --- Creation évaluation e = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation test", coefficient=1.0, ) # --- Création d'une évaluation "de rattrapage" e_rat = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="02/01/2020", + date_debut=datetime.datetime(2020, 1, 2), description="evaluation rattrapage", coefficient=1.0, evaluation_type=scu.EVALUATION_RATTRAPAGE, @@ -139,7 +140,7 @@ def test_notes_rattrapage(test_client): # Création évaluation session 2: e_session2 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="02/01/2020", + date_debut=datetime.datetime(2020, 1, 2), description="evaluation session 2", coefficient=1.0, evaluation_type=scu.EVALUATION_SESSION2, diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py index 5472c17b61..49eb7c24b6 100644 --- a/tests/unit/test_sco_basic.py +++ b/tests/unit/test_sco_basic.py @@ -11,6 +11,8 @@ Au besoin, créer un base de test neuve: ./tools/create_database.sh SCODOC_TEST """ +import datetime + from app.models import FormSemestreInscription, Identite from config import TestConfig @@ -96,7 +98,7 @@ def run_sco_basic(verbose=False) -> FormSemestre: # --- Création évaluation e = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="01/01/2020", + date_debut=datetime.datetime(2020, 1, 1), description="evaluation test", coefficient=1.0, ) @@ -104,7 +106,7 @@ def run_sco_basic(verbose=False) -> FormSemestre: # --- Saisie toutes les notes de l'évaluation for idx, etud in enumerate(etuds): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e["id"], + evaluation_id=e["evaluation_id"], etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) @@ -130,14 +132,14 @@ def run_sco_basic(verbose=False) -> FormSemestre: # --- Une autre évaluation e2 = G.create_evaluation( moduleimpl_id=moduleimpl_id, - jour="02/01/2020", + date_debut=datetime.datetime(2020, 1, 2), description="evaluation test 2", coefficient=1.0, ) # Saisie les notes des 5 premiers étudiants: for idx, etud in enumerate(etuds[:5]): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e2["id"], + evaluation_id=e2["evaluation_id"], etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) @@ -159,7 +161,7 @@ def run_sco_basic(verbose=False) -> FormSemestre: # Saisie des notes qui manquent: for idx, etud in enumerate(etuds[5:]): etudids_changed, nb_suppress, existing_decisions = G.create_note( - evaluation_id=e2["id"], + evaluation_id=e2["evaluation_id"], etudid=etud["etudid"], note=NOTES_T[idx % len(NOTES_T)], ) diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index bc51849db7..577855ba5d 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -158,7 +158,7 @@ def create_evaluations(formsemestre: FormSemestre, publish_incomplete=True): for modimpl in formsemestre.modimpls: evaluation = Evaluation( moduleimpl=modimpl, - jour=formsemestre.date_debut, + date_debut=formsemestre.date_debut, description=f"Exam {modimpl.module.titre}", coefficient=1.0, note_max=20.0, diff --git a/tools/fakedatabase/create_test_api_database.py b/tools/fakedatabase/create_test_api_database.py index 55fbd4d4b9..7ee00b32a0 100644 --- a/tools/fakedatabase/create_test_api_database.py +++ b/tools/fakedatabase/create_test_api_database.py @@ -238,7 +238,8 @@ def create_evaluations(formsemestre: FormSemestre): "Création d'une evaluation dans chaque modimpl du semestre" for moduleimpl in formsemestre.modimpls: args = { - "jour": datetime.date(2022, 3, 1) + datetime.timedelta(days=moduleimpl.id), + "jour": datetime.date(2022, 3, 1) + + datetime.timedelta(days=moduleimpl.id), # TODO à changer "heure_debut": "8h00", "heure_fin": "9h00", "description": f"Evaluation-{moduleimpl.module.code}",