Compare commits

...

3 Commits

10 changed files with 128 additions and 100 deletions

View File

@ -52,7 +52,8 @@ def formations():
@as_json @as_json
def formations_ids(): def formations_ids():
""" """
Retourne la liste de toutes les id de formations (tous départements) Retourne la liste de toutes les id de formations
(tous départements, ou du département indiqué dans la route)
Exemple de résultat : [ 17, 99, 32 ] Exemple de résultat : [ 17, 99, 32 ]
""" """

View File

@ -23,9 +23,12 @@ from app.models.but_refcomp import (
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None): def orebut_import_refcomp(
xml_data: str, dept_id: int, orig_filename=None
) -> ApcReferentielCompetences:
"""Importation XML Orébut """Importation XML Orébut
peut lever TypeError ou ScoFormatError peut lever TypeError ou ScoFormatError
L'objet créé est ajouté et commité.
Résultat: instance de ApcReferentielCompetences Résultat: instance de ApcReferentielCompetences
""" """
# Vérifie que le même fichier n'a pas déjà été chargé: # Vérifie que le même fichier n'a pas déjà été chargé:
@ -41,7 +44,7 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
try: try:
root = ElementTree.XML(xml_data) root = ElementTree.XML(xml_data)
except ElementTree.ParseError as exc: except ElementTree.ParseError as exc:
raise ScoFormatError(f"fichier XML Orébut invalide (2): {exc.args}") raise ScoFormatError(f"fichier XML Orébut invalide (2): {exc.args}") from exc
if root.tag != "referentiel_competence": if root.tag != "referentiel_competence":
raise ScoFormatError("élément racine 'referentiel_competence' manquant") raise ScoFormatError("élément racine 'referentiel_competence' manquant")
args = ApcReferentielCompetences.attr_from_xml(root.attrib) args = ApcReferentielCompetences.attr_from_xml(root.attrib)
@ -60,7 +63,8 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
# ne devrait plus se produire car pas d'unicité de l'id: donc inutile # ne devrait plus se produire car pas d'unicité de l'id: donc inutile
db.session.rollback() db.session.rollback()
raise ScoValueError( raise ScoValueError(
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({competence.attrib["id"]}) f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({
competence.attrib["id"]})
""" """
) from exc ) from exc
ref.competences.append(c) ref.competences.append(c)

View File

@ -148,6 +148,7 @@ class ModuleImplResults:
evals_notes = pd.DataFrame(index=self.etudids, dtype=float) evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
self.evaluations_completes = [] self.evaluations_completes = []
self.evaluations_completes_dict = {} self.evaluations_completes_dict = {}
self.etudids_attente = set() # empty
for evaluation in moduleimpl.evaluations: for evaluation in moduleimpl.evaluations:
eval_df = self._load_evaluation_notes(evaluation) eval_df = self._load_evaluation_notes(evaluation)
# is_complete ssi # is_complete ssi
@ -155,13 +156,13 @@ class ModuleImplResults:
# ou évaluation déclarée "à prise en compte immédiate" # ou évaluation déclarée "à prise en compte immédiate"
# ou rattrapage, 2eme session, bonus # ou rattrapage, 2eme session, bonus
# ET pas bloquée par date (is_blocked) # ET pas bloquée par date (is_blocked)
is_blocked = evaluation.is_blocked()
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem. etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
is_complete = ( is_complete = (
(evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE) (evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
or (evaluation.publish_incomplete) or (evaluation.publish_incomplete)
or (not etudids_sans_note) or (not etudids_sans_note)
) and not evaluation.is_blocked() ) and not is_blocked
self.evaluations_completes.append(is_complete) self.evaluations_completes.append(is_complete)
self.evaluations_completes_dict[evaluation.id] = is_complete self.evaluations_completes_dict[evaluation.id] = is_complete
self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note
@ -178,16 +179,21 @@ class ModuleImplResults:
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)] eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
# Nombre de notes (non vides, incluant ATT etc) des inscrits: # Nombre de notes (non vides, incluant ATT etc) des inscrits:
nb_notes = eval_notes_inscr.notna().sum() nb_notes = eval_notes_inscr.notna().sum()
# Etudiants avec notes en attente:
# = ceux avec note ATT if is_blocked:
eval_etudids_attente = set( eval_etudids_attente = set()
eval_notes_inscr.iloc[ else:
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy() # Etudiants avec notes en attente:
].index # = ceux avec note ATT
) eval_etudids_attente = set(
if evaluation.publish_incomplete: eval_notes_inscr.iloc[
# et en "immédiat", tous ceux sans note (eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
eval_etudids_attente |= etudids_sans_note ].index
)
if evaluation.publish_incomplete:
# et en "immédiat", tous ceux sans note
eval_etudids_attente |= etudids_sans_note
# Synthèse pour état du module: # Synthèse pour état du module:
self.etudids_attente |= eval_etudids_attente self.etudids_attente |= eval_etudids_attente
self.evaluations_etat[evaluation.id] = EvaluationEtat( self.evaluations_etat[evaluation.id] = EvaluationEtat(

View File

@ -209,6 +209,7 @@ class ResultatsSemestre(ResultatsCache):
"evalcomplete" : bool, "evalcomplete" : bool,
"last_modif" : datetime.datetime | None, # saisie de note la plus récente "last_modif" : datetime.datetime | None, # saisie de note la plus récente
"nb_notes" : int, # nb notes d'étudiants inscrits "nb_notes" : int, # nb notes d'étudiants inscrits
"nb_attente" : int, # nb de notes en ATTente (même si bloquée)
}, },
"evaluation_id" : int, "evaluation_id" : int,
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1) "jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
@ -236,6 +237,7 @@ class ResultatsSemestre(ResultatsCache):
"etat": { "etat": {
"blocked": evaluation.is_blocked(), "blocked": evaluation.is_blocked(),
"evalcomplete": etat.is_complete, "evalcomplete": etat.is_complete,
"nb_attente": etat.nb_attente,
"nb_notes": etat.nb_notes, "nb_notes": etat.nb_notes,
"last_modif": last_modif, "last_modif": last_modif,
}, },

View File

@ -873,7 +873,7 @@ class FormSemestre(db.Model):
descr_sem += " " + self.modalite descr_sem += " " + self.modalite
return descr_sem return descr_sem
def get_abs_count(self, etudid): def get_abs_count(self, etudid) -> tuple[int, int, int]:
"""Les comptes d'absences de cet étudiant dans ce semestre: """Les comptes d'absences de cet étudiant dans ce semestre:
tuple (nb abs non just, nb abs justifiées, nb abs total) tuple (nb abs non just, nb abs justifiées, nb abs total)
Utilise un cache. Utilise un cache.

View File

@ -30,17 +30,18 @@
(coût théorique en heures équivalent TD) (coût théorique en heures équivalent TD)
""" """
from flask import request from flask import request, Response
from app.models import FormSemestre from app.models import FormSemestre
from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import sco_version import sco_version
def formsemestre_table_estim_cost( def formsemestre_table_estim_cost(
formsemestre_id, formsemestre: FormSemestre,
n_group_td=1, n_group_td=1,
n_group_tp=1, n_group_tp=1,
coef_tp=1, coef_tp=1,
@ -55,8 +56,6 @@ def formsemestre_table_estim_cost(
peut conduire à une sur-estimation du coût s'il y a des modules optionnels peut conduire à une sur-estimation du coût s'il y a des modules optionnels
(dans ce cas, retoucher le tableau excel exporté). (dans ce cas, retoucher le tableau excel exporté).
""" """
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
rows = [] rows = []
for modimpl in formsemestre.modimpls: for modimpl in formsemestre.modimpls:
rows.append( rows.append(
@ -76,14 +75,14 @@ def formsemestre_table_estim_cost(
+ coef_cours * row["heures_cours"] + coef_cours * row["heures_cours"]
+ coef_tp * row["heures_tp"] + coef_tp * row["heures_tp"]
) )
sum_cours = sum([t["heures_cours"] for t in rows]) sum_cours = sum(t["heures_cours"] for t in rows)
sum_td = sum([t["heures_td"] for t in rows]) sum_td = sum(t["heures_td"] for t in rows)
sum_tp = sum([t["heures_tp"] for t in rows]) sum_tp = sum(t["heures_tp"] for t in rows)
sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp
assert abs(sum([t["HeqTD"] for t in rows]) - sum_heqtd) < 0.01, "%s != %s" % ( # assert abs(sum(t["HeqTD"] for t in rows) - sum_heqtd) < 0.01, "%s != %s" % (
sum([t["HeqTD"] for t in rows]), # sum(t["HeqTD"] for t in rows),
sum_heqtd, # sum_heqtd,
) # )
rows.append( rows.append(
{ {
@ -117,7 +116,7 @@ def formsemestre_table_estim_cost(
), ),
rows=rows, rows=rows,
html_sortable=True, html_sortable=True,
preferences=sco_preferences.SemPreferences(formsemestre_id), preferences=sco_preferences.SemPreferences(formsemestre.id),
html_class="table_leftalign table_listegroupe", html_class="table_leftalign table_listegroupe",
xls_before_table=[ xls_before_table=[
[formsemestre.titre_annee()], [formsemestre.titre_annee()],
@ -146,47 +145,45 @@ def formsemestre_table_estim_cost(
return tab return tab
# view
def formsemestre_estim_cost( def formsemestre_estim_cost(
formsemestre_id, formsemestre_id: int,
n_group_td=1, n_group_td: int | str = 1,
n_group_tp=1, n_group_tp: int | str = 1,
coef_tp=1, coef_tp: float | str = 1.0,
coef_cours=1.5, coef_cours: float | str = 1.5,
fmt="html", fmt="html",
): ) -> str | Response:
"""Page (formulaire) estimation coûts""" """Page (formulaire) estimation coûts"""
try:
n_group_td = int(n_group_td)
n_group_tp = int(n_group_tp)
coef_tp = float(coef_tp)
coef_cours = float(coef_cours)
except ValueError as exc:
raise ScoValueError("paramètre invalide: utiliser des nombres") from exc
n_group_td = int(n_group_td) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
n_group_tp = int(n_group_tp)
coef_tp = float(coef_tp)
coef_cours = float(coef_cours)
tab = formsemestre_table_estim_cost( tab = formsemestre_table_estim_cost(
formsemestre_id, formsemestre,
n_group_td=n_group_td, n_group_td=n_group_td,
n_group_tp=n_group_tp, n_group_tp=n_group_tp,
coef_tp=coef_tp, coef_tp=coef_tp,
coef_cours=coef_cours, coef_cours=coef_cours,
) )
h = """ tab.html_before_table = f"""
<form name="f" method="get" action="%s"> <form name="f" method="get" action="{request.base_url}">
<input type="hidden" name="formsemestre_id" value="%s"></input> <input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br> Nombre de groupes de TD: <input type="text" name="n_group_td" value="{n_group_td}" onchange="document.f.submit()"/><br>
Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/> Nombre de groupes de TP: <input type="text" name="n_group_tp" value="{n_group_tp}" onchange="document.f.submit()"/>
&nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/> &nbsp;Coefficient heures TP: <input type="text" name="coef_tp" value="{coef_tp}" onchange="document.f.submit()"/>
<br> <br>
</form> </form>
""" % ( """
request.base_url,
formsemestre_id,
n_group_td,
n_group_tp,
coef_tp,
)
tab.html_before_table = h
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % ( tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
request.base_url, request.base_url,
formsemestre_id, formsemestre.id,
n_group_td, n_group_td,
n_group_tp, n_group_tp,
coef_tp, coef_tp,

View File

@ -279,11 +279,18 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
nb_eval_completes (= prises en compte) nb_eval_completes (= prises en compte)
nb_evals_en_cours (= avec des notes, mais pas complete) nb_evals_en_cours (= avec des notes, mais pas complete)
nb_evals_vides (= sans aucune note) nb_evals_vides (= sans aucune note)
nb_evals_attente (= avec des notes en ATTente et pas bloquée)
date derniere modif date derniere modif
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note. Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
""" """
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, nb_evals_blocked = 0, 0, 0, 0 (
nb_evals_completes,
nb_evals_en_cours,
nb_evals_vides,
nb_evals_blocked,
nb_evals_attente,
) = (0, 0, 0, 0, 0)
dates = [] dates = []
for e in etat_evals: for e in etat_evals:
if e["etat"]["blocked"]: if e["etat"]["blocked"]:
@ -294,6 +301,8 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
nb_evals_vides += 1 nb_evals_vides += 1
elif not e["etat"]["blocked"]: elif not e["etat"]["blocked"]:
nb_evals_en_cours += 1 nb_evals_en_cours += 1
if e["etat"]["nb_attente"] and not e["etat"]["blocked"]:
nb_evals_attente += 1
last_modif = e["etat"]["last_modif"] last_modif = e["etat"]["last_modif"]
if last_modif is not None: if last_modif is not None:
dates.append(e["etat"]["last_modif"]) dates.append(e["etat"]["last_modif"])
@ -303,6 +312,7 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
return { return {
"nb_evals": len(etat_evals), "nb_evals": len(etat_evals),
"nb_evals_attente": nb_evals_attente,
"nb_evals_blocked": nb_evals_blocked, "nb_evals_blocked": nb_evals_blocked,
"nb_evals_completes": nb_evals_completes, "nb_evals_completes": nb_evals_completes,
"nb_evals_en_cours": nb_evals_en_cours, "nb_evals_en_cours": nb_evals_en_cours,

View File

@ -1312,7 +1312,9 @@ def formsemestre_tableau_modules(
if etat["attente"]: if etat["attente"]:
H.append( H.append(
f""" <span><a class="redlink" href="{moduleimpl_status_url}" f""" <span><a class="redlink" href="{moduleimpl_status_url}"
title="Il y a des notes en attente"><span class="evals_attente">en attente</span></a></span>""" title="Il y a des notes en attente"><span class="evals_attente">{
etat["nb_evals_attente"]
} en attente</span></a></span>"""
) )
if not mod_is_conforme: if not mod_is_conforme:
H.append( H.append(

View File

@ -332,28 +332,29 @@ def fiche_etud(etudid=None):
) )
# fiche admission # fiche admission
infos_admission = _infos_admission(etud, restrict_etud_data) if etud.admission:
has_adm_notes = any( infos_admission = _infos_admission(etud, restrict_etud_data)
infos_admission[k] for k in ("math", "physique", "anglais", "francais") has_adm_notes = any(
) infos_admission[k] for k in ("math", "physique", "anglais", "francais")
has_bac_info = any(
infos_admission[k]
for k in (
"bac_specialite",
"annee_bac",
"rapporteur",
"commentaire",
"classement",
"type_admission",
"rap",
) )
) has_bac_info = any(
if has_bac_info or has_adm_notes: infos_admission[k]
adm_tmpl = """<!-- Donnees admission --> for k in (
<div class="fichetitre">Informations admission</div> "bac_specialite",
""" "annee_bac",
if has_adm_notes: "rapporteur",
adm_tmpl += """ "commentaire",
"classement",
"type_admission",
"rap",
)
)
if has_bac_info or has_adm_notes:
adm_tmpl = """<!-- Donnees admission -->
<div class="fichetitre">Informations admission</div>
"""
if has_adm_notes:
adm_tmpl += """
<table> <table>
<tr><th>Bac</th><th>Année</th><th>Rg</th> <tr><th>Bac</th><th>Année</th><th>Rg</th>
<th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr> <th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr>
@ -364,24 +365,26 @@ def fiche_etud(etudid=None):
<td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td> <td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td>
</tr> </tr>
</table> </table>
""" """
adm_tmpl += """ adm_tmpl += """
<div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div> <div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div>
<div class="info_lycee">%(info_lycee)s</div>""" <div class="info_lycee">%(info_lycee)s</div>"""
if infos_admission["type_admission"] or infos_admission["classement"]: if infos_admission["type_admission"] or infos_admission["classement"]:
adm_tmpl += """<div class="vadmission">""" adm_tmpl += """<div class="vadmission">"""
if infos_admission["type_admission"]: if infos_admission["type_admission"]:
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """ adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
if infos_admission["classement"]: if infos_admission["classement"]:
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>""" adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
if infos_admission["type_admission"] or infos_admission["classement"]: if infos_admission["type_admission"] or infos_admission["classement"]:
adm_tmpl += "</div>" adm_tmpl += "</div>"
if infos_admission["rap"]: if infos_admission["rap"]:
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>""" adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
adm_tmpl += """</div>""" adm_tmpl += """</div>"""
else:
adm_tmpl = "" # pas de boite "info admission"
info["adm_data"] = adm_tmpl % infos_admission
else: else:
adm_tmpl = "" # pas de boite "info admission" info["adm_data"] = ""
info["adm_data"] = adm_tmpl % infos_admission
# Fichiers archivés: # Fichiers archivés:
info["fichiers_archive_htm"] = ( info["fichiers_archive_htm"] = (
@ -654,7 +657,7 @@ def _format_adresse(adresse: Adresse | None) -> dict:
def _infos_admission(etud: Identite, restrict_etud_data: bool) -> dict: def _infos_admission(etud: Identite, restrict_etud_data: bool) -> dict:
"""dict with adminission data, restricted or not""" """dict with admission data, restricted or not"""
# info sur rapporteur et son commentaire # info sur rapporteur et son commentaire
rap = "" rap = ""
if not restrict_etud_data: if not restrict_etud_data:
@ -799,8 +802,11 @@ def etud_info_html(etudid, with_photo="1", debug=False):
code_cursus, _ = sco_report.get_code_cursus_etud( code_cursus, _ = sco_report.get_code_cursus_etud(
etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", " etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", "
) )
bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite) if etud.admission:
bac_abbrev = bac.abbrev() bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite)
bac_abbrev = bac.abbrev()
else:
bac_abbrev = "-"
H = f"""<div class="etud_info_div"> H = f"""<div class="etud_info_div">
<div class="eid_left"> <div class="eid_left">
<div class="eid_nom"><div><a class="stdlink" target="_blank" href="{ <div class="eid_nom"><div><a class="stdlink" target="_blank" href="{

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.6.951" SCOVERSION = "9.6.952"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"