From 740235ef01c3212a0ec09a67780ab60e7cf40ddf Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 29 Aug 2023 11:27:24 +0200 Subject: [PATCH] Modernise code appreciations sur bulletins --- app/but/bulletin_but.py | 4 -- app/but/bulletin_but_pdf.py | 8 +-- app/but/bulletin_but_xml_compat.py | 13 ++-- app/models/etudiants.py | 15 ++-- app/models/notes.py | 26 +++++++ app/scodoc/sco_bulletins.py | 32 +++------ app/scodoc/sco_bulletins_example.py | 6 +- app/scodoc/sco_bulletins_generator.py | 45 +++++++----- app/scodoc/sco_bulletins_json.py | 22 +++--- app/scodoc/sco_bulletins_legacy.py | 66 ++++++++++++------ app/scodoc/sco_bulletins_standard.py | 41 ++++++----- app/scodoc/sco_bulletins_xml.py | 14 ++-- app/scodoc/sco_etud.py | 25 ------- app/templates/bul_foot.j2 | 4 +- app/views/notes.py | 99 +++++++++++++++------------ 15 files changed, 224 insertions(+), 196 deletions(-) diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index 2e556908..74d167eb 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -531,10 +531,6 @@ class BulletinBUT: ] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}" d["rang_txt"] = "Rang " + d["rang_nt"] - # --- Appréciations - d.update( - sco_bulletins.get_appreciations_list(self.res.formsemestre.id, etud.id) - ) d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"])) return d diff --git a/app/but/bulletin_but_pdf.py b/app/but/bulletin_but_pdf.py index ce215c14..5e89ab70 100644 --- a/app/but/bulletin_but_pdf.py +++ b/app/but/bulletin_but_pdf.py @@ -12,8 +12,8 @@ La génération du bulletin PDF suit le chemin suivant: bul_dict = bulletin_but.BulletinBUT(formsemestre).bulletin_etud_complet(etud) -- sco_bulletins_generator.make_formsemestre_bulletinetud(infos) -- instance de BulletinGeneratorStandardBUT(infos) +- sco_bulletins_generator.make_formsemestre_bulletin_etud() +- instance de BulletinGeneratorStandardBUT - BulletinGeneratorStandardBUT.generate(format="pdf") sco_bulletins_generator.BulletinGenerator.generate() .generate_pdf() @@ -42,7 +42,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): multi_pages = True # plusieurs pages par bulletins small_fontsize = "8" - def bul_table(self, format="html"): + def bul_table(self, fmt="html"): """Génère la table centrale du bulletin de notes Renvoie: - en HTML: une chaine @@ -71,7 +71,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard): html_class_ignore_default=True, html_with_td_classes=True, ) - table_objects = table.gen(format=format) + table_objects = table.gen(format=fmt) objects += table_objects # objects += [KeepInFrame(0, 0, table_objects, mode="shrink")] if i != 2: diff --git a/app/but/bulletin_but_xml_compat.py b/app/but/bulletin_but_xml_compat.py index 45668ac5..75dade0a 100644 --- a/app/but/bulletin_but_xml_compat.py +++ b/app/but/bulletin_but_xml_compat.py @@ -40,7 +40,7 @@ from xml.etree.ElementTree import Element from app import log from app.but import bulletin_but -from app.models import FormSemestre, Identite +from app.models import BulAppreciations, FormSemestre, Identite import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc import codes_cursus @@ -315,16 +315,13 @@ def bulletin_but_xml_compat( else: doc.append(Element("decision", code="", etat="DEM")) # --- Appreciations - cnx = ndb.GetDBConnexion() - apprecs = sco_etud.appreciations_list( - cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} - ) - for appr in apprecs: + appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid) + for appreciation in appreciations: x_appr = Element( "appreciation", - date=ndb.DateDMYtoISO(appr["date"]), + date=appreciation.date.isoformat() if appreciation.date else "", ) - x_appr.text = quote_xml_attr(appr["comment"]) + x_appr.text = quote_xml_attr(appreciation.comment_safe()) doc.append(x_appr) if is_appending: diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 5c4ea31e..e00f8465 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -296,26 +296,27 @@ class Identite(db.Model): from app.scodoc import sco_photos d = { + "boursier": self.boursier or "", + "civilite_etat_civil": self.civilite_etat_civil, "civilite": self.civilite, "code_ine": self.code_ine or "", "code_nip": self.code_nip or "", "date_naissance": self.date_naissance.strftime("%d/%m/%Y") if self.date_naissance else "", - "dept_id": self.dept_id, "dept_acronym": self.departement.acronym, + "dept_id": self.dept_id, + "dept_naissance": self.dept_naissance or "", "email": self.get_first_email() or "", "emailperso": self.get_first_email("emailperso"), + "etat_civil": self.etat_civil, "etudid": self.id, - "nom": self.nom_disp(), - "prenom": self.prenom or "", - "nomprenom": self.nomprenom or "", "lieu_naissance": self.lieu_naissance or "", - "dept_naissance": self.dept_naissance or "", "nationalite": self.nationalite or "", - "boursier": self.boursier or "", - "civilite_etat_civil": self.civilite_etat_civil, + "nom": self.nom_disp(), + "nomprenom": self.nomprenom or "", "prenom_etat_civil": self.prenom_etat_civil, + "prenom": self.prenom or "", } if include_urls and has_request_context(): # test request context so we can use this func in tests under the flask shell diff --git a/app/models/notes.py b/app/models/notes.py index 2024a436..61eb1773 100644 --- a/app/models/notes.py +++ b/app/models/notes.py @@ -5,6 +5,7 @@ import sqlalchemy as sa from app import db +from app.scodoc import safehtml import app.scodoc.sco_utils as scu @@ -26,6 +27,31 @@ class BulAppreciations(db.Model): author = db.Column(db.Text) # le pseudo (user_name), sans contrainte comment = db.Column(db.Text) # texte libre + @classmethod + def get_appreciations_list( + cls, formsemestre_id: int, etudid: int + ) -> list["BulAppreciations"]: + "Liste des appréciations pour cet étudiant dans ce semestre" + return ( + BulAppreciations.query.filter_by( + etudid=etudid, formsemestre_id=formsemestre_id + ) + .order_by(BulAppreciations.date) + .all() + ) + + @classmethod + def summarize(cls, appreciations: list["BulAppreciations"]) -> list[str]: + "Liste de chaines résumant une liste d'appréciations, pour bulletins" + return [ + f"{x.date.strftime('%d/%m/%Y') if x.date else ''}: {x.comment or ''}" + for x in appreciations + ] + + def comment_safe(self) -> str: + "Le comment, safe pour inclusion dans HTML (None devient '')" + return safehtml.html_to_safe_html(self.comment or "") + class NotesNotes(db.Model): """Une note""" diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index ce5f6509..ba27c9b9 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -224,9 +224,6 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): elif I["etud_etat"] == codes_cursus.DEF: I["demission"] = "(Défaillant)" - # --- Appreciations - I.update(get_appreciations_list(formsemestre_id, etudid)) - # --- Notes ues = nt.get_ues_stat_dict() modimpls = nt.get_modimpls_dict() @@ -417,21 +414,6 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"): return C -def get_appreciations_list(formsemestre_id: int, etudid: int) -> dict: - """Appréciations pour cet étudiant dans ce semestre""" - cnx = ndb.GetDBConnexion() - apprecs = sco_etud.appreciations_list( - cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} - ) - d = { - "appreciations_list": apprecs, - "appreciations_txt": [x["date"] + ": " + x["comment"] for x in apprecs], - } - # deprecated / keep it for backward compat in templates: - d["appreciations"] = d["appreciations_txt"] - return d - - def _get_etud_etat_html(etat: str) -> str: """chaine html représentant l'état (backward compat sco7)""" if etat == scu.INSCRIT: @@ -1035,16 +1017,18 @@ def do_formsemestre_bulletinetud( bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id) if format == "html": - htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud( - bul_dict, version=version, format="html" + htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud( + bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html" ) return htm, bul_dict["filigranne"] elif format == "pdf" or format == "pdfpart": - bul, filename = sco_bulletins_generator.make_formsemestre_bulletinetud( + bul, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud( bul_dict, + etud=etud, + formsemestre=formsemestre, version=version, - format="pdf", + fmt="pdf", stand_alone=(format != "pdfpart"), with_img_signatures_pdf=with_img_signatures_pdf, ) @@ -1062,8 +1046,8 @@ def do_formsemestre_bulletinetud( if not can_send_bulletin_by_mail(formsemestre.id): raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") - pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud( - bul_dict, version=version, format="pdf" + pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud( + bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="pdf" ) if prefer_mail_perso: diff --git a/app/scodoc/sco_bulletins_example.py b/app/scodoc/sco_bulletins_example.py index 83ad85c3..663fc8d0 100644 --- a/app/scodoc/sco_bulletins_example.py +++ b/app/scodoc/sco_bulletins_example.py @@ -47,14 +47,14 @@ class BulletinGeneratorExample(sco_bulletins_standard.BulletinGeneratorStandard) # En général, on veut définir un format de table spécial, sans changer le reste (titre, pied de page). # Si on veut changer le reste, surcharger les méthodes: # .bul_title_pdf(self) : partie haute du bulletin - # .bul_part_below(self, format='') : infos sous la table + # .bul_part_below(self, fmt='') : infos sous la table # .bul_signatures_pdf(self) : signatures - def bul_table(self, format=""): + def bul_table(self, fmt=""): """Défini la partie centrale de notre bulletin PDF. Doit renvoyer une liste d'objets PLATYPUS """ - assert format == "pdf" # garde fou + assert fmt == "pdf" # garde fou return [ Paragraph( sco_pdf.SU( diff --git a/app/scodoc/sco_bulletins_generator.py b/app/scodoc/sco_bulletins_generator.py index b122d7a4..499833e1 100644 --- a/app/scodoc/sco_bulletins_generator.py +++ b/app/scodoc/sco_bulletins_generator.py @@ -31,8 +31,8 @@ class BulletinGenerator: description supported_formats = [ 'pdf', 'html' ] .bul_title_pdf() - .bul_table(format) - .bul_part_below(format) + .bul_table(fmt) + .bul_part_below(fmt) .bul_signatures_pdf() .__init__ et .generate(format) methodes appelees par le client (sco_bulletin) @@ -62,6 +62,7 @@ from reportlab.platypus import Table, TableStyle, Image, KeepInFrame from flask import request from flask_login import current_user +from app.models import FormSemestre, Identite from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import NoteProcessError from app import log @@ -85,9 +86,11 @@ class BulletinGenerator: self, bul_dict, authuser=None, - version="long", + etud: Identite = None, filigranne=None, + formsemestre: FormSemestre = None, server_name=None, + version="long", with_img_signatures_pdf: bool = True, ): from app.scodoc import sco_preferences @@ -98,9 +101,11 @@ class BulletinGenerator: self.infos = bul_dict # legacy code compat # authuser nécessaire pour version HTML qui contient liens dépendants de l'utilisateur self.authuser = authuser - self.version = version + self.etud: Identite = etud self.filigranne = filigranne + self.formsemestre: FormSemestre = formsemestre self.server_name = server_name + self.version = version self.with_img_signatures_pdf = with_img_signatures_pdf # Store preferences for convenience: formsemestre_id = self.bul_dict["formsemestre_id"] @@ -151,9 +156,9 @@ class BulletinGenerator: """Return bulletin as an HTML string""" H = ['
'] # table des notes: - H.append(self.bul_table(format="html")) # pylint: disable=no-member + H.append(self.bul_table(fmt="html")) # pylint: disable=no-member # infos sous la table: - H.append(self.bul_part_below(format="html")) # pylint: disable=no-member + H.append(self.bul_part_below(fmt="html")) # pylint: disable=no-member H.append("
") return "\n".join(H) @@ -169,7 +174,7 @@ class BulletinGenerator: nomprenom = self.bul_dict["etud"]["nomprenom"] etat_civil = self.bul_dict["etud"]["etat_civil"] marque_debut_bulletin = sco_pdf.DebutBulletin( - self.bul_dict["etat_civil"], + etat_civil, filigranne=self.bul_dict["filigranne"], footer_content=f"""ScoDoc - Bulletin de {nomprenom} - {time.strftime("%d/%m/%Y %H:%M")}""", ) @@ -179,9 +184,9 @@ class BulletinGenerator: index_obj_debut = len(story) # table des notes - story += self.bul_table(format="pdf") # pylint: disable=no-member + story += self.bul_table(fmt="pdf") # pylint: disable=no-member # infos sous la table - story += self.bul_part_below(format="pdf") # pylint: disable=no-member + story += self.bul_part_below(fmt="pdf") # pylint: disable=no-member # signatures story += self.bul_signatures_pdf() # pylint: disable=no-member if self.scale_table_in_page: @@ -249,10 +254,12 @@ class BulletinGenerator: # --------------------------------------------------------------------------- -def make_formsemestre_bulletinetud( +def make_formsemestre_bulletin_etud( bul_dict, + etud: Identite = None, + formsemestre: FormSemestre = None, version=None, # short, long, selectedevals - format="pdf", # html, pdf + fmt="pdf", # html, pdf stand_alone=True, with_img_signatures_pdf: bool = True, ): @@ -277,7 +284,7 @@ def make_formsemestre_bulletinetud( # si pas trouvé (modifs locales bizarres ,), ré-essaye avec la valeur par défaut bulletin_default_class_name(), ): - if bul_dict.get("type") == "BUT" and format.startswith("pdf"): + if bul_dict.get("type") == "BUT" and fmt.startswith("pdf"): gen_class = bulletin_get_class(bul_class_name + "BUT") if gen_class is None: gen_class = bulletin_get_class(bul_class_name) @@ -290,28 +297,32 @@ def make_formsemestre_bulletinetud( bul_generator = gen_class( bul_dict, authuser=current_user, - version=version, + etud=etud, filigranne=bul_dict["filigranne"], + formsemestre=formsemestre, server_name=request.url_root, + version=version, with_img_signatures_pdf=with_img_signatures_pdf, ) - if format not in bul_generator.supported_formats: + if fmt not in bul_generator.supported_formats: # use standard generator log( "Bulletin format %s not supported by %s, using %s" - % (format, bul_class_name, bulletin_default_class_name()) + % (fmt, bul_class_name, bulletin_default_class_name()) ) bul_class_name = bulletin_default_class_name() gen_class = bulletin_get_class(bul_class_name) bul_generator = gen_class( bul_dict, authuser=current_user, - version=version, + etud=etud, filigranne=bul_dict["filigranne"], + formsemestre=formsemestre, server_name=request.url_root, + version=version, with_img_signatures_pdf=with_img_signatures_pdf, ) - data = bul_generator.generate(format=format, stand_alone=stand_alone) + data = bul_generator.generate(format=fmt, stand_alone=stand_alone) finally: PDFLOCK.release() diff --git a/app/scodoc/sco_bulletins_json.py b/app/scodoc/sco_bulletins_json.py index fc4d4b42..a4ee2ed3 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, UniteEns +from app.models import BulAppreciations, Evaluation, Matiere, UniteEns from app.models.etudiants import Identite from app.models.formsemestre import FormSemestre @@ -303,18 +303,14 @@ def formsemestre_bulletinetud_published_dict( d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions)) # --- Appréciations - cnx = ndb.GetDBConnexion() - apprecs = sco_etud.appreciations_list( - cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} - ) - d["appreciation"] = [] - for app in apprecs: - d["appreciation"].append( - dict( - comment=quote_xml_attr(app["comment"]), - date=ndb.DateDMYtoISO(app["date"]), - ) - ) + appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid) + d["appreciation"] = [ + { + "comment": quote_xml_attr(appreciation["comment"]), + "date": appreciation.date.isoformat() if appreciation.date else "", + } + for appreciation in appreciations + ] # return d diff --git a/app/scodoc/sco_bulletins_legacy.py b/app/scodoc/sco_bulletins_legacy.py index 280c6f7b..419a2437 100644 --- a/app/scodoc/sco_bulletins_legacy.py +++ b/app/scodoc/sco_bulletins_legacy.py @@ -34,10 +34,14 @@ CE FORMAT N'EVOLUERA PLUS ET EST CONSIDERE COMME OBSOLETE. """ + +from flask import g, url_for + from reportlab.lib.colors import Color, blue from reportlab.lib.units import cm, mm from reportlab.platypus import Paragraph, Spacer, Table +from app.models import BulAppreciations from app.scodoc import sco_bulletins_generator from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_formsemestre @@ -65,14 +69,14 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): ) # impose un espace vertical entre le titre et la table qui suit return objects - def bul_table(self, format="html"): + def bul_table(self, fmt="html"): """Table bulletin""" - if format == "pdf": + if fmt == "pdf": return self.bul_table_pdf() - elif format == "html": + elif fmt == "html": return self.bul_table_html() else: - raise ValueError("invalid bulletin format (%s)" % format) + raise ValueError(f"invalid bulletin format ({fmt})") def bul_table_pdf(self): """Génère la table centrale du bulletin de notes @@ -239,16 +243,16 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): # --------------- return "\n".join(H) - def bul_part_below(self, format="html"): + def bul_part_below(self, fmt="html"): """Génère les informations placées sous la table de notes (absences, appréciations, décisions de jury...) """ - if format == "pdf": + if fmt == "pdf": return self.bul_part_below_pdf() - elif format == "html": + elif fmt == "html": return self.bul_part_below_html() else: - raise ValueError("invalid bulletin format (%s)" % format) + raise ValueError("invalid bulletin format (%s)" % fmt) def bul_part_below_pdf(self): """ @@ -277,11 +281,17 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): ) # ----- APPRECIATIONS - if self.infos.get("appreciations_list", False): + appreciations = BulAppreciations.get_appreciations_list( + self.formsemestre.id, self.etud.id + ) + if appreciations: objects.append(Spacer(1, 3 * mm)) objects.append( Paragraph( - SU("Appréciation : " + "\n".join(self.infos["appreciations_txt"])), + SU( + "Appréciation : " + + "\n".join(BulAppreciations.summarize(appreciations)) + ), self.CellStyle, ) ) @@ -325,24 +335,40 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator): authuser.has_permission(Permission.ScoEtudInscrit) ) H.append('
') - if I["appreciations_list"]: + appreciations = BulAppreciations.get_appreciations_list( + self.formsemestre.id, self.etud.id + ) + if appreciations: H.append("

Appréciations

") - for app in I["appreciations_list"]: + for appreciation in appreciations: if can_edit_app: - mlink = ( - 'modifier supprimer' - % (app["id"], app["id"]) - ) + mlink = f"""modifier + supprimer""" else: mlink = "" H.append( - '

%s%s%s

' - % (app["date"], app["comment"], mlink) + f"""

+ { + appreciation.date.strftime("%d/%m/%y") if appreciation.date else "" + } + {appreciation.comment_safe()} + {mlink} +

""" ) if can_edit_app: H.append( - '

Ajouter une appréciation

' - % self.infos + f"""

+ Ajouter une appréciation +

""" ) H.append("
") # --------------- diff --git a/app/scodoc/sco_bulletins_standard.py b/app/scodoc/sco_bulletins_standard.py index 0039dba5..dd3e8d7d 100644 --- a/app/scodoc/sco_bulletins_standard.py +++ b/app/scodoc/sco_bulletins_standard.py @@ -51,6 +51,7 @@ from reportlab.lib.colors import Color, blue from reportlab.lib.units import cm, mm from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table +from app.models import BulAppreciations import app.scodoc.sco_utils as scu from app.scodoc import ( gen_tables, @@ -92,7 +93,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): ) # impose un espace vertical entre le titre et la table qui suit return objects - def bul_table(self, format="html"): + def bul_table(self, fmt="html"): """Génère la table centrale du bulletin de notes Renvoie: - en HTML: une chaine @@ -112,9 +113,9 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): html_with_td_classes=True, ) - return T.gen(format=format) + return T.gen(format=fmt) - def bul_part_below(self, format="html"): + def bul_part_below(self, fmt="html"): """Génère les informations placées sous la table de notes (absences, appréciations, décisions de jury...) Renvoie: @@ -156,45 +157,53 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): # ---- APPRECIATIONS # le dir. des etud peut ajouter des appreciations, # mais aussi le chef (perm. ScoEtudInscrit) - can_edit_app = (self.authuser.id in self.infos["responsables"]) or ( + can_edit_app = (self.formsemestre.est_responsable(self.authuser)) or ( self.authuser.has_permission(Permission.ScoEtudInscrit) ) H.append('
') - for app in self.infos["appreciations_list"]: + appreciations = BulAppreciations.get_appreciations_list( + self.formsemestre.id, self.etud.id + ) + for appreciation in appreciations: if can_edit_app: mlink = f"""modifier supprimer""" else: mlink = "" H.append( - f"""

{app["date"]}{ - app["comment"]}{mlink} -

""" + f"""

+ { + appreciation.date.strftime("%d/%m/%Y") + if appreciation.date else ""} + {appreciation.comment_safe()} + {mlink} +

+ """ ) if can_edit_app: H.append( f"""

Ajouter une appréciation

""" % self.infos ) H.append("
") # Appréciations sur PDF: - if self.infos.get("appreciations_list", False): + if appreciations: story.append(Spacer(1, 3 * mm)) try: story.append( Paragraph( SU( "Appréciation : " - + "\n".join(self.infos["appreciations_txt"]) + + "\n".join(BulAppreciations.summarize(appreciations)) ), self.CellStyle, ) @@ -221,14 +230,14 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): H.append('
' + field + "
") # ----- - if format == "pdf": + if fmt == "pdf": if self.scale_table_in_page: # le scaling (pour tenir sur une page) semble incompatible avec # le KeepTogether() return story else: return [KeepTogether(story)] - elif format == "html": + elif fmt == "html": return "\n".join(H) def bul_signatures_pdf(self): diff --git a/app/scodoc/sco_bulletins_xml.py b/app/scodoc/sco_bulletins_xml.py index 4d96009b..3eafc5ca 100644 --- a/app/scodoc/sco_bulletins_xml.py +++ b/app/scodoc/sco_bulletins_xml.py @@ -50,8 +50,7 @@ import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat -from app.models.evaluations import Evaluation -from app.models.formsemestre import FormSemestre +from app.models import BulAppreciations, Evaluation, FormSemestre from app.scodoc import sco_assiduites from app.scodoc import codes_cursus from app.scodoc import sco_edit_ue @@ -415,16 +414,13 @@ def make_xml_formsemestre_bulletinetud( else: doc.append(Element("decision", code="", etat="DEM")) # --- Appreciations - cnx = ndb.GetDBConnexion() - apprecs = sco_etud.appreciations_list( - cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} - ) - for appr in apprecs: + appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid) + for appreciation in appreciations: x_appr = Element( "appreciation", - date=ndb.DateDMYtoISO(appr["date"]), + date=appreciation.date.isoformat() if appreciation.date else "", ) - x_appr.text = quote_xml_attr(appr["comment"]) + x_appr.text = quote_xml_attr(appreciation.comment_safe()) doc.append(x_appr) if is_appending: diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 2fb1f52d..9365ef7c 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -741,31 +741,6 @@ def add_annotations_to_etud_list(etuds): etud["annotations_str"] = ", ".join(l) -# -------- APPRECIATIONS (sur bulletins) ------------------- -# Les appreciations sont dans la table postgres notes_appreciations -_appreciationsEditor = ndb.EditableTable( - "notes_appreciations", - "id", - ( - "id", - "date", - "etudid", - "formsemestre_id", - "author", - "comment", - "author", - ), - sortkey="date desc", - convert_null_outputs_to_empty=True, - output_formators={"comment": safehtml.html_to_safe_html, "date": ndb.DateISOtoDMY}, -) - -appreciations_create = _appreciationsEditor.create -appreciations_delete = _appreciationsEditor.delete -appreciations_list = _appreciationsEditor.list -appreciations_edit = _appreciationsEditor.edit - - # -------- Noms des Lycées à partir du code def read_etablissements(): filename = os.path.join(scu.SCO_TOOLS_DIR, scu.CONFIG.ETABL_FILENAME) diff --git a/app/templates/bul_foot.j2 b/app/templates/bul_foot.j2 index e2085cb7..e06718f1 100644 --- a/app/templates/bul_foot.j2 +++ b/app/templates/bul_foot.j2 @@ -23,9 +23,9 @@ app.comment}}{% if can_edit_appreciations %}modifier + scodoc_dept=g.scodoc_dept, appreciation_id=app.id)}}">modifier supprimer{% endif %} + scodoc_dept=g.scodoc_dept, appreciation_id=app.id, suppress=1)}}">supprimer{% endif %}

{% endfor %} diff --git a/app/views/notes.py b/app/views/notes.py index 3b3ee2a6..11c25dbe 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -58,6 +58,7 @@ from app.but.forms import jury_but_forms from app.comp import jury, res_sem from app.comp.res_compat import NotesTableCompat from app.models import ( + BulAppreciations, Evaluation, Formation, ScolarAutorisationInscription, @@ -316,14 +317,14 @@ def formsemestre_bulletinetud( if formsemestre.formation.is_apc() and format == "html": return render_template( "but/bulletin.j2", - appreciations=models.BulAppreciations.query.filter_by( - etudid=etudid, formsemestre_id=formsemestre.id - ).order_by(models.BulAppreciations.date), + appreciations=BulAppreciations.get_appreciations_list( + formsemestre.id, etud.id + ), bul_url=url_for( "notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, - etudid=etudid, + etudid=etud.id, format="json", force_publishing=1, # pour ScoDoc lui même version=version, @@ -2039,64 +2040,72 @@ sco_publish( def appreciation_add_form( etudid=None, formsemestre_id=None, - id=None, # si id, edit + appreciation_id=None, # si id, edit suppress=False, # si true, supress id ): "form ajout ou edition d'une appreciation" - cnx = ndb.GetDBConnexion() - if id: # edit mode - apps = sco_etud.appreciations_list(cnx, args={"id": id}) - if not apps: + if appreciation_id: # edit mode + appreciation = db.session.get(BulAppreciations, appreciation_id) + if appreciation is None: raise ScoValueError("id d'appreciation invalide !") - app = apps[0] - formsemestre_id = app["formsemestre_id"] - etudid = app["etudid"] + formsemestre_id = appreciation.formsemestre_id + etudid = appreciation.etudid + etud: Identite = Identite.query.filter_by( + id=etudid, dept_id=g.scodoc_dept_id + ).first_or_404() vals = scu.get_request_args() if "edit" in vals: edit = int(vals["edit"]) - elif id: + elif appreciation_id: edit = 1 else: edit = 0 - sem = sco_formsemestre.get_formsemestre(formsemestre_id) + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) # check custom access permission - can_edit_app = (current_user.id in sem["responsables"]) or ( + can_edit_app = formsemestre.est_responsable(current_user) or ( current_user.has_permission(Permission.ScoEtudInscrit) ) if not can_edit_app: raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation") # - bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % ( - formsemestre_id, - etudid, + bul_url = url_for( + "notes.formsemestre_bulletinetud", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + etudid=etudid, ) + if suppress: - sco_etud.appreciations_delete(cnx, id) - logdb(cnx, method="appreciation_suppress", etudid=etudid, msg="") - return flask.redirect(bull_url) + db.session.delete(appreciation) + Scolog.logdb( + method="appreciation_suppress", + etudid=etudid, + ) + db.session.commit() + flash("appréciation supprimée") + return flask.redirect(bul_url) # - etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] - if id: - a = "Edition" + if appreciation_id: + action = "Édition" else: - a = "Ajout" + action = "Ajout" H = [ - html_sco_header.sco_header() - + "

%s d'une appréciation sur %s

" % (a, etud["nomprenom"]) + html_sco_header.sco_header(), + f"""

{action} d'une appréciation sur {etud.nomprenom}

""", ] F = html_sco_header.sco_footer() descr = [ ("edit", {"input_type": "hidden", "default": edit}), ("etudid", {"input_type": "hidden"}), ("formsemestre_id", {"input_type": "hidden"}), - ("id", {"input_type": "hidden"}), + ("appreciation_id", {"input_type": "hidden"}), ("comment", {"title": "", "input_type": "textarea", "rows": 4, "cols": 60}), ] - if id: + if appreciation_id: initvalues = { "etudid": etudid, "formsemestre_id": formsemestre_id, - "comment": app["comment"], + "comment": appreciation.comment, } else: initvalues = {} @@ -2111,31 +2120,33 @@ def appreciation_add_form( if tf[0] == 0: return "\n".join(H) + "\n" + tf[1] + F elif tf[0] == -1: - return flask.redirect(bull_url) + return flask.redirect(bul_url) else: - args = { - "etudid": etudid, - "formsemestre_id": formsemestre_id, - "author": current_user.user_name, - "comment": tf[2]["comment"], - } if edit: - args["id"] = id - sco_etud.appreciations_edit(cnx, args) + appreciation.author = (current_user.user_name,) + appreciation.comment = tf[2]["comment"].strip() + flash("appréciation modifiée") else: # nouvelle - sco_etud.appreciations_create(cnx, args) + appreciation = BulAppreciations( + etudid=etudid, + formsemestre_id=formsemestre_id, + author=current_user.user_name, + comment=tf[2]["comment"].strip(), + ) + flash("appréciation ajoutée") + db.session.add(appreciation) # log - logdb( - cnx, + Scolog.logdb( method="appreciation_add", etudid=etudid, - msg=tf[2]["comment"], + msg=appreciation.comment_safe(), ) + db.session.commit() # ennuyeux mais necessaire (pour le PDF seulement) sco_cache.invalidate_formsemestre( pdfonly=True, formsemestre_id=formsemestre_id ) # > appreciation_add - return flask.redirect(bull_url) + return flask.redirect(bul_url) # --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES