Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
9 changed files with 220 additions and 214 deletions
Showing only changes of commit 0bf3c22cd0 - Show all commits

View File

@ -56,6 +56,7 @@ class EvaluationEtat:
evaluation_id: int
nb_attente: int
nb_notes: int # nb notes d'étudiants inscrits au semestre et au modimpl
is_complete: bool
def to_dict(self):
@ -168,13 +169,15 @@ class ModuleImplResults:
# NULL en base => ABS (= -999)
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
# Ce merge ne garde que les étudiants inscrits au module
# et met à NULL les notes non présentes
# et met à NULL (NaN) les notes non présentes
# (notes non saisies ou etuds non inscrits au module):
evals_notes = evals_notes.merge(
eval_df, how="left", left_index=True, right_index=True
)
# Notes en attente: (ne prend en compte que les inscrits, non démissionnaires)
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
# Nombre de notes (non vides, incluant ATT etc) des inscrits:
nb_notes = eval_notes_inscr.notna().sum()
eval_etudids_attente = set(
eval_notes_inscr.iloc[
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
@ -184,6 +187,7 @@ class ModuleImplResults:
self.evaluations_etat[evaluation.id] = EvaluationEtat(
evaluation_id=evaluation.id,
nb_attente=len(eval_etudids_attente),
nb_notes=nb_notes,
is_complete=is_complete,
)
# au moins une note en ATT dans ce modimpl:

View File

@ -9,12 +9,13 @@
from collections import Counter, defaultdict
from collections.abc import Generator
import datetime
from functools import cached_property
from operator import attrgetter
import numpy as np
import pandas as pd
import sqlalchemy as sa
from flask import g, url_for
from app import db
@ -22,14 +23,19 @@ from app.comp import res_sem
from app.comp.res_cache import ResultatsCache
from app.comp.jury import ValidationsSemestre
from app.comp.moy_mod import ModuleImplResults
from app.models import FormSemestre, FormSemestreUECoef
from app.models import Identite
from app.models import ModuleImpl, ModuleImplInscription
from app.models import ScolarAutorisationInscription
from app.models.ues import UniteEns
from app.models import (
Evaluation,
FormSemestre,
FormSemestreUECoef,
Identite,
ModuleImpl,
ModuleImplInscription,
ScolarAutorisationInscription,
UniteEns,
)
from app.scodoc.sco_cache import ResultatsSemestreCache
from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_exceptions import ScoValueError, ScoTemporaryError
from app.scodoc import sco_utils as scu
@ -192,16 +198,80 @@ class ResultatsSemestre(ResultatsCache):
*[mr.etudids_attente for mr in self.modimpls_results.values()]
)
# # Etat des évaluations
# # (se substitue à do_evaluation_etat, sans les moyennes par groupes)
# def get_evaluations_etats(evaluation_id: int) -> dict:
# """Renvoie dict avec les clés:
# last_modif
# nb_evals_completes
# nb_evals_en_cours
# nb_evals_vides
# attente
# """
# Etat des évaluations
def get_evaluation_etat(self, evaluation: Evaluation) -> dict:
"""État d'une évaluation
{
"coefficient" : float, # 0 si None
"description" : str, # de l'évaluation, "" si None
"etat" {
"evalcomplete" : bool,
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
"nb_notes" : int, # nb notes d'étudiants inscrits
},
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
"publish_incomplete" : bool,
}
"""
mod_results = self.modimpls_results.get(evaluation.moduleimpl_id)
if mod_results is None:
raise ScoTemporaryError() # argh !
etat = mod_results.evaluations_etat.get(evaluation.id)
if etat is None:
raise ScoTemporaryError() # argh !
# Date de dernière saisie de note
cursor = db.session.execute(
sa.text(
"SELECT MAX(date) FROM notes_notes WHERE evaluation_id = :evaluation_id"
),
{"evaluation_id": evaluation.id},
)
date_modif = cursor.one_or_none()
last_modif = date_modif[0] if date_modif else None
return {
"coefficient": evaluation.coefficient or 0.0,
"description": evaluation.description or "",
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
"publish_incomplete": evaluation.publish_incomplete,
"etat": {
"evalcomplete": etat.is_complete,
"nb_notes": etat.nb_notes,
"last_modif": last_modif,
},
}
def get_mod_evaluation_etat_list(self, modimpl: ModuleImpl) -> list[dict]:
"""Liste des états des évaluations de ce module
[ evaluation_etat, ... ] (voir get_evaluation_etat)
trié par (numero desc, date_debut desc)
"""
# nouvelle version 2024-02-02
return list(
reversed(
[
self.get_evaluation_etat(evaluation)
for evaluation in modimpl.evaluations
]
)
)
# modernisation de get_mod_evaluation_etat_list
# utilisé par:
# sco_evaluations.do_evaluation_etat_in_mod
# e["etat"]["evalcomplete"]
# e["etat"]["nb_notes"]
# e["etat"]["last_modif"]
#
# sco_formsemestre_status.formsemestre_description_table
# "jour" (qui est e.date_debut or datetime.date(1900, 1, 1))
# "description"
# "coefficient"
# e["etat"]["evalcomplete"]
# publish_incomplete
#
# sco_formsemestre_status.formsemestre_tableau_modules
# e["etat"]["nb_notes"]
#
# --- JURY...
def get_formsemestre_validations(self) -> ValidationsSemestre:

View File

@ -423,30 +423,37 @@ class NotesTableCompat(ResultatsSemestre):
)
return evaluations
def get_evaluations_etats(self) -> list[dict]:
"""Liste de toutes les évaluations du semestre
[ {...evaluation et son etat...} ]"""
# TODO: à moderniser (voir dans ResultatsSemestre)
# utilisé par
# do_evaluation_etat_in_sem
def get_evaluations_etats(self) -> dict[int, dict]:
""" "état" de chaque évaluation du semestre
{
evaluation_id : {
"evalcomplete" : bool,
"last_modif" : datetime | None
"nb_notes" : int,
}, ...
}
"""
# utilisé par do_evaluation_etat_in_sem
evaluations_etats = {}
for modimpl in self.formsemestre.modimpls_sorted:
for evaluation in modimpl.evaluations:
evaluation_etat = self.get_evaluation_etat(evaluation)
evaluations_etats[evaluation.id] = evaluation_etat["etat"]
return evaluations_etats
from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"):
self._evaluations_etats = sco_evaluations.do_evaluation_list_in_sem(
self.formsemestre.id
)
return self._evaluations_etats
def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
"""Liste des états des évaluations de ce module"""
# XXX TODO à moderniser: lent, recharge des données que l'on a déjà...
return [
e
for e in self.get_evaluations_etats()
if e["moduleimpl_id"] == moduleimpl_id
]
# ancienne version < 2024-02-02
# def get_mod_evaluation_etat_list(self, moduleimpl_id) -> list[dict]:
# """Liste des états des évaluations de ce module
# ordonnée selon (numero desc, date_debut desc)
# """
# # à moderniser: lent, recharge des données que l'on a déjà...
# # remplacemé par ResultatsSemestre.get_mod_evaluation_etat_list
# #
# return [
# e
# for e in self.get_evaluations_etats()
# if e["moduleimpl_id"] == moduleimpl_id
# ]
def get_moduleimpls_attente(self):
"""Liste des modimpls du semestre ayant des notes en attente"""

View File

@ -40,7 +40,7 @@ from app import db
from app.auth.models import User
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import Evaluation, FormSemestre
from app.models import Evaluation, FormSemestre, ModuleImpl
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
@ -280,82 +280,14 @@ def do_evaluation_etat(
}
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
"""Liste les évaluations de tous les modules de ce semestre.
Triée par module, numero desc, date_debut desc
Donne pour chaque eval son état (voir do_evaluation_etat)
{ evaluation_id,nb_inscrits, nb_notes, nb_abs, nb_neutre, moy, median, last_modif ... }
Exemple:
[ {
'coefficient': 1.0,
'description': 'QCM et cas pratiques',
'etat': {
'evalattente': False,
'evalcomplete': True,
'evaluation_id': 'GEAEVAL82883',
'gr_incomplets': [],
'gr_moyennes': [{
'gr_median': '12.00', # sur 20
'gr_moy': '11.88',
'gr_nb_att': 0,
'gr_nb_notes': 166,
'group_id': 'GEAG266762',
'group_name': None
}],
'groups': {'GEAG266762': {'etudid': 'GEAEID80603',
'group_id': 'GEAG266762',
'group_name': None,
'partition_id': 'GEAP266761'}
},
'last_modif': datetime.datetime(2015, 12, 3, 15, 15, 16),
'median': '12.00',
'moy': '11.84',
'nb_abs': 2,
'nb_att': 0,
'nb_inscrits': 166,
'nb_neutre': 0,
'nb_notes': 168,
'nb_notes_total': 169
},
'evaluation_id': 'GEAEVAL82883',
'evaluation_type': 0,
'heure_debut': datetime.time(8, 0),
'heure_fin': datetime.time(9, 30),
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900
'moduleimpl_id': 'GEAMIP80490',
'note_max': 20.0,
'numero': 0,
'publish_incomplete': 0,
'visibulletin': 1} ]
"""
req = """SELECT E.id AS evaluation_id, E.*
FROM notes_evaluation E, notes_moduleimpl MI
WHERE MI.formsemestre_id = %(formsemestre_id)s
and MI.id = E.moduleimpl_id
ORDER BY MI.id, numero desc, date_debut desc
"""
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(req, {"formsemestre_id": formsemestre_id})
res = cursor.dictfetchall()
# etat de chaque evaluation:
for r in res:
if with_etat:
r["etat"] = do_evaluation_etat(r["evaluation_id"])
r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1)
return res
def _eval_etat(evals):
"""evals: list of mappings (etats)
def _summarize_evals_etats(evals: list[dict]) -> dict:
"""Synthétise les états d'une liste d'évaluations
evals: list of mappings (etats),
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
-> nb_eval_completes, nb_evals_en_cours,
nb_evals_vides, date derniere modif
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
"""
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
dates = []
@ -370,11 +302,8 @@ def _eval_etat(evals):
if last_modif is not None:
dates.append(e["etat"]["last_modif"])
if dates:
dates = scu.sort_dates(dates)
last_modif = dates[-1] # date de derniere modif d'une note dans un module
else:
last_modif = ""
# date de derniere modif d'une note dans un module
last_modif = sorted(dates)[-1] if dates else ""
return {
"nb_evals_completes": nb_evals_completes,
@ -384,37 +313,42 @@ def _eval_etat(evals):
}
def do_evaluation_etat_in_sem(formsemestre_id):
"""-> nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
date derniere modif, attente
XXX utilisé par
- formsemestre_status_head
- gen_formsemestre_recapcomplet_xml
- gen_formsemestre_recapcomplet_json
"nb_evals_completes"
"nb_evals_en_cours"
"nb_evals_vides"
"date_derniere_note"
"last_modif"
"attente"
def do_evaluation_etat_in_sem(formsemestre: FormSemestre) -> dict:
"""-> { nb_eval_completes, nb_evals_en_cours, nb_evals_vides,
date derniere modif, attente }
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
# Note: utilisé par
# - formsemestre_status_head
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
# pour la ligne
# Évaluations: 20 ok, 8 en cours, 5 vides (dernière note saisie le 11/01/2024 à 19h49)
# attente
#
# - gen_formsemestre_recapcomplet_xml
# - gen_formsemestre_recapcomplet_json
# nb_evals_completes, nb_evals_en_cours, nb_evals_vides, last_modif
#
# "nb_evals_completes"
# "nb_evals_en_cours"
# "nb_evals_vides"
# "last_modif"
# "attente"
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
evals = nt.get_evaluations_etats()
etat = _eval_etat(evals)
evaluations_etats = nt.get_evaluations_etats()
# raccordement moche...
etat = _summarize_evals_etats([{"etat": v} for v in evaluations_etats.values()])
# Ajoute information sur notes en attente
etat["attente"] = len(nt.get_moduleimpls_attente()) > 0
return etat
def do_evaluation_etat_in_mod(nt, moduleimpl_id):
def do_evaluation_etat_in_mod(nt, modimpl: ModuleImpl):
"""état des évaluations dans ce module"""
evals = nt.get_mod_evaluation_etat_list(moduleimpl_id)
etat = _eval_etat(evals)
evals = nt.get_mod_evaluation_etat_list(modimpl)
etat = _summarize_evals_etats(evals)
# Il y a-t-il des notes en attente dans ce module ?
etat["attente"] = nt.modimpls_results[moduleimpl_id].en_attente
etat["attente"] = nt.modimpls_results[modimpl.id].en_attente
return etat

View File

@ -230,3 +230,15 @@ class APIInvalidParams(Exception):
class ScoFormationConflict(Exception):
"""Conflit cohérence formation (APC)"""
class ScoTemporaryError(ScoValueError):
"""Erreurs temporaires rarissimes (caches ?)"""
def __init__(self, msg: str = ""):
msg = """
<p>"Erreur temporaire</p>
<p>Veuillez -essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
</p>
"""
super().__init__(msg)

View File

@ -627,9 +627,7 @@ def formsemestre_description_table(
# car l'UE de rattachement n'a pas d'intérêt en BUT
rows.append(ue_info)
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=modimpl.id
)
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
enseignants = ", ".join(ens.get_prenomnom() for ens in modimpl.enseignants)
row = {
@ -638,7 +636,7 @@ def formsemestre_description_table(
"Code": modimpl.module.code or "",
"Module": modimpl.module.abbrev or modimpl.module.titre,
"_Module_class": "scotext",
"Inscrits": len(mod_inscrits),
"Inscrits": mod_nb_inscrits,
"Responsable": sco_users.user_info(modimpl.responsable_id)["nomprenom"],
"_Responsable_class": "scotext",
"Enseignants": enseignants,
@ -680,7 +678,7 @@ def formsemestre_description_table(
if with_evals:
# Ajoute lignes pour evaluations
evals = nt.get_mod_evaluation_etat_list(modimpl.id)
evals = nt.get_mod_evaluation_etat_list(modimpl)
evals.reverse() # ordre chronologique
# Ajoute etat:
eval_rows = []
@ -942,10 +940,10 @@ def html_expr_diagnostic(diagnostics):
def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None):
"""En-tête HTML des pages "semestre" """
sem: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
if not sem:
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
if not formsemestre:
raise ScoValueError("Semestre inexistant (il a peut être été supprimé ?)")
formation: Formation = sem.formation
formation: Formation = formsemestre.formation
parcours = formation.get_cursus()
page_title = page_title or "Modules de "
@ -957,25 +955,25 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
f"""<table>
<tr><td class="fichetitre2">Formation: </td><td>
<a href="{url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=sem.formation.id)}"
scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)}"
class="discretelink" title="Formation {
formation.acronyme}, v{formation.version}">{formation.titre}</a>
""",
]
if sem.semestre_id >= 0:
H.append(f", {parcours.SESSION_NAME} {sem.semestre_id}")
if sem.modalite:
H.append(f"&nbsp;en {sem.modalite}")
if sem.etapes:
if formsemestre.semestre_id >= 0:
H.append(f", {parcours.SESSION_NAME} {formsemestre.semestre_id}")
if formsemestre.modalite:
H.append(f"&nbsp;en {formsemestre.modalite}")
if formsemestre.etapes:
H.append(
f"""&nbsp;&nbsp;&nbsp;(étape <b><tt>{
sem.etapes_apo_str() or "-"
formsemestre.etapes_apo_str() or "-"
}</tt></b>)"""
)
H.append("</td></tr>")
if formation.is_apc():
# Affiche les parcours BUT cochés. Si aucun, tous ceux du référentiel.
sem_parcours = sem.get_parcours_apc()
sem_parcours = formsemestre.get_parcours_apc()
H.append(
f"""
<tr><td class="fichetitre2">Parcours: </td>
@ -984,7 +982,7 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
"""
)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
H.append(
'<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
% evals
@ -1002,11 +1000,11 @@ def formsemestre_status_head(formsemestre_id: int = None, page_title: str = None
"""<span class="fontred">Il y a des notes en attente !</span>
Le classement des étudiants n'a qu'une valeur indicative."""
)
if sem.bul_hide_xml:
if formsemestre.bul_hide_xml:
warnings.append("""Bulletins non publiés sur la passerelle.""")
if sem.block_moyennes:
if formsemestre.block_moyennes:
warnings.append("Calcul des moyennes bloqué !")
if sem.semestre_id >= 0 and not sem.est_sur_une_annee():
if formsemestre.semestre_id >= 0 and not formsemestre.est_sur_une_annee():
warnings.append("""<em>Ce semestre couvre plusieurs années scolaires !</em>""")
if warnings:
H += [
@ -1028,18 +1026,14 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
# S'assure que les groupes de parcours sont à jour:
if int(check_parcours):
formsemestre.setup_parcours_groups()
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
formsemestre_id=formsemestre_id
)
modimpls = formsemestre.modimpls_sorted
nt = res_sem.load_formsemestre_results(formsemestre)
# Construit la liste de tous les enseignants de ce semestre:
mails_enseignants = set(u.email for u in formsemestre.responsables)
for modimpl in modimpls:
mails_enseignants.add(sco_users.user_info(modimpl["responsable_id"])["email"])
mails_enseignants |= set(
[sco_users.user_info(m["ens_id"])["email"] for m in modimpl["ens"]]
)
mails_enseignants.add(sco_users.user_info(modimpl.responsable_id)["email"])
mails_enseignants |= {u.email for u in modimpl.enseignants if u.email}
can_edit = formsemestre.can_be_edited_by(current_user)
can_change_all_notes = current_user.has_permission(Permission.EditAllNotes) or (
@ -1089,13 +1083,13 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
if nt.parcours.APC_SAE:
# BUT: tableau ressources puis SAE
ressources = [
m for m in modimpls if m["module"]["module_type"] == ModuleType.RESSOURCE
m for m in modimpls if m.module.module_type == ModuleType.RESSOURCE
]
saes = [m for m in modimpls if m["module"]["module_type"] == ModuleType.SAE]
saes = [m for m in modimpls if m.module.module_type == ModuleType.SAE]
autres = [
m
for m in modimpls
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
]
H += [
f"""
@ -1136,7 +1130,7 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
modimpls_classic = [
m
for m in modimpls
if m["module"]["module_type"] not in (ModuleType.RESSOURCE, ModuleType.SAE)
if m.module.module_type not in (ModuleType.RESSOURCE, ModuleType.SAE)
]
H += [
"<p>",
@ -1168,8 +1162,10 @@ def formsemestre_status(formsemestre_id=None, check_parcours=True):
adrlist = list(mails_enseignants - {None, ""})
if adrlist:
H.append(
'<p><a class="stdlink" href="mailto:?cc=%s">Courrier aux %d enseignants du semestre</a></p>'
% (",".join(adrlist), len(adrlist))
f"""<p>
<a class="stdlink" href="mailto:?cc={','.join(adrlist)}">Courrier aux {
len(adrlist)} enseignants du semestre</a>
</p>"""
)
return "".join(H) + html_sco_header.sco_footer()
@ -1189,7 +1185,7 @@ _TABLEAU_MODULES_FOOT = """</table>"""
def formsemestre_tableau_modules(
modimpls: list[dict],
modimpls: list[ModuleImpl],
nt,
formsemestre: FormSemestre,
can_edit=True,
@ -1200,11 +1196,11 @@ def formsemestre_tableau_modules(
H = []
prev_ue_id = None
for modimpl in modimpls:
mod: Module = db.session.get(Module, modimpl["module_id"])
mod: Module = modimpl.module
moduleimpl_status_url = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl["moduleimpl_id"],
moduleimpl_id=modimpl.id,
)
mod_descr = "Module " + (mod.titre or "")
if mod.is_apc():
@ -1221,48 +1217,45 @@ def formsemestre_tableau_modules(
mod_descr += " (pas de coefficients) "
else:
mod_descr += ", coef. " + str(mod.coefficient)
mod_ens = sco_users.user_info(modimpl["responsable_id"])["nomcomplet"]
if modimpl["ens"]:
mod_ens = sco_users.user_info(modimpl.responsable_id)["nomcomplet"]
if modimpl.enseignants.count():
mod_ens += " (resp.), " + ", ".join(
[sco_users.user_info(e["ens_id"])["nomcomplet"] for e in modimpl["ens"]]
[u.get_nomcomplet() for u in modimpl.enseignants]
)
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=modimpl["moduleimpl_id"]
)
ue = modimpl["ue"]
if show_ues and (prev_ue_id != ue["ue_id"]):
prev_ue_id = ue["ue_id"]
titre = ue["titre"]
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
ue = modimpl.module.ue
if show_ues and (prev_ue_id != ue.id):
prev_ue_id = ue.id
titre = ue.titre
if use_ue_coefs:
titre += f""" <b>(coef. {ue["coefficient"] or 0.0})</b>"""
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
H.append(
f"""<tr class="formsemestre_status_ue"><td colspan="4">
<span class="status_ue_acro">{ue["acronyme"]}</span>
<span class="status_ue_acro">{ue.acronyme}</span>
<span class="status_ue_title">{titre}</span>
</td><td colspan="2">"""
)
expr = sco_compute_moy.get_ue_expression(
formsemestre.id, ue["ue_id"], html_quote=True
formsemestre.id, ue.id, html_quote=True
)
if expr:
H.append(
f""" <span class="formula" title="mode de calcul de la moyenne d'UE">{expr}</span>
<span class="warning">formule inutilisée en 9.2: <a href="{
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue["ue_id"] )
url_for("notes.delete_ue_expr", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ue_id=ue.id )
}
">supprimer</a></span>"""
)
H.append("</td></tr>")
if modimpl["ue"]["type"] != codes_cursus.UE_STANDARD:
if ue.type != codes_cursus.UE_STANDARD:
fontorange = " fontorange" # style css additionnel
else:
fontorange = ""
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl["moduleimpl_id"])
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
# if nt.parcours.APC_SAE:
# tbd style si module non conforme
if (
@ -1282,10 +1275,10 @@ def formsemestre_tableau_modules(
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
class="formsemestre_status_link">{mod.abbrev or mod.titre or ""}</a>
</td>
<td class="formsemestre_status_inscrits">{len(mod_inscrits)}</td>
<td class="formsemestre_status_inscrits">{mod_nb_inscrits}</td>
<td class="resp scotext">
<a class="discretelink" href="{moduleimpl_status_url}" title="{mod_ens}">{
sco_users.user_info(modimpl["responsable_id"])["prenomnom"]
sco_users.user_info(modimpl.responsable_id)["prenomnom"]
}</a>
</td>
<td>
@ -1339,10 +1332,7 @@ def formsemestre_tableau_modules(
)
elif mod.module_type == ModuleType.MALUS:
nb_malus_notes = sum(
[
e["etat"]["nb_notes"]
for e in nt.get_mod_evaluation_etat_list(modimpl["moduleimpl_id"])
]
e["etat"]["nb_notes"] for e in nt.get_mod_evaluation_etat_list(modimpl)
)
H.append(
f"""<td class="malus">

View File

@ -367,7 +367,7 @@ def gen_formsemestre_recapcomplet_xml(
doc = ElementTree.Element(
"recapsemestre", formsemestre_id=str(formsemestre_id), date=docdate
)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
doc.append(
ElementTree.Element(
"evals_info",
@ -408,7 +408,7 @@ def gen_formsemestre_recapcomplet_json(
docdate = ""
else:
docdate = datetime.datetime.now().isoformat()
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre)
js_data = {
"docdate": docdate,
"formsemestre_id": formsemestre_id,

View File

@ -1440,17 +1440,6 @@ EMO_PREV_ARROW = "&#10094;"
EMO_NEXT_ARROW = "&#10095;"
def sort_dates(L, reverse=False):
"""Return sorted list of dates, allowing None items (they are put at the beginning)"""
mindate = datetime.datetime(datetime.MINYEAR, 1, 1)
try:
return sorted(L, key=lambda x: x or mindate, reverse=reverse)
except:
# Helps debugging
log("sort_dates( %s )" % L)
raise
def heterogeneous_sorting_key(x):
"key to sort non homogeneous sequences"
return (float(x), "") if isinstance(x, (bool, float, int)) else (-1e34, str(x))

View File

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