diff --git a/app/comp/res_common.py b/app/comp/res_common.py index fb064f489..006cbe6b9 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -183,7 +183,7 @@ class ResultatsSemestre(ResultatsCache): sum_coefs_ue = 0.0 for ue in self.formsemestre.query_ues(): ue_cap = self.get_etud_ue_status(etudid, ue.id) - if ue_cap["is_capitalized"]: + if ue_cap and ue_cap["is_capitalized"]: recompute_mg = True coef = ue_cap["coef_ue"] if not np.isnan(ue_cap["moy"]): @@ -338,13 +338,12 @@ class NotesTableCompat(ResultatsSemestre): return self.formsemestre.get_infos_dict() @cached_property - def inscrlist(self) -> list[dict]: # utilisé par PE seulement + def inscrlist(self) -> list[dict]: # utilisé par PE """Liste des inscrits au semestre (avec DEM et DEF), sous forme de dict etud, classée dans l'ordre alphabétique de noms. """ - etuds = self.formsemestre.get_inscrits(include_demdef=True) - etuds.sort(key=lambda e: e.sort_key) + etuds = self.formsemestre.get_inscrits(include_demdef=True, sorted=True) return [e.to_dict_scodoc7() for e in etuds] @cached_property @@ -419,12 +418,12 @@ class NotesTableCompat(ResultatsSemestre): Return: True|False, message explicatif """ - return self.parcours.check_barre_ues( - [ - self.get_etud_ue_status(etudid, ue.id) - for ue in self.formsemestre.query_ues() - ] - ) + ue_status_list = [] + for ue in self.formsemestre.query_ues(): + ue_status = self.get_etud_ue_status(etudid, ue.id) + if ue_status: + ue_status_list.append(ue_status) + return self.parcours.check_barre_ues(ue_status_list) def get_etud_decision_ues(self, etudid: int) -> dict: """Decisions du jury pour les UE de cet etudiant, ou None s'il n'y en pas eu. @@ -622,8 +621,11 @@ class NotesTableCompat(ResultatsSemestre): ue_is_cap = {} for ue in ues: ue_status = self.get_etud_ue_status(etudid, ue.id) - moy_ues.append(ue_status["moy"]) - ue_is_cap[ue.id] = ue_status["is_capitalized"] + if ue_status: + moy_ues.append(ue_status["moy"]) + ue_is_cap[ue.id] = ue_status["is_capitalized"] + else: + moy_ues.append("?") t = [moy_gen] + list(moy_ues) # Moyennes modules: for modimpl in self.formsemestre.modimpls_sorted: diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 72e2088f5..d919ee97c 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -14,6 +14,7 @@ from app import models from app.scodoc import notesdb as ndb from app.scodoc.sco_bac import Baccalaureat +import app.scodoc.sco_utils as scu class Identite(db.Model): @@ -74,6 +75,13 @@ class Identite(db.Model): """ return {"M": "M.", "F": "Mme", "X": ""}[self.civilite] + def sex_nom(self, no_accents=False) -> str: + "'M. DUPONTÉ', ou si no_accents, 'M. DUPONTE'" + s = f"{self.civilite_str} {(self.nom_usuel or self.nom).upper()}" + if no_accents: + return scu.suppress_accents(s) + return s + def nom_disp(self) -> str: "Nom à afficher" if self.nom_usuel: diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index d874a3ad1..2d6006c20 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -363,15 +363,19 @@ class FormSemestre(db.Model): etudid, self.date_debut.isoformat(), self.date_fin.isoformat() ) - def get_inscrits(self, include_demdef=False) -> list[Identite]: + def get_inscrits(self, include_demdef=False, sorted=False) -> list[Identite]: """Liste des étudiants inscrits à ce semestre Si include_demdef, tous les étudiants, avec les démissionnaires et défaillants. + Si sorted, tri par clé sort_key """ if include_demdef: - return [ins.etud for ins in self.inscriptions] + etuds = [ins.etud for ins in self.inscriptions] else: - return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT] + etuds = [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT] + if sorted: + etuds.sort(key=lambda e: e.sort_key) + return etuds @cached_property def etudids_actifs(self) -> set: diff --git a/app/models/notes.py b/app/models/notes.py index 7e5583579..239d7bb43 100644 --- a/app/models/notes.py +++ b/app/models/notes.py @@ -4,8 +4,9 @@ """ from app import db -from app.models import SHORT_STR_LEN -from app.models import CODE_STR_LEN + +import app.scodoc.notesdb as ndb +import app.scodoc.sco_utils as scu class BulAppreciations(db.Model): @@ -67,3 +68,32 @@ class NotesNotesLog(db.Model): comment = db.Column(db.Text) # texte libre date = db.Column(db.DateTime(timezone=True), server_default=db.func.now()) uid = db.Column(db.Integer, db.ForeignKey("user.id")) + + +def etud_has_notes_attente(etudid, formsemestre_id): + """Vrai si cet etudiant a au moins une note en attente dans ce semestre. + (ne compte que les notes en attente dans des évaluation avec coef. non nul). + """ + # XXX ancienne méthode de notes_table à ré-écrire + cnx = ndb.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) + cursor.execute( + """SELECT n.* + FROM notes_notes n, notes_evaluation e, notes_moduleimpl m, + notes_moduleimpl_inscription i + WHERE n.etudid = %(etudid)s + and n.value = %(code_attente)s + and n.evaluation_id = e.id + and e.moduleimpl_id = m.id + and m.formsemestre_id = %(formsemestre_id)s + and e.coefficient != 0 + and m.id = i.moduleimpl_id + and i.etudid=%(etudid)s + """, + { + "formsemestre_id": formsemestre_id, + "etudid": etudid, + "code_attente": scu.NOTES_ATTENTE, + }, + ) + return len(cursor.fetchall()) > 0 diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index d014d9faf..7fc03ce89 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -954,9 +954,12 @@ class NotesTable: Return: True|False, message explicatif """ - return self.parcours.check_barre_ues( - [self.get_etud_ue_status(etudid, ue["ue_id"]) for ue in self._ues] - ) + ue_status_list = [] + for ue in self._ues: + ue_status = self.get_etud_ue_status(etudid, ue["ue_id"]) + if ue_status: + ue_status_list.append(ue_status) + return self.parcours.check_barre_ues(ue_status_list) def get_table_moyennes_triees(self): return self.T @@ -1160,9 +1163,11 @@ class NotesTable: nt_cap = sco_cache.NotesTableCache.get( ue_cap["formsemestre_id"] ) # > UE capitalisees par un etud - moy_ue_cap = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"])[ - "moy" - ] + ue_cap_status = nt_cap.get_etud_ue_status(etudid, ue_cap["ue_id"]) + if ue_cap_status: + moy_ue_cap = ue_cap_status["moy"] + else: + moy_ue_cap = "" ue_cap["moy_ue"] = moy_ue_cap if ( isinstance(moy_ue_cap, float) diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index 4cae17724..904b939ae 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -419,7 +419,7 @@ class ApoEtud(dict): ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) code_decision_ue = decisions_ue[ue["ue_id"]]["code"] return dict( - N=_apo_fmt_note(ue_status["moy"]), + N=_apo_fmt_note(ue_status["moy"] if ue_status else ""), B=20, J="", R=ScoDocSiteConfig.get_code_apo(code_decision_ue), diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index dbf4a8dc9..ee57b60e7 100644 --- a/app/scodoc/sco_bulletins_json.py +++ b/app/scodoc/sco_bulletins_json.py @@ -209,7 +209,7 @@ def formsemestre_bulletinetud_published_dict( acronyme=scu.quote_xml_attr(ue["acronyme"]), titre=scu.quote_xml_attr(ue["titre"]), note=dict( - value=scu.fmt_note(ue_status["cur_moy_ue"]), + value=scu.fmt_note(ue_status["cur_moy_ue"] if ue_status else ""), min=scu.fmt_note(ue["min"]), max=scu.fmt_note(ue["max"]), moy=scu.fmt_note( diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 2195d1eef..fa599fa56 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -56,19 +56,24 @@ import time import traceback from pydoc import html -from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate +from reportlab.platypus.doctemplate import BaseDocTemplate from flask import g, request -import app.scodoc.sco_utils as scu from app import log, ScoValueError +from app.comp import res_sem +from app.comp.res_common import NotesTableCompat +from app.models import FormSemestre + from app.scodoc import sco_cache from app.scodoc import sco_formsemestre from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_etud -import sco_version from app.scodoc.sco_logos import find_logo +import app.scodoc.sco_utils as scu + +import sco_version def pdfassemblebulletins( @@ -178,22 +183,21 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): if cached: return cached[1], cached[0] fragments = [] - sem = sco_formsemestre.get_formsemestre(formsemestre_id) # Make each bulletin - nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etudids, get_sexnom + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) bookmarks = {} filigrannes = {} i = 1 - for etudid in nt.get_etudids(): + for etud in formsemestre.get_inscrits(include_demdef=True, sorted=True): frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud( formsemestre_id, - etudid, + etud.id, format="pdfpart", version=version, ) fragments += frag filigrannes[i] = filigranne - bookmarks[i] = scu.suppress_accents(nt.get_sexnom(etudid)) + bookmarks[i] = etud.sex_nom(no_accents=True) i = i + 1 # infos = {"DeptName": sco_preferences.get_preference("DeptName", formsemestre_id)} @@ -206,7 +210,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): pdfdoc = pdfassemblebulletins( formsemestre_id, fragments, - sem["titremois"], + formsemestre.titre_mois(), infos, bookmarks, filigranne=filigrannes, @@ -216,7 +220,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): sco_pdf.PDFLOCK.release() # dt = time.strftime("%Y-%m-%d") - filename = "bul-%s-%s.pdf" % (sem["titre_num"], dt) + filename = "bul-%s-%s.pdf" % (formsemestre.titre_num(), dt) filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "") # fill cache sco_cache.SemBulletinsPDFCache.set( diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py index 8133501bf..015867e32 100644 --- a/app/scodoc/sco_bulletins_xml.py +++ b/app/scodoc/sco_bulletins_xml.py @@ -217,7 +217,7 @@ def make_xml_formsemestre_bulletinetud( ) doc.append(x_ue) if ue["type"] != sco_codes_parcours.UE_SPORT: - v = ue_status["cur_moy_ue"] + v = ue_status["cur_moy_ue"] if ue_status else "" else: v = nt.bonus[etudid] if nt.bonus is not None else 0.0 x_ue.append( diff --git a/app/scodoc/sco_formsemestre_inscriptions.py b/app/scodoc/sco_formsemestre_inscriptions.py index 48d24f519..6ecfa32f2 100644 --- a/app/scodoc/sco_formsemestre_inscriptions.py +++ b/app/scodoc/sco_formsemestre_inscriptions.py @@ -475,7 +475,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id): raise ScoValueError("Modification impossible: semestre verrouille") etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_etud_ue_status + nt = sco_cache.NotesTableCache.get(formsemestre_id) F = html_sco_header.sco_footer() H = [ @@ -527,7 +527,7 @@ def formsemestre_inscription_option(etudid, formsemestre_id): if ue["type"] != UE_STANDARD: ue_descr += " %s" % UE_TYPE_NAME[ue["type"]] ue_status = nt.get_etud_ue_status(etudid, ue_id) - if ue_status["is_capitalized"]: + if ue_status and ue_status["is_capitalized"]: sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"]) ue_descr += ' (capitalisée le %s)' % ( sem_origin["formsemestre_id"], diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 7148ad166..f755bb118 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -31,7 +31,6 @@ import time import flask from flask import url_for, g, request -from app.api.sco_api import formsemestre import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -40,6 +39,7 @@ from app import log from app.comp import res_sem from app.comp.res_common import NotesTableCompat from app.models import FormSemestre +from app.models.notes import etud_has_notes_attente from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.scolog import logdb @@ -53,9 +53,7 @@ from app.scodoc import sco_cache from app.scodoc import sco_edit_ue from app.scodoc import sco_etud from app.scodoc import sco_formsemestre -from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_inscriptions -from app.scodoc import sco_formsemestre_status from app.scodoc import sco_parcours_dut from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue from app.scodoc import sco_photos @@ -72,9 +70,8 @@ def formsemestre_validation_etud_form( sortcol=None, readonly=True, ): - nt = sco_cache.NotesTableCache.get( - formsemestre_id - ) # > get_table_moyennes_triees, get_etud_decision_sem + formsemestre = FormSemestre.query.get_or_404(formsemestre_id) + nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) T = nt.get_table_moyennes_triees() if not etudid and etud_index is None: raise ValueError("formsemestre_validation_etud_form: missing argument etudid") @@ -202,7 +199,7 @@ def formsemestre_validation_etud_form( decision_jury = Se.nt.get_etud_decision_sem(etudid) # Bloque si note en attente - if nt.etud_has_notes_attente(etudid): + if etud_has_notes_attente(etudid, formsemestre_id): H.append( tf_error_message( f"""Impossible de statuer sur cet étudiant: il a des notes en @@ -550,7 +547,6 @@ def formsemestre_recap_parcours_table( formsemestre = FormSemestre.query.get(sem["formsemestre_id"]) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) - # nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"]) if is_cur: type_sem = "*" # now unused class_sem = "sem_courant" @@ -649,7 +645,7 @@ def formsemestre_recap_parcours_table( else: code = "" ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) - moy_ue = ue_status["moy"] + moy_ue = ue_status["moy"] if ue_status else "" explanation_ue = [] # list of strings if code == ADM: class_ue = "ue_adm" @@ -657,12 +653,12 @@ def formsemestre_recap_parcours_table( class_ue = "ue_cmp" else: class_ue = "ue" - if ue_status["is_external"]: # validation externe + if ue_status and ue_status["is_external"]: # validation externe explanation_ue.append("UE externe.") # log('x'*12+' EXTERNAL %s' % notes_table.fmt_note(moy_ue)) XXXXXXX # log('UE=%s' % pprint.pformat(ue)) # log('explanation_ue=%s\n'%explanation_ue) - if ue_status["is_capitalized"]: + if ue_status and ue_status["is_capitalized"]: class_ue += " ue_capitalized" explanation_ue.append( "Capitalisée le %s." % (ue_status["event_date"] or "?") @@ -709,7 +705,10 @@ def formsemestre_recap_parcours_table( # ECTS validables dans chaque UE for ue in ues: ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"]) - H.append('