modernisation/methodes sur Identite/bul. head.

This commit is contained in:
Emmanuel Viennet 2022-03-06 22:40:20 +01:00
parent c923a5015b
commit 8f911234b2
14 changed files with 334 additions and 107 deletions

View File

@ -224,7 +224,7 @@ class BulletinBUT:
(bulletins non publiés).
"""
res = self.res
etat_inscription = etud.etat_inscription(formsemestre.id)
etat_inscription = etud.inscription_etat(formsemestre.id)
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
published = (not formsemestre.bul_hide_xml) or force_publishing
d = {

View File

@ -72,7 +72,7 @@ def bulletin_but_xml_compat(
etud: Identite = Identite.query.get_or_404(etudid)
results = bulletin_but.ResultatsSemestreBUT(formsemestre)
nb_inscrits = results.get_inscriptions_counts()[scu.INSCRIT]
# etat_inscription = etud.etat_inscription(formsemestre.id)
# etat_inscription = etud.inscription_etat(formsemestre.id)
etat_inscription = results.formsemestre.etuds_inscriptions[etudid].etat
if (not formsemestre.bul_hide_xml) or force_publishing:
published = 1

View File

@ -4,12 +4,14 @@
et données rattachées (adresses, annotations, ...)
"""
import datetime
from functools import cached_property
from flask import abort, url_for
from flask import g, request
import sqlalchemy
from sqlalchemy import desc, text
from app import db
from app import db, log
from app import models
from app.scodoc import notesdb as ndb
@ -82,6 +84,11 @@ class Identite(db.Model):
return scu.suppress_accents(s)
return s
@property
def e(self):
"terminaison en français: 'ne', '', 'ou '(e)'"
return {"M": "", "F": "e"}.get(self.civilite, "(e)")
def nom_disp(self) -> str:
"Nom à afficher"
if self.nom_usuel:
@ -123,7 +130,7 @@ class Identite(db.Model):
def get_first_email(self, field="email") -> str:
"Le mail associé à la première adrese de l'étudiant, ou None"
return self.adresses[0].email or None if self.adresses.count() > 0 else None
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
def to_dict_scodoc7(self):
"""Représentation dictionnaire,
@ -134,7 +141,7 @@ class Identite(db.Model):
# ScoDoc7 output_formators: (backward compat)
e["etudid"] = self.id
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
e["ne"] = self.e
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
def to_dict_bul(self, include_urls=True):
@ -172,6 +179,23 @@ class Identite(db.Model):
]
return r[0] if r else None
def inscriptions_courantes(self) -> list: # -> list[FormSemestreInscription]:
"""Liste des inscriptions à des semestres _courants_
(il est rare qu'il y en ai plus d'une, mais c'est possible).
Triées par date de début de semestre décroissante (le plus récent en premier).
"""
from app.models.formsemestre import FormSemestre, FormSemestreInscription
return (
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
.filter(
FormSemestreInscription.etudid == self.id,
text("date_debut < now() and date_fin > now()"),
)
.order_by(desc(FormSemestre.date_debut))
.all()
)
def inscription_courante_date(self, date_debut, date_fin):
"""La première inscription à un formsemestre incluant la
période [date_debut, date_fin]
@ -183,8 +207,8 @@ class Identite(db.Model):
]
return r[0] if r else None
def etat_inscription(self, formsemestre_id):
"""etat de l'inscription de cet étudiant au semestre:
def inscription_etat(self, formsemestre_id):
"""État de l'inscription de cet étudiant au semestre:
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
"""
# voir si ce n'est pas trop lent:
@ -195,6 +219,110 @@ class Identite(db.Model):
return ins.etat
return False
def inscription_descr(self) -> dict:
"""Description de l'état d'inscription"""
inscription_courante = self.inscription_courante()
if inscription_courante:
titre_sem = inscription_courante.formsemestre.titre_mois()
return {
"etat_in_cursem": inscription_courante.etat,
"inscription_courante": inscription_courante,
"inscription": titre_sem,
"inscription_str": "Inscrit en " + titre_sem,
"situation": self.descr_situation_etud(),
}
else:
if self.formsemestre_inscriptions:
# cherche l'inscription la plus récente:
fin_dernier_sem = max(
[
inscr.formsemestre.date_debut
for inscr in self.formsemestre_inscriptions
]
)
if fin_dernier_sem > datetime.date.today():
inscription = "futur"
situation = "futur élève"
else:
inscription = "ancien"
situation = "ancien élève"
else:
inscription = ("non inscrit",)
situation = inscription
return {
"etat_in_cursem": "?",
"inscription_courante": None,
"inscription": inscription,
"inscription_str": inscription,
"situation": situation,
}
def descr_situation_etud(self) -> str:
"""Chaîne décrivant la situation _actuelle_ de l'étudiant.
Exemple:
"inscrit en BUT R&T semestre 2 FI (Jan 2022 - Jul 2022) le 16/01/2022"
ou
"non inscrit"
"""
inscriptions_courantes = self.inscriptions_courantes()
if inscriptions_courantes:
inscr = inscriptions_courantes[0]
if inscr.etat == scu.INSCRIT:
situation = f"inscrit{self.e} en {inscr.formsemestre.titre_mois()}"
# Cherche la date d'inscription dans scolar_events:
events = models.ScolarEvent.query.filter_by(
etudid=self.id,
formsemestre_id=inscr.formsemestre.id,
event_type="INSCRIPTION",
).all()
if not events:
log(
f"*** situation inconsistante pour {self} (inscrit mais pas d'event)"
)
date_ins = "???" # ???
else:
date_ins = events[0].event_date
situation += date_ins.strftime(" le %d/%m/%Y")
else:
situation = f"démission de {inscr.formsemestre.titre_mois()}"
# Cherche la date de demission dans scolar_events:
events = models.ScolarEvent.query.filter_by(
etudid=self.id,
formsemestre_id=inscr.formsemestre.id,
event_type="DEMISSION",
).all()
if not events:
log(
f"*** situation inconsistante pour {self} (demission mais pas d'event)"
)
date_dem = "???" # ???
else:
date_dem = events[0].event_date
situation += date_dem.strftime(" le %d/%m/%Y")
else:
situation = "non inscrit" + self.e
return situation
def photo_html(self, title=None, size="small") -> str:
"""HTML img tag for the photo, either in small size (h90)
or original size (size=="orig")
"""
from app.scodoc import sco_photos
# sco_photo traite des dicts:
return sco_photos.etud_photo_html(
etud=dict(
etudid=self.id,
code_nip=self.code_nip,
nomprenom=self.nomprenom,
nom_disp=self.nom_disp(),
photo_filename=self.photo_filename,
),
title=title,
size=size,
)
def make_etud_args(
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True

View File

@ -12,7 +12,6 @@ from app import log
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models import UniteEns
import app.scodoc.sco_utils as scu
from app.models.ues import UniteEns

View File

@ -6,7 +6,8 @@ import flask_sqlalchemy
from app import db
from app.comp import df_cache
from app.models import Identite, Module
from app.models.etudiants import Identite
from app.models.modules import Module
import app.scodoc.notesdb as ndb
from app.scodoc import sco_utils as scu

View File

@ -29,13 +29,11 @@
"""
import email
import pprint
import time
from flask import g, request
from flask import url_for
from flask import render_template, url_for
from flask_login import current_user
from flask_mail import Message
from app import email
from app import log
@ -802,18 +800,10 @@ def formsemestre_bulletinetud(
prefer_mail_perso=False,
):
"page bulletin de notes"
try:
etud = sco_etud.get_etud_info(filled=True)[0]
etudid = etud["etudid"]
except:
sco_etud.log_unknown_etud()
raise ScoValueError("étudiant inconnu")
etud: Identite = Identite.query.get_or_404(etudid)
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
if not formsemestre:
# API, donc erreurs admises
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
sem = formsemestre.to_dict()
bulletin = do_formsemestre_bulletinetud(
formsemestre,
@ -825,37 +815,39 @@ def formsemestre_bulletinetud(
prefer_mail_perso=prefer_mail_perso,
)[0]
if format not in {"html", "pdfmail"}:
filename = scu.bul_filename(sem, etud, format)
filename = scu.bul_filename(formsemestre, etud, format)
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
H = [
_formsemestre_bulletinetud_header_html(
etud, etudid, formsemestre, format, version
),
_formsemestre_bulletinetud_header_html(etud, formsemestre, format, version),
bulletin,
]
H.append("""<p>Situation actuelle: """)
if etud["inscription_formsemestre_id"]:
inscription_courante = etud.inscription_courante()
if inscription_courante:
H.append(
f"""<a class="stdlink" href="{url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=etud["inscription_formsemestre_id"])
formsemestre_id=inscription_courante.formsemestre_id)
}">"""
)
H.append(etud["inscriptionstr"])
if etud["inscription_formsemestre_id"]:
inscription_descr = etud.inscription_descr()
H.append(inscription_descr["inscription_str"])
if inscription_courante:
H.append("""</a>""")
H.append("""</p>""")
if sem["modalite"] == "EXT":
if formsemestre.modalite == "EXT":
H.append(
"""<p><a
href="formsemestre_ext_edit_ue_validations?formsemestre_id=%s&etudid=%s"
f"""<p><a
href="{url_for('notes.formsemestre_ext_edit_ue_validations',
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid)}"
class="stdlink">
Editer les validations d'UE dans ce semestre extérieur
Éditer les validations d'UE dans ce semestre extérieur
</a></p>"""
% (formsemestre_id, etudid)
)
# Place du diagramme radar
H.append(
@ -1048,16 +1040,15 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
)
def _formsemestre_bulletinetud_header_html(
etud,
etudid,
def _formsemestre_bulletinetud_header_html_old_XXX(
etud: Identite,
formsemestre: FormSemestre,
format=None,
version=None,
):
H = [
html_sco_header.sco_header(
page_title="Bulletin de %(nomprenom)s" % etud,
page_title=f"Bulletin de {etud.nomprenom}",
javascripts=[
"js/bulletin.js",
"libjs/d3.v3.min.js",
@ -1068,8 +1059,8 @@ def _formsemestre_bulletinetud_header_html(
f"""<table class="bull_head"><tr><td>
<h2><a class="discretelink" href="{
url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
)}">{etud["nomprenom"]}</a></h2>
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
)}">{etud.nomprenom}</a></h2>
<form name="f" method="GET" action="{request.base_url}">
Bulletin <span class="bull_liensemestre"><a href="{
@ -1082,7 +1073,7 @@ def _formsemestre_bulletinetud_header_html(
<td>établi le {time.strftime("%d/%m/%Y à %Hh%M")} (notes sur 20)</td>
<td><span class="rightjust">
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
<input type="hidden" name="etudid" value="{etudid}"></input>
<input type="hidden" name="etudid" value="{etud.id}"></input>
<input type="hidden" name="format" value="{format}"></input>
<select name="version" onchange="document.f.submit()" class="noprint">
""",
@ -1100,8 +1091,52 @@ def _formsemestre_bulletinetud_header_html(
H.append("""</select></td>""")
# Menu
endpoint = "notes.formsemestre_bulletinetud"
menu_autres_operations = make_menu_autres_operations(
formsemestre, etud, endpoint, version
)
menuBul = [
H.append("""<td class="bulletin_menubar"><div class="bulletin_menubar">""")
H.append(menu_autres_operations)
H.append("""</div></td>""")
H.append(
'<td> <a href="%s">%s</a></td>'
% (
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etud.id,
format="pdf",
version=version,
),
scu.ICON_PDF,
)
)
H.append("""</tr></table>""")
#
H.append(
"""</form></span></td><td class="bull_photo"><a href="%s">%s</a>
"""
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id),
sco_photos.etud_photo_html(etud, title="fiche de " + etud.nomprenom),
)
)
H.append(
"""</td></tr>
</table>
"""
)
return "".join(H)
def make_menu_autres_operations(
formsemestre: FormSemestre, etud: Identite, endpoint: str, version: str
) -> str:
etud_email = etud.get_first_email() or ""
etud_perso = etud.get_first_email("emailperso") or ""
menu_items = [
{
"title": "Réglages bulletins",
"endpoint": "notes.formsemestre_edit_options",
@ -1124,43 +1159,42 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
"version": version,
"format": "pdf",
},
},
{
"title": "Envoi par mail à %s" % etud["email"],
"title": f"Envoi par mail à {etud_email}",
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
"version": version,
"format": "pdfmail",
},
# possible slt si on a un mail...
"enabled": etud["email"] and can_send_bulletin_by_mail(formsemestre.id),
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
},
{
"title": "Envoi par mail à %s (adr. personnelle)" % etud["emailperso"],
"title": f"Envoi par mail à {etud_perso} (adr. personnelle)",
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
"version": version,
"format": "pdfmail",
"prefer_mail_perso": 1,
},
# possible slt si on a un mail...
"enabled": etud["emailperso"]
and can_send_bulletin_by_mail(formsemestre.id),
"enabled": etud_perso and can_send_bulletin_by_mail(formsemestre.id),
},
{
"title": "Version json",
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
"version": version,
"format": "json",
},
@ -1170,7 +1204,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": endpoint,
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
"version": version,
"format": "xml",
},
@ -1180,7 +1214,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.appreciation_add_form",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": (
formsemestre.can_be_edited_by(current_user)
@ -1192,7 +1226,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.formsemestre_ext_create_form",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": current_user.has_permission(Permission.ScoImplement),
},
@ -1201,7 +1235,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.formsemestre_validate_previous_ue",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
},
@ -1210,7 +1244,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.external_ue_create_form",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
},
@ -1219,7 +1253,7 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.formsemestre_validation_etud_form",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
},
@ -1228,43 +1262,44 @@ def _formsemestre_bulletinetud_header_html(
"endpoint": "notes.formsemestre_pvjury_pdf",
"args": {
"formsemestre_id": formsemestre.id,
"etudid": etudid,
"etudid": etud.id,
},
"enabled": True,
},
]
return htmlutils.make_menu("Autres opérations", menu_items, alone=True)
H.append("""<td class="bulletin_menubar"><div class="bulletin_menubar">""")
H.append(htmlutils.make_menu("Autres opérations", menuBul, alone=True))
H.append("""</div></td>""")
H.append(
'<td> <a href="%s">%s</a></td>'
% (
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etudid,
format="pdf",
def _formsemestre_bulletinetud_header_html(
etud,
formsemestre: FormSemestre,
format=None,
version=None,
):
H = [
html_sco_header.sco_header(
page_title=f"Bulletin de {etud.nomprenom}",
javascripts=[
"js/bulletin.js",
"libjs/d3.v3.min.js",
"js/radar_bulletin.js",
],
cssstyles=["css/radar_bulletin.css"],
),
render_template(
"bul_head.html",
etud=etud,
format=format,
formsemestre=formsemestre,
menu_autres_operations=make_menu_autres_operations(
etud=etud,
formsemestre=formsemestre,
endpoint="notes.formsemestre_bulletinetud",
version=version,
),
scu.ICON_PDF,
)
)
H.append("""</tr></table>""")
#
H.append(
"""</form></span></td><td class="bull_photo"><a href="%s">%s</a>
"""
% (
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
)
)
H.append(
"""</td></tr>
</table>
"""
)
return "".join(H)
scu=scu,
time=time,
version=version,
),
]
return "\n".join(H)

View File

@ -117,7 +117,7 @@ class BulletinGenerator:
def get_filename(self):
"""Build a filename to be proposed to the web client"""
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
return scu.bul_filename(sem, self.infos["etud"], "pdf")
return scu.bul_filename_old(sem, self.infos["etud"], "pdf")
def generate(self, format="", stand_alone=True):
"""Return bulletin in specified format"""

View File

@ -138,7 +138,7 @@ def formsemestre_bulletinetud_published_dict(
if not published:
return d # stop !
etat_inscription = etud.etat_inscription(formsemestre.id)
etat_inscription = etud.inscription_etat(formsemestre.id)
if etat_inscription != scu.INSCRIT:
d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
return d

View File

@ -33,8 +33,7 @@ import os
import time
from operator import itemgetter
from flask import url_for, g, request
from flask_mail import Message
from flask import url_for, g
from app import email
from app import log
@ -46,7 +45,6 @@ from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
from app.scodoc import safehtml
from app.scodoc import sco_preferences
from app.scodoc.scolog import logdb
from app.scodoc.TrivialFormulator import TrivialFormulator
def format_etud_ident(etud):
@ -860,7 +858,7 @@ def list_scolog(etudid):
return cursor.dictfetchall()
def fill_etuds_info(etuds, add_admission=True):
def fill_etuds_info(etuds: list[dict], add_admission=True):
"""etuds est une liste d'etudiants (mappings)
Pour chaque etudiant, ajoute ou formatte les champs
-> informations pour fiche etudiant ou listes diverses
@ -977,7 +975,10 @@ def etud_inscriptions_infos(etudid: int, ne="") -> dict:
def descr_situation_etud(etudid: int, ne="") -> str:
"""chaîne décrivant la situation actuelle de l'étudiant"""
"""Chaîne décrivant la situation actuelle de l'étudiant
XXX Obsolete, utiliser Identite.descr_situation_etud() dans
les nouveaux codes
"""
from app.scodoc import sco_formsemestre
cnx = ndb.GetDBConnexion()

View File

@ -351,6 +351,7 @@ def copy_portal_photo_to_fs(etud):
"""Copy the photo from portal (distant website) to local fs.
Returns rel. path or None if copy failed, with a diagnostic message
"""
if "nomprenom" not in etud:
sco_etud.format_etud_ident(etud)
url = photo_portal_url(etud)
if not url:

View File

@ -608,7 +608,7 @@ def is_valid_filename(filename):
return VALID_EXP.match(filename)
def bul_filename(sem, etud, format):
def bul_filename_old(sem: dict, etud: dict, format):
"""Build a filename for this bulletin"""
dt = time.strftime("%Y-%m-%d")
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
@ -616,6 +616,14 @@ def bul_filename(sem, etud, format):
return filename
def bul_filename(formsemestre, etud, format):
"""Build a filename for this bulletin"""
dt = time.strftime("%Y-%m-%d")
filename = f"bul-{formsemestre.sem.titre_num}-{dt}-{etud.nom}.{format}"
filename = make_filename(filename)
return filename
def flash_errors(form):
"""Flashes form errors (version sommaire)"""
for field, errors in form.errors.items():

View File

@ -41,7 +41,7 @@ class releveBUT extends HTMLElement {
}
set showData(data) {
this.showInformations(data);
// this.showInformations(data);
this.showSemestre(data);
this.showSynthese(data);
this.showEvaluations(data);
@ -68,13 +68,7 @@ class releveBUT extends HTMLElement {
<div>
<div class="wait"></div>
<main class="releve">
<!--------------------------->
<!-- Info. étudiant -->
<!--------------------------->
<section class=etudiant>
<img class=studentPic src="" alt="Photo de l'étudiant" width=100 height=120>
<div class=infoEtudiant></div>
</section>
<!--------------------------------------------------------------------------------------->
<!-- Zone spéciale pour que les IUT puisse ajouter des infos locales sur la passerelle -->

View File

@ -0,0 +1,58 @@
{# -*- mode: jinja-html -*- #}
{# L'en-tête des bulletins HTML #}
{# was _formsemestre_bulletinetud_header_html #}
<table class="bull_head">
<tr>
<td>
<h2><a class="discretelink" href="{{
url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid,
)}}">{{etud.nomprenom}}</a></h2>
<form name="f" method="GET" action="{{request.base_url}}">
Bulletin <span class="bull_liensemestre"><a href="{{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id)}}
">{{formsemestre.titre_mois()}}</a></span>
<br/>
<table>
<tr>
<td>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</td>
<td><span class="rightjust">
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}"></input>
<input type="hidden" name="etudid" value="{{etud.id}}"></input>
<input type="hidden" name="format" value="{{format}}"></input>
<select name="version" onchange="document.f.submit()" class="noprint">
{% for (v, e) in (
("short", "Version courte"),
("selectedevals", "Version intermédiaire"),
("long", "Version complète"),
) %}
<option value="{{v}}" {% if (v == version) %}selected{% endif %}>{{e}}</option>
{% endfor %}
</select>
</span>
</td>
<td class="bulletin_menubar">
<div class="bulletin_menubar">{{menu_autres_operations|safe}}</div>
</td>
<td><a href="{{url_for(
'notes.formsemestre_bulletinetud',
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
etudid=etud.id,
format='pdf',
version=version,
)}}">{{scu.ICON_PDF|safe}}</a>
</td>
</tr>
</table>
</form>
</td>
<td class="bull_photo"><a href="{{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
}}">{{etud.photo_html(title="fiche de " + etud["nom"])|safe}}</a>
</td>
</tr>
</table>

View File

@ -6,6 +6,8 @@
{% endblock %}
{% block app_content %}
<h2>Totoro</h2>
<releve-but></releve-but>
<script src="/ScoDoc/static/js/releve-but.js"></script>