From b1bc8b3f415f6075d5b0c0c58df3027380a80390 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 4 Dec 2021 21:04:09 +0100 Subject: [PATCH] =?UTF-8?q?pr=C3=A9paratifs/refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/etudiants.py | 26 +++++++ app/models/formations.py | 2 +- app/models/formsemestre.py | 101 +++++++++++++++++++++++++++- app/scodoc/sco_abs.py | 24 ++++--- app/scodoc/sco_edit_ue.py | 2 +- app/scodoc/sco_etud.py | 30 --------- app/scodoc/sco_formsemestre.py | 4 +- app/scodoc/sco_formsemestre_edit.py | 26 ++----- app/scodoc/sco_news.py | 2 +- app/scodoc/sco_placement.py | 2 +- app/scodoc/sco_utils.py | 30 +++++++++ 11 files changed, 182 insertions(+), 67 deletions(-) diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 0c153b8a41..3bb1159884 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -49,6 +49,32 @@ class Identite(db.Model): def __repr__(self): return f"" + def civilite_str(self): + """returns 'M.' ou 'Mme' ou '' (pour le genre neutre, + personnes ne souhaitant pas d'affichage). + """ + return {"M": "M.", "F": "Mme", "X": ""}[self.civilite] + + def nom_disp(self): + "nom à afficher" + if self.nom_usuel: + return ( + (self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel + ) + else: + return self.nom + + def inscription_courante(self): + """La première inscription à un formsemestre _actuellement_ en cours. + None s'il n'y en a pas (ou plus, ou pas encore). + """ + r = [ + ins + for ins in self.formsemestre_inscriptions + if ins.formsemestre.est_courant() + ] + return r[0] if r else None + class Adresse(db.Model): """Adresse d'un étudiant diff --git a/app/models/formations.py b/app/models/formations.py index a49ce655c3..e1f02bea9f 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -241,7 +241,7 @@ class Module(db.Model): def is_apc(self): "True si module SAÉ ou Ressource" - return scu.ModuleType(self.module_type) in { + return self.module_type and scu.ModuleType(self.module_type) in { scu.ModuleType.RESSOURCE, scu.ModuleType.SAE, } diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 3ffcec5b1f..fa30fffc0b 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -2,6 +2,7 @@ """ScoDoc models: formsemestre """ +import datetime from typing import Any import flask_sqlalchemy @@ -13,11 +14,14 @@ from app.models import CODE_STR_LEN from app.models import UniteEns import app.scodoc.notesdb as ndb +import app.scodoc.sco_utils as scu from app.scodoc import sco_evaluation_db from app.models.formations import UniteEns, Module from app.models.moduleimpls import ModuleImpl from app.models.etudiants import Identite from app.scodoc import sco_codes_parcours +from app.scodoc import sco_preferences +from app.scodoc.sco_vdi import ApoEtapeVDI class FormSemestre(db.Model): @@ -40,7 +44,7 @@ class FormSemestre(db.Model): ) # False si verrouillé modalite = db.Column( db.String(SHORT_STR_LEN), db.ForeignKey("notes_form_modalites.modalite") - ) + ) # "FI", "FAP", "FC", ... # gestion compensation sem DUT: gestion_compensation = db.Column( db.Boolean(), nullable=False, default=False, server_default="false" @@ -89,7 +93,12 @@ class FormSemestre(db.Model): viewonly=True, lazy="dynamic", ) - + responsables = db.relationship( + "User", + secondary="notes_formsemestre_responsables", + lazy=True, + backref=db.backref("formsemestres", lazy=True), + ) # Ancien id ScoDoc7 pour les migrations de bases anciennes # ne pas utiliser après migrate_scodoc7_dept_archives scodoc7_id = db.Column(db.Text(), nullable=True) @@ -99,6 +108,18 @@ class FormSemestre(db.Model): if self.modalite is None: self.modalite = FormationModalite.DEFAULT_MODALITE + def to_dict(self): + d = dict(self.__dict__) + d.pop("_sa_instance_state", None) + # ScoDoc7 output_formators: (backward compat) + d["formsemestre_id"] = self.id + d["date_debut"] = ( + self.date_debut.strftime("%d/%m/%Y") if self.date_debut else "" + ) + d["date_fin"] = self.date_fin.strftime("%d/%m/%Y") if self.date_fin else "" + d["responsables"] = [u.id for u in self.responsables] + return d + def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery: """UE des modules de ce semestre. - Formations classiques: les UEs auxquelles appartiennent @@ -120,6 +141,76 @@ class FormSemestre(db.Model): sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT) return sem_ues + def est_courant(self) -> bool: + """Vrai si la date actuelle (now) est dans le semestre + (les dates de début et fin sont incluses) + """ + today = datetime.date.today() + return (self.date_debut <= today) and (today <= self.date_fin) + + def est_decale(self): + """Vrai si semestre "décalé" + c'est à dire semestres impairs commençant entre janvier et juin + et les pairs entre juillet et decembre + """ + if self.semestre_id <= 0: + return False # formations sans semestres + return (self.semestre_id % 2 and self.date_debut.month <= 6) or ( + not self.semestre_id % 2 and self.date_debut.month > 6 + ) + + def etapes_apo_str(self) -> str: + """Chaine décrivant les étapes de ce semestre + ex: "V1RT, V1RT3, V1RT4" + """ + if not self.etapes: + return "" + return ", ".join([str(x.etape_apo) for x in self.etapes]) + + def responsables_str(self, abbrev_prenom=True) -> str: + """chaîne "J. Dupond, X. Martin" + ou "Jacques Dupond, Xavier Martin" + """ + if not self.responsables: + return "" + if abbrev_prenom: + return ", ".join([u.get_prenomnom() for u in self.responsables]) + else: + return ", ".join([u.get_nomcomplet() for u in self.responsables]) + + def session_id(self) -> str: + """identifiant externe de semestre de formation + Exemple: RT-DUT-FI-S1-ANNEE + + DEPT-TYPE-MODALITE+-S?|SPECIALITE + + TYPE=DUT|LP*|M* + MODALITE=FC|FI|FA (si plusieurs, en inverse alpha) + + SPECIALITE=[A-Z]+ EON,ASSUR, ... (si pas Sn ou SnD) + + ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013) + """ + imputation_dept = sco_preferences.get_preference("ImputationDept", self.id) + if not imputation_dept: + imputation_dept = sco_preferences.get_preference("DeptName") + imputation_dept = imputation_dept.upper() + parcours_name = self.formation.get_parcours().NAME + modalite = self.modalite + # exception pour code Apprentissage: + modalite = (modalite or "").replace("FAP", "FA").replace("APP", "FA") + if self.semestre_id > 0: + decale = "D" if self.est_decale() else "" + semestre_id = f"S{self.semestre_id}{decale}" + else: + semestre_id = self.formation.code_specialite or "" + annee_sco = str( + scu.annee_scolaire_debut(self.date_debut.year, self.date_debut.month) + ) + return scu.sanitize_string( + "-".join((imputation_dept, parcours_name, modalite, semestre_id, annee_sco)) + ) + # Association id des utilisateurs responsables (aka directeurs des etudes) du semestre notes_formsemestre_responsables = db.Table( @@ -144,6 +235,12 @@ class FormsemestreEtape(db.Model): ) etape_apo = db.Column(db.String(APO_CODE_STR_LEN)) + def __repr__(self): + return f"" + + def as_apovdi(self): + return ApoEtapeVDI(self.etape_apo) + class FormationModalite(db.Model): """Modalités de formation, utilisées pour la présentation diff --git a/app/scodoc/sco_abs.py b/app/scodoc/sco_abs.py index 7d5e7976ea..28bfa9885a 100644 --- a/app/scodoc/sco_abs.py +++ b/app/scodoc/sco_abs.py @@ -1028,20 +1028,26 @@ def get_abs_count(etudid, sem): tuple (nb abs non justifiées, nb abs justifiées) Utilise un cache. """ - date_debut = sem["date_debut_iso"] - date_fin = sem["date_fin_iso"] - key = str(etudid) + "_" + date_debut + "_" + date_fin + return get_abs_count_in_interval(etudid, sem["date_debut_iso"], sem["date_fin_iso"]) + + +def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso): + """Les comptes d'absences de cet étudiant entre ces deux dates, incluses: + tuple (nb abs non justifiées, nb abs justifiées) + Utilise un cache. + """ + key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso r = sco_cache.AbsSemEtudCache.get(key) if not r: - nb_abs = count_abs( # was CountAbs XXX + nb_abs = count_abs( etudid=etudid, - debut=date_debut, - fin=date_fin, + debut=date_debut_iso, + fin=date_fin_iso, ) - nb_abs_just = count_abs_just( # XXX was CountAbsJust + nb_abs_just = count_abs_just( etudid=etudid, - debut=date_debut, - fin=date_fin, + debut=date_debut_iso, + fin=date_fin_iso, ) r = (nb_abs, nb_abs_just) ans = sco_cache.AbsSemEtudCache.set(key, r) diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 23d63bff7e..9b48fad5f9 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -584,7 +584,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); H.append( f"""
    -
  • {descr_refcomp} {msg_refcomp}
  • diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 4c96f52642..7dc30c8298 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -47,36 +47,6 @@ from app.scodoc import sco_preferences from app.scodoc.scolog import logdb from app.scodoc.TrivialFormulator import TrivialFormulator -MONTH_NAMES_ABBREV = [ - "Jan ", - "Fév ", - "Mars", - "Avr ", - "Mai ", - "Juin", - "Jul ", - "Août", - "Sept", - "Oct ", - "Nov ", - "Déc ", -] - -MONTH_NAMES = [ - "janvier", - "février", - "mars", - "avril", - "mai", - "juin", - "juillet", - "août", - "septembre", - "octobre", - "novembre", - "décembre", -] - def format_etud_ident(etud): """Format identite de l'étudiant (modifié en place) diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 307d118838..0e1a1a81bb 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -194,7 +194,7 @@ def _formsemestre_enrich(sem): sem["titreannee"] += "-" + annee_fin sem["annee"] += "-" + annee_fin # et les dates sous la forme "oct 2007 - fev 2008" - months = sco_etud.MONTH_NAMES_ABBREV + months = scu.MONTH_NAMES_ABBREV if mois_debut: mois_debut = months[int(mois_debut) - 1] if mois_fin: @@ -470,7 +470,7 @@ def sem_une_annee(sem): return debut == fin -def sem_est_courant(sem): +def sem_est_courant(sem): # -> FormSemestre.est_courant """Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)""" now = time.strftime("%Y-%m-%d") debut = ndb.DateDMYtoISO(sem["date_debut"]) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index ce4278dbd7..364f430fcc 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -1621,28 +1621,14 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): # ----- identification externe des sessions (pour SOJA et autres logiciels) def get_formsemestre_session_id(sem, F, parcours): """Identifiant de session pour ce semestre - Exemple: RT-DUT-FI-S1-ANNEE - - DEPT-TYPE-MODALITE+-S?|SPECIALITE - - TYPE=DUT|LP*|M* - MODALITE=FC|FI|FA (si plusieurs, en inverse alpha) - - SPECIALITE=[A-Z]+ EON,ASSUR, ... (si pas Sn ou SnD) - - ANNEE=annee universitaire de debut (exemple: un S2 de 2013-2014 sera S2-2013) - + Obsolete: vooir FormSemestre.session_id() #sco7 """ - # sem = sco_formsemestre.get_formsemestre( formsemestre_id) - # F = sco_formations.formation_list( args={ 'formation_id' : sem['formation_id'] } )[0] - # parcours = sco_codes_parcours.get_parcours_from_code(F['type_parcours']) - - ImputationDept = sco_preferences.get_preference( + imputation_dept = sco_preferences.get_preference( "ImputationDept", sem["formsemestre_id"] ) - if not ImputationDept: - ImputationDept = sco_preferences.get_preference("DeptName") - ImputationDept = ImputationDept.upper() + if not imputation_dept: + imputation_dept = sco_preferences.get_preference("DeptName") + imputation_dept = imputation_dept.upper() parcours_type = parcours.NAME modalite = sem["modalite"] modalite = ( @@ -1656,5 +1642,5 @@ def get_formsemestre_session_id(sem, F, parcours): annee_sco = str(scu.annee_scolaire_debut(sem["annee_debut"], sem["mois_debut_ord"])) return scu.sanitize_string( - "-".join((ImputationDept, parcours_type, modalite, semestre_id, annee_sco)) + "-".join((imputation_dept, parcours_type, modalite, semestre_id, annee_sco)) ) diff --git a/app/scodoc/sco_news.py b/app/scodoc/sco_news.py index 5522e5cd98..5c08313d60 100644 --- a/app/scodoc/sco_news.py +++ b/app/scodoc/sco_news.py @@ -151,7 +151,7 @@ def scolar_news_summary(n=5): n[k] = _scolar_news_editor.output_formators[k](n[k]) # date resumee j, m = n["date"].split("/")[:2] - mois = sco_etud.MONTH_NAMES_ABBREV[int(m) - 1] + mois = scu.MONTH_NAMES_ABBREV[int(m) - 1] n["formatted_date"] = "%s %s %s" % (j, mois, n["hm"]) # indication semestre si ajout notes: infos = _get_formsemestre_infos_from_news(n) diff --git a/app/scodoc/sco_placement.py b/app/scodoc/sco_placement.py index 165a766bba..b3e29db9ca 100644 --- a/app/scodoc/sco_placement.py +++ b/app/scodoc/sco_placement.py @@ -212,7 +212,7 @@ def placement_eval_selectetuds(evaluation_id): ) return runner.exec_placement() # calcul et generation du fichier htmls = [ - html_sco_header.sco_header(init_jquery_ui=True), + html_sco_header.sco_header(), sco_evaluations.evaluation_describe(evaluation_id=evaluation_id), "

    Placement et émargement des étudiants

    ", render_template("scodoc/forms/placement.html", form=form), diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 90c11f5214..bbf4700d85 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -127,6 +127,36 @@ EVALUATION_NORMALE = 0 EVALUATION_RATTRAPAGE = 1 EVALUATION_SESSION2 = 2 +MONTH_NAMES_ABBREV = ( + "Jan ", + "Fév ", + "Mars", + "Avr ", + "Mai ", + "Juin", + "Jul ", + "Août", + "Sept", + "Oct ", + "Nov ", + "Déc ", +) + +MONTH_NAMES = ( + "janvier", + "février", + "mars", + "avril", + "mai", + "juin", + "juillet", + "août", + "septembre", + "octobre", + "novembre", + "décembre", +) + def fmt_note(val, note_max=None, keep_numeric=False): """conversion note en str pour affichage dans tables HTML ou PDF.