Bulletins: améliore page, modifie API pour récuperer le bul. but court avec le même appel.

This commit is contained in:
Emmanuel Viennet 2023-10-13 22:25:44 +02:00
parent a732b8161e
commit 3000cfe7ba
15 changed files with 126 additions and 66 deletions

View File

@ -20,6 +20,7 @@ from sqlalchemy.dialects.postgresql import VARCHAR
import app import app
from app.api import api_bp as bp, api_web_bp from app.api import api_bp as bp, api_web_bp
from app.api import tools from app.api import tools
from app.but import bulletin_but_court
from app.decorators import scodoc, permission_required from app.decorators import scodoc, permission_required
from app.models import ( from app.models import (
Admission, Admission,
@ -35,6 +36,7 @@ from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import json_error, suppress_accents from app.scodoc.sco_utils import json_error, suppress_accents
import app.scodoc.sco_photos as sco_photos import app.scodoc.sco_photos as sco_photos
import app.scodoc.sco_utils as scu
# Un exemple: # Un exemple:
# @bp.route("/api_function/<int:arg>") # @bp.route("/api_function/<int:arg>")
@ -364,7 +366,7 @@ def bulletin(
formsemestre_id : l'id d'un formsemestre formsemestre_id : l'id d'un formsemestre
code_type : "etudid", "nip" ou "ine" code_type : "etudid", "nip" ou "ine"
code : valeur du code INE, NIP ou etudid, selon code_type. code : valeur du code INE, NIP ou etudid, selon code_type.
version : type de bulletin (par défaut, "long"): short, long, long_mat version : type de bulletin (par défaut, "long"): short, long, selectedevals, butcourt
pdf : si spécifié, bulletin au format PDF (et non JSON). pdf : si spécifié, bulletin au format PDF (et non JSON).
Exemple de résultat : voir https://scodoc.org/ScoDoc9API/#bulletin Exemple de résultat : voir https://scodoc.org/ScoDoc9API/#bulletin
@ -372,6 +374,8 @@ def bulletin(
if version == "pdf": if version == "pdf":
version = "long" version = "long"
pdf = True pdf = True
if version not in scu.BULLETINS_VERSIONS_BUT:
return json_error(404, "version invalide")
# return f"{code_type}={code}, version={version}, pdf={pdf}" # return f"{code_type}={code}, version={version}, pdf={pdf}"
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404() formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404() dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
@ -394,6 +398,12 @@ def bulletin(
etud = query.first() etud = query.first()
if etud is None: if etud is None:
return json_error(404, message="etudiant inexistant") return json_error(404, message="etudiant inexistant")
if version == "butcourt":
if pdf:
return bulletin_but_court.bulletin_but(formsemestre_id, etud.id, fmt="pdf")
else:
return json_error(404, message="butcourt available only in pdf")
if pdf: if pdf:
pdf_response, _ = do_formsemestre_bulletinetud( pdf_response, _ = do_formsemestre_bulletinetud(
formsemestre, formsemestre,

View File

@ -24,6 +24,7 @@ from app.scodoc import codes_cursus
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.codes_cursus import UE_SPORT, DEF from app.scodoc.codes_cursus import UE_SPORT, DEF
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_utils import fmt_note from app.scodoc.sco_utils import fmt_note
@ -344,6 +345,8 @@ class BulletinBUT:
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai - Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
(bulletins non publiés). (bulletins non publiés).
""" """
if version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
res = self.res res = self.res
formsemestre = res.formsemestre formsemestre = res.formsemestre
etat_inscription = etud.inscription_etat(formsemestre.id) etat_inscription = etud.inscription_etat(formsemestre.id)

View File

@ -115,5 +115,6 @@ def bulletin_but(formsemestre_id: int, etudid: int = None, fmt="html"):
datetime=datetime, datetime=datetime,
sco=ScoData(formsemestre=formsemestre, etud=etud), sco=ScoData(formsemestre=formsemestre, etud=etud),
time=time, time=time,
version="butcourt",
**args, **args,
) )

View File

@ -775,11 +775,20 @@ class FormSemestre(db.Model):
etuds.sort(key=lambda e: e.sort_key) etuds.sort(key=lambda e: e.sort_key)
return etuds return etuds
def get_partitions_list(self, with_default=True) -> list[Partition]: def get_partitions_list(
self, with_default=True, only_listed=False
) -> list[Partition]:
"""Liste des partitions pour ce semestre (list of dicts), """Liste des partitions pour ce semestre (list of dicts),
triées par numéro, avec la partition par défaut en fin de liste. triées par numéro, avec la partition par défaut en fin de liste.
""" """
partitions = [p for p in self.partitions if p.partition_name is not None] if only_listed:
partitions = [
p
for p in self.partitions
if p.partition_name is not None and p.show_in_lists
]
else:
partitions = [p for p in self.partitions if p.partition_name is not None]
if with_default: if with_default:
partitions += [p for p in self.partitions if p.partition_name is None] partitions += [p for p in self.partitions if p.partition_name is None]
return partitions return partitions

View File

@ -344,6 +344,8 @@ def do_formsemestre_archive(
gen_formsemestre_recapcomplet_json, gen_formsemestre_recapcomplet_json,
) )
if bul_version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
sem_archive_id = formsemestre_id sem_archive_id = formsemestre_id
@ -537,7 +539,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement.
"Version intermédiaire", "Version intermédiaire",
"Version complète", "Version complète",
], ],
"allowed_values": scu.BULLETINS_VERSIONS, "allowed_values": scu.BULLETINS_VERSIONS.keys(),
"default": "long", "default": "long",
}, },
), ),

View File

@ -283,7 +283,6 @@ def make_formsemestre_bulletin_etud(
formsemestre_id = bul_dict["formsemestre_id"] formsemestre_id = bul_dict["formsemestre_id"]
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id) bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
gen_class = None gen_class = None
for bul_class_name in ( for bul_class_name in (
sco_preferences.get_preference("bul_class_name", formsemestre_id), sco_preferences.get_preference("bul_class_name", formsemestre_id),

View File

@ -85,7 +85,7 @@ def formsemestre_bulletinetud_published_dict(
etudid, etudid,
force_publishing=False, force_publishing=False,
xml_nodate=False, xml_nodate=False,
xml_with_decisions=False, # inclue les decisions même si non publiées xml_with_decisions=False, # inclure les décisions même si non publiées
version="long", version="long",
) -> dict: ) -> dict:
"""Dictionnaire representant les informations _publiees_ du bulletin de notes """Dictionnaire representant les informations _publiees_ du bulletin de notes
@ -95,8 +95,8 @@ def formsemestre_bulletinetud_published_dict(
short (sans les évaluations) short (sans les évaluations)
long (avec les évaluations) long (avec les évaluations)
short_mat (sans évaluations, et structuration en matières) # non implémenté: short_mat (sans évaluations, et structuration en matières)
long_mat (avec évaluations, et structuration en matières) # long_mat (avec évaluations, et structuration en matières)
""" """
from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins

View File

@ -215,6 +215,8 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
"Document pdf avec tous les bulletins du semestre, et filename" "Document pdf avec tous les bulletins du semestre, et filename"
from app.scodoc import sco_bulletins from app.scodoc import sco_bulletins
if version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
cached = sco_cache.SemBulletinsPDFCache.get(str(formsemestre_id) + "_" + version) cached = sco_cache.SemBulletinsPDFCache.get(str(formsemestre_id) + "_" + version)
if cached: if cached:
return cached[1], cached[0] return cached[1], cached[0]

View File

@ -1317,7 +1317,7 @@ class BasePreferences:
"labels": sco_bulletins_generator.bulletin_class_descriptions(), "labels": sco_bulletins_generator.bulletin_class_descriptions(),
"allowed_values": sco_bulletins_generator.bulletin_class_names(), "allowed_values": sco_bulletins_generator.bulletin_class_names(),
"title": "Format des bulletins", "title": "Format des bulletins",
"explanation": "format de présentation des bulletins de note (web et pdf)", "explanation": "format de présentation des bulletins de note (web et pdf), non utilisé en BUT.",
"category": "bul", "category": "bul",
}, },
), ),

View File

@ -639,9 +639,16 @@ def get_mime_suffix(format_code: str) -> tuple[str, str]:
TYPE_ADMISSION_DEFAULT = "Inconnue" TYPE_ADMISSION_DEFAULT = "Inconnue"
TYPES_ADMISSION = (TYPE_ADMISSION_DEFAULT, "APB", "APB-PC", "CEF", "Direct") TYPES_ADMISSION = (TYPE_ADMISSION_DEFAULT, "APB", "APB-PC", "CEF", "Direct")
BULLETINS_VERSIONS = ("short", "selectedevals", "long") BULLETINS_VERSIONS = {
"short": "Version courte",
"selectedevals": "Version intermédiaire",
"long": "Version complète",
}
BULLETINS_VERSIONS_BUT = BULLETINS_VERSIONS | {
"butcourt": "Version courte spéciale BUT"
}
# Support for ScoDoc7 compatibility # ----- Support for ScoDoc7 compatibility
def ScoURL(): def ScoURL():

View File

@ -3078,11 +3078,17 @@ a.bull_link:hover {
div.bulletin_menubar { div.bulletin_menubar {
padding-left: 25px; padding-left: 25px;
} }
div.bull_titre_semestre {
margin-top: 8px;
margin-bottom: 8px;
font-size: 120%;
}
div.bull_titre_semestre .parcours {
margin-left: 12px;
}
.bull_liensemestre { .bull_liensemestre {
font-weight: bold; font-weight: bold;
} }
.bull_liensemestre a { .bull_liensemestre a {
color: rgb(255, 0, 0); color: rgb(255, 0, 0);
text-decoration: none; text-decoration: none;

View File

@ -14,21 +14,32 @@
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}"></input> <input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}"></input>
<input type="hidden" name="etudid" value="{{etud.id}}"></input> <input type="hidden" name="etudid" value="{{etud.id}}"></input>
<input type="hidden" name="fmt" value="{{fmt}}"></input> <input type="hidden" name="fmt" value="{{fmt}}"></input>
Bulletin <div class="bull_titre_semestre">
<span class="bull_liensemestre"> Bulletin
{{formsemestre.html_link_status() | safe}} <span class="bull_liensemestre">
</span> {{formsemestre.html_link_status() | safe}}
{% if formsemestre.etuds_inscriptions[etud.id].parcour %}
<span class="parcours">Parcours {{formsemestre.etuds_inscriptions[etud.id].parcour.code}}</span>
{% endif %}
</span>
</div>
<div> <div>
<em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em> <em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em>
<span class="rightjust"> <span class="rightjust">
<select name="version" onchange="document.f.submit()" class="noprint"> <select name="version" onchange="self.location.href='{{
{% for (v, e) in ( url_for('notes.formsemestre_bulletinetud',
("short", "Version courte"), scodoc_dept=g.scodoc_dept,
("selectedevals", "Version intermédiaire"), formsemestre_id=formsemestre.id,
("long", "Version complète"), etudid=etud.id,
) %} )
<option value="{{v}}" {% if (v == version) %}selected{% endif %}>{{e}}</option> }}&version='+this.value;"" class="noprint">
{% if formsemestre.formation.is_apc() %}
{% set menu_items = scu.BULLETINS_VERSIONS_BUT.items() %}
{% else %}
{% set menu_items = scu.BULLETINS_VERSIONS.items() %}
{% endif %}
{% for (v, e) in menu_items %}
<option value="{{v}}" {% if (v == version) %}selected{% endif %}>{{e}}</option>
{% endfor %} {% endfor %}
</select> </select>
</span> </span>
@ -46,13 +57,15 @@
</div> </div>
{% if formsemestre.formation.is_apc() %} {% if formsemestre.formation.is_apc() %}
<div> <div>
<a style="margin-left: 32px;" class="stdlink" {% if version != "butcourt" %}
href="{{url_for( <a style="margin-left: 32px;" class="stdlink"
'notes.bulletin_but_html', href="{{url_for(
scodoc_dept=g.scodoc_dept, 'notes.bulletin_but_html',
formsemestre_id=formsemestre.id, scodoc_dept=g.scodoc_dept,
etudid=etud.id formsemestre_id=formsemestre.id,
)}}">version courte spéciale BUT</a> etudid=etud.id
)}}">version courte spéciale BUT</a>
{% endif %}
<a style="margin-left: 32px;" class="stdlink" <a style="margin-left: 32px;" class="stdlink"
href="{{url_for('notes.validation_rcues', href="{{url_for('notes.validation_rcues',
scodoc_dept=g.scodoc_dept, etudid=etud.id, scodoc_dept=g.scodoc_dept, etudid=etud.id,

View File

@ -39,40 +39,10 @@
{%- endmacro %} {%- endmacro %}
{% block app_content %} {% block app_content %}
<div class="but_bul_court_links">
<a href="{{url_for( {% include 'bul_head.j2' %}
'notes.bulletin_but_pdf', scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=formsemestre.id
)}}" class="stdlink">version pdf {{scu.ICON_PDF|safe}}</a>
<a style="margin-left: 32px;"
href="{{url_for(
'notes.formsemestre_bulletinetud',
scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=formsemestre.id
)}}" class="stdlink">version complète</a>
<a style="margin-left: 32px;" class="stdlink"
href="{{url_for('notes.validation_rcues',
scodoc_dept=g.scodoc_dept, etudid=etud.id,
formsemestre_id=formsemestre.id
)}}">visualiser les compétences BUT</a>
</div>
<div class="but_bul_court"> <div class="but_bul_court">
<div id="infos_etudiant">
<div class="nom">{{etud.nomprenom}}</div>
<div class="formation">BUT {{formsemestre.formation.referentiel_competence.specialite}}</div>
{% if formsemestre.etuds_inscriptions[etud.id].parcour %}
<div class="parcours">Parcours {{formsemestre.etuds_inscriptions[etud.id].parcour.code}}</div>
{% endif %}
<div class="annee_scolaire">Année {{formsemestre.annee_scolaire_str()}}</div>
<div class="semestre">Semestre {{formsemestre.semestre_id}}</div>
</div>
<div id="logo">
<a href="{{
url_for('scolar.ficheEtud', scodoc_dept=g.scodoc_dept, etudid=etud.id)
}}">{{etud.photo_html()|safe}}</a>
</div>
{% if bul.options.show_abs %} {% if bul.options.show_abs %}
<div id="assiduite"> <div id="assiduite">
<div class="ligne-entete">Absences {{bul.semestre.absences.metrique}}</div> <div class="ligne-entete">Absences {{bul.semestre.absences.metrique}}</div>

View File

@ -289,6 +289,8 @@ def formsemestre_bulletinetud(
code_ine=None, code_ine=None,
): ):
fmt = fmt or "html" fmt = fmt or "html"
if version not in scu.BULLETINS_VERSIONS_BUT:
raise ScoValueError("version de bulletin demandée invalide")
if not isinstance(etudid, int): if not isinstance(etudid, int):
raise ScoInvalidIdType("formsemestre_bulletinetud: etudid must be an integer !") raise ScoInvalidIdType("formsemestre_bulletinetud: etudid must be an integer !")
if formsemestre_id is not None and not isinstance(formsemestre_id, int): if formsemestre_id is not None and not isinstance(formsemestre_id, int):
@ -312,6 +314,15 @@ def formsemestre_bulletinetud(
raise ScoValueError( raise ScoValueError(
"Paramètre manquant: spécifier etudid, code_nip ou code_ine" "Paramètre manquant: spécifier etudid, code_nip ou code_ine"
) )
if version == "butcourt":
return redirect(
url_for(
"notes.bulletin_but_pdf" if fmt == "pdf" else "notes.bulletin_but_html",
scodoc_dept=g.scodoc_dept,
etudid=etud.id,
formsemestre_id=formsemestre_id,
)
)
if fmt == "json": if fmt == "json":
return sco_bulletins.get_formsemestre_bulletin_etud_json( return sco_bulletins.get_formsemestre_bulletin_etud_json(
formsemestre, etud, version=version, force_publishing=force_publishing formsemestre, etud, version=version, force_publishing=force_publishing
@ -1852,6 +1863,8 @@ sco_publish(
@scodoc7func @scodoc7func
def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"): def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
"Publie les bulletins dans un classeur PDF" "Publie les bulletins dans un classeur PDF"
if version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, version=version formsemestre_id, version=version
) )
@ -1871,7 +1884,7 @@ _EXPL_BULL = """Versions des bulletins:
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scodoc7func @scodoc7func
def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None): def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None):
"""Choix version puis envois classeur bulletins pdf""" """Choix version puis envoi classeur bulletins pdf"""
if version: if version:
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
formsemestre_id, version=version formsemestre_id, version=version
@ -1890,6 +1903,8 @@ def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None):
@scodoc7func @scodoc7func
def etud_bulletins_pdf(etudid, version="selectedevals"): def etud_bulletins_pdf(etudid, version="selectedevals"):
"Publie tous les bulletins d'un etudiants dans un classeur PDF" "Publie tous les bulletins d'un etudiants dans un classeur PDF"
if version not in scu.BULLETINS_VERSIONS:
raise ScoValueError("version de bulletin demandée invalide")
pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf(etudid, version=version) pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf(etudid, version=version)
return scu.sendPDFFile(pdfdoc, filename) return scu.sendPDFFile(pdfdoc, filename)

View File

@ -762,6 +762,29 @@ def test_etudiant_bulletin_semestre(api_headers):
bul = r.json() bul = r.json()
assert len(bul) == 14 # HARDCODED assert len(bul) == 14 # HARDCODED
######## Bulletin BUT court en pdf #########
r = requests.get(
API_URL + "/etudiant/ine/" + str(INE) + "/formsemestre/1/bulletin/butcourt/pdf",
headers=api_headers,
verify=CHECK_CERTIFICATE,
timeout=scu.SCO_TEST_API_TIMEOUT,
)
assert r.status_code == 200
assert r.content[:4] == b"%PDF"
######## Bulletin BUT format intermédiaire en pdf #########
r = requests.get(
API_URL
+ "/etudiant/ine/"
+ str(INE)
+ "/formsemestre/1/bulletin/selectedevals/pdf",
headers=api_headers,
verify=CHECK_CERTIFICATE,
timeout=scu.SCO_TEST_API_TIMEOUT,
)
assert r.status_code == 200
assert r.content[:4] == b"%PDF"
################### LONG + PDF ##################### ################### LONG + PDF #####################
# ######### Test etudid ######### # ######### Test etudid #########