1
0
forked from ScoDoc/ScoDoc

Modernise code appreciations sur bulletins

This commit is contained in:
Emmanuel Viennet 2023-08-29 11:27:24 +02:00
parent 36a5c15e3a
commit 740235ef01
15 changed files with 224 additions and 196 deletions

View File

@ -531,10 +531,6 @@ class BulletinBUT:
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}" ] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
d["rang_txt"] = "Rang " + d["rang_nt"] 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"])) d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
return d return d

View File

@ -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) bul_dict = bulletin_but.BulletinBUT(formsemestre).bulletin_etud_complet(etud)
- sco_bulletins_generator.make_formsemestre_bulletinetud(infos) - sco_bulletins_generator.make_formsemestre_bulletin_etud()
- instance de BulletinGeneratorStandardBUT(infos) - instance de BulletinGeneratorStandardBUT
- BulletinGeneratorStandardBUT.generate(format="pdf") - BulletinGeneratorStandardBUT.generate(format="pdf")
sco_bulletins_generator.BulletinGenerator.generate() sco_bulletins_generator.BulletinGenerator.generate()
.generate_pdf() .generate_pdf()
@ -42,7 +42,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
multi_pages = True # plusieurs pages par bulletins multi_pages = True # plusieurs pages par bulletins
small_fontsize = "8" 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 """Génère la table centrale du bulletin de notes
Renvoie: Renvoie:
- en HTML: une chaine - en HTML: une chaine
@ -71,7 +71,7 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
html_class_ignore_default=True, html_class_ignore_default=True,
html_with_td_classes=True, html_with_td_classes=True,
) )
table_objects = table.gen(format=format) table_objects = table.gen(format=fmt)
objects += table_objects objects += table_objects
# objects += [KeepInFrame(0, 0, table_objects, mode="shrink")] # objects += [KeepInFrame(0, 0, table_objects, mode="shrink")]
if i != 2: if i != 2:

View File

@ -40,7 +40,7 @@ from xml.etree.ElementTree import Element
from app import log from app import log
from app.but import bulletin_but 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.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
@ -315,16 +315,13 @@ def bulletin_but_xml_compat(
else: else:
doc.append(Element("decision", code="", etat="DEM")) doc.append(Element("decision", code="", etat="DEM"))
# --- Appreciations # --- Appreciations
cnx = ndb.GetDBConnexion() appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid)
apprecs = sco_etud.appreciations_list( for appreciation in appreciations:
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
for appr in apprecs:
x_appr = Element( x_appr = Element(
"appreciation", "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) doc.append(x_appr)
if is_appending: if is_appending:

View File

@ -296,26 +296,27 @@ class Identite(db.Model):
from app.scodoc import sco_photos from app.scodoc import sco_photos
d = { d = {
"boursier": self.boursier or "",
"civilite_etat_civil": self.civilite_etat_civil,
"civilite": self.civilite, "civilite": self.civilite,
"code_ine": self.code_ine or "", "code_ine": self.code_ine or "",
"code_nip": self.code_nip or "", "code_nip": self.code_nip or "",
"date_naissance": self.date_naissance.strftime("%d/%m/%Y") "date_naissance": self.date_naissance.strftime("%d/%m/%Y")
if self.date_naissance if self.date_naissance
else "", else "",
"dept_id": self.dept_id,
"dept_acronym": self.departement.acronym, "dept_acronym": self.departement.acronym,
"dept_id": self.dept_id,
"dept_naissance": self.dept_naissance or "",
"email": self.get_first_email() or "", "email": self.get_first_email() or "",
"emailperso": self.get_first_email("emailperso"), "emailperso": self.get_first_email("emailperso"),
"etat_civil": self.etat_civil,
"etudid": self.id, "etudid": self.id,
"nom": self.nom_disp(),
"prenom": self.prenom or "",
"nomprenom": self.nomprenom or "",
"lieu_naissance": self.lieu_naissance or "", "lieu_naissance": self.lieu_naissance or "",
"dept_naissance": self.dept_naissance or "",
"nationalite": self.nationalite or "", "nationalite": self.nationalite or "",
"boursier": self.boursier or "", "nom": self.nom_disp(),
"civilite_etat_civil": self.civilite_etat_civil, "nomprenom": self.nomprenom or "",
"prenom_etat_civil": self.prenom_etat_civil, "prenom_etat_civil": self.prenom_etat_civil,
"prenom": self.prenom or "",
} }
if include_urls and has_request_context(): if include_urls and has_request_context():
# test request context so we can use this func in tests under the flask shell # test request context so we can use this func in tests under the flask shell

View File

@ -5,6 +5,7 @@
import sqlalchemy as sa import sqlalchemy as sa
from app import db from app import db
from app.scodoc import safehtml
import app.scodoc.sco_utils as scu 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 author = db.Column(db.Text) # le pseudo (user_name), sans contrainte
comment = db.Column(db.Text) # texte libre 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): class NotesNotes(db.Model):
"""Une note""" """Une note"""

View File

@ -224,9 +224,6 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
elif I["etud_etat"] == codes_cursus.DEF: elif I["etud_etat"] == codes_cursus.DEF:
I["demission"] = "(Défaillant)" I["demission"] = "(Défaillant)"
# --- Appreciations
I.update(get_appreciations_list(formsemestre_id, etudid))
# --- Notes # --- Notes
ues = nt.get_ues_stat_dict() ues = nt.get_ues_stat_dict()
modimpls = nt.get_modimpls_dict() modimpls = nt.get_modimpls_dict()
@ -417,21 +414,6 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
return C 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: def _get_etud_etat_html(etat: str) -> str:
"""chaine html représentant l'état (backward compat sco7)""" """chaine html représentant l'état (backward compat sco7)"""
if etat == scu.INSCRIT: if etat == scu.INSCRIT:
@ -1035,16 +1017,18 @@ def do_formsemestre_bulletinetud(
bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id) bul_dict = formsemestre_bulletinetud_dict(formsemestre.id, etud.id)
if format == "html": if format == "html":
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud( htm, _ = sco_bulletins_generator.make_formsemestre_bulletin_etud(
bul_dict, version=version, format="html" bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="html"
) )
return htm, bul_dict["filigranne"] return htm, bul_dict["filigranne"]
elif format == "pdf" or format == "pdfpart": 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, bul_dict,
etud=etud,
formsemestre=formsemestre,
version=version, version=version,
format="pdf", fmt="pdf",
stand_alone=(format != "pdfpart"), stand_alone=(format != "pdfpart"),
with_img_signatures_pdf=with_img_signatures_pdf, 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): if not can_send_bulletin_by_mail(formsemestre.id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletinetud( pdfdata, filename = sco_bulletins_generator.make_formsemestre_bulletin_etud(
bul_dict, version=version, format="pdf" bul_dict, etud=etud, formsemestre=formsemestre, version=version, fmt="pdf"
) )
if prefer_mail_perso: if prefer_mail_perso:

View File

@ -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). # 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: # Si on veut changer le reste, surcharger les méthodes:
# .bul_title_pdf(self) : partie haute du bulletin # .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 # .bul_signatures_pdf(self) : signatures
def bul_table(self, format=""): def bul_table(self, fmt=""):
"""Défini la partie centrale de notre bulletin PDF. """Défini la partie centrale de notre bulletin PDF.
Doit renvoyer une liste d'objets PLATYPUS Doit renvoyer une liste d'objets PLATYPUS
""" """
assert format == "pdf" # garde fou assert fmt == "pdf" # garde fou
return [ return [
Paragraph( Paragraph(
sco_pdf.SU( sco_pdf.SU(

View File

@ -31,8 +31,8 @@ class BulletinGenerator:
description description
supported_formats = [ 'pdf', 'html' ] supported_formats = [ 'pdf', 'html' ]
.bul_title_pdf() .bul_title_pdf()
.bul_table(format) .bul_table(fmt)
.bul_part_below(format) .bul_part_below(fmt)
.bul_signatures_pdf() .bul_signatures_pdf()
.__init__ et .generate(format) methodes appelees par le client (sco_bulletin) .__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 import request
from flask_login import current_user from flask_login import current_user
from app.models import FormSemestre, Identite
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError from app.scodoc.sco_exceptions import NoteProcessError
from app import log from app import log
@ -85,9 +86,11 @@ class BulletinGenerator:
self, self,
bul_dict, bul_dict,
authuser=None, authuser=None,
version="long", etud: Identite = None,
filigranne=None, filigranne=None,
formsemestre: FormSemestre = None,
server_name=None, server_name=None,
version="long",
with_img_signatures_pdf: bool = True, with_img_signatures_pdf: bool = True,
): ):
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
@ -98,9 +101,11 @@ class BulletinGenerator:
self.infos = bul_dict # legacy code compat self.infos = bul_dict # legacy code compat
# authuser nécessaire pour version HTML qui contient liens dépendants de l'utilisateur # authuser nécessaire pour version HTML qui contient liens dépendants de l'utilisateur
self.authuser = authuser self.authuser = authuser
self.version = version self.etud: Identite = etud
self.filigranne = filigranne self.filigranne = filigranne
self.formsemestre: FormSemestre = formsemestre
self.server_name = server_name self.server_name = server_name
self.version = version
self.with_img_signatures_pdf = with_img_signatures_pdf self.with_img_signatures_pdf = with_img_signatures_pdf
# Store preferences for convenience: # Store preferences for convenience:
formsemestre_id = self.bul_dict["formsemestre_id"] formsemestre_id = self.bul_dict["formsemestre_id"]
@ -151,9 +156,9 @@ class BulletinGenerator:
"""Return bulletin as an HTML string""" """Return bulletin as an HTML string"""
H = ['<div class="notes_bulletin">'] H = ['<div class="notes_bulletin">']
# table des notes: # 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: # 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("</div>") H.append("</div>")
return "\n".join(H) return "\n".join(H)
@ -169,7 +174,7 @@ class BulletinGenerator:
nomprenom = self.bul_dict["etud"]["nomprenom"] nomprenom = self.bul_dict["etud"]["nomprenom"]
etat_civil = self.bul_dict["etud"]["etat_civil"] etat_civil = self.bul_dict["etud"]["etat_civil"]
marque_debut_bulletin = sco_pdf.DebutBulletin( marque_debut_bulletin = sco_pdf.DebutBulletin(
self.bul_dict["etat_civil"], etat_civil,
filigranne=self.bul_dict["filigranne"], filigranne=self.bul_dict["filigranne"],
footer_content=f"""ScoDoc - Bulletin de {nomprenom} - {time.strftime("%d/%m/%Y %H:%M")}""", 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) index_obj_debut = len(story)
# table des notes # 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 # 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 # signatures
story += self.bul_signatures_pdf() # pylint: disable=no-member story += self.bul_signatures_pdf() # pylint: disable=no-member
if self.scale_table_in_page: if self.scale_table_in_page:
@ -249,10 +254,12 @@ class BulletinGenerator:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def make_formsemestre_bulletinetud( def make_formsemestre_bulletin_etud(
bul_dict, bul_dict,
etud: Identite = None,
formsemestre: FormSemestre = None,
version=None, # short, long, selectedevals version=None, # short, long, selectedevals
format="pdf", # html, pdf fmt="pdf", # html, pdf
stand_alone=True, stand_alone=True,
with_img_signatures_pdf: bool = 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 # si pas trouvé (modifs locales bizarres ,), ré-essaye avec la valeur par défaut
bulletin_default_class_name(), 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") gen_class = bulletin_get_class(bul_class_name + "BUT")
if gen_class is None: if gen_class is None:
gen_class = bulletin_get_class(bul_class_name) gen_class = bulletin_get_class(bul_class_name)
@ -290,28 +297,32 @@ def make_formsemestre_bulletinetud(
bul_generator = gen_class( bul_generator = gen_class(
bul_dict, bul_dict,
authuser=current_user, authuser=current_user,
version=version, etud=etud,
filigranne=bul_dict["filigranne"], filigranne=bul_dict["filigranne"],
formsemestre=formsemestre,
server_name=request.url_root, server_name=request.url_root,
version=version,
with_img_signatures_pdf=with_img_signatures_pdf, 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 # use standard generator
log( log(
"Bulletin format %s not supported by %s, using %s" "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() bul_class_name = bulletin_default_class_name()
gen_class = bulletin_get_class(bul_class_name) gen_class = bulletin_get_class(bul_class_name)
bul_generator = gen_class( bul_generator = gen_class(
bul_dict, bul_dict,
authuser=current_user, authuser=current_user,
version=version, etud=etud,
filigranne=bul_dict["filigranne"], filigranne=bul_dict["filigranne"],
formsemestre=formsemestre,
server_name=request.url_root, server_name=request.url_root,
version=version,
with_img_signatures_pdf=with_img_signatures_pdf, 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: finally:
PDFLOCK.release() PDFLOCK.release()

View File

@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import but_validations 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.etudiants import Identite
from app.models.formsemestre import FormSemestre 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)) d.update(dict_decision_jury(etud, formsemestre, with_decisions=xml_with_decisions))
# --- Appréciations # --- Appréciations
cnx = ndb.GetDBConnexion() appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid)
apprecs = sco_etud.appreciations_list( d["appreciation"] = [
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id} {
) "comment": quote_xml_attr(appreciation["comment"]),
d["appreciation"] = [] "date": appreciation.date.isoformat() if appreciation.date else "",
for app in apprecs: }
d["appreciation"].append( for appreciation in appreciations
dict( ]
comment=quote_xml_attr(app["comment"]),
date=ndb.DateDMYtoISO(app["date"]),
)
)
# #
return d return d

View File

@ -34,10 +34,14 @@
CE FORMAT N'EVOLUERA PLUS ET EST CONSIDERE COMME OBSOLETE. 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.colors import Color, blue
from reportlab.lib.units import cm, mm from reportlab.lib.units import cm, mm
from reportlab.platypus import Paragraph, Spacer, Table 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_generator
from app.scodoc import sco_bulletins_pdf from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_formsemestre 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 ) # impose un espace vertical entre le titre et la table qui suit
return objects return objects
def bul_table(self, format="html"): def bul_table(self, fmt="html"):
"""Table bulletin""" """Table bulletin"""
if format == "pdf": if fmt == "pdf":
return self.bul_table_pdf() return self.bul_table_pdf()
elif format == "html": elif fmt == "html":
return self.bul_table_html() return self.bul_table_html()
else: else:
raise ValueError("invalid bulletin format (%s)" % format) raise ValueError(f"invalid bulletin format ({fmt})")
def bul_table_pdf(self): def bul_table_pdf(self):
"""Génère la table centrale du bulletin de notes """Génère la table centrale du bulletin de notes
@ -239,16 +243,16 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
# --------------- # ---------------
return "\n".join(H) 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 """Génère les informations placées sous la table de notes
(absences, appréciations, décisions de jury...) (absences, appréciations, décisions de jury...)
""" """
if format == "pdf": if fmt == "pdf":
return self.bul_part_below_pdf() return self.bul_part_below_pdf()
elif format == "html": elif fmt == "html":
return self.bul_part_below_html() return self.bul_part_below_html()
else: else:
raise ValueError("invalid bulletin format (%s)" % format) raise ValueError("invalid bulletin format (%s)" % fmt)
def bul_part_below_pdf(self): def bul_part_below_pdf(self):
""" """
@ -277,11 +281,17 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
) )
# ----- APPRECIATIONS # ----- 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(Spacer(1, 3 * mm))
objects.append( objects.append(
Paragraph( Paragraph(
SU("Appréciation : " + "\n".join(self.infos["appreciations_txt"])), SU(
"Appréciation : "
+ "\n".join(BulAppreciations.summarize(appreciations))
),
self.CellStyle, self.CellStyle,
) )
) )
@ -325,24 +335,40 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
authuser.has_permission(Permission.ScoEtudInscrit) authuser.has_permission(Permission.ScoEtudInscrit)
) )
H.append('<div class="bull_appreciations">') H.append('<div class="bull_appreciations">')
if I["appreciations_list"]: appreciations = BulAppreciations.get_appreciations_list(
self.formsemestre.id, self.etud.id
)
if appreciations:
H.append("<p><b>Appréciations</b></p>") H.append("<p><b>Appréciations</b></p>")
for app in I["appreciations_list"]: for appreciation in appreciations:
if can_edit_app: if can_edit_app:
mlink = ( mlink = f"""<a class="stdlink" href="{
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&suppress=1">supprimer</a>' url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dpt, appreciation_id=appreciation.id)
% (app["id"], app["id"]) }">modifier</a>
) <a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dpt, appreciation_id=appreciation.id, suppress=1)
}">supprimer</a>"""
else: else:
mlink = "" mlink = ""
H.append( H.append(
'<p><span class="bull_appreciations_date">%s</span>%s<span class="bull_appreciations_link">%s</span></p>' f"""<p>
% (app["date"], app["comment"], mlink) <span class="bull_appreciations_date">{
appreciation.date.strftime("%d/%m/%y") if appreciation.date else ""
}</span>
{appreciation.comment_safe()}
<span class="bull_appreciations_link">{mlink}</span>
</p>"""
) )
if can_edit_app: if can_edit_app:
H.append( H.append(
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>' f"""<p>
% self.infos <a class="stdlink" href="{
url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept,
etudid=self.etud.id,
formsemestre_id=self.formsemestre.id)
}">Ajouter une appréciation</a>
</p>"""
) )
H.append("</div>") H.append("</div>")
# --------------- # ---------------

View File

@ -51,6 +51,7 @@ from reportlab.lib.colors import Color, blue
from reportlab.lib.units import cm, mm from reportlab.lib.units import cm, mm
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
from app.models import BulAppreciations
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import ( from app.scodoc import (
gen_tables, gen_tables,
@ -92,7 +93,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
) # impose un espace vertical entre le titre et la table qui suit ) # impose un espace vertical entre le titre et la table qui suit
return objects return objects
def bul_table(self, format="html"): def bul_table(self, fmt="html"):
"""Génère la table centrale du bulletin de notes """Génère la table centrale du bulletin de notes
Renvoie: Renvoie:
- en HTML: une chaine - en HTML: une chaine
@ -112,9 +113,9 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
html_with_td_classes=True, 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 """Génère les informations placées sous la table de notes
(absences, appréciations, décisions de jury...) (absences, appréciations, décisions de jury...)
Renvoie: Renvoie:
@ -156,45 +157,53 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
# ---- APPRECIATIONS # ---- APPRECIATIONS
# le dir. des etud peut ajouter des appreciations, # le dir. des etud peut ajouter des appreciations,
# mais aussi le chef (perm. ScoEtudInscrit) # 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) self.authuser.has_permission(Permission.ScoEtudInscrit)
) )
H.append('<div class="bull_appreciations">') H.append('<div class="bull_appreciations">')
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: if can_edit_app:
mlink = f"""<a class="stdlink" href="{ mlink = f"""<a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, id=app['id']) url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, appreciation_id=appreciation.id)
}">modifier</a> }">modifier</a>
<a class="stdlink" href="{ <a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, id=app['id'], suppress=1) url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, appreciation_id=appreciation.id, suppress=1)
}">supprimer</a>""" }">supprimer</a>"""
else: else:
mlink = "" mlink = ""
H.append( H.append(
f"""<p><span class="bull_appreciations_date">{app["date"]}</span>{ f"""<p>
app["comment"]}<span class="bull_appreciations_link">{mlink}</span> <span class="bull_appreciations_date">{
</p>""" appreciation.date.strftime("%d/%m/%Y")
if appreciation.date else ""}</span>
{appreciation.comment_safe()}
<span class="bull_appreciations_link">{mlink}</span>
</p>
"""
) )
if can_edit_app: if can_edit_app:
H.append( H.append(
f"""<p><a class="stdlink" href="{ f"""<p><a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept, url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept,
etudid=self.infos['etudid'], etudid=self.etud.etudid,
formsemestre_id=self.infos['formsemestre_id'] formsemestre_id=self.formsemestre.id
) )
}">Ajouter une appréciation</a></p>""" }">Ajouter une appréciation</a></p>"""
% self.infos % self.infos
) )
H.append("</div>") H.append("</div>")
# Appréciations sur PDF: # Appréciations sur PDF:
if self.infos.get("appreciations_list", False): if appreciations:
story.append(Spacer(1, 3 * mm)) story.append(Spacer(1, 3 * mm))
try: try:
story.append( story.append(
Paragraph( Paragraph(
SU( SU(
"Appréciation : " "Appréciation : "
+ "\n".join(self.infos["appreciations_txt"]) + "\n".join(BulAppreciations.summarize(appreciations))
), ),
self.CellStyle, self.CellStyle,
) )
@ -221,14 +230,14 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
H.append('<div class="bul_decision">' + field + "</div>") H.append('<div class="bul_decision">' + field + "</div>")
# ----- # -----
if format == "pdf": if fmt == "pdf":
if self.scale_table_in_page: if self.scale_table_in_page:
# le scaling (pour tenir sur une page) semble incompatible avec # le scaling (pour tenir sur une page) semble incompatible avec
# le KeepTogether() # le KeepTogether()
return story return story
else: else:
return [KeepTogether(story)] return [KeepTogether(story)]
elif format == "html": elif fmt == "html":
return "\n".join(H) return "\n".join(H)
def bul_signatures_pdf(self): def bul_signatures_pdf(self):

View File

@ -50,8 +50,7 @@ import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log from app import log
from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat from app.but.bulletin_but_xml_compat import bulletin_but_xml_compat
from app.models.evaluations import Evaluation from app.models import BulAppreciations, Evaluation, FormSemestre
from app.models.formsemestre import FormSemestre
from app.scodoc import sco_assiduites from app.scodoc import sco_assiduites
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
@ -415,16 +414,13 @@ def make_xml_formsemestre_bulletinetud(
else: else:
doc.append(Element("decision", code="", etat="DEM")) doc.append(Element("decision", code="", etat="DEM"))
# --- Appreciations # --- Appreciations
cnx = ndb.GetDBConnexion() appreciations = BulAppreciations.get_appreciations_list(formsemestre.id, etudid)
apprecs = sco_etud.appreciations_list( for appreciation in appreciations:
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
)
for appr in apprecs:
x_appr = Element( x_appr = Element(
"appreciation", "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) doc.append(x_appr)
if is_appending: if is_appending:

View File

@ -741,31 +741,6 @@ def add_annotations_to_etud_list(etuds):
etud["annotations_str"] = ", ".join(l) 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 # -------- Noms des Lycées à partir du code
def read_etablissements(): def read_etablissements():
filename = os.path.join(scu.SCO_TOOLS_DIR, scu.CONFIG.ETABL_FILENAME) filename = os.path.join(scu.SCO_TOOLS_DIR, scu.CONFIG.ETABL_FILENAME)

View File

@ -23,9 +23,9 @@
app.comment}}<span app.comment}}<span
class="bull_appreciations_link">{% if can_edit_appreciations %}<a class="bull_appreciations_link">{% if can_edit_appreciations %}<a
class="stdlink" href="{{url_for('notes.appreciation_add_form', class="stdlink" href="{{url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept, id=app.id)}}">modifier</a> scodoc_dept=g.scodoc_dept, appreciation_id=app.id)}}">modifier</a>
<a class="stdlink" href="{{url_for('notes.appreciation_add_form', <a class="stdlink" href="{{url_for('notes.appreciation_add_form',
scodoc_dept=g.scodoc_dept, id=app.id, suppress=1)}}">supprimer</a>{% endif %} scodoc_dept=g.scodoc_dept, appreciation_id=app.id, suppress=1)}}">supprimer</a>{% endif %}
</span> </span>
</p> </p>
{% endfor %} {% endfor %}

View File

@ -58,6 +58,7 @@ from app.but.forms import jury_but_forms
from app.comp import jury, res_sem from app.comp import jury, res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import ( from app.models import (
BulAppreciations,
Evaluation, Evaluation,
Formation, Formation,
ScolarAutorisationInscription, ScolarAutorisationInscription,
@ -316,14 +317,14 @@ def formsemestre_bulletinetud(
if formsemestre.formation.is_apc() and format == "html": if formsemestre.formation.is_apc() and format == "html":
return render_template( return render_template(
"but/bulletin.j2", "but/bulletin.j2",
appreciations=models.BulAppreciations.query.filter_by( appreciations=BulAppreciations.get_appreciations_list(
etudid=etudid, formsemestre_id=formsemestre.id formsemestre.id, etud.id
).order_by(models.BulAppreciations.date), ),
bul_url=url_for( bul_url=url_for(
"notes.formsemestre_bulletinetud", "notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
etudid=etudid, etudid=etud.id,
format="json", format="json",
force_publishing=1, # pour ScoDoc lui même force_publishing=1, # pour ScoDoc lui même
version=version, version=version,
@ -2039,64 +2040,72 @@ sco_publish(
def appreciation_add_form( def appreciation_add_form(
etudid=None, etudid=None,
formsemestre_id=None, formsemestre_id=None,
id=None, # si id, edit appreciation_id=None, # si id, edit
suppress=False, # si true, supress id suppress=False, # si true, supress id
): ):
"form ajout ou edition d'une appreciation" "form ajout ou edition d'une appreciation"
cnx = ndb.GetDBConnexion() if appreciation_id: # edit mode
if id: # edit mode appreciation = db.session.get(BulAppreciations, appreciation_id)
apps = sco_etud.appreciations_list(cnx, args={"id": id}) if appreciation is None:
if not apps:
raise ScoValueError("id d'appreciation invalide !") raise ScoValueError("id d'appreciation invalide !")
app = apps[0] formsemestre_id = appreciation.formsemestre_id
formsemestre_id = app["formsemestre_id"] etudid = appreciation.etudid
etudid = app["etudid"] etud: Identite = Identite.query.filter_by(
id=etudid, dept_id=g.scodoc_dept_id
).first_or_404()
vals = scu.get_request_args() vals = scu.get_request_args()
if "edit" in vals: if "edit" in vals:
edit = int(vals["edit"]) edit = int(vals["edit"])
elif id: elif appreciation_id:
edit = 1 edit = 1
else: else:
edit = 0 edit = 0
sem = sco_formsemestre.get_formsemestre(formsemestre_id) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
# check custom access permission # 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) current_user.has_permission(Permission.ScoEtudInscrit)
) )
if not can_edit_app: if not can_edit_app:
raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation") raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation")
# #
bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % ( bul_url = url_for(
formsemestre_id, "notes.formsemestre_bulletinetud",
etudid, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
) )
if suppress: if suppress:
sco_etud.appreciations_delete(cnx, id) db.session.delete(appreciation)
logdb(cnx, method="appreciation_suppress", etudid=etudid, msg="") Scolog.logdb(
return flask.redirect(bull_url) 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 appreciation_id:
if id: action = "Édition"
a = "Edition"
else: else:
a = "Ajout" action = "Ajout"
H = [ H = [
html_sco_header.sco_header() html_sco_header.sco_header(),
+ "<h2>%s d'une appréciation sur %s</h2>" % (a, etud["nomprenom"]) f"""<h2>{action} d'une appréciation sur {etud.nomprenom}</h2>""",
] ]
F = html_sco_header.sco_footer() F = html_sco_header.sco_footer()
descr = [ descr = [
("edit", {"input_type": "hidden", "default": edit}), ("edit", {"input_type": "hidden", "default": edit}),
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
("formsemestre_id", {"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}), ("comment", {"title": "", "input_type": "textarea", "rows": 4, "cols": 60}),
] ]
if id: if appreciation_id:
initvalues = { initvalues = {
"etudid": etudid, "etudid": etudid,
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
"comment": app["comment"], "comment": appreciation.comment,
} }
else: else:
initvalues = {} initvalues = {}
@ -2111,31 +2120,33 @@ def appreciation_add_form(
if tf[0] == 0: if tf[0] == 0:
return "\n".join(H) + "\n" + tf[1] + F return "\n".join(H) + "\n" + tf[1] + F
elif tf[0] == -1: elif tf[0] == -1:
return flask.redirect(bull_url) return flask.redirect(bul_url)
else: else:
args = {
"etudid": etudid,
"formsemestre_id": formsemestre_id,
"author": current_user.user_name,
"comment": tf[2]["comment"],
}
if edit: if edit:
args["id"] = id appreciation.author = (current_user.user_name,)
sco_etud.appreciations_edit(cnx, args) appreciation.comment = tf[2]["comment"].strip()
flash("appréciation modifiée")
else: # nouvelle 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 # log
logdb( Scolog.logdb(
cnx,
method="appreciation_add", method="appreciation_add",
etudid=etudid, etudid=etudid,
msg=tf[2]["comment"], msg=appreciation.comment_safe(),
) )
db.session.commit()
# ennuyeux mais necessaire (pour le PDF seulement) # ennuyeux mais necessaire (pour le PDF seulement)
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
pdfonly=True, formsemestre_id=formsemestre_id pdfonly=True, formsemestre_id=formsemestre_id
) # > appreciation_add ) # > appreciation_add
return flask.redirect(bull_url) return flask.redirect(bul_url)
# --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES # --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES