Compare commits

...

3 Commits

28 changed files with 1205 additions and 1126 deletions

View File

@ -9,12 +9,14 @@
import collections
import datetime
import pandas as pd
import numpy as np
from flask import g, has_request_context, url_for
from app import db
from app.comp.moy_mod import ModuleImplResults
from app.comp.res_but import ResultatsSemestreBUT
from app.models import Evaluation, FormSemestre, Identite
from app.models import Evaluation, FormSemestre, Identite, ModuleImpl
from app.models.groups import GroupDescr
from app.models.ues import UniteEns
from app.scodoc import sco_bulletins, sco_utils as scu
@ -249,59 +251,88 @@ class BulletinBUT:
# "moy": fmt_note(moyennes_etuds.mean()),
},
"evaluations": (
[
self.etud_eval_results(etud, e)
for e in modimpl.evaluations
if (e.visibulletin or version == "long")
and (e.id in modimpl_results.evaluations_etat)
and (
modimpl_results.evaluations_etat[e.id].is_complete
or self.prefs["bul_show_all_evals"]
)
]
self.etud_list_modimpl_evaluations(
etud, modimpl, modimpl_results, version
)
if version != "short"
else []
),
}
return d
def etud_eval_results(self, etud, e: Evaluation) -> dict:
def etud_list_modimpl_evaluations(
self,
etud: Identite,
modimpl: ModuleImpl,
modimpl_results: ModuleImplResults,
version: str,
) -> list[dict]:
"""Liste des résultats aux évaluations de ce modimpl à montrer pour cet étudiant"""
evaluation: Evaluation
eval_results = []
for evaluation in modimpl.evaluations:
if (
(evaluation.visibulletin or version == "long")
and (evaluation.id in modimpl_results.evaluations_etat)
and (
modimpl_results.evaluations_etat[evaluation.id].is_complete
or self.prefs["bul_show_all_evals"]
)
):
eval_notes = self.res.modimpls_results[modimpl.id].evals_notes[
evaluation.id
]
if (evaluation.evaluation_type == Evaluation.EVALUATION_NORMALE) or (
not np.isnan(eval_notes[etud.id])
):
eval_results.append(
self.etud_eval_results(etud, evaluation, eval_notes)
)
return eval_results
def etud_eval_results(
self, etud: Identite, evaluation: Evaluation, eval_notes: pd.DataFrame
) -> dict:
"dict resultats d'un étudiant à une évaluation"
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
eval_notes = self.res.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
modimpls_evals_poids = self.res.modimpls_evals_poids[e.moduleimpl_id]
modimpls_evals_poids = self.res.modimpls_evals_poids[evaluation.moduleimpl_id]
try:
etud_ues_ids = self.res.etud_ues_ids(etud.id)
poids = {
ue.acronyme: modimpls_evals_poids[ue.id][e.id]
ue.acronyme: modimpls_evals_poids[ue.id][evaluation.id]
for ue in self.res.ues
if (ue.type != UE_SPORT) and (ue.id in etud_ues_ids)
}
except KeyError:
poids = collections.defaultdict(lambda: 0.0)
d = {
"id": e.id,
"id": evaluation.id,
"coef": (
fmt_note(e.coefficient)
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
fmt_note(evaluation.coefficient)
if evaluation.evaluation_type == Evaluation.EVALUATION_NORMALE
else None
),
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
"description": e.description,
"evaluation_type": e.evaluation_type,
"date_debut": (
evaluation.date_debut.isoformat() if evaluation.date_debut else None
),
"date_fin": (
evaluation.date_fin.isoformat() if evaluation.date_fin else None
),
"description": evaluation.description,
"evaluation_type": evaluation.evaluation_type,
"note": (
{
"value": fmt_note(
eval_notes[etud.id],
note_max=e.note_max,
note_max=evaluation.note_max,
),
"min": fmt_note(notes_ok.min(), note_max=e.note_max),
"max": fmt_note(notes_ok.max(), note_max=e.note_max),
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
"min": fmt_note(notes_ok.min(), note_max=evaluation.note_max),
"max": fmt_note(notes_ok.max(), note_max=evaluation.note_max),
"moy": fmt_note(notes_ok.mean(), note_max=evaluation.note_max),
}
if not e.is_blocked()
if not evaluation.is_blocked()
else {}
),
"poids": poids,
@ -309,17 +340,25 @@ class BulletinBUT:
url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e.id,
evaluation_id=evaluation.id,
)
if has_request_context()
else "na"
),
# deprecated (supprimer avant #sco9.7)
"date": e.date_debut.isoformat() if e.date_debut else None,
"heure_debut": (
e.date_debut.time().isoformat("minutes") if e.date_debut else None
"date": (
evaluation.date_debut.isoformat() if evaluation.date_debut else None
),
"heure_debut": (
evaluation.date_debut.time().isoformat("minutes")
if evaluation.date_debut
else None
),
"heure_fin": (
evaluation.date_fin.time().isoformat("minutes")
if evaluation.date_fin
else None
),
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
}
return d

View File

@ -48,6 +48,7 @@ from typing import Any
from urllib.parse import urlparse, urlencode, parse_qs, urlunparse
from openpyxl.utils import get_column_letter
import reportlab
from reportlab.platypus import Paragraph, Spacer
from reportlab.platypus import Table, KeepInFrame
from reportlab.lib.colors import Color
@ -812,7 +813,10 @@ if __name__ == "__main__":
document,
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = doc.getvalue()
with open("/tmp/gen_table.pdf", "wb") as f:
f.write(data)

View File

@ -446,7 +446,8 @@ def _ue_mod_bulletin(
):
"""Infos sur les modules (et évaluations) dans une UE
(ajoute les informations aux modimpls)
Result: liste de modules de l'UE avec les infos dans chacun (seulement ceux où l'étudiant est inscrit).
Result: liste de modules de l'UE avec les infos dans chacun (seulement
ceux l'étudiant est inscrit).
"""
bul_show_mod_rangs = sco_preferences.get_preference(
"bul_show_mod_rangs", formsemestre_id

View File

@ -61,7 +61,7 @@ from flask_login import current_user
from app.models import FormSemestre, Identite, ScoDocSiteConfig
from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import NoteProcessError
from app.scodoc.sco_exceptions import NoteProcessError, ScoPDFFormatError
from app import log
from app.scodoc import sco_formsemestre
from app.scodoc import sco_pdf
@ -228,7 +228,15 @@ class BulletinGenerator:
preferences=sco_preferences.SemPreferences(formsemestre_id),
)
)
document.build(story)
try:
document.build(story)
except (
ValueError,
KeyError,
reportlab.platypus.doctemplate.LayoutError,
) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return data

View File

@ -103,7 +103,7 @@ class ScoPDFFormatError(ScoValueError):
super().__init__(
f"""Erreur dans un format pdf:
<p>{msg}</p>
<p>Vérifiez les paramètres (polices de caractères, balisage)
<p>Vérifiez les paramètres (polices de caractères, balisage, réglages bulletins...)
dans les paramètres ou préférences.
</p>
""",

View File

@ -458,7 +458,12 @@ def pdf_basic_page(
if title:
head = Paragraph(SU(title), StyleSheet["Heading3"])
objects = [head] + objects
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return data

View File

@ -50,7 +50,7 @@ from app.scodoc import sco_bulletins_pdf
from app.scodoc import sco_pv_dict
from app.scodoc import sco_pdf
from app.scodoc import sco_preferences
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_exceptions import ScoPDFFormatError, ScoValueError
from app.scodoc.sco_cursus_dut import SituationEtudCursus
from app.scodoc.sco_pv_templates import CourrierIndividuelTemplate, jury_titres
import sco_version
@ -132,7 +132,11 @@ def pdf_lettres_individuelles(
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return data
@ -241,13 +245,14 @@ def pdf_lettre_individuelle(sem, decision, etud: Identite, params, signature=Non
titre_jury_court = "s"
else:
titre_jury_court = ""
params[
"autorisations_txt"
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
etud.e,
titre_jury_court,
titre_jury_court,
decision["autorisations_descr"],
params["autorisations_txt"] = (
"""Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>"""
% (
etud.e,
titre_jury_court,
titre_jury_court,
decision["autorisations_descr"],
)
)
else:
params["autorisations_txt"] = ""

View File

@ -126,7 +126,11 @@ def pvjury_pdf(
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return data

View File

@ -47,12 +47,11 @@ from app import db, log
from app.models import Identite
import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_exceptions import ScoPDFFormatError, ScoValueError
from app.scodoc.sco_pdf import SU
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_import_etuds
from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_groups_view
from app.scodoc import sco_pdf
@ -388,7 +387,10 @@ def _trombino_pdf(groups_infos):
preferences=sco_preferences.SemPreferences(sem["formsemestre_id"]),
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
report.seek(0)
return send_file(
report,
@ -465,7 +467,10 @@ def _listeappel_photos_pdf(groups_infos):
preferences=sco_preferences.SemPreferences(sem["formsemestre_id"]),
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return scu.sendPDFFile(data, filename)

View File

@ -31,7 +31,7 @@
"""
import io
import reportlab
from reportlab.lib import colors
from reportlab.lib.colors import black
from reportlab.lib.pagesizes import A4, A3
@ -277,10 +277,12 @@ def pdf_trombino_tours(
preferences=sco_preferences.SemPreferences(),
)
)
try:
document.build(objects)
except (ValueError, KeyError) as exc:
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return scu.sendPDFFile(data, filename)
@ -470,7 +472,10 @@ def pdf_feuille_releve_absences(
preferences=sco_preferences.SemPreferences(),
)
)
document.build(objects)
try:
document.build(objects)
except (ValueError, KeyError, reportlab.platypus.doctemplate.LayoutError) as exc:
raise ScoPDFFormatError(str(exc)) from exc
data = report.getvalue()
return scu.sendPDFFile(data, filename)

View File

@ -1,6 +1,6 @@
function _partition_set_attr(partition_id, attr_name, attr_value) {
$.post(
SCO_URL + "/partition_set_attr",
SCO_URL + "partition_set_attr",
{
partition_id: partition_id,
attr: attr_name,

View File

@ -33,7 +33,7 @@ function update_ue_list() {
let ue_code = $("#tf_ue_code")[0].value;
let query =
SCO_URL +
"/Notes/ue_sharing_code?ue_code=" +
"Notes/ue_sharing_code?ue_code=" +
ue_code +
"&hide_ue_id=" +
ue_id +

View File

@ -16,7 +16,7 @@ function display_itemsuivis(active) {
.off("click")
.click(function (e) {
e.preventDefault();
$.post(SCO_URL + "/itemsuivi_create", {
$.post(SCO_URL + "itemsuivi_create", {
etudid: etudid,
fmt: "json",
}).done(item_insert_new);
@ -26,7 +26,7 @@ function display_itemsuivis(active) {
}
// add existing items
$.get(
SCO_URL + "/itemsuivi_list_etud",
SCO_URL + "itemsuivi_list_etud",
{ etudid: etudid, fmt: "json" },
function (L) {
for (var i in L) {
@ -95,7 +95,7 @@ function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
dp.blur(function (e) {
var date = this.value;
// console.log('selected text: ' + date);
$.post(SCO_URL + "/itemsuivi_set_date", {
$.post(SCO_URL + "itemsuivi_set_date", {
item_date: date,
itemsuivi_id: itemsuivi_id,
});
@ -103,7 +103,7 @@ function item_nodes(itemsuivi_id, item_date, situation, tags, readonly) {
dp.datepicker({
onSelect: function (date, instance) {
// console.log('selected: ' + date + 'for itemsuivi_id ' + itemsuivi_id);
$.post(SCO_URL + "/itemsuivi_set_date", {
$.post(SCO_URL + "itemsuivi_set_date", {
item_date: date,
itemsuivi_id: itemsuivi_id,
});
@ -161,7 +161,7 @@ function Date2DMY(date) {
}
function itemsuivi_suppress(itemsuivi_id) {
$.post(SCO_URL + "/itemsuivi_suppress", { itemsuivi_id: itemsuivi_id });
$.post(SCO_URL + "itemsuivi_suppress", { itemsuivi_id: itemsuivi_id });
// Clear items and rebuild:
$("ul.listdebouches li.itemsuivi").remove();
display_itemsuivis(0);

View File

@ -37,7 +37,7 @@ $().ready(function () {
ajax: {
url:
SCO_URL +
"/etud_info_html?etudid=" +
"etud_info_html?etudid=" +
get_etudid_from_elem(elems[i]) +
qs,
type: "GET",

View File

@ -19,7 +19,7 @@ function loadGroupes() {
$("#gmsg")[0].style.display = "block";
var partition_id = document.formGroup.partition_id.value;
$.get(SCO_URL + "/XMLgetGroupsInPartition", {
$.get(SCO_URL + "XMLgetGroupsInPartition", {
partition_id: partition_id,
}).done(function (data) {
var nodes = data.getElementsByTagName("group");
@ -384,7 +384,7 @@ function handleError(msg) {
}
function submitGroups() {
var url = SCO_URL + "/setGroups";
var url = SCO_URL + "setGroups";
// build post request body: groupname \n etudid; ...
var groupsLists = "";
var groupsToCreate = "";
@ -443,7 +443,7 @@ function GotoAnother() {
} else
document.location =
SCO_URL +
"/affect_groups?partition_id=" +
"affect_groups?partition_id=" +
document.formGroup.other_partition_id.value;
}

View File

@ -5,7 +5,7 @@ $().ready(function () {
for (var i = 0; i < spans.length; i++) {
var sp = spans[i];
var etudid = sp.id;
$(sp).load(SCO_URL + "/etud_photo_html?etudid=" + etudid);
$(sp).load(SCO_URL + "etud_photo_html?etudid=" + etudid);
}
});
@ -194,7 +194,7 @@ $().ready(function () {
ajax: {
url:
SCO_URL +
"/etud_info_html?with_photo=0&etudid=" +
"etud_info_html?with_photo=0&etudid=" +
get_etudid_from_elem(elems[i]),
},
text: "Loading...",

View File

@ -34,7 +34,7 @@ function get_notes_and_draw(formsemestre_id, etudid) {
*/
var query =
SCO_URL +
"/Notes/formsemestre_bulletinetud?formsemestre_id=" +
"Notes/formsemestre_bulletinetud?formsemestre_id=" +
formsemestre_id +
"&etudid=" +
etudid +

View File

@ -42,7 +42,7 @@ async function save_note(elem, v, etudid) {
$("#sco_msg").html("en cours...").show();
try {
const response = await fetch(
SCO_URL + "/../api/evaluation/" + evaluation_id + "/notes/set",
SCO_URL + "../api/evaluation/" + evaluation_id + "/notes/set",
{
method: "POST",
headers: {

View File

@ -6,7 +6,7 @@ $(function () {
delay: 300, // wait 300ms before suggestions
minLength: 2, // min nb of chars before suggest
position: { collision: "flip" }, // automatic menu position up/down
source: SCO_URL + "/search_etud_by_name",
source: SCO_URL + "search_etud_by_name",
select: function (event, ui) {
$(".in-expnom").val(ui.item.value);
$("#form-chercheetud").submit();

View File

@ -5,6 +5,6 @@ $().ready(function () {
for (var i = 0; i < spans.size(); i++) {
var sp = spans[i];
var etudid = sp.id;
$(sp).load(SCO_URL + "/etud_photo_html?etudid=" + etudid);
$(sp).load(SCO_URL + "etud_photo_html?etudid=" + etudid);
}
});

View File

@ -22,7 +22,7 @@ document.addEventListener("DOMContentLoaded", () => {
async function delete_validation(etudid, validation_type, validation_id) {
const response = await fetch(
`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${validation_id}/delete`,
`${SCO_URL}../api/etudiant/${etudid}/jury/${validation_type}/${validation_id}/delete`,
{
method: "POST",
}
@ -38,7 +38,7 @@ async function delete_validation(etudid, validation_type, validation_id) {
function update_ue_list() {
var ue_id = $("#tf_ue_id")[0].value;
if (ue_id) {
var query = SCO_URL + "/Notes/ue_sharing_code?ue_id=" + ue_id;
var query = SCO_URL + "Notes/ue_sharing_code?ue_id=" + ue_id;
$.get(query, "", function (data) {
$("#ue_list_code").html(data);
});

View File

@ -149,7 +149,7 @@ Calendrier de l'assiduité
list-style-type: none;
}
.pageContent {
margin-top: 1vh;
@ -335,7 +335,7 @@ Calendrier de l'assiduité
document.querySelectorAll('[assi_id]').forEach((el, i) => {
el.addEventListener('click', () => {
const assi_id = el.getAttribute('assi_id');
window.open(`${SCO_URL}/Assiduites/tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assi_id}`);
window.open(`${SCO_URL}Assiduites/tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assi_id}`);
})
});

View File

@ -102,6 +102,6 @@
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
<script>
const SCO_URL = "{% if g.scodoc_dept %}{{
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}{% endif %}";
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}{% endif %}";
</script>
{% endblock %}

View File

@ -152,7 +152,7 @@ document.addEventListener('DOMContentLoaded', function() {
calendar = new Calendar(container, options);
fetch(`${SCO_URL}/../api/formsemestre/{{formsemestre.id}}/edt?{{groups_query_args|safe}}&show_modules_titles={{show_modules_titles}}`)
fetch(`${SCO_URL}../api/formsemestre/{{formsemestre.id}}/edt?{{groups_query_args|safe}}&show_modules_titles={{show_modules_titles}}`)
.then(r=>{return r.json()})
.then(events=>{
if (typeof events == 'string') {

View File

@ -17,8 +17,8 @@ et permet de les effacer une par une.
<p class="help">
<b>Attention</b>, il vous appartient de vérifier la cohérence du résultat !
En principe, <b>l'usage de cette page devrait rester exceptionnel</b>.
Aucune annulation n'est ici possible (vous devrez re-saisir les décisions via les
pages de saisie de jury habituelles).
Aucune annulation n'est ici possible (vous devrez re-saisir les décisions via les
pages de saisie de jury habituelles).
</p>
{% if sem_vals.first() %}
<div class="jury_decisions_list jury_decisions_sems">
@ -27,7 +27,7 @@ pages de saisie de jury habituelles).
{% for v in sem_vals %}
<li>{{v.html()|safe}}
<form>
<button
<button
data-v_id="{{v.id}}" data-type="validation_formsemestre" data-etudid="{{etud.id}}"
>effacer</button></form>
</li>
@ -101,8 +101,8 @@ pages de saisie de jury habituelles).
{% endif %}
{% if not(
sem_vals.first() or ue_vals.first() or rcue_vals.first()
or annee_but_vals.first() or autorisations.first())
sem_vals.first() or ue_vals.first() or rcue_vals.first()
or annee_but_vals.first() or autorisations.first())
%}
<div>
<p class="fontred">aucune décision enregistrée</p>
@ -123,7 +123,7 @@ pages de saisie de jury habituelles).
<script>
document.addEventListener('DOMContentLoaded', () => {
const buttons = document.querySelectorAll('.jury_decisions_list button');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
// Handle button click event here
@ -132,10 +132,10 @@ document.addEventListener('DOMContentLoaded', () => {
const v_id = event.target.dataset.v_id;
const validation_type = event.target.dataset.type;
if (confirm("Supprimer cette validation ?")) {
fetch(`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
fetch(`${SCO_URL}../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
{
method: "POST",
}).then(response => {
}).then(response => {
// Handle the response
if (response.ok) {
location.reload();

View File

@ -51,7 +51,7 @@
<script>
window.onload = function () { enableTooltips("gtrcontent") };
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}";
</script>
{% endblock %}

View File

@ -159,8 +159,9 @@ def anonymize_users(cursor):
# Change les noms/prenoms/mail
cursor.execute("""SELECT * FROM "user";""")
users = cursor.fetchall() # fetch tout car modifie cette table ds la boucle
nb_users = len(users)
used_user_names = {u["user_name"] for u in users}
for user in users:
for i, user in enumerate(users):
user_name = user["user_name"]
nom, prenom = random.choice(NOMS), random.choice(PRENOMS)
new_name = (prenom[0] + nom).lower()
@ -168,7 +169,7 @@ def anonymize_users(cursor):
while new_name in used_user_names:
new_name += "x"
used_user_names.add(new_name)
print(f"{user_name} > {new_name}")
print(f"{i}/{nb_users}\t{user_name} > {new_name}")
cursor.execute(
"""UPDATE "user"
SET nom=%(nom)s, prenom=%(prenom)s, email=%(email)s, user_name=%(new_name)s
@ -234,6 +235,7 @@ if __name__ == "__main__":
cursor = cnx.cursor(cursor_factory=psycopg2.extras.DictCursor)
anonymize_db(cursor)
rename_students(cursor)
if PROCESS_USERS:
anonymize_users(cursor)

File diff suppressed because it is too large Load Diff