Modification codage dates évaluations

This commit is contained in:
Emmanuel Viennet 2023-08-25 17:58:57 +02:00
parent e6a0d224d1
commit 3d513bb169
43 changed files with 844 additions and 635 deletions

View File

@ -297,7 +297,7 @@ class BulletinBUT:
)
if has_request_context()
else "na",
# deprecated
# 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

View File

@ -218,12 +218,8 @@ def bulletin_but_xml_compat(
jour=e.date_debut.isoformat()
if e.date_debut
else "",
heure_debut=e.date_debut.time().isoformat("minutes")
if e.date_debut
else "",
heure_fin=e.date_fin.time().isoformat("minutes")
if e.date_fin
else "",
heure_debut=e.heure_debut(),
heure_fin=e.heure_fin(),
)
x_mod.append(x_eval)
try:

View File

@ -250,7 +250,7 @@ class ModuleImplResults:
).reshape(-1, 1)
# was _list_notes_evals_titles
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list:
def get_evaluations_completes(self, moduleimpl: ModuleImpl) -> list[Evaluation]:
"Liste des évaluations complètes"
return [
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id]

View File

@ -80,8 +80,8 @@ class ResultatsSemestre(ResultatsCache):
self.moy_gen_rangs_by_group = None # virtual
self.modimpl_inscr_df: pd.DataFrame = None
"Inscriptions: row etudid, col modimlpl_id"
self.modimpls_results: ModuleImplResults = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }"
self.modimpls_results: dict[int, ModuleImplResults] = None
"Résultats de chaque modimpl (classique ou BUT)"
self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.modimpl_coefs_df: pd.DataFrame = None
@ -192,6 +192,17 @@ 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
# """
# --- JURY...
def get_formsemestre_validations(self) -> ValidationsSemestre:
"""Load validations if not already stored, set attribute and return value"""

View File

@ -16,7 +16,13 @@ from app import db, log
from app.comp import moy_sem
from app.comp.aux_stats import StatsMoyenne
from app.comp.res_common import ResultatsSemestre
from app.models import Identite, FormSemestre, ModuleImpl, ScolarAutorisationInscription
from app.models import (
Evaluation,
Identite,
FormSemestre,
ModuleImpl,
ScolarAutorisationInscription,
)
from app.scodoc.codes_cursus import UE_SPORT, DEF
from app.scodoc import sco_utils as scu
@ -389,7 +395,7 @@ class NotesTableCompat(ResultatsSemestre):
"ects_total": ects_total,
}
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
def get_modimpl_evaluations_completes(self, moduleimpl_id: int) -> list[Evaluation]:
"""Liste d'informations (compat NotesTable) sur évaluations completes
de ce module.
Évaluation "complete" ssi toutes notes saisies ou en attente.
@ -398,34 +404,24 @@ class NotesTableCompat(ResultatsSemestre):
modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results:
return [] # safeguard
evals_results = []
evaluations = []
for e in modimpl.evaluations:
if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict()
d["heure_debut"] = e.heure_debut # datetime.time
d["heure_fin"] = e.heure_fin
d["jour"] = e.jour # datetime
d["notes"] = {
etud.id: {
"etudid": etud.id,
"value": modimpl_results.evals_notes[e.id][etud.id],
}
for etud in self.etuds
}
d["etat"] = {
"evalattente": modimpl_results.evaluations_etat[e.id].nb_attente,
}
evals_results.append(d)
evaluations.append(e)
elif e.id not in modimpl_results.evaluations_completes_dict:
# ne devrait pas arriver ? XXX
log(
f"Warning: 220213 get_evals_in_mod {e.id} not in mod {moduleimpl_id} ?"
f"Warning: 220213 get_modimpl_evaluations_completes {e.id} not in mod {moduleimpl_id} ?"
)
return evals_results
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):
"""[ {...evaluation et son etat...} ]"""
# TODO: à moderniser
from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"):

View File

@ -18,8 +18,8 @@ from app.models.ues import UniteEns
from app.scodoc import sco_cache
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.sco_xml import quote_xml_attr
MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
NOON = datetime.time(12, 00)
@ -46,6 +46,7 @@ class Evaluation(db.Model):
visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true"
)
"visible sur les bulletins version intermédiaire"
publish_incomplete = db.Column(
db.Boolean, nullable=False, default=False, server_default="false"
)
@ -67,9 +68,8 @@ class Evaluation(db.Model):
def create(
cls,
moduleimpl: ModuleImpl = None,
jour=None,
heure_debut=None,
heure_fin=None,
date_debut: datetime.datetime = None,
date_fin: datetime.datetime = None,
description=None,
note_max=None,
coefficient=None,
@ -124,7 +124,7 @@ class Evaluation(db.Model):
next_eval = None
t = date_debut
for e in evaluations:
if e.date_debut > t:
if e.date_debut and e.date_debut > t:
next_eval = e
break
if next_eval:
@ -140,26 +140,26 @@ class Evaluation(db.Model):
def to_dict(self) -> dict:
"Représentation dict (riche, compat ScoDoc 7)"
e = dict(self.__dict__)
e.pop("_sa_instance_state", None)
e_dict = dict(self.__dict__)
e_dict.pop("_sa_instance_state", None)
# ScoDoc7 output_formators
e["evaluation_id"] = self.id
e["date_debut"] = e.date_debut.isoformat() if e.date_debut else None
e["date_fin"] = e.date_debut.isoformat() if e.date_fin else None
e["numero"] = ndb.int_null_is_zero(e["numero"])
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
e_dict["evaluation_id"] = self.id
e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None
e_dict["date_fin"] = self.date_debut.isoformat() if self.date_fin else None
e_dict["numero"] = self.numero or 0
e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
# Deprecated
e["jour"] = e.date_debut.strftime("%d/%m/%Y") if e.date_debut else ""
e_dict["jour"] = self.date_debut.strftime("%d/%m/%Y") if self.date_debut else ""
return evaluation_enrich_dict(e)
return evaluation_enrich_dict(self, e_dict)
def to_dict_api(self) -> dict:
"Représentation dict pour API JSON"
return {
"coefficient": self.coefficient,
"date_debut": self.date_debut.isoformat(),
"date_fin": self.date_fin.isoformat(),
"date_debut": self.date_debut.isoformat() if self.date_debut else "",
"date_fin": self.date_fin.isoformat() if self.date_fin else "",
"description": self.description,
"evaluation_type": self.evaluation_type,
"id": self.id,
@ -168,14 +168,36 @@ class Evaluation(db.Model):
"numero": self.numero,
"poids": self.get_ue_poids_dict(),
"publish_incomplete": self.publish_incomplete,
"visi_bulletin": self.visibulletin,
"visibulletin": self.visibulletin,
# Deprecated (supprimer avant #sco9.7)
"date": self.date_debut.date().isoformat() if self.date_debut else "",
"heure_debut": self.date_debut.time().isoformat()
if self.date_debut
else "",
"heure_fin": self.date_fin.time().isoformat() if self.date_fin else "",
}
def to_dict_bul(self) -> dict:
"dict pour les bulletins json"
# c'est la version API avec quelques champs legacy en plus
e_dict = self.to_dict_api()
# Pour les bulletins (json ou xml), quote toujours la description
e_dict["description"] = quote_xml_attr(self.description or "")
# deprecated fields:
e_dict["evaluation_id"] = self.id
e_dict["jour"] = e_dict["date_debut"] # chaine iso
e_dict["heure_debut"] = (
self.date_debut.time().isoformat() if self.date_debut else ""
)
e_dict["heure_fin"] = self.date_fin.time().isoformat() if self.date_fin else ""
return e_dict
def from_dict(self, data):
"""Set evaluation attributes from given dict values."""
check_convert_evaluation_args(self.moduleimpl, data)
if data.get("numero") is None:
data["numero"] = Evaluation.get_max_numero() + 1
data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1
for k in self.__dict__.keys():
if k != "_sa_instance_state" and k != "id" and k in data:
setattr(self, k, data[k])
@ -217,28 +239,64 @@ class Evaluation(db.Model):
db.session.commit()
def descr_heure(self) -> str:
"Description de la plage horaire pour affichages"
if self.heure_debut and (
not self.heure_fin or self.heure_fin == self.heure_debut
):
return f"""à {self.heure_debut.strftime("%Hh%M")}"""
elif self.heure_debut and self.heure_fin:
return f"""de {self.heure_debut.strftime("%Hh%M")} à {self.heure_fin.strftime("%Hh%M")}"""
"Description de la plage horaire pour affichages ('de 13h00 à 14h00')"
if self.date_debut and (not self.date_fin or self.date_fin == self.date_debut):
return f"""à {self.date_debut.strftime("%Hh%M")}"""
elif self.date_debut and self.date_fin:
return f"""de {self.date_debut.strftime("%Hh%M")
} à {self.date_fin.strftime("%Hh%M")}"""
else:
return ""
def descr_duree(self) -> str:
"Description de la durée pour affichages"
if self.heure_debut is None and self.heure_fin is None:
"Description de la durée pour affichages ('3h' ou '2h30')"
if self.date_debut is None or self.date_fin is None:
return ""
debut = self.heure_debut or DEFAULT_EVALUATION_TIME
fin = self.heure_fin or DEFAULT_EVALUATION_TIME
d = (fin.hour * 60 + fin.minute) - (debut.hour * 60 + debut.minute)
duree = f"{d//60}h"
if d % 60:
duree += f"{d%60:02d}"
minutes = (self.date_fin - self.date_debut).seconds // 60
duree = f"{minutes // 60}h"
minutes = minutes % 60
if minutes != 0:
duree += f"{minutes:02d}"
return duree
def descr_date(self) -> str:
"""Description de la date pour affichages
'sans date'
'le 21/9/2021 à 13h'
'le 21/9/2021 de 13h à 14h30'
'du 21/9/2021 à 13h30 au 23/9/2021 à 15h'
"""
if self.date_debut is None:
return "sans date"
def _h(dt: datetime.datetime) -> str:
if dt.minute:
return dt.strftime("%Hh%M")
return f"{dt.hour}h"
if self.date_fin is None:
return (
f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}"
)
if self.date_debut.date() == self.date_fin.date(): # même jour
if self.date_debut.time() == self.date_fin.time():
return f"le {self.date_debut.strftime('%d/%m/%Y')} à {_h(self.date_debut())}"
return f"""le {self.date_debut.strftime('%d/%m/%Y')} de {
_h(self.date_debut())} à {_h(self.date_fin())}"""
# évaluation sur plus d'une journée
return f"""du {self.date_debut.strftime('%d/%m/%Y')} à {
_h(self.date_debut())} au {self.date_fin.strftime('%d/%m/%Y')} à {_h(self.date_fin())}"""
def heure_debut(self) -> str:
"""L'heure de début (sans la date), en ISO.
Chaine vide si non renseignée."""
return self.date_debut.time().isoformat("minutes") if self.date_debut else ""
def heure_fin(self) -> str:
"""L'heure de fin (sans la date), en ISO.
Chaine vide si non renseignée."""
return self.date_fin.time().isoformat("minutes") if self.date_fin else ""
def clone(self, not_copying=()):
"""Clone, not copying the given attrs
Attention: la copie n'a pas d'id avant le prochain commit
@ -381,41 +439,28 @@ class EvaluationUEPoids(db.Model):
return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
# Fonction héritée de ScoDoc7 à refactorer
def evaluation_enrich_dict(e: dict):
# Fonction héritée de ScoDoc7
def evaluation_enrich_dict(e: Evaluation, e_dict: dict):
"""add or convert some fields in an evaluation dict"""
# For ScoDoc7 compat
heure_debut_dt = e["date_debut"].time()
heure_fin_dt = e["date_fin"].time()
e["heure_debut"] = heure_debut_dt.strftime("%Hh%M")
e["heure_fin"] = heure_fin_dt.strftime("%Hh%M")
e["jour_iso"] = e["date_debut"].isoformat() # XXX
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"]
d = _time_duration_HhM(heure_debut, heure_fin)
if d is not None:
m = d % 60
e["duree"] = "%dh" % (d / 60)
if m != 0:
e["duree"] += "%02d" % m
else:
e["duree"] = ""
if heure_debut and (not heure_fin or heure_fin == heure_debut):
e["descrheure"] = " à " + heure_debut
elif heure_debut and heure_fin:
e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin)
else:
e["descrheure"] = ""
e_dict["heure_debut"] = e.date_debut.strftime("%Hh%M") if e.date_debut else ""
e_dict["heure_fin"] = e.date_fin.strftime("%Hh%M") if e.date_fin else ""
e_dict["jour_iso"] = e.date_debut.isoformat() if e.date_debut else ""
# Calcule durée en minutes
e_dict["descrheure"] = e.descr_heure()
e_dict["descrduree"] = e.descr_duree()
# matin, apresmidi: utile pour se referer aux absences:
if e["jour"] and heure_debut_dt < datetime.time(12, 00):
e["matin"] = 1
# note août 2023: si l'évaluation s'étend sur plusieurs jours,
# cet indicateur n'a pas grand sens
if e.date_debut and e.date_debut.time() < datetime.time(12, 00):
e_dict["matin"] = 1
else:
e["matin"] = 0
if e["jour"] and heure_fin_dt > datetime.time(12, 00):
e["apresmidi"] = 1
e_dict["matin"] = 0
if e.date_fin and e.date_fin.time() > datetime.time(12, 00):
e_dict["apresmidi"] = 1
else:
e["apresmidi"] = 0
return e
e_dict["apresmidi"] = 0
return e_dict
def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict):
@ -426,71 +471,87 @@ def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: dict):
May raise ScoValueError.
"""
# --- description
description = data.get("description", "")
if len(description) > scu.MAX_TEXT_LEN:
data["description"] = data.get("description", "") or ""
if len(data["description"]) > scu.MAX_TEXT_LEN:
raise ScoValueError("description too large")
# --- evaluation_type
try:
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
raise ScoValueError("Invalid evaluation_type value")
except ValueError:
raise ScoValueError("Invalid evaluation_type value")
raise ScoValueError("invalid evaluation_type value")
except ValueError as exc:
raise ScoValueError("invalid evaluation_type value") from exc
# --- note_max (bareme)
note_max = data.get("note_max", 20.0) or 20.0
try:
note_max = float(note_max)
except ValueError:
raise ScoValueError("Invalid note_max value")
except ValueError as exc:
raise ScoValueError("invalid note_max value") from exc
if note_max < 0:
raise ScoValueError("Invalid note_max value (must be positive or null)")
raise ScoValueError("invalid note_max value (must be positive or null)")
data["note_max"] = note_max
# --- coefficient
coef = data.get("coefficient", 1.0) or 1.0
try:
coef = float(coef)
except ValueError:
raise ScoValueError("Invalid coefficient value")
except ValueError as exc:
raise ScoValueError("invalid coefficient value") from exc
if coef < 0:
raise ScoValueError("Invalid coefficient value (must be positive or null)")
raise ScoValueError("invalid coefficient value (must be positive or null)")
data["coefficient"] = coef
# --- jour (date de l'évaluation)
jour = data.get("jour", None)
if jour and not isinstance(jour, datetime.date):
if date_format == "dmy":
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")]
jour = datetime.date(y, m, d)
else: # ISO
jour = datetime.date.fromisoformat(jour)
formsemestre = moduleimpl.formsemestre
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut):
# --- date de l'évaluation
formsemestre = moduleimpl.formsemestre
date_debut = data.get("date_debut", None)
if date_debut:
if isinstance(date_debut, str):
data["date_debut"] = datetime.datetime.fromisoformat(date_debut)
if data["date_debut"].tzinfo is None:
data["date_debut"] = scu.TIME_ZONE.localize(data["date_debut"])
if not (
formsemestre.date_debut
<= data["date_debut"].date()
<= formsemestre.date_fin
):
raise ScoValueError(
f"""La date de l'évaluation ({jour.strftime("%d/%m/%Y")}) n'est pas dans le semestre !""",
f"""La date de début de l'évaluation ({
data["date_debut"].strftime("%d/%m/%Y")
}) n'est pas dans le semestre !""",
dest_url="javascript:history.back();",
)
data["jour"] = jour
# --- heures
heure_debut = data.get("heure_debut", None)
if heure_debut and not isinstance(heure_debut, datetime.time):
if date_format == "dmy":
data["heure_debut"] = heure_to_time(heure_debut)
else: # ISO
data["heure_debut"] = datetime.time.fromisoformat(heure_debut)
heure_fin = data.get("heure_fin", None)
if heure_fin and not isinstance(heure_fin, datetime.time):
if date_format == "dmy":
data["heure_fin"] = heure_to_time(heure_fin)
else: # ISO
data["heure_fin"] = datetime.time.fromisoformat(heure_fin)
if jour and ((not heure_debut) or (not heure_fin)):
raise ScoValueError("Les heures doivent être précisées")
if heure_debut and heure_fin:
duration = ((data["heure_fin"].hour * 60) + data["heure_fin"].minute) - (
(data["heure_debut"].hour * 60) + data["heure_debut"].minute
)
if duration < 0 or duration > 60 * 12:
date_fin = data.get("date_fin", None)
if date_fin:
if isinstance(date_fin, str):
data["date_fin"] = datetime.datetime.fromisoformat(date_fin)
if data["date_fin"].tzinfo is None:
data["date_fin"] = scu.TIME_ZONE.localize(data["date_fin"])
if not (
formsemestre.date_debut <= data["date_fin"].date() <= formsemestre.date_fin
):
raise ScoValueError(
f"""La date de fin de l'évaluation ({
data["date_fin"].strftime("%d/%m/%Y")
}) n'est pas dans le semestre !""",
dest_url="javascript:history.back();",
)
if date_debut and date_fin:
duration = data["date_fin"] - data["date_debut"]
if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION:
raise ScoValueError("Heures de l'évaluation incohérentes !")
# # --- heures
# heure_debut = data.get("heure_debut", None)
# if heure_debut and not isinstance(heure_debut, datetime.time):
# if date_format == "dmy":
# data["heure_debut"] = heure_to_time(heure_debut)
# else: # ISO
# data["heure_debut"] = datetime.time.fromisoformat(heure_debut)
# heure_fin = data.get("heure_fin", None)
# if heure_fin and not isinstance(heure_fin, datetime.time):
# if date_format == "dmy":
# data["heure_fin"] = heure_to_time(heure_fin)
# else: # ISO
# data["heure_fin"] = datetime.time.fromisoformat(heure_fin)
def heure_to_time(heure: str) -> datetime.time:

View File

@ -30,6 +30,7 @@ from app.models.but_refcomp import (
)
from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
from app.models.formations import Formation
from app.models.groups import GroupDescr, Partition
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
@ -350,6 +351,21 @@ class FormSemestre(db.Model):
_cache[key] = ues
return ues
def get_evaluations(self) -> list[Evaluation]:
"Liste de toutes les évaluations du semestre, triées par module/numero"
return (
Evaluation.query.join(ModuleImpl)
.filter_by(formsemestre_id=self.id)
.join(Module)
.order_by(
Module.numero,
Module.code,
Evaluation.numero,
Evaluation.date_debut.desc(),
)
.all()
)
@cached_property
def modimpls_sorted(self) -> list[ModuleImpl]:
"""Liste des modimpls du semestre (y compris bonus)

View File

@ -509,7 +509,7 @@ def _get_abs_description(a, cursor=None):
return ""
def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None):
def list_abs_jour(date, am=True, pm=True, is_abs=True, is_just=None) -> list[dict]:
"""Liste des absences et/ou justificatifs ce jour.
is_abs: None (peu importe), True, False
is_just: idem
@ -535,7 +535,7 @@ WHERE A.jour = %(date)s
return A
def list_abs_non_just_jour(date, am=True, pm=True):
def list_abs_non_just_jour(date, am=True, pm=True) -> list[dict]:
"Liste des absences non justifiees ce jour"
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)

View File

@ -972,12 +972,12 @@ def _tables_abs_etud(
FROM notes_evaluation eval,
notes_moduleimpl_inscription mi,
notes_moduleimpl m
WHERE eval.jour = %(jour)s
WHERE DATE(eval.date_debut) = %(date_debut)s
and eval.moduleimpl_id = m.id
and mi.moduleimpl_id = m.id
and mi.etudid = %(etudid)s
""",
{"jour": a["jour"].strftime("%Y-%m-%d"), "etudid": etudid},
{"date_debut": a["jour"], "etudid": etudid},
)
a["evals"] = cursor.dictfetchall()
cursor.execute(

View File

@ -47,6 +47,7 @@ from app.comp.res_but import ResultatsSemestreBUT
from app.comp.res_compat import NotesTableCompat
from app.models import (
ApcParcours,
Evaluation,
Formation,
FormSemestre,
Identite,
@ -482,6 +483,7 @@ def _ue_mod_bulletin(
mods = [] # result
ue_attente = False # true si une eval en attente dans cette UE
for modimpl in ue_modimpls:
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
mod_attente = False
mod = modimpl.copy()
mod_moy = nt.get_etud_mod_moy(
@ -531,10 +533,13 @@ def _ue_mod_bulletin(
scu.fmt_coef(modimpl["module"]["coefficient"]),
sco_users.user_info(modimpl["responsable_id"])["nomcomplet"],
)
link_mod = (
'<a class="bull_link" href="moduleimpl_status?moduleimpl_id=%s" title="%s">'
% (modimpl["moduleimpl_id"], mod["mod_descr_txt"])
)
link_mod = f"""<a class="bull_link" href="{
url_for("notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=modimpl["moduleimpl_id"]
)
}" title="{mod["mod_descr_txt"]}">"""
if sco_preferences.get_preference("bul_show_codemodules", formsemestre_id):
mod["code"] = modimpl["module"]["code"]
mod["code_html"] = link_mod + (mod["code"] or "") + "</a>"
@ -561,91 +566,88 @@ def _ue_mod_bulletin(
mod["code_txt"] = ""
mod["code_html"] = ""
# Evaluations: notes de chaque eval
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
evaluations_completes = nt.get_modimpl_evaluations_completes(
modimpl["moduleimpl_id"]
)
# On liste séparément les éval. complètes ou non
mod["evaluations"] = []
for e in evals:
e = e.copy()
if e["visibulletin"] or version == "long":
# affiche "bonus" quand les points de malus sont négatifs
if is_malus:
val = e["notes"].get(etudid, {"value": "NP"})[
"value"
] # NA si etud demissionnaire
if val == "NP" or val > 0:
e["name"] = "Points de malus sur cette UE"
else:
e["name"] = "Points de bonus sur cette UE"
mod["evaluations_incompletes"] = []
complete_eval_ids = {e.id for e in evaluations_completes}
all_evals: list[Evaluation] = Evaluation.query.filter_by(
moduleimpl_id=modimpl["moduleimpl_id"]
).order_by(Evaluation.numero, Evaluation.date_debut)
# (plus ancienne d'abord)
for e in all_evals:
if not e.visibulletin and version != "long":
continue
is_complete = e.id in complete_eval_ids
e_dict = e.to_dict_bul()
# Note à l'évaluation:
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
# Affiche "bonus" quand les points de malus sont négatifs
if is_malus:
if val == "NP":
e_dict["name"] = "Points de bonus/malus sur cette UE"
elif val > 0:
e_dict["name"] = "Points de malus sur cette UE"
else:
e["name"] = e["description"] or f"le {e['jour']}"
e["target_html"] = url_for(
e_dict["name"] = "Points de bonus sur cette UE"
else:
e_dict[
"name"
] = f"""{e.description or ""} {
e.descr_date()
if e.date_debut and not is_complete
else ""}"""
e_dict["target_html"] = url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e["evaluation_id"],
evaluation_id=e.id,
format="html",
tf_submitted=1,
)
e[
e_dict[
"name_html"
] = f"""<a class="bull_link" href="{
e['target_html']}">{e['name']}</a>"""
val = e["notes"].get(etudid, {"value": "NP"})["value"]
# val est NP si etud demissionnaire
if val == "NP":
e["note_txt"] = "nd"
e["note_html"] = '<span class="note_nd">nd</span>'
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
else:
# (-0.15) s'affiche "bonus de 0.15"
if is_malus:
val = abs(val)
e["note_txt"] = scu.fmt_note(val, note_max=e["note_max"])
e["note_html"] = e["note_txt"]
if is_malus:
e["coef_txt"] = ""
e_dict['target_html']}">{e_dict['name']}</a>"""
if is_complete: # évaluation complète
# val est NP si etud demissionnaire
if val == "NP":
e_dict["note_txt"] = "nd"
e_dict["note_html"] = '<span class="note_nd">nd</span>'
e_dict["coef_txt"] = scu.fmt_coef(e["coefficient"])
else:
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE:
e["coef_txt"] = "rat."
elif e["evaluation_type"] == scu.EVALUATION_SESSION2:
e["coef_txt"] = "Ses. 2"
if e["etat"]["evalattente"]:
# (-0.15) s'affiche "bonus de 0.15"
if is_malus:
val = abs(val)
e_dict["note_txt"] = e_dict["note_html"] = scu.fmt_note(
val, note_max=e.note_max
)
else: # évaluation incomplète: pas de note
e_dict["note_txt"] = e_dict["note_html"] = ""
if is_malus:
e_dict["coef_txt"] = ""
else:
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE:
e_dict["coef_txt"] = "rat."
elif e.evaluation_type == scu.EVALUATION_SESSION2:
e_dict["coef_txt"] = "Ses. 2"
if modimpl_results.evaluations_etat[e.id].nb_attente:
mod_attente = True # une eval en attente dans ce module
if ((not is_malus) or (val != "NP")) and (
(
e["evaluation_type"] == scu.EVALUATION_NORMALE
or not np.isnan(val)
)
(e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val))
):
# ne liste pas les eval malus sans notes
# ni les rattrapages et sessions 2 si pas de note
mod["evaluations"].append(e)
if e.id in complete_eval_ids:
mod["evaluations"].append(e_dict)
else:
mod["evaluations_incompletes"].append(e_dict)
# Evaluations incomplètes ou futures:
mod["evaluations_incompletes"] = []
if sco_preferences.get_preference("bul_show_all_evals", formsemestre_id):
complete_eval_ids = set([e["evaluation_id"] for e in evals])
all_evals = sco_evaluation_db.do_evaluation_list(
args={"moduleimpl_id": modimpl["moduleimpl_id"]}
)
all_evals.reverse() # plus ancienne d'abord
for e in all_evals:
if e["evaluation_id"] not in complete_eval_ids:
e = e.copy()
mod["evaluations_incompletes"].append(e)
e["name"] = (e["description"] or "") + " (%s)" % e["jour"]
e["target_html"] = url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e["evaluation_id"],
tf_submitted=1,
format="html",
)
e["name_html"] = '<a class="bull_link" href="%s">%s</a>' % (
e["target_html"],
e["name"],
)
e["note_txt"] = e["note_html"] = ""
e["coef_txt"] = scu.fmt_coef(e["coefficient"])
# Classement
if (
bul_show_mod_rangs

View File

@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import but_validations
from app.models import Evaluation, Matiere, ModuleImpl, UniteEns
from app.models import Evaluation, Matiere, UniteEns
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
@ -46,7 +46,6 @@ import app.scodoc.notesdb as ndb
from app.scodoc import sco_assiduites
from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_photos
@ -333,6 +332,7 @@ def _list_modimpls(
mod_moy = scu.fmt_note(nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid))
if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
continue
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
mod = modimpl["module"]
# if mod['ects'] is None:
# ects = ''
@ -363,66 +363,42 @@ def _list_modimpls(
mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1])
# --- notes de chaque eval:
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
evaluations_completes = nt.get_modimpl_evaluations_completes(
modimpl["moduleimpl_id"]
)
mod_dict["evaluation"] = []
if version != "short":
for e in evals:
if e["visibulletin"] or version == "long":
val = e["notes"].get(etudid, {"value": "NP"})["value"]
for e in evaluations_completes:
if e.visibulletin or version == "long":
# Note à l'évaluation:
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
# nb: val est NA si etud démissionnaire
val = scu.fmt_note(val, note_max=e["note_max"])
eval_dict = dict(
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(e["heure_fin"], null_is_empty=True),
coefficient=e["coefficient"],
evaluation_type=e["evaluation_type"],
# CM : ajout pour permettre de faire le lien sur
# les bulletins en ligne avec l'évaluation:
evaluation_id=e["evaluation_id"],
description=quote_xml_attr(e["description"]),
note=val,
)
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
if prefs["bul_show_minmax_eval"]:
eval_dict["min"] = etat["mini"] # chaine, sur 20
eval_dict["max"] = etat["maxi"]
if prefs["bul_show_moypromo"]:
eval_dict["moy"] = etat["moy"]
e_dict = e.to_dict_bul()
e_dict["note"] = scu.fmt_note(val, note_max=e.note_max)
mod_dict["evaluation"].append(eval_dict)
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]:
# XXX à revoir pour utiliser modimplresult
etat = sco_evaluations.do_evaluation_etat(e.id)
if prefs["bul_show_minmax_eval"]:
e_dict["min"] = etat["mini"] # chaine, sur 20
e_dict["max"] = etat["maxi"]
if prefs["bul_show_moypromo"]:
e_dict["moy"] = etat["moy"]
mod_dict["evaluation"].append(e_dict)
# Evaluations incomplètes ou futures:
complete_eval_ids = set([e["evaluation_id"] for e in evals])
complete_eval_ids = {e.id for e in evaluations_completes}
if prefs["bul_show_all_evals"]:
evaluations = Evaluation.query.filter_by(
evaluations: list[Evaluation] = Evaluation.query.filter_by(
moduleimpl_id=modimpl["moduleimpl_id"]
).order_by(Evaluation.date_debut)
# plus ancienne d'abord
for e in evaluations:
if e.id not in complete_eval_ids:
mod_dict["evaluation"].append(
dict(
date_debut=e.date_debut.isoformat()
if e.date_debut
else None,
date_fin=e.date_fin.isoformat() if e.date_fin else None,
coefficient=e.coefficient,
description=quote_xml_attr(e.description or ""),
incomplete="1",
# Deprecated:
jour=e.date_debut.isoformat() if e.date_debut else "",
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
)
)
e_dict = e.to_dict_bul()
e_dict["incomplete"] = 1
mod_dict["evaluation"].append(e_dict)
modules_dict.append(mod_dict)
return modules_dict

View File

@ -242,6 +242,7 @@ def make_xml_formsemestre_bulletinetud(
# Liste les modules de l'UE
ue_modimpls = [mod for mod in modimpls if mod["module"]["ue_id"] == ue["ue_id"]]
for modimpl in ue_modimpls:
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
mod_moy = scu.fmt_note(
nt.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
)
@ -290,33 +291,24 @@ def make_xml_formsemestre_bulletinetud(
)
)
# --- notes de chaque eval:
evals = nt.get_evals_in_mod(modimpl["moduleimpl_id"])
evaluations_completes = nt.get_modimpl_evaluations_completes(
modimpl["moduleimpl_id"]
)
if version != "short":
for e in evals:
if e["visibulletin"] or version == "long":
x_eval = Element(
"evaluation",
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=str(e["coefficient"]),
evaluation_type=str(e["evaluation_type"]),
description=quote_xml_attr(e["description"]),
# notes envoyées sur 20, ceci juste pour garder trace:
note_max_origin=str(e["note_max"]),
)
for e in evaluations_completes:
if e.visibulletin or version == "long":
# pour xml, tout convertir en chaines
e_dict = {k: str(v) for k, v in e.to_dict_bul().items()}
# notes envoyées sur 20, ceci juste pour garder trace:
e_dict["note_max_origin"] = str(e.note_max)
x_eval = Element("evaluation", **e_dict)
x_mod.append(x_eval)
val = e["notes"].get(etudid, {"value": "NP"})[
"value"
] # NA si etud demissionnaire
val = scu.fmt_note(val, note_max=e["note_max"])
# Note à l'évaluation:
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
val = scu.fmt_note(val, note_max=e.note_max)
x_eval.append(Element("note", value=val))
# Evaluations incomplètes ou futures:
complete_eval_ids = set([e["evaluation_id"] for e in evals])
complete_eval_ids = {e.id for e in evaluations_completes}
if sco_preferences.get_preference(
"bul_show_all_evals", formsemestre_id
):
@ -325,21 +317,8 @@ def make_xml_formsemestre_bulletinetud(
).order_by(Evaluation.date_debut)
for e in evaluations:
if e.id not in complete_eval_ids:
x_eval = Element(
"evaluation",
jour=ndb.DateDMYtoISO(e["jour"], null_is_empty=True),
heure_debut=ndb.TimetoISO8601(
e["heure_debut"], null_is_empty=True
),
heure_fin=ndb.TimetoISO8601(
e["heure_fin"], null_is_empty=True
),
coefficient=str(e["coefficient"]),
description=quote_xml_attr(e["description"]),
incomplete="1",
# notes envoyées sur 20, ceci juste pour garder trace:
note_max_origin=str(e["note_max"] or ""),
)
e_dict = e.to_dict_bul()
x_eval = Element("evaluation", **e_dict)
x_mod.append(x_eval)
# UE capitalisee (listee seulement si meilleure que l'UE courante)
if ue_status["is_capitalized"]:

View File

@ -29,8 +29,9 @@
"""
from flask import url_for, g
from app import db
from app.models import Evaluation, Identite
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc import html_sco_header
from app.scodoc import sco_abs
from app.scodoc import sco_etud
@ -40,25 +41,8 @@ from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
# matin et/ou après-midi ?
def _eval_demijournee(E):
"1 si matin, 0 si apres midi, 2 si toute la journee"
am, pm = False, False
if E["heure_debut"] < "13:00":
am = True
if E["heure_fin"] > "13:00":
pm = True
if am and pm:
demijournee = 2
elif am:
demijournee = 1
else:
demijournee = 0
pm = True
return am, pm, demijournee
def evaluation_check_absences(evaluation_id):
def evaluation_check_absences(evaluation: Evaluation):
"""Vérifie les absences au moment de cette évaluation.
Cas incohérents que l'on peut rencontrer pour chaque étudiant:
note et absent
@ -66,51 +50,58 @@ def evaluation_check_absences(evaluation_id):
ABS et absent justifié
EXC et pas noté absent
EXC et pas justifie
Ramene 3 listes d'etudid
Ramene 5 listes d'etudid
"""
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
if not E["jour"]:
if not evaluation.date_debut:
return [], [], [], [], [] # evaluation sans date
am, pm, demijournee = _eval_demijournee(E)
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
# Liste les absences à ce moment:
A = sco_abs.list_abs_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
As = set([x["etudid"] for x in A]) # ensemble des etudiants absents
NJ = sco_abs.list_abs_non_just_jour(ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm)
NJs = set([x["etudid"] for x in NJ]) # ensemble des etudiants absents non justifies
Just = sco_abs.list_abs_jour(
ndb.DateDMYtoISO(E["jour"]), am=am, pm=pm, is_abs=None, is_just=True
absences = sco_abs.list_abs_jour(evaluation.date_debut, am=am, pm=pm)
abs_etudids = set([x["etudid"] for x in absences]) # ensemble des etudiants absents
abs_non_just = sco_abs.list_abs_non_just_jour(
evaluation.date_debut.date(), am=am, pm=pm
)
Justs = set([x["etudid"] for x in Just]) # ensemble des etudiants avec justif
abs_nj_etudids = set(
[x["etudid"] for x in abs_non_just]
) # ensemble des etudiants absents non justifies
justifs = sco_abs.list_abs_jour(
evaluation.date_debut.date(), am=am, pm=pm, is_abs=None, is_just=True
)
just_etudids = set(
[x["etudid"] for x in justifs]
) # ensemble des etudiants avec justif
# Les notes:
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
ValButAbs = [] # une note mais noté absent
AbsNonSignalee = [] # note ABS mais pas noté absent
ExcNonSignalee = [] # note EXC mais pas noté absent
ExcNonJust = [] # note EXC mais absent non justifie
AbsButExc = [] # note ABS mais justifié
for etudid, _ in sco_groups.do_evaluation_listeetuds_groups(
evaluation_id, getallstudents=True
evaluation.id, getallstudents=True
):
if etudid in notes_db:
val = notes_db[etudid]["value"]
if (
val != None and val != scu.NOTES_NEUTRALISE and val != scu.NOTES_ATTENTE
) and etudid in As:
val is not None
and val != scu.NOTES_NEUTRALISE
and val != scu.NOTES_ATTENTE
) and etudid in abs_etudids:
# note valide et absent
ValButAbs.append(etudid)
if val is None and not etudid in As:
if val is None and not etudid in abs_etudids:
# absent mais pas signale comme tel
AbsNonSignalee.append(etudid)
if val == scu.NOTES_NEUTRALISE and not etudid in As:
if val == scu.NOTES_NEUTRALISE and not etudid in abs_etudids:
# Neutralisé mais pas signale absent
ExcNonSignalee.append(etudid)
if val == scu.NOTES_NEUTRALISE and etudid in NJs:
if val == scu.NOTES_NEUTRALISE and etudid in abs_nj_etudids:
# EXC mais pas justifié
ExcNonJust.append(etudid)
if val is None and etudid in Justs:
if val is None and etudid in just_etudids:
# ABS mais justificatif
AbsButExc.append(etudid)
@ -119,9 +110,16 @@ def evaluation_check_absences(evaluation_id):
def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True):
"""Affiche état vérification absences d'une évaluation"""
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
am, pm, demijournee = _eval_demijournee(E)
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
am, pm = evaluation.is_matin(), evaluation.is_apresmidi()
# 1 si matin, 0 si apres midi, 2 si toute la journee:
match am, pm:
case False, True:
demijournee = 0
case True, False:
demijournee = 1
case _:
demijournee = 2
(
ValButAbs,
@ -129,19 +127,23 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
ExcNonSignalee,
ExcNonJust,
AbsButExc,
) = evaluation_check_absences(evaluation_id)
) = evaluation_check_absences(evaluation)
if with_header:
H = [
html_sco_header.html_sem_header("Vérification absences à l'évaluation"),
sco_evaluations.evaluation_describe(evaluation_id=evaluation_id),
"""<p class="help">Vérification de la cohérence entre les notes saisies et les absences signalées.</p>""",
sco_evaluations.evaluation_describe(evaluation_id=evaluation.id),
"""<p class="help">Vérification de la cohérence entre les notes saisies
et les absences signalées.</p>""",
]
else:
# pas de header, mais un titre
H = [
"""<h2 class="eval_check_absences">%s du %s """
% (E["description"], E["jour"])
f"""<h2 class="eval_check_absences">{
evaluation.description or "évaluation"
} du {
evaluation.date_debut.strftime("%d/%m/%Y") if evaluation.date_debut else ""
} """
]
if (
not ValButAbs
@ -157,26 +159,27 @@ def evaluation_check_absences_html(evaluation_id, with_header=True, show_ok=True
if not etudids and show_ok:
H.append("<li>aucun</li>")
for etudid in etudids:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
etud: Identite = db.session.get(Identite, etudid)
H.append(
'<li><a class="discretelink" href="%s">'
% url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
)
+ "%(nomprenom)s</a>" % etud
f"""<li><a class="discretelink" href="{
url_for(
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
)
}">{etud.nomprenom}</a>"""
)
if linkabs:
H.append(
f"""<a class="stdlink" href="{url_for(
'absences.doSignaleAbsence',
url = url_for(
"absences.doSignaleAbsence",
scodoc_dept=g.scodoc_dept,
etudid=etud["etudid"],
datedebut=E["jour"],
datefin=E["jour"],
etudid=etudid,
# par defaut signale le jour du début de l'éval
datedebut=evaluation.date_debut.strftime("%d/%m/%Y"),
datefin=evaluation.date_debut.strftime("%d/%m/%Y"),
demijournee=demijournee,
moduleimpl_id=E["moduleimpl_id"],
)
}">signaler cette absence</a>"""
moduleimpl_id=evaluation.moduleimpl_id,
)
H.append(
f"""<a class="stdlink" href="{url}">signaler cette absence</a>"""
)
H.append("</li>")
H.append("</ul>")
@ -231,7 +234,7 @@ def formsemestre_check_absences_html(formsemestre_id):
# Modules, dans l'ordre
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(formsemestre_id=formsemestre_id)
for M in Mlist:
evals = sco_evaluation_db.do_evaluation_list(
evals = sco_evaluation_db.get_evaluation_dict(
{"moduleimpl_id": M["moduleimpl_id"]}
)
if evals:

View File

@ -37,14 +37,13 @@ from flask_login import current_user
from app import db, log
from app.models import Evaluation, ModuleImpl, ScolarNews
from app.models.evaluations import evaluation_enrich_dict, check_convert_evaluation_args
from app.models.evaluations import check_convert_evaluation_args
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc import sco_cache
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
_evaluationEditor = ndb.EditableTable(
@ -53,9 +52,8 @@ _evaluationEditor = ndb.EditableTable(
(
"evaluation_id",
"moduleimpl_id",
"jour",
"heure_debut",
"heure_fin",
"date_debut",
"date_fin",
"description",
"note_max",
"coefficient",
@ -64,15 +62,11 @@ _evaluationEditor = ndb.EditableTable(
"evaluation_type",
"numero",
),
sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord
sortkey="numero, date_debut desc", # plus recente d'abord
output_formators={
"jour": ndb.DateISOtoDMY,
"numero": ndb.int_null_is_zero,
},
input_formators={
"jour": ndb.DateDMYtoISO,
"heure_debut": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
"heure_fin": ndb.TimetoISO8601, # converti par evaluation_enrich_dict
"visibulletin": bool,
"publish_incomplete": bool,
"evaluation_type": int,
@ -80,8 +74,9 @@ _evaluationEditor = ndb.EditableTable(
)
def do_evaluation_list(args, sortkey=None):
"""List evaluations, sorted by numero (or most recent date first).
def get_evaluation_dict(args: dict) -> list[dict]:
"""Liste evaluations, triées numero (or most recent date first).
Fonction de transition pour ancien code ScoDoc7.
Ajoute les champs:
'duree' : '2h30'
@ -89,13 +84,8 @@ def do_evaluation_list(args, sortkey=None):
'apresmidi' : 1 (termine après 12:00) ou 0
'descrheure' : ' de 15h00 à 16h30'
"""
cnx = ndb.GetDBConnexion()
evals = _evaluationEditor.list(cnx, args, sortkey=sortkey)
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
for e in evals:
evaluation_enrich_dict(e)
return evals
return [e.to_dict() for e in Evaluation.query.filter_by(**args)]
def do_evaluation_list_in_formsemestre(formsemestre_id):
@ -103,7 +93,7 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
evals = []
for modimpl in mods:
evals += do_evaluation_list(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
evals += get_evaluation_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
return evals
@ -219,12 +209,12 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
Evaluation.moduleimpl_evaluation_renumber(
evaluation.moduleimpl, only_if_unumbered=True
)
e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
e = get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
after = int(after) # 0: deplace avant, 1 deplace apres
if after not in (0, 1):
raise ValueError('invalid value for "after"')
mod_evals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]})
mod_evals = get_evaluation_dict({"moduleimpl_id": e["moduleimpl_id"]})
if len(mod_evals) > 1:
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
neigh = None # object to swap with

View File

@ -27,7 +27,7 @@
"""Formulaire ajout/édition d'une évaluation
"""
import datetime
import time
import flask
@ -38,6 +38,7 @@ from flask import request
from app import db
from app.models import Evaluation, FormSemestre, ModuleImpl
from app.models.evaluations import heure_to_time
import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType
@ -47,7 +48,6 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences
@ -139,15 +139,8 @@ def evaluation_create_form(
heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)]
#
initvalues["visibulletin"] = initvalues.get("visibulletin", True)
if initvalues["visibulletin"]:
initvalues["visibulletinlist"] = ["X"]
else:
initvalues["visibulletinlist"] = []
initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
vals = scu.get_request_args()
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
vals["visibulletinlist"] = []
#
ue_coef_dict = modimpl.module.get_ue_coef_dict()
if is_apc: # BUT: poids vers les UE
@ -236,11 +229,9 @@ def evaluation_create_form(
},
),
(
"visibulletinlist",
"visibulletin",
{
"input_type": "checkbox",
"allowed_values": ["X"],
"labels": [""],
"input_type": "boolcheckbox",
"title": "Visible sur bulletins",
"explanation": "(pour les bulletins en version intermédiaire)",
},
@ -349,15 +340,37 @@ def evaluation_create_form(
return flask.redirect(dest_url)
else:
# form submission
if tf[2]["visibulletinlist"]:
tf[2]["visibulletin"] = True
args = tf[2]
# modifie le codage des dates
# (nb: ce formulaire ne permet de créer que des évaluation sur la même journée)
if args.get("jour"):
try:
date_debut = datetime.datetime.strptime(args["jour"], "%d/%m/%Y")
except ValueError as exc:
raise ScoValueError("Date (j/m/a) invalide") from exc
else:
tf[2]["visibulletin"] = False
date_debut = None
args.pop("jour", None)
if args.get("heure_debut"):
try:
heure_debut = heure_to_time(args["heure_debut"])
except ValueError as exc:
raise ScoValueError("Heure début invalide") from exc
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
args.pop("heure_debut", None)
if args.get("heure_fin"):
try:
heure_fin = heure_to_time(args["heure_fin"])
except ValueError as exc:
raise ScoValueError("Heure fin invalide") from exc
args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
args.pop("heure_fin", None)
#
if edit:
sco_evaluation_db.do_evaluation_edit(tf[2])
evaluation.from_dict(args)
else:
# création d'une evaluation
evaluation = Evaluation.create(moduleimpl=modimpl, **tf[2])
evaluation = Evaluation.create(moduleimpl=modimpl, **args)
db.session.add(evaluation)
db.session.commit()
evaluation_id = evaluation.id
@ -366,6 +379,6 @@ def evaluation_create_form(
evaluation = db.session.get(Evaluation, evaluation_id)
for ue in sem_ues:
evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"])
db.session.add(evaluation)
db.session.commit()
db.session.add(evaluation)
db.session.commit()
return flask.redirect(dest_url)

View File

@ -126,8 +126,8 @@ def evaluations_recap_table(formsemestre: FormSemestre) -> list[dict]:
evaluation_id=evaluation_id,
),
"_titre_target_attrs": 'class="discretelink"',
"date": e.jour.strftime("%d/%m/%Y") if e.jour else "",
"_date_order": e.jour.isoformat() if e.jour else "",
"date": e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "",
"_date_order": e.date_debut.isoformat() if e.date_debut else "",
"complete": "oui" if eval_etat.is_complete else "non",
"_complete_target": "#",
"_complete_target_attrs": 'class="bull_link" title="prise en compte dans les moyennes"'

View File

@ -101,6 +101,14 @@ def do_evaluation_etat(
) -> dict:
"""Donne infos sur l'état de l'évaluation.
Ancienne fonction, lente: préférer ModuleImplResults pour tout calcul.
XXX utilisée par de très nombreuses fonctions, dont
- _eval_etat<do_evaluation_etat_in_sem (en cours de remplacement)
- _eval_etat<do_evaluation_etat_in_mod<formsemestre_tableau_modules
qui a seulement besoin de
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, attente
renvoie:
{
nb_inscrits : inscrits au module
nb_notes
@ -124,7 +132,7 @@ def do_evaluation_etat(
) # { etudid : note }
# ---- Liste des groupes complets et incomplets
E = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
@ -275,7 +283,8 @@ def do_evaluation_etat(
def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
"""Liste les evaluations de tous les modules de ce semestre.
"""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 ... }
@ -315,7 +324,7 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
'evaluation_type': 0,
'heure_debut': datetime.time(8, 0),
'heure_fin': datetime.time(9, 30),
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1
'jour': datetime.date(2015, 11, 3), // vide => 1/1/1900
'moduleimpl_id': 'GEAMIP80490',
'note_max': 20.0,
'numero': 0,
@ -323,11 +332,11 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
'visibulletin': 1} ]
"""
req = """SELECT E.id AS evaluation_id, E.*
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, jour desc, heure_debut DESC
ORDER BY MI.id, numero desc, date_debut desc
"""
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@ -335,9 +344,9 @@ def do_evaluation_list_in_sem(formsemestre_id, with_etat=True):
res = cursor.dictfetchall()
# etat de chaque evaluation:
for r in res:
r["jour"] = r["jour"] or datetime.date(1900, 1, 1) # pour les comparaisons
if with_etat:
r["etat"] = do_evaluation_etat(r["evaluation_id"])
r["jour"] = r["date_debut"] or datetime.date(1900, 1, 1)
return res
@ -379,7 +388,20 @@ 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"""
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"
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
evals = nt.get_evaluations_etats()
@ -403,88 +425,97 @@ def formsemestre_evaluations_cal(formsemestre_id):
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
evals = nt.get_evaluations_etats()
nb_evals = len(evals)
evaluations = formsemestre.get_evaluations() # TODO
nb_evals = len(evaluations)
color_incomplete = "#FF6060"
color_complete = "#A0FFA0"
color_futur = "#70E0FF"
today = time.strftime("%Y-%m-%d")
year = formsemestre.date_debut.year
if formsemestre.date_debut.month < 8:
year -= 1 # calendrier septembre a septembre
year = formsemestre.annee_scolaire()
events = {} # (day, halfday) : event
for e in evals:
etat = e["etat"]
if not e["jour"]:
continue
day = e["jour"].strftime("%Y-%m-%d")
mod = sco_moduleimpl.moduleimpl_withmodule_list(
moduleimpl_id=e["moduleimpl_id"]
)[0]
txt = mod["module"]["code"] or mod["module"]["abbrev"] or "eval"
if e["heure_debut"]:
debut = e["heure_debut"].strftime("%Hh%M")
for e in evaluations:
if e.date_debut is None:
continue # éval. sans date
txt = e.moduleimpl.module.code or e.moduleimpl.module.abbrev or "éval."
if e.date_debut == e.date_fin:
heure_debut_txt, heure_fin_txt = "?", "?"
else:
debut = "?"
if e["heure_fin"]:
fin = e["heure_fin"].strftime("%Hh%M")
else:
fin = "?"
description = "%s, de %s à %s" % (mod["module"]["titre"], debut, fin)
if etat["evalcomplete"]:
heure_debut_txt = e.date_debut.strftime("%Hh%M") if e.date_debut else "?"
heure_fin_txt = e.date_fin.strftime("%Hh%M") if e.date_fin else "?"
description = f"""{
e.moduleimpl.module.titre
}, de {heure_debut_txt} à {heure_fin_txt}"""
# Etat (notes completes) de l'évaluation:
modimpl_result = nt.modimpls_results[e.moduleimpl.id]
if modimpl_result.evaluations_etat[e.id].is_complete:
color = color_complete
else:
color = color_incomplete
if day > today:
if e.date_debut > datetime.datetime.now(scu.TIME_ZONE):
color = color_futur
href = "moduleimpl_status?moduleimpl_id=%s" % e["moduleimpl_id"]
# if e['heure_debut'].hour < 12:
# halfday = True
# else:
# halfday = False
if not day in events:
# events[(day,halfday)] = [day, txt, color, href, halfday, description, mod]
events[day] = [day, txt, color, href, description, mod]
href = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=e.moduleimpl_id,
)
day = e.date_debut.date().isoformat() # yyyy-mm-dd
event = events.get(day)
if not event:
events[day] = [day, txt, color, href, description, e.moduleimpl]
else:
e = events[day]
if e[-1]["moduleimpl_id"] != mod["moduleimpl_id"]:
if event[-1].id != e.moduleimpl.id:
# plusieurs evals de modules differents a la meme date
e[1] += ", " + txt
e[4] += ", " + description
if not etat["evalcomplete"]:
e[2] = color_incomplete
if day > today:
e[2] = color_futur
event[1] += ", " + txt
event[4] += ", " + description
if color == color_incomplete:
event[2] = color_incomplete
if color == color_futur:
event[2] = color_futur
CalHTML = sco_abs.YearTable(
cal_html = sco_abs.YearTable(
year, events=list(events.values()), halfday=False, pad_width=None
)
H = [
return f"""
{
html_sco_header.html_sem_header(
"Evaluations du semestre",
cssstyles=["css/calabs.css"],
),
'<div class="cal_evaluations">',
CalHTML,
"</div>",
"<p>soit %s évaluations planifiées;" % nb_evals,
"""<ul><li>en <span style="background-color: %s">rouge</span> les évaluations passées auxquelles il manque des notes</li>
<li>en <span style="background-color: %s">vert</span> les évaluations déjà notées</li>
<li>en <span style="background-color: %s">bleu</span> les évaluations futures</li></ul></p>"""
% (color_incomplete, color_complete, color_futur),
"""<p><a href="formsemestre_evaluations_delai_correction?formsemestre_id=%s" class="stdlink">voir les délais de correction</a></p>
"""
% (formsemestre_id,),
html_sco_header.sco_footer(),
]
return "\n".join(H)
)
}
<div class="cal_evaluations">
{ cal_html }
</div>
<p>soit {nb_evals} évaluations planifiées;
</p>
<ul>
<li>en <span style=
"background-color: {color_incomplete}">rouge</span>
les évaluations passées auxquelles il manque des notes
</li>
<li>en <span style=
"background-color: {color_complete}">vert</span>
les évaluations déjà notées
</li>
<li>en <span style=
"background-color: {color_futur}">bleu</span>
les évaluations futures
</li>
</ul>
<p><a href="{
url_for("notes.formsemestre_evaluations_delai_correction",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
)
}" class="stdlink">voir les délais de correction</a>
</p>
{ html_sco_header.sco_footer() }
"""
def evaluation_date_first_completion(evaluation_id):
def evaluation_date_first_completion(evaluation_id) -> datetime.datetime:
"""Première date à laquelle l'évaluation a été complète
ou None si actuellement incomplète
"""
@ -496,7 +527,7 @@ def evaluation_date_first_completion(evaluation_id):
# Il faut considerer les inscriptions au semestre
# (pour avoir l'etat et le groupe) et aussi les inscriptions
# au module (pour gerer les modules optionnels correctement)
# E = do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
# E = get_evaluation_dict({"id":evaluation_id})[0]
# M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
# formsemestre_id = M["formsemestre_id"]
# insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre_id)
@ -536,40 +567,44 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
evals = nt.get_evaluations_etats()
T = []
for e in evals:
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or (
Mod["module_type"] == ModuleType.MALUS
evaluations = formsemestre.get_evaluations()
rows = []
for e in evaluations:
if (e.evaluation_type != scu.EVALUATION_NORMALE) or (
e.moduleimpl.module.module_type == ModuleType.MALUS
):
continue
e["date_first_complete"] = evaluation_date_first_completion(e["evaluation_id"])
if e["date_first_complete"]:
e["delai_correction"] = (e["date_first_complete"].date() - e["jour"]).days
date_first_complete = evaluation_date_first_completion(e.id)
if date_first_complete and e.date_fin:
delai_correction = (date_first_complete.date() - e.date_fin).days
else:
e["delai_correction"] = None
delai_correction = None
e["module_code"] = Mod["code"]
e["_module_code_target"] = url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=M["moduleimpl_id"],
rows.append(
{
"date_first_complete": date_first_complete,
"delai_correction": delai_correction,
"jour": e.date_debut.strftime("%d/%m/%Y")
if e.date_debut
else "sans date",
"_jour_target": url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e["evaluation_id"],
),
"module_code": e.moduleimpl.module.code,
"_module_code_target": url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=e.moduleimpl.id,
),
"module_titre": e.moduleimpl.module.abbrev or e.moduleimpl.module.titre,
"responsable_id": e.moduleimpl.responsable_id,
"responsable_nomplogin": sco_users.user_info(
e.moduleimpl.responsable_id
)["nomplogin"],
}
)
e["module_titre"] = Mod["titre"]
e["responsable_id"] = M["responsable_id"]
e["responsable_nomplogin"] = sco_users.user_info(M["responsable_id"])[
"nomplogin"
]
e["_jour_target"] = url_for(
"notes.evaluation_listenotes",
scodoc_dept=g.scodoc_dept,
evaluation_id=e["evaluation_id"],
)
T.append(e)
columns_ids = (
"module_code",
@ -592,16 +627,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
tab = GenTable(
titles=titles,
columns_ids=columns_ids,
rows=T,
rows=rows,
html_class="table_leftalign table_coldate",
html_sortable=True,
html_title="<h2>Correction des évaluations du semestre</h2>",
caption="Correction des évaluations du semestre",
preferences=sco_preferences.SemPreferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id),
origin="Généré par %s le " % sco_version.SCONAME
+ scu.timedate_human_repr()
+ "",
origin=f"""Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}""",
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
)
return tab.make_page(format=format)
@ -612,7 +645,7 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True):
"""HTML description of evaluation, for page headers
edit_in_place: allow in-place editing when permitted (not implemented)
"""
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
moduleimpl_id = E["moduleimpl_id"]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]

View File

@ -512,8 +512,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line
# description evaluation
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
ws.append_single_cell_row(
"Evaluation du %s (coef. %g)"
% (evaluation.jour or "sans date", evaluation.coefficient or 0.0),
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})",
style,
)
# ligne blanche

View File

@ -1245,7 +1245,9 @@ def do_formsemestre_clone(
moduleimpl_id=mod_orig["moduleimpl_id"]
):
# copie en enlevant la date
new_eval = e.clone(not_copying=("jour", "moduleimpl_id"))
new_eval = e.clone(
not_copying=("date_debut", "date_fin", "moduleimpl_id")
)
new_eval.moduleimpl_id = mid
# Copie les poids APC de l'évaluation
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
@ -1443,7 +1445,7 @@ def do_formsemestre_delete(formsemestre_id):
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
for mod in mods:
# evaluations
evals = sco_evaluation_db.do_evaluation_list(
evals = sco_evaluation_db.get_evaluation_dict(
args={"moduleimpl_id": mod["moduleimpl_id"]}
)
for e in evals:

View File

@ -498,7 +498,7 @@ def retreive_formsemestre_from_request() -> int:
modimpl = modimpl[0]
formsemestre_id = modimpl["formsemestre_id"]
elif "evaluation_id" in args:
E = sco_evaluation_db.do_evaluation_list(
E = sco_evaluation_db.get_evaluation_dict(
{"evaluation_id": args["evaluation_id"]}
)
if not E:
@ -620,7 +620,7 @@ def formsemestre_description_table(
columns_ids += ["Inscrits", "Responsable", "Enseignants"]
if with_evals:
columns_ids += [
"jour",
"date_evaluation",
"description",
"coefficient",
"evalcomplete_str",
@ -630,7 +630,7 @@ def formsemestre_description_table(
titles = {title: title for title in columns_ids}
titles.update({f"ue_{ue.id}": ue.acronyme for ue in ues})
titles["ects"] = "ECTS"
titles["jour"] = "Évaluation"
titles["date_evaluation"] = "Évaluation"
titles["description"] = ""
titles["coefficient"] = "Coef. éval."
titles["evalcomplete_str"] = "Complète"
@ -738,8 +738,10 @@ def formsemestre_description_table(
scodoc_dept=g.scodoc_dept,
evaluation_id=e["evaluation_id"],
)
e["_jour_order"] = e["jour"].isoformat()
e["jour"] = e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
e["_date_evaluation_order"] = e["jour"].isoformat()
e["date_evaluation"] = (
e["jour"].strftime("%d/%m/%Y") if e["jour"] else ""
)
e["UE"] = row["UE"]
e["_UE_td_attrs"] = row["_UE_td_attrs"]
e["Code"] = row["Code"]

View File

@ -69,38 +69,44 @@ def do_evaluation_listenotes(
mode = None
if moduleimpl_id:
mode = "module"
evals = sco_evaluation_db.do_evaluation_list({"moduleimpl_id": moduleimpl_id})
evals = sco_evaluation_db.get_evaluation_dict({"moduleimpl_id": moduleimpl_id})
elif evaluation_id:
mode = "eval"
evals = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})
evals = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})
else:
raise ValueError("missing argument: evaluation or module")
if not evals:
return "<p>Aucune évaluation !</p>", "ScoDoc"
E = evals[0] # il y a au moins une evaluation
modimpl = db.session.get(ModuleImpl, E["moduleimpl_id"])
eval_dict = evals[0] # il y a au moins une evaluation
modimpl = db.session.get(ModuleImpl, eval_dict["moduleimpl_id"])
# description de l'evaluation
if mode == "eval":
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)]
page_title = f"Notes {E['description'] or modimpl.module.code}"
page_title = f"Notes {eval_dict['description'] or modimpl.module.code}"
else:
H = []
page_title = f"Notes {modimpl.module.code}"
# groupes
groups = sco_groups.do_evaluation_listegroupes(
E["evaluation_id"], include_default=True
eval_dict["evaluation_id"], include_default=True
)
grlabs = [g["group_name"] or "tous" for g in groups] # legendes des boutons
grnams = [str(g["group_id"]) for g in groups] # noms des checkbox
if len(evals) > 1:
descr = [
("moduleimpl_id", {"default": E["moduleimpl_id"], "input_type": "hidden"})
(
"moduleimpl_id",
{"default": eval_dict["moduleimpl_id"], "input_type": "hidden"},
)
]
else:
descr = [
("evaluation_id", {"default": E["evaluation_id"], "input_type": "hidden"})
(
"evaluation_id",
{"default": eval_dict["evaluation_id"], "input_type": "hidden"},
)
]
if len(grnams) > 1:
descr += [
@ -199,7 +205,7 @@ def do_evaluation_listenotes(
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=E["moduleimpl_id"],
moduleimpl_id=eval_dict["moduleimpl_id"],
)
),
"",

View File

@ -61,7 +61,7 @@ from app.tables import list_etuds
# menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"Menu avec actions sur une evaluation"
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"])
@ -203,11 +203,10 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
# Evaluations, la plus RECENTE en tête
# Evaluations, par numéros ou la plus RECENTE en tête
evaluations = modimpl.evaluations.order_by(
Evaluation.numero.desc(),
Evaluation.jour.desc(),
Evaluation.heure_debut.desc(),
Evaluation.date_debut.desc(),
).all()
nb_evaluations = len(evaluations)
max_poids = max(
@ -571,10 +570,8 @@ def _ligne_evaluation(
# visualisation des poids (Hinton map)
H.append(_evaluation_poids_html(evaluation, max_poids))
H.append("""<div class="evaluation_titre">""")
if evaluation.jour:
H.append(
f"""Le {evaluation.jour.strftime("%d/%m/%Y")} {evaluation.descr_heure()}"""
)
if evaluation.date_debut:
H.append(evaluation.descr_date())
else:
H.append(
f"""<a href="{url_for("notes.evaluation_edit",

View File

@ -138,7 +138,7 @@ class PlacementForm(FlaskForm):
def set_evaluation_infos(self, evaluation_id):
"""Initialise les données du formulaire avec les données de l'évaluation."""
eval_data = sco_evaluation_db.do_evaluation_list(
eval_data = sco_evaluation_db.get_evaluation_dict(
{"evaluation_id": evaluation_id}
)
if not eval_data:
@ -239,7 +239,7 @@ class PlacementRunner:
self.groups_ids = [
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
]
self.eval_data = sco_evaluation_db.do_evaluation_list(
self.eval_data = sco_evaluation_db.get_evaluation_dict(
{"evaluation_id": self.evaluation_id}
)[0]
self.groups = sco_groups.listgroups(self.groups_ids)

View File

@ -884,15 +884,15 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
modimpl = evaluation.moduleimpl
formsemestre = modimpl.formsemestre
mod_responsable = sco_users.user_info(modimpl.responsable_id)
if evaluation.jour:
indication_date = evaluation.jour.isoformat()
if evaluation.date_debut:
indication_date = evaluation.date_debut.date().isoformat()
else:
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
date_str = (
f"""du {evaluation.jour.strftime("%d/%m/%Y")}"""
if evaluation.jour
f"""du {evaluation.date_debut.strftime("%d/%m/%Y")}"""
if evaluation.date_debut
else "(sans date)"
)
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"} {date_str}"""
@ -1107,7 +1107,9 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
# Information sur absence (tenant compte de la demi-journée)
jour_iso = evaluation.jour.isoformat() if evaluation.jour else ""
jour_iso = (
evaluation.date_debut.date().isoformat() if evaluation.date_debut else ""
)
warn_abs_lst = []
if evaluation.is_matin():
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True)

View File

@ -149,7 +149,7 @@ def list_operations(evaluation_id):
def evaluation_list_operations(evaluation_id):
"""Page listing operations on evaluation"""
E = sco_evaluation_db.do_evaluation_list({"evaluation_id": evaluation_id})[0]
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Ops = list_operations(evaluation_id)
@ -179,7 +179,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"""
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
rows = ndb.SimpleDictFetch(
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour,
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.date_debut,
u.user_name, e.id as evaluation_id
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
notes_modules mod, identite i, "user" u
@ -197,6 +197,12 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
keep_numeric = format in scu.FORMATS_NUMERIQUES
for row in rows:
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric)
row["date_evaluation"] = (
row["date_debut"].strftime("%d/%m/%Y %H:%M") if row["date_debut"] else ""
)
row["_date_evaluation_order"] = (
row["date_debut"].isoformat() if row["date_debut"] else ""
)
columns_ids = (
"date",
"code_nip",
@ -207,7 +213,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"titre",
"evaluation_id",
"description",
"jour",
"date_evaluation",
"comment",
)
titles = {
@ -221,7 +227,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"evaluation_id": "evaluation_id",
"titre": "Module",
"description": "Evaluation",
"jour": "Date éval.",
"date_evaluation": "Date éval.",
}
tab = GenTable(
titles=titles,

View File

@ -75,6 +75,8 @@ MAX_TEXT_LEN = 64 * 1024
STATIC_DIR = (
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION
)
# La time zone du serveur:
TIME_ZONE = timezone("/".join(os.path.realpath("/etc/localtime").split("/")[-2:]))
# ----- CALCUL ET PRESENTATION DES NOTES
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis

View File

@ -385,7 +385,9 @@ class TableRecap(tb.Table):
first_eval_of_mod = True
for e in evals:
col_id = f"eval_{e.id}"
title = f'{modimpl.module.code} {eval_index} {e.jour.isoformat() if e.jour else ""}'
title = f"""{modimpl.module.code} {eval_index} {
e.date_debut.strftime("%d/%m/%Y") if e.date_debut else ""
}"""
col_classes = []
if first_eval:
col_classes.append("first")

View File

@ -1765,7 +1765,7 @@ sco_publish(
@scodoc7func
def evaluation_delete(evaluation_id):
"""Form delete evaluation"""
El = sco_evaluation_db.do_evaluation_list(args={"evaluation_id": evaluation_id})
El = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})
if not El:
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
E = El[0]

View File

@ -116,3 +116,75 @@ def POST_JSON(path: str, data: dict = {}, headers: dict = None, errmsg=None, dep
if r.status_code != 200:
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
return r.json() # decode la reponse JSON
def check_fields(data: dict, fields: dict = None):
"""
Vérifie que le dictionnaire data contient les bonnes clés
et les bons types de valeurs.
Args:
data (dict): un dictionnaire (json de retour de l'api)
fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
"""
if fields is None:
fields = ASSIDUITES_FIELDS
assert set(data.keys()) == set(fields.keys())
for key in data:
if key in ("moduleimpl_id", "desc", "user_id", "external_data"):
assert (
isinstance(data[key], fields[key]) or data[key] is None
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
else:
assert isinstance(
data[key], fields[key]
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
def check_failure_get(path: str, headers: dict, err: str = None):
"""
Vérifie que la requête GET renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth de l'api
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
try:
GET(path=path, headers=headers)
# ^ Renvoi un 404
except APIError as api_err:
if err is not None:
assert api_err.payload["message"] == err
else:
raise APIError("Le GET n'aurait pas du fonctionner")
def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
"""
Vérifie que la requête POST renvoie bien un 404
Args:
path (str): la route de l'api
headers (dict): le token d'auth
data (dict): un dictionnaire (json) à envoyer
err (str, optional): L'erreur qui est sensée être fournie par l'api.
Raises:
APIError: Une erreur car la requête a fonctionné (mauvais comportement)
"""
try:
data = POST_JSON(path=path, headers=headers, data=data)
# ^ Renvoie un 404
except APIError as api_err:
if err is not None:
assert (
api_err.payload["message"] == err
), f"received: {api_err.payload['message']}"
else:
raise APIError("Le GET n'aurait pas du fonctionner")

View File

@ -18,6 +18,7 @@ Utilisation :
"""
import re
from types import NoneType
import requests
@ -513,13 +514,16 @@ def test_etudiant_bulletin_semestre(api_headers):
assert evaluation["description"] is None or isinstance(
evaluation["description"], str
)
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
assert isinstance(evaluation["heure_debut"], str)
assert isinstance(evaluation["heure_fin"], str)
assert isinstance(evaluation["coef"], str)
assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str)
assert isinstance(evaluation["date_debut"], (str, NoneType))
assert isinstance(evaluation["date_fin"], (str, NoneType))
# Deprecated (supprimer avant #sco9.7):
assert isinstance(evaluation["date"], (str, NoneType))
assert isinstance(evaluation["heure_debut"], (str, NoneType))
assert isinstance(evaluation["heure_fin"], (str, NoneType))
assert (
verify_fields(
@ -567,13 +571,16 @@ def test_etudiant_bulletin_semestre(api_headers):
assert evaluation["description"] is None or isinstance(
evaluation["description"], str
)
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
assert isinstance(evaluation["heure_debut"], str)
assert isinstance(evaluation["heure_fin"], str)
assert isinstance(evaluation["coef"], str)
assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str)
assert isinstance(evaluation["date_fin"], (str, NoneType))
assert isinstance(evaluation["date_debut"], (str, NoneType))
# Deprecated fields (supprimer avant #sco9.7)
assert isinstance(evaluation["date"], (str, NoneType))
assert isinstance(evaluation["heure_debut"], (str, NoneType))
assert isinstance(evaluation["heure_fin"], (str, NoneType))
assert (
verify_fields(

View File

@ -18,6 +18,7 @@ Utilisation :
"""
import json
import requests
from types import NoneType
from app.scodoc import sco_utils as scu
@ -301,14 +302,18 @@ def test_bulletins(api_headers):
assert evaluation["description"] is None or isinstance(
evaluation["description"], str
)
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
assert isinstance(evaluation["heure_debut"], str)
assert isinstance(evaluation["heure_fin"], str)
assert isinstance(evaluation["date_debut"], (str, NoneType))
assert isinstance(evaluation["date_fin"], (str, NoneType))
assert isinstance(evaluation["coef"], str)
assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str)
# Deprecated (supprimer avant #sco9.7):
assert isinstance(evaluation["date"], (str, NoneType))
assert isinstance(evaluation["heure_debut"], (str, NoneType))
assert isinstance(evaluation["heure_fin"], (str, NoneType))
assert (
verify_fields(
evaluation["poids"],
@ -354,14 +359,18 @@ def test_bulletins(api_headers):
assert evaluation["description"] is None or isinstance(
evaluation["description"], str
)
assert evaluation["date"] is None or isinstance(evaluation["date"], str)
assert isinstance(evaluation["heure_debut"], str)
assert isinstance(evaluation["heure_fin"], str)
assert isinstance(evaluation["date_debut"], (str, NoneType))
assert isinstance(evaluation["date_fin"], (str, NoneType))
assert isinstance(evaluation["coef"], str)
assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str)
# Deprecated (supprimer avant #sco9.7):
assert isinstance(evaluation["date"], (str, NoneType))
assert isinstance(evaluation["heure_debut"], (str, NoneType))
assert isinstance(evaluation["heure_fin"], (str, NoneType))
assert (
verify_fields(
evaluation["poids"],

View File

@ -491,7 +491,7 @@ EVAL_FIELDS = {
"numero",
"poids",
"publish_incomplete",
"visi_bulletin",
"visibulletin",
"etat",
"nb_inscrits",
"nb_notes_manquantes",
@ -565,7 +565,7 @@ EVALUATIONS_FIELDS = {
"numero",
"poids",
"publish_incomplete",
"visi_bulletin",
"visibulletin",
}
NOTES_FIELDS = {

View File

@ -7,7 +7,7 @@ A utiliser avec debug.py (côté serveur).
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
facilement des tests ou de reproduire des bugs.
"""
import datetime
from functools import wraps
import random
import sys
@ -304,9 +304,8 @@ class ScoFake(object):
def create_evaluation(
self,
moduleimpl_id=None,
jour=None,
heure_debut="8h00",
heure_fin="9h00",
date_debut: datetime.datetime = None,
date_fin: datetime.datetime = None,
description=None,
note_max=20,
coefficient=None,
@ -314,7 +313,7 @@ class ScoFake(object):
publish_incomplete=None,
evaluation_type=None,
numero=None,
) -> int:
) -> dict:
args = locals()
del args["self"]
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
@ -322,7 +321,9 @@ class ScoFake(object):
evaluation: Evaluation = Evaluation.create(moduleimpl=moduleimpl, **args)
db.session.add(evaluation)
db.session.commit()
return evaluation.id
eval_dict = evaluation.to_dict()
eval_dict["id"] = evaluation.id
return eval_dict
@logging_meth
def create_note(
@ -414,7 +415,7 @@ class ScoFake(object):
for e_idx in range(1, nb_evaluations_per_module + 1):
e = self.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour=date_debut,
date_debut=datetime.datetime.strptime(date_debut, "%d/%m/%Y"),
description="evaluation test %s" % e_idx,
coefficient=1.0,
)
@ -435,7 +436,7 @@ class ScoFake(object):
for e in eval_list:
for idx, etud in enumerate(etuds):
self.create_note(
evaluation_id=e["id"],
evaluation_id=e["evaluation_id"],
etudid=etud["etudid"],
note=notes[idx % len(notes)],
)

View File

@ -1,9 +1,9 @@
"""
Quelques fonctions d'initialisation pour tests unitaires
"""
import datetime
from app import db, models
import app.scodoc.sco_utils as scu
from app.scodoc import codes_cursus
@ -133,7 +133,7 @@ def build_modules_with_evaluations(
for _ in range(nb_evals_per_modimpl):
e = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2021",
date_debut=datetime.datetime(2021, 1, 1),
description="evaluation 1",
coefficient=0,
)

View File

@ -5,7 +5,7 @@
Créer et justifier des absences en utilisant le parametre demijournee
"""
# test écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en juillet 2021
import datetime
import json
from tests.unit import sco_fake_gen
@ -163,7 +163,7 @@ def test_abs_basic(test_client):
# --- Création d'une évaluation
e = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="22/01/2021",
date_debut=datetime.datetime(2021, 1, 22),
description="evaluation test",
coefficient=1.0,
)

View File

@ -22,7 +22,7 @@ from tests.unit import test_sco_basic
DEPT = TestConfig.DEPT_TEST
def test_bulletin(test_client):
def test_bulletin_data_classic(test_client):
"""Vérifications sur les bulletins de notes"""
G = sco_fake_gen.ScoFake(verbose=False)
app.set_sco_dept(DEPT)

View File

@ -2,6 +2,7 @@
Test modèles évaluations avec poids BUT
et calcul moyennes modules
"""
import datetime
import numpy as np
import pandas as pd
@ -46,7 +47,7 @@ def test_evaluation_poids(test_client):
)
_e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2021",
date_debut=datetime.datetime(2021, 1, 1),
description="evaluation 1",
coefficient=0,
)
@ -218,7 +219,7 @@ def test_module_moy(test_client):
# Crée une deuxième évaluation dans le même moduleimpl:
evaluation2_id = G.create_evaluation(
moduleimpl_id=evaluation1.moduleimpl_id,
jour="02/01/2021",
date_debut=datetime.datetime(2021, 1, 2),
description="evaluation 2",
coefficient=coef_e2,
)["evaluation_id"]

View File

@ -15,12 +15,11 @@ import app
from app import db
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import Evaluation, FormSemestre, ModuleImpl
from app.scodoc import sco_cache
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre
from app.scodoc import notesdb as ndb
from config import TestConfig
from tests.unit.test_sco_basic import run_sco_basic
@ -58,23 +57,24 @@ def test_cache_evaluations(test_client):
# prépare le département avec quelques semestres:
run_sco_basic()
#
sems = sco_formsemestre.do_formsemestre_list()
assert len(sems)
sem_evals = []
for sem in sems:
sem_evals = sco_evaluations.do_evaluation_list_in_sem(
sem["formsemestre_id"], with_etat=False
formsemestres = FormSemestre.query
assert formsemestres.count()
evaluation = None
for formsemestre in formsemestres:
evaluation: Evaluation = (
Evaluation.query.join(ModuleImpl)
.filter_by(formsemestre_id=formsemestre.id)
.first()
)
if sem_evals:
if evaluation is not None:
break
if not sem_evals:
if evaluation is None:
raise Exception("no evaluations")
#
evaluation_id = sem_evals[0]["evaluation_id"]
eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
eval_notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
# should have been be cached, except if empty
if eval_notes:
assert sco_cache.EvaluationCache.get(evaluation_id)
sco_cache.invalidate_formsemestre(sem["formsemestre_id"])
assert sco_cache.EvaluationCache.get(evaluation.id)
sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id)
# should have been erased from cache:
assert not sco_cache.EvaluationCache.get(evaluation_id)
assert not sco_cache.EvaluationCache.get(evaluation.id)

View File

@ -2,6 +2,7 @@
Vérif moyennes de modules des bulletins
et aussi moyennes modules et UE internes (via nt)
"""
import datetime
import numpy as np
from flask import g
from config import TestConfig
@ -93,13 +94,13 @@ def test_notes_modules(test_client):
coef_2 = 2.0
e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 1",
coefficient=coef_1,
)
e2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 2",
coefficient=coef_2,
)
@ -107,16 +108,16 @@ def test_notes_modules(test_client):
note_1 = 12.0
note_2 = 13.0
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=note_1
evaluation_id=e1["evaluation_id"], etudid=etuds[0]["etudid"], note=note_1
)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etuds[0]["etudid"], note=note_2
evaluation_id=e2["evaluation_id"], etudid=etuds[0]["etudid"], note=note_2
)
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=note_1 / 2
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=note_1 / 2
)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=note_2 / 3
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=note_2 / 3
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -138,16 +139,24 @@ def test_notes_modules(test_client):
)
# Absence à une évaluation
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=note_2)
_, _, _ = G.create_note(
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
) # abs
_, _, _ = G.create_note(
evaluation_id=e2["evaluation_id"], etudid=etudid, note=note_2
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
# Absences aux deux évaluations
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=None) # abs
_, _, _ = G.create_note(
evaluation_id=e1["evaluation_id"], etudid=etudid, note=None
) # abs
_, _, _ = G.create_note(
evaluation_id=e2["evaluation_id"], etudid=etudid, note=None
) # abs
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
)
@ -162,9 +171,11 @@ def test_notes_modules(test_client):
)
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
)
_, _, _ = G.create_note(
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -179,9 +190,11 @@ def test_notes_modules(test_client):
expected_moy_ue=note_1,
)
# Note en attente ATT <-> scu.NOTES_ATTENTE
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
evaluation_id=e1["evaluation_id"], etudid=etudid, note=note_1
)
_, _, _ = G.create_note(
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -197,10 +210,10 @@ def test_notes_modules(test_client):
)
# Neutralisation (EXC) des 2 évals
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -216,10 +229,10 @@ def test_notes_modules(test_client):
)
# Attente (ATT) sur les 2 evals
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_ATTENTE
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_ATTENTE
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_ATTENTE
) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"]
@ -277,7 +290,7 @@ def test_notes_modules(test_client):
{"etudid": etudid, "moduleimpl_id": moduleimpl_id},
formsemestre_id=formsemestre_id,
)
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=12.5)
_, _, _ = G.create_note(evaluation_id=e1["evaluation_id"], etudid=etudid, note=12.5)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
mod_stats = nt.get_mod_stats(moduleimpl_id)
@ -301,11 +314,13 @@ def test_notes_modules(test_client):
# Note dans module 2:
e_m2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id2,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation mod 2",
coefficient=1.0,
)
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=19.5)
_, _, _ = G.create_note(
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=19.5
)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_status = nt.get_etud_ue_status(etudid, ue_id)
@ -314,20 +329,22 @@ def test_notes_modules(test_client):
# 2 modules, notes EXC dans le premier, note valide n dans le second
# la moyenne de l'UE doit être n
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
evaluation_id=e1["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
evaluation_id=e2["evaluation_id"], etudid=etudid, note=scu.NOTES_NEUTRALISE
) # EXC
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=12.5)
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=11.0
evaluation_id=e_m2["evaluation_id"], etudid=etudid, note=12.5
)
_, _, _ = G.create_note(
evaluation_id=e2["id"], etudid=etuds[1]["etudid"], note=11.0
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
)
_, _, _ = G.create_note(
evaluation_id=e_m2["id"], etudid=etuds[1]["etudid"], note=11.0
evaluation_id=e2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
)
_, _, _ = G.create_note(
evaluation_id=e_m2["evaluation_id"], etudid=etuds[1]["etudid"], note=11.0
)
b = sco_bulletins.formsemestre_bulletinetud_dict(
@ -385,16 +402,20 @@ def test_notes_modules_att_dem(test_client):
coef_1 = 1.0
e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 1",
coefficient=coef_1,
)
# Attente (ATT) sur les 2 evals
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[0]["etudid"], note=scu.NOTES_ATTENTE
evaluation_id=e1["evaluation_id"],
etudid=etuds[0]["etudid"],
note=scu.NOTES_ATTENTE,
) # ATT
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=scu.NOTES_ATTENTE
evaluation_id=e1["evaluation_id"],
etudid=etuds[1]["etudid"],
note=scu.NOTES_ATTENTE,
) # ATT
# Démission du premier étudiant
sco_formsemestre_inscriptions.do_formsemestre_demission(
@ -435,7 +456,7 @@ def test_notes_modules_att_dem(test_client):
# Saisie note ABS pour le deuxième etud
_, _, _ = G.create_note(
evaluation_id=e1["id"], etudid=etuds[1]["etudid"], note=None
evaluation_id=e1["evaluation_id"], etudid=etuds[1]["etudid"], note=None
)
nt = check_nt(
etuds[1]["etudid"],

View File

@ -1,5 +1,6 @@
"""Test calculs rattrapages
"""
import datetime
import app
from app import db
@ -58,14 +59,14 @@ def test_notes_rattrapage(test_client):
# --- Creation évaluation
e = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation test",
coefficient=1.0,
)
# --- Création d'une évaluation "de rattrapage"
e_rat = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="02/01/2020",
date_debut=datetime.datetime(2020, 1, 2),
description="evaluation rattrapage",
coefficient=1.0,
evaluation_type=scu.EVALUATION_RATTRAPAGE,
@ -139,7 +140,7 @@ def test_notes_rattrapage(test_client):
# Création évaluation session 2:
e_session2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="02/01/2020",
date_debut=datetime.datetime(2020, 1, 2),
description="evaluation session 2",
coefficient=1.0,
evaluation_type=scu.EVALUATION_SESSION2,

View File

@ -11,6 +11,8 @@ Au besoin, créer un base de test neuve:
./tools/create_database.sh SCODOC_TEST
"""
import datetime
from app.models import FormSemestreInscription, Identite
from config import TestConfig
@ -96,7 +98,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Création évaluation
e = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="01/01/2020",
date_debut=datetime.datetime(2020, 1, 1),
description="evaluation test",
coefficient=1.0,
)
@ -104,7 +106,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Saisie toutes les notes de l'évaluation
for idx, etud in enumerate(etuds):
etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e["id"],
evaluation_id=e["evaluation_id"],
etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)],
)
@ -130,14 +132,14 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Une autre évaluation
e2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id,
jour="02/01/2020",
date_debut=datetime.datetime(2020, 1, 2),
description="evaluation test 2",
coefficient=1.0,
)
# Saisie les notes des 5 premiers étudiants:
for idx, etud in enumerate(etuds[:5]):
etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e2["id"],
evaluation_id=e2["evaluation_id"],
etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)],
)
@ -159,7 +161,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# Saisie des notes qui manquent:
for idx, etud in enumerate(etuds[5:]):
etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e2["id"],
evaluation_id=e2["evaluation_id"],
etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)],
)

View File

@ -158,7 +158,7 @@ def create_evaluations(formsemestre: FormSemestre, publish_incomplete=True):
for modimpl in formsemestre.modimpls:
evaluation = Evaluation(
moduleimpl=modimpl,
jour=formsemestre.date_debut,
date_debut=formsemestre.date_debut,
description=f"Exam {modimpl.module.titre}",
coefficient=1.0,
note_max=20.0,

View File

@ -238,7 +238,8 @@ def create_evaluations(formsemestre: FormSemestre):
"Création d'une evaluation dans chaque modimpl du semestre"
for moduleimpl in formsemestre.modimpls:
args = {
"jour": datetime.date(2022, 3, 1) + datetime.timedelta(days=moduleimpl.id),
"jour": datetime.date(2022, 3, 1)
+ datetime.timedelta(days=moduleimpl.id), # TODO à changer
"heure_debut": "8h00",
"heure_fin": "9h00",
"description": f"Evaluation-{moduleimpl.module.code}",