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']}"
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

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)
- 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:

View File

@ -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:

View File

@ -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

View File

@ -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"""

View File

@ -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:

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).
# 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(

View File

@ -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 = ['<div class="notes_bulletin">']
# 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("</div>")
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()

View File

@ -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

View File

@ -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('<div class="bull_appreciations">')
if I["appreciations_list"]:
H.append("<p><b>Appréciations</b></p>")
for app in I["appreciations_list"]:
if can_edit_app:
mlink = (
'<a class="stdlink" href="appreciation_add_form?id=%s">modifier</a> <a class="stdlink" href="appreciation_add_form?id=%s&suppress=1">supprimer</a>'
% (app["id"], app["id"])
appreciations = BulAppreciations.get_appreciations_list(
self.formsemestre.id, self.etud.id
)
if appreciations:
H.append("<p><b>Appréciations</b></p>")
for appreciation in appreciations:
if can_edit_app:
mlink = f"""<a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dpt, appreciation_id=appreciation.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:
mlink = ""
H.append(
'<p><span class="bull_appreciations_date">%s</span>%s<span class="bull_appreciations_link">%s</span></p>'
% (app["date"], app["comment"], mlink)
f"""<p>
<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:
H.append(
'<p><a class="stdlink" href="appreciation_add_form?etudid=%(etudid)s&formsemestre_id=%(formsemestre_id)s">Ajouter une appréciation</a></p>'
% self.infos
f"""<p>
<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>")
# ---------------

View File

@ -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('<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:
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>
<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>"""
else:
mlink = ""
H.append(
f"""<p><span class="bull_appreciations_date">{app["date"]}</span>{
app["comment"]}<span class="bull_appreciations_link">{mlink}</span>
</p>"""
f"""<p>
<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:
H.append(
f"""<p><a class="stdlink" href="{
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept,
etudid=self.infos['etudid'],
formsemestre_id=self.infos['formsemestre_id']
etudid=self.etud.etudid,
formsemestre_id=self.formsemestre.id
)
}">Ajouter une appréciation</a></p>"""
% self.infos
)
H.append("</div>")
# 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('<div class="bul_decision">' + field + "</div>")
# -----
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):

View File

@ -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:

View File

@ -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)

View File

@ -23,9 +23,9 @@
app.comment}}<span
class="bull_appreciations_link">{% if can_edit_appreciations %}<a
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',
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>
</p>
{% 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.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()
+ "<h2>%s d'une appréciation sur %s</h2>" % (a, etud["nomprenom"])
html_sco_header.sco_header(),
f"""<h2>{action} d'une appréciation sur {etud.nomprenom}</h2>""",
]
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