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() if has_request_context()
else "na", else "na",
# deprecated # deprecated (supprimer avant #sco9.7)
"date": e.date_debut.isoformat() if e.date_debut else None, "date": e.date_debut.isoformat() if e.date_debut else None,
"heure_debut": e.date_debut.time().isoformat("minutes") "heure_debut": e.date_debut.time().isoformat("minutes")
if e.date_debut if e.date_debut

View File

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

View File

@ -250,7 +250,7 @@ class ModuleImplResults:
).reshape(-1, 1) ).reshape(-1, 1)
# was _list_notes_evals_titles # 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" "Liste des évaluations complètes"
return [ return [
e for e in moduleimpl.evaluations if self.evaluations_completes_dict[e.id] 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.moy_gen_rangs_by_group = None # virtual
self.modimpl_inscr_df: pd.DataFrame = None self.modimpl_inscr_df: pd.DataFrame = None
"Inscriptions: row etudid, col modimlpl_id" "Inscriptions: row etudid, col modimlpl_id"
self.modimpls_results: ModuleImplResults = None self.modimpls_results: dict[int, ModuleImplResults] = None
"Résultats de chaque modimpl: dict { modimpl.id : ModuleImplResults(Classique ou BUT) }" "Résultats de chaque modimpl (classique ou BUT)"
self.etud_coef_ue_df = None self.etud_coef_ue_df = None
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)""" """coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
self.modimpl_coefs_df: pd.DataFrame = None self.modimpl_coefs_df: pd.DataFrame = None
@ -192,6 +192,17 @@ class ResultatsSemestre(ResultatsCache):
*[mr.etudids_attente for mr in self.modimpls_results.values()] *[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... # --- JURY...
def get_formsemestre_validations(self) -> ValidationsSemestre: def get_formsemestre_validations(self) -> ValidationsSemestre:
"""Load validations if not already stored, set attribute and return value""" """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 import moy_sem
from app.comp.aux_stats import StatsMoyenne from app.comp.aux_stats import StatsMoyenne
from app.comp.res_common import ResultatsSemestre 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.codes_cursus import UE_SPORT, DEF
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -389,7 +395,7 @@ class NotesTableCompat(ResultatsSemestre):
"ects_total": ects_total, "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 """Liste d'informations (compat NotesTable) sur évaluations completes
de ce module. de ce module.
Évaluation "complete" ssi toutes notes saisies ou en attente. Évaluation "complete" ssi toutes notes saisies ou en attente.
@ -398,34 +404,24 @@ class NotesTableCompat(ResultatsSemestre):
modimpl_results = self.modimpls_results.get(moduleimpl_id) modimpl_results = self.modimpls_results.get(moduleimpl_id)
if not modimpl_results: if not modimpl_results:
return [] # safeguard return [] # safeguard
evals_results = [] evaluations = []
for e in modimpl.evaluations: for e in modimpl.evaluations:
if modimpl_results.evaluations_completes_dict.get(e.id, False): if modimpl_results.evaluations_completes_dict.get(e.id, False):
d = e.to_dict() evaluations.append(e)
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)
elif e.id not in modimpl_results.evaluations_completes_dict: elif e.id not in modimpl_results.evaluations_completes_dict:
# ne devrait pas arriver ? XXX # ne devrait pas arriver ? XXX
log( 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 from app.scodoc import sco_evaluations
if not hasattr(self, "_evaluations_etats"): 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 import sco_cache
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_xml import quote_xml_attr
MAX_EVALUATION_DURATION = datetime.timedelta(days=365) MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
NOON = datetime.time(12, 00) NOON = datetime.time(12, 00)
@ -46,6 +46,7 @@ class Evaluation(db.Model):
visibulletin = db.Column( visibulletin = db.Column(
db.Boolean, nullable=False, default=True, server_default="true" db.Boolean, nullable=False, default=True, server_default="true"
) )
"visible sur les bulletins version intermédiaire"
publish_incomplete = db.Column( publish_incomplete = db.Column(
db.Boolean, nullable=False, default=False, server_default="false" db.Boolean, nullable=False, default=False, server_default="false"
) )
@ -67,9 +68,8 @@ class Evaluation(db.Model):
def create( def create(
cls, cls,
moduleimpl: ModuleImpl = None, moduleimpl: ModuleImpl = None,
jour=None, date_debut: datetime.datetime = None,
heure_debut=None, date_fin: datetime.datetime = None,
heure_fin=None,
description=None, description=None,
note_max=None, note_max=None,
coefficient=None, coefficient=None,
@ -124,7 +124,7 @@ class Evaluation(db.Model):
next_eval = None next_eval = None
t = date_debut t = date_debut
for e in evaluations: for e in evaluations:
if e.date_debut > t: if e.date_debut and e.date_debut > t:
next_eval = e next_eval = e
break break
if next_eval: if next_eval:
@ -140,26 +140,26 @@ class Evaluation(db.Model):
def to_dict(self) -> dict: def to_dict(self) -> dict:
"Représentation dict (riche, compat ScoDoc 7)" "Représentation dict (riche, compat ScoDoc 7)"
e = dict(self.__dict__) e_dict = dict(self.__dict__)
e.pop("_sa_instance_state", None) e_dict.pop("_sa_instance_state", None)
# ScoDoc7 output_formators # ScoDoc7 output_formators
e["evaluation_id"] = self.id e_dict["evaluation_id"] = self.id
e["date_debut"] = e.date_debut.isoformat() if e.date_debut else None e_dict["date_debut"] = self.date_debut.isoformat() if self.date_debut else None
e["date_fin"] = e.date_debut.isoformat() if e.date_fin else None e_dict["date_fin"] = self.date_debut.isoformat() if self.date_fin else None
e["numero"] = ndb.int_null_is_zero(e["numero"]) e_dict["numero"] = self.numero or 0
e["poids"] = self.get_ue_poids_dict() # { ue_id : poids } e_dict["poids"] = self.get_ue_poids_dict() # { ue_id : poids }
# Deprecated # 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: def to_dict_api(self) -> dict:
"Représentation dict pour API JSON" "Représentation dict pour API JSON"
return { return {
"coefficient": self.coefficient, "coefficient": self.coefficient,
"date_debut": self.date_debut.isoformat(), "date_debut": self.date_debut.isoformat() if self.date_debut else "",
"date_fin": self.date_fin.isoformat(), "date_fin": self.date_fin.isoformat() if self.date_fin else "",
"description": self.description, "description": self.description,
"evaluation_type": self.evaluation_type, "evaluation_type": self.evaluation_type,
"id": self.id, "id": self.id,
@ -168,14 +168,36 @@ class Evaluation(db.Model):
"numero": self.numero, "numero": self.numero,
"poids": self.get_ue_poids_dict(), "poids": self.get_ue_poids_dict(),
"publish_incomplete": self.publish_incomplete, "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): def from_dict(self, data):
"""Set evaluation attributes from given dict values.""" """Set evaluation attributes from given dict values."""
check_convert_evaluation_args(self.moduleimpl, data) check_convert_evaluation_args(self.moduleimpl, data)
if data.get("numero") is None: 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(): for k in self.__dict__.keys():
if k != "_sa_instance_state" and k != "id" and k in data: if k != "_sa_instance_state" and k != "id" and k in data:
setattr(self, k, data[k]) setattr(self, k, data[k])
@ -217,28 +239,64 @@ class Evaluation(db.Model):
db.session.commit() db.session.commit()
def descr_heure(self) -> str: def descr_heure(self) -> str:
"Description de la plage horaire pour affichages" "Description de la plage horaire pour affichages ('de 13h00 à 14h00')"
if self.heure_debut and ( if self.date_debut and (not self.date_fin or self.date_fin == self.date_debut):
not self.heure_fin or self.heure_fin == self.heure_debut return f"""à {self.date_debut.strftime("%Hh%M")}"""
): elif self.date_debut and self.date_fin:
return f"""à {self.heure_debut.strftime("%Hh%M")}""" return f"""de {self.date_debut.strftime("%Hh%M")
elif self.heure_debut and self.heure_fin: } à {self.date_fin.strftime("%Hh%M")}"""
return f"""de {self.heure_debut.strftime("%Hh%M")} à {self.heure_fin.strftime("%Hh%M")}"""
else: else:
return "" return ""
def descr_duree(self) -> str: def descr_duree(self) -> str:
"Description de la durée pour affichages" "Description de la durée pour affichages ('3h' ou '2h30')"
if self.heure_debut is None and self.heure_fin is None: if self.date_debut is None or self.date_fin is None:
return "" return ""
debut = self.heure_debut or DEFAULT_EVALUATION_TIME minutes = (self.date_fin - self.date_debut).seconds // 60
fin = self.heure_fin or DEFAULT_EVALUATION_TIME duree = f"{minutes // 60}h"
d = (fin.hour * 60 + fin.minute) - (debut.hour * 60 + debut.minute) minutes = minutes % 60
duree = f"{d//60}h" if minutes != 0:
if d % 60: duree += f"{minutes:02d}"
duree += f"{d%60:02d}"
return duree 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=()): def clone(self, not_copying=()):
"""Clone, not copying the given attrs """Clone, not copying the given attrs
Attention: la copie n'a pas d'id avant le prochain commit 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}>" return f"<EvaluationUEPoids {self.evaluation} {self.ue} poids={self.poids}>"
# Fonction héritée de ScoDoc7 à refactorer # Fonction héritée de ScoDoc7
def evaluation_enrich_dict(e: dict): def evaluation_enrich_dict(e: Evaluation, e_dict: dict):
"""add or convert some fields in an evaluation dict""" """add or convert some fields in an evaluation dict"""
# For ScoDoc7 compat # For ScoDoc7 compat
heure_debut_dt = e["date_debut"].time() e_dict["heure_debut"] = e.date_debut.strftime("%Hh%M") if e.date_debut else ""
heure_fin_dt = e["date_fin"].time() e_dict["heure_fin"] = e.date_fin.strftime("%Hh%M") if e.date_fin else ""
e["heure_debut"] = heure_debut_dt.strftime("%Hh%M") e_dict["jour_iso"] = e.date_debut.isoformat() if e.date_debut else ""
e["heure_fin"] = heure_fin_dt.strftime("%Hh%M") # Calcule durée en minutes
e["jour_iso"] = e["date_debut"].isoformat() # XXX e_dict["descrheure"] = e.descr_heure()
heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] e_dict["descrduree"] = e.descr_duree()
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"] = ""
# matin, apresmidi: utile pour se referer aux absences: # matin, apresmidi: utile pour se referer aux absences:
# note août 2023: si l'évaluation s'étend sur plusieurs jours,
if e["jour"] and heure_debut_dt < datetime.time(12, 00): # cet indicateur n'a pas grand sens
e["matin"] = 1 if e.date_debut and e.date_debut.time() < datetime.time(12, 00):
e_dict["matin"] = 1
else: else:
e["matin"] = 0 e_dict["matin"] = 0
if e["jour"] and heure_fin_dt > datetime.time(12, 00): if e.date_fin and e.date_fin.time() > datetime.time(12, 00):
e["apresmidi"] = 1 e_dict["apresmidi"] = 1
else: else:
e["apresmidi"] = 0 e_dict["apresmidi"] = 0
return e return e_dict
def check_convert_evaluation_args(moduleimpl: ModuleImpl, data: 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. May raise ScoValueError.
""" """
# --- description # --- description
description = data.get("description", "") data["description"] = data.get("description", "") or ""
if len(description) > scu.MAX_TEXT_LEN: if len(data["description"]) > scu.MAX_TEXT_LEN:
raise ScoValueError("description too large") raise ScoValueError("description too large")
# --- evaluation_type # --- evaluation_type
try: try:
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0) data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
if not data["evaluation_type"] in VALID_EVALUATION_TYPES: if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
raise ScoValueError("Invalid evaluation_type value") raise ScoValueError("invalid evaluation_type value")
except ValueError: except ValueError as exc:
raise ScoValueError("Invalid evaluation_type value") raise ScoValueError("invalid evaluation_type value") from exc
# --- note_max (bareme) # --- note_max (bareme)
note_max = data.get("note_max", 20.0) or 20.0 note_max = data.get("note_max", 20.0) or 20.0
try: try:
note_max = float(note_max) note_max = float(note_max)
except ValueError: except ValueError as exc:
raise ScoValueError("Invalid note_max value") raise ScoValueError("invalid note_max value") from exc
if note_max < 0: 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 data["note_max"] = note_max
# --- coefficient # --- coefficient
coef = data.get("coefficient", 1.0) or 1.0 coef = data.get("coefficient", 1.0) or 1.0
try: try:
coef = float(coef) coef = float(coef)
except ValueError: except ValueError as exc:
raise ScoValueError("Invalid coefficient value") raise ScoValueError("invalid coefficient value") from exc
if coef < 0: 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 data["coefficient"] = coef
# --- jour (date de l'évaluation) # --- date de l'évaluation
jour = data.get("jour", None) formsemestre = moduleimpl.formsemestre
if jour and not isinstance(jour, datetime.date): date_debut = data.get("date_debut", None)
if date_format == "dmy": if date_debut:
y, m, d = [int(x) for x in ndb.DateDMYtoISO(jour).split("-")] if isinstance(date_debut, str):
jour = datetime.date(y, m, d) data["date_debut"] = datetime.datetime.fromisoformat(date_debut)
else: # ISO if data["date_debut"].tzinfo is None:
jour = datetime.date.fromisoformat(jour) data["date_debut"] = scu.TIME_ZONE.localize(data["date_debut"])
formsemestre = moduleimpl.formsemestre if not (
if (jour > formsemestre.date_fin) or (jour < formsemestre.date_debut): formsemestre.date_debut
<= data["date_debut"].date()
<= formsemestre.date_fin
):
raise ScoValueError( 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();", dest_url="javascript:history.back();",
) )
data["jour"] = jour date_fin = data.get("date_fin", None)
# --- heures if date_fin:
heure_debut = data.get("heure_debut", None) if isinstance(date_fin, str):
if heure_debut and not isinstance(heure_debut, datetime.time): data["date_fin"] = datetime.datetime.fromisoformat(date_fin)
if date_format == "dmy": if data["date_fin"].tzinfo is None:
data["heure_debut"] = heure_to_time(heure_debut) data["date_fin"] = scu.TIME_ZONE.localize(data["date_fin"])
else: # ISO if not (
data["heure_debut"] = datetime.time.fromisoformat(heure_debut) formsemestre.date_debut <= data["date_fin"].date() <= formsemestre.date_fin
heure_fin = data.get("heure_fin", None) ):
if heure_fin and not isinstance(heure_fin, datetime.time): raise ScoValueError(
if date_format == "dmy": f"""La date de fin de l'évaluation ({
data["heure_fin"] = heure_to_time(heure_fin) data["date_fin"].strftime("%d/%m/%Y")
else: # ISO }) n'est pas dans le semestre !""",
data["heure_fin"] = datetime.time.fromisoformat(heure_fin) dest_url="javascript:history.back();",
if jour and ((not heure_debut) or (not heure_fin)): )
raise ScoValueError("Les heures doivent être précisées") if date_debut and date_fin:
if heure_debut and heure_fin: duration = data["date_fin"] - data["date_debut"]
duration = ((data["heure_fin"].hour * 60) + data["heure_fin"].minute) - ( if duration.total_seconds() < 0 or duration > MAX_EVALUATION_DURATION:
(data["heure_debut"].hour * 60) + data["heure_debut"].minute
)
if duration < 0 or duration > 60 * 12:
raise ScoValueError("Heures de l'évaluation incohérentes !") 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: 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.config import ScoDocSiteConfig
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
from app.models.formations import Formation from app.models.formations import Formation
from app.models.groups import GroupDescr, Partition from app.models.groups import GroupDescr, Partition
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
@ -350,6 +351,21 @@ class FormSemestre(db.Model):
_cache[key] = ues _cache[key] = ues
return 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 @cached_property
def modimpls_sorted(self) -> list[ModuleImpl]: def modimpls_sorted(self) -> list[ModuleImpl]:
"""Liste des modimpls du semestre (y compris bonus) """Liste des modimpls du semestre (y compris bonus)

View File

@ -509,7 +509,7 @@ def _get_abs_description(a, cursor=None):
return "" 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. """Liste des absences et/ou justificatifs ce jour.
is_abs: None (peu importe), True, False is_abs: None (peu importe), True, False
is_just: idem is_just: idem
@ -535,7 +535,7 @@ WHERE A.jour = %(date)s
return A 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" "Liste des absences non justifiees ce jour"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)

View File

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

View File

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

View File

@ -37,7 +37,7 @@ from app import db, ScoDocJSONEncoder
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import but_validations 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.etudiants import Identite
from app.models.formsemestre import FormSemestre 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_assiduites
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_photos 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)) 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 if mod_moy == "NI": # ne mentionne pas les modules ou n'est pas inscrit
continue continue
modimpl_results = nt.modimpls_results.get(modimpl["moduleimpl_id"])
mod = modimpl["module"] mod = modimpl["module"]
# if mod['ects'] is None: # if mod['ects'] is None:
# ects = '' # ects = ''
@ -363,66 +363,42 @@ def _list_modimpls(
mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1]) mod_dict["effectif"] = dict(value=nt.mod_rangs[modimpl["moduleimpl_id"]][1])
# --- notes de chaque eval: # --- 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"] = [] mod_dict["evaluation"] = []
if version != "short": if version != "short":
for e in evals: for e in evaluations_completes:
if e["visibulletin"] or version == "long": if e.visibulletin or version == "long":
val = e["notes"].get(etudid, {"value": "NP"})["value"] # Note à l'évaluation:
val = modimpl_results.evals_notes[e.id].get(etudid, "NP")
# nb: val est NA si etud démissionnaire # nb: val est NA si etud démissionnaire
val = scu.fmt_note(val, note_max=e["note_max"]) e_dict = e.to_dict_bul()
eval_dict = dict( e_dict["note"] = scu.fmt_note(val, note_max=e.note_max)
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"]
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: # 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"]: if prefs["bul_show_all_evals"]:
evaluations = Evaluation.query.filter_by( evaluations: list[Evaluation] = Evaluation.query.filter_by(
moduleimpl_id=modimpl["moduleimpl_id"] moduleimpl_id=modimpl["moduleimpl_id"]
).order_by(Evaluation.date_debut) ).order_by(Evaluation.date_debut)
# plus ancienne d'abord # plus ancienne d'abord
for e in evaluations: for e in evaluations:
if e.id not in complete_eval_ids: if e.id not in complete_eval_ids:
mod_dict["evaluation"].append( e_dict = e.to_dict_bul()
dict( e_dict["incomplete"] = 1
date_debut=e.date_debut.isoformat() mod_dict["evaluation"].append(e_dict)
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
),
)
)
modules_dict.append(mod_dict) modules_dict.append(mod_dict)
return modules_dict return modules_dict

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@
"""Formulaire ajout/édition d'une évaluation """Formulaire ajout/édition d'une évaluation
""" """
import datetime
import time import time
import flask import flask
@ -38,6 +38,7 @@ from flask import request
from app import db from app import db
from app.models import Evaluation, FormSemestre, ModuleImpl from app.models import Evaluation, FormSemestre, ModuleImpl
from app.models.evaluations import heure_to_time
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_utils import ModuleType 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_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences 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)] 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) initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
vals = scu.get_request_args() 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() ue_coef_dict = modimpl.module.get_ue_coef_dict()
if is_apc: # BUT: poids vers les UE if is_apc: # BUT: poids vers les UE
@ -236,11 +229,9 @@ def evaluation_create_form(
}, },
), ),
( (
"visibulletinlist", "visibulletin",
{ {
"input_type": "checkbox", "input_type": "boolcheckbox",
"allowed_values": ["X"],
"labels": [""],
"title": "Visible sur bulletins", "title": "Visible sur bulletins",
"explanation": "(pour les bulletins en version intermédiaire)", "explanation": "(pour les bulletins en version intermédiaire)",
}, },
@ -349,15 +340,37 @@ def evaluation_create_form(
return flask.redirect(dest_url) return flask.redirect(dest_url)
else: else:
# form submission # form submission
if tf[2]["visibulletinlist"]: args = tf[2]
tf[2]["visibulletin"] = True # 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: 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: if edit:
sco_evaluation_db.do_evaluation_edit(tf[2]) evaluation.from_dict(args)
else: else:
# création d'une evaluation # création d'une evaluation
evaluation = Evaluation.create(moduleimpl=modimpl, **tf[2]) evaluation = Evaluation.create(moduleimpl=modimpl, **args)
db.session.add(evaluation) db.session.add(evaluation)
db.session.commit() db.session.commit()
evaluation_id = evaluation.id evaluation_id = evaluation.id
@ -366,6 +379,6 @@ def evaluation_create_form(
evaluation = db.session.get(Evaluation, evaluation_id) evaluation = db.session.get(Evaluation, evaluation_id)
for ue in sem_ues: for ue in sem_ues:
evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"]) evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"])
db.session.add(evaluation) db.session.add(evaluation)
db.session.commit() db.session.commit()
return flask.redirect(dest_url) return flask.redirect(dest_url)

View File

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

View File

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

View File

@ -1245,7 +1245,9 @@ def do_formsemestre_clone(
moduleimpl_id=mod_orig["moduleimpl_id"] moduleimpl_id=mod_orig["moduleimpl_id"]
): ):
# copie en enlevant la date # 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 new_eval.moduleimpl_id = mid
# Copie les poids APC de l'évaluation # Copie les poids APC de l'évaluation
new_eval.set_ue_poids_dict(e.get_ue_poids_dict()) 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) mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
for mod in mods: for mod in mods:
# evaluations # evaluations
evals = sco_evaluation_db.do_evaluation_list( evals = sco_evaluation_db.get_evaluation_dict(
args={"moduleimpl_id": mod["moduleimpl_id"]} args={"moduleimpl_id": mod["moduleimpl_id"]}
) )
for e in evals: for e in evals:

View File

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

View File

@ -69,38 +69,44 @@ def do_evaluation_listenotes(
mode = None mode = None
if moduleimpl_id: if moduleimpl_id:
mode = "module" 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: elif evaluation_id:
mode = "eval" 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: else:
raise ValueError("missing argument: evaluation or module") raise ValueError("missing argument: evaluation or module")
if not evals: if not evals:
return "<p>Aucune évaluation !</p>", "ScoDoc" return "<p>Aucune évaluation !</p>", "ScoDoc"
E = evals[0] # il y a au moins une evaluation eval_dict = evals[0] # il y a au moins une evaluation
modimpl = db.session.get(ModuleImpl, E["moduleimpl_id"]) modimpl = db.session.get(ModuleImpl, eval_dict["moduleimpl_id"])
# description de l'evaluation # description de l'evaluation
if mode == "eval": if mode == "eval":
H = [sco_evaluations.evaluation_describe(evaluation_id=evaluation_id)] 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: else:
H = [] H = []
page_title = f"Notes {modimpl.module.code}" page_title = f"Notes {modimpl.module.code}"
# groupes # groupes
groups = sco_groups.do_evaluation_listegroupes( 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 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 grnams = [str(g["group_id"]) for g in groups] # noms des checkbox
if len(evals) > 1: if len(evals) > 1:
descr = [ descr = [
("moduleimpl_id", {"default": E["moduleimpl_id"], "input_type": "hidden"}) (
"moduleimpl_id",
{"default": eval_dict["moduleimpl_id"], "input_type": "hidden"},
)
] ]
else: else:
descr = [ descr = [
("evaluation_id", {"default": E["evaluation_id"], "input_type": "hidden"}) (
"evaluation_id",
{"default": eval_dict["evaluation_id"], "input_type": "hidden"},
)
] ]
if len(grnams) > 1: if len(grnams) > 1:
descr += [ descr += [
@ -199,7 +205,7 @@ def do_evaluation_listenotes(
url_for( url_for(
"notes.moduleimpl_status", "notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept, 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 # menu evaluation dans moduleimpl
def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str: def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
"Menu avec actions sur une evaluation" "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] modimpl = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
group_id = sco_groups.get_default_group(modimpl["formsemestre_id"]) 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) 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( evaluations = modimpl.evaluations.order_by(
Evaluation.numero.desc(), Evaluation.numero.desc(),
Evaluation.jour.desc(), Evaluation.date_debut.desc(),
Evaluation.heure_debut.desc(),
).all() ).all()
nb_evaluations = len(evaluations) nb_evaluations = len(evaluations)
max_poids = max( max_poids = max(
@ -571,10 +570,8 @@ def _ligne_evaluation(
# visualisation des poids (Hinton map) # visualisation des poids (Hinton map)
H.append(_evaluation_poids_html(evaluation, max_poids)) H.append(_evaluation_poids_html(evaluation, max_poids))
H.append("""<div class="evaluation_titre">""") H.append("""<div class="evaluation_titre">""")
if evaluation.jour: if evaluation.date_debut:
H.append( H.append(evaluation.descr_date())
f"""Le {evaluation.jour.strftime("%d/%m/%Y")} {evaluation.descr_heure()}"""
)
else: else:
H.append( H.append(
f"""<a href="{url_for("notes.evaluation_edit", f"""<a href="{url_for("notes.evaluation_edit",

View File

@ -138,7 +138,7 @@ class PlacementForm(FlaskForm):
def set_evaluation_infos(self, evaluation_id): def set_evaluation_infos(self, evaluation_id):
"""Initialise les données du formulaire avec les données de l'évaluation.""" """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} {"evaluation_id": evaluation_id}
) )
if not eval_data: if not eval_data:
@ -239,7 +239,7 @@ class PlacementRunner:
self.groups_ids = [ self.groups_ids = [
gid if gid != TOUS else form.tous_id for gid in form["groups"].data 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} {"evaluation_id": self.evaluation_id}
)[0] )[0]
self.groups = sco_groups.listgroups(self.groups_ids) 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 modimpl = evaluation.moduleimpl
formsemestre = modimpl.formsemestre formsemestre = modimpl.formsemestre
mod_responsable = sco_users.user_info(modimpl.responsable_id) mod_responsable = sco_users.user_info(modimpl.responsable_id)
if evaluation.jour: if evaluation.date_debut:
indication_date = evaluation.jour.isoformat() indication_date = evaluation.date_debut.date().isoformat()
else: else:
indication_date = scu.sanitize_filename(evaluation.description or "")[:12] indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}" eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
date_str = ( date_str = (
f"""du {evaluation.jour.strftime("%d/%m/%Y")}""" f"""du {evaluation.date_debut.strftime("%d/%m/%Y")}"""
if evaluation.jour if evaluation.date_debut
else "(sans date)" else "(sans date)"
) )
eval_titre = f"""{evaluation.description if evaluation.description else "évaluation"} {date_str}""" 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) e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id)
# Information sur absence (tenant compte de la demi-journée) # 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 = [] warn_abs_lst = []
if evaluation.is_matin(): if evaluation.is_matin():
nbabs = sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True) 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): def evaluation_list_operations(evaluation_id):
"""Page listing operations on evaluation""" """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] M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Ops = list_operations(evaluation_id) 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) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
rows = ndb.SimpleDictFetch( 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 u.user_name, e.id as evaluation_id
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi, FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
notes_modules mod, identite i, "user" u 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 keep_numeric = format in scu.FORMATS_NUMERIQUES
for row in rows: for row in rows:
row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric) 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 = ( columns_ids = (
"date", "date",
"code_nip", "code_nip",
@ -207,7 +213,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"titre", "titre",
"evaluation_id", "evaluation_id",
"description", "description",
"jour", "date_evaluation",
"comment", "comment",
) )
titles = { titles = {
@ -221,7 +227,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
"evaluation_id": "evaluation_id", "evaluation_id": "evaluation_id",
"titre": "Module", "titre": "Module",
"description": "Evaluation", "description": "Evaluation",
"jour": "Date éval.", "date_evaluation": "Date éval.",
} }
tab = GenTable( tab = GenTable(
titles=titles, titles=titles,

View File

@ -75,6 +75,8 @@ MAX_TEXT_LEN = 64 * 1024
STATIC_DIR = ( STATIC_DIR = (
os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION 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 # ----- CALCUL ET PRESENTATION DES NOTES
NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis NOTES_PRECISION = 1e-4 # evite eventuelles erreurs d'arrondis

View File

@ -385,7 +385,9 @@ class TableRecap(tb.Table):
first_eval_of_mod = True first_eval_of_mod = True
for e in evals: for e in evals:
col_id = f"eval_{e.id}" 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 = [] col_classes = []
if first_eval: if first_eval:
col_classes.append("first") col_classes.append("first")

View File

@ -1765,7 +1765,7 @@ sco_publish(
@scodoc7func @scodoc7func
def evaluation_delete(evaluation_id): def evaluation_delete(evaluation_id):
"""Form delete evaluation""" """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: if not El:
raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id) raise ScoValueError("Evaluation inexistante ! (%s)" % evaluation_id)
E = El[0] 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: if r.status_code != 200:
raise APIError(errmsg or f"erreur status={r.status_code} !", r.json()) raise APIError(errmsg or f"erreur status={r.status_code} !", r.json())
return r.json() # decode la reponse 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 import re
from types import NoneType
import requests import requests
@ -513,13 +514,16 @@ def test_etudiant_bulletin_semestre(api_headers):
assert evaluation["description"] is None or isinstance( assert evaluation["description"] is None or isinstance(
evaluation["description"], str 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["coef"], str)
assert isinstance(evaluation["poids"], dict) assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict) assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str) 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 ( assert (
verify_fields( verify_fields(
@ -567,13 +571,16 @@ def test_etudiant_bulletin_semestre(api_headers):
assert evaluation["description"] is None or isinstance( assert evaluation["description"] is None or isinstance(
evaluation["description"], str 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["coef"], str)
assert isinstance(evaluation["poids"], dict) assert isinstance(evaluation["poids"], dict)
assert isinstance(evaluation["note"], dict) assert isinstance(evaluation["note"], dict)
assert isinstance(evaluation["url"], str) 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 ( assert (
verify_fields( verify_fields(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,12 +15,11 @@ import app
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat 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_cache
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import notesdb as ndb
from config import TestConfig from config import TestConfig
from tests.unit.test_sco_basic import run_sco_basic 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: # prépare le département avec quelques semestres:
run_sco_basic() run_sco_basic()
# #
sems = sco_formsemestre.do_formsemestre_list() formsemestres = FormSemestre.query
assert len(sems) assert formsemestres.count()
sem_evals = [] evaluation = None
for sem in sems: for formsemestre in formsemestres:
sem_evals = sco_evaluations.do_evaluation_list_in_sem( evaluation: Evaluation = (
sem["formsemestre_id"], with_etat=False Evaluation.query.join(ModuleImpl)
.filter_by(formsemestre_id=formsemestre.id)
.first()
) )
if sem_evals: if evaluation is not None:
break break
if not sem_evals: if evaluation is None:
raise Exception("no evaluations") 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 # should have been be cached, except if empty
if eval_notes: if eval_notes:
assert sco_cache.EvaluationCache.get(evaluation_id) assert sco_cache.EvaluationCache.get(evaluation.id)
sco_cache.invalidate_formsemestre(sem["formsemestre_id"]) sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id)
# should have been erased from cache: # 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 Vérif moyennes de modules des bulletins
et aussi moyennes modules et UE internes (via nt) et aussi moyennes modules et UE internes (via nt)
""" """
import datetime
import numpy as np import numpy as np
from flask import g from flask import g
from config import TestConfig from config import TestConfig
@ -93,13 +94,13 @@ def test_notes_modules(test_client):
coef_2 = 2.0 coef_2 = 2.0
e1 = G.create_evaluation( e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 1", description="evaluation 1",
coefficient=coef_1, coefficient=coef_1,
) )
e2 = G.create_evaluation( e2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 2", description="evaluation 2",
coefficient=coef_2, coefficient=coef_2,
) )
@ -107,16 +108,16 @@ def test_notes_modules(test_client):
note_1 = 12.0 note_1 = 12.0
note_2 = 13.0 note_2 = 13.0
_, _, _ = G.create_note( _, _, _ = 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( _, _, _ = 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( _, _, _ = 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( _, _, _ = 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( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
@ -138,16 +139,24 @@ def test_notes_modules(test_client):
) )
# Absence à une évaluation # Absence à une évaluation
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs _, _, _ = G.create_note(
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=note_2) 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( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
) )
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2) 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) assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
# Absences aux deux évaluations # Absences aux deux évaluations
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=None) # abs _, _, _ = G.create_note(
_, _, _ = G.create_note(evaluation_id=e2["id"], etudid=etudid, note=None) # abs 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( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
) )
@ -162,9 +171,11 @@ def test_notes_modules(test_client):
) )
# Note excusée EXC <-> scu.NOTES_NEUTRALISE # Note excusée EXC <-> scu.NOTES_NEUTRALISE
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note( _, _, _ = 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 ) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
@ -179,9 +190,11 @@ def test_notes_modules(test_client):
expected_moy_ue=note_1, expected_moy_ue=note_1,
) )
# Note en attente ATT <-> scu.NOTES_ATTENTE # Note en attente ATT <-> scu.NOTES_ATTENTE
_, _, _ = G.create_note(evaluation_id=e1["id"], etudid=etudid, note=note_1)
_, _, _ = G.create_note( _, _, _ = 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 ) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
@ -197,10 +210,10 @@ def test_notes_modules(test_client):
) )
# Neutralisation (EXC) des 2 évals # Neutralisation (EXC) des 2 évals
_, _, _ = G.create_note( _, _, _ = 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 ) # EXC
_, _, _ = G.create_note( _, _, _ = 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 ) # EXC
b = sco_bulletins.formsemestre_bulletinetud_dict( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
@ -216,10 +229,10 @@ def test_notes_modules(test_client):
) )
# Attente (ATT) sur les 2 evals # Attente (ATT) sur les 2 evals
_, _, _ = G.create_note( _, _, _ = 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 ) # ATT
_, _, _ = G.create_note( _, _, _ = 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 ) # ATT
b = sco_bulletins.formsemestre_bulletinetud_dict( b = sco_bulletins.formsemestre_bulletinetud_dict(
sem["formsemestre_id"], etud["etudid"] sem["formsemestre_id"], etud["etudid"]
@ -277,7 +290,7 @@ def test_notes_modules(test_client):
{"etudid": etudid, "moduleimpl_id": moduleimpl_id}, {"etudid": etudid, "moduleimpl_id": moduleimpl_id},
formsemestre_id=formsemestre_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) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
mod_stats = nt.get_mod_stats(moduleimpl_id) mod_stats = nt.get_mod_stats(moduleimpl_id)
@ -301,11 +314,13 @@ def test_notes_modules(test_client):
# Note dans module 2: # Note dans module 2:
e_m2 = G.create_evaluation( e_m2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id2, moduleimpl_id=moduleimpl_id2,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation mod 2", description="evaluation mod 2",
coefficient=1.0, 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) formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue_status = nt.get_etud_ue_status(etudid, ue_id) 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 # 2 modules, notes EXC dans le premier, note valide n dans le second
# la moyenne de l'UE doit être n # la moyenne de l'UE doit être n
_, _, _ = G.create_note( _, _, _ = 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 ) # EXC
_, _, _ = G.create_note( _, _, _ = 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 ) # EXC
_, _, _ = G.create_note(evaluation_id=e_m2["id"], etudid=etudid, note=12.5)
_, _, _ = G.create_note( _, _, _ = 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( _, _, _ = 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( _, _, _ = 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( b = sco_bulletins.formsemestre_bulletinetud_dict(
@ -385,16 +402,20 @@ def test_notes_modules_att_dem(test_client):
coef_1 = 1.0 coef_1 = 1.0
e1 = G.create_evaluation( e1 = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation 1", description="evaluation 1",
coefficient=coef_1, coefficient=coef_1,
) )
# Attente (ATT) sur les 2 evals # Attente (ATT) sur les 2 evals
_, _, _ = G.create_note( _, _, _ = 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 ) # ATT
_, _, _ = G.create_note( _, _, _ = 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 ) # ATT
# Démission du premier étudiant # Démission du premier étudiant
sco_formsemestre_inscriptions.do_formsemestre_demission( 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 # Saisie note ABS pour le deuxième etud
_, _, _ = G.create_note( _, _, _ = 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( nt = check_nt(
etuds[1]["etudid"], etuds[1]["etudid"],

View File

@ -1,5 +1,6 @@
"""Test calculs rattrapages """Test calculs rattrapages
""" """
import datetime
import app import app
from app import db from app import db
@ -58,14 +59,14 @@ def test_notes_rattrapage(test_client):
# --- Creation évaluation # --- Creation évaluation
e = G.create_evaluation( e = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation test", description="evaluation test",
coefficient=1.0, coefficient=1.0,
) )
# --- Création d'une évaluation "de rattrapage" # --- Création d'une évaluation "de rattrapage"
e_rat = G.create_evaluation( e_rat = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="02/01/2020", date_debut=datetime.datetime(2020, 1, 2),
description="evaluation rattrapage", description="evaluation rattrapage",
coefficient=1.0, coefficient=1.0,
evaluation_type=scu.EVALUATION_RATTRAPAGE, evaluation_type=scu.EVALUATION_RATTRAPAGE,
@ -139,7 +140,7 @@ def test_notes_rattrapage(test_client):
# Création évaluation session 2: # Création évaluation session 2:
e_session2 = G.create_evaluation( e_session2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="02/01/2020", date_debut=datetime.datetime(2020, 1, 2),
description="evaluation session 2", description="evaluation session 2",
coefficient=1.0, coefficient=1.0,
evaluation_type=scu.EVALUATION_SESSION2, 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 ./tools/create_database.sh SCODOC_TEST
""" """
import datetime
from app.models import FormSemestreInscription, Identite from app.models import FormSemestreInscription, Identite
from config import TestConfig from config import TestConfig
@ -96,7 +98,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Création évaluation # --- Création évaluation
e = G.create_evaluation( e = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="01/01/2020", date_debut=datetime.datetime(2020, 1, 1),
description="evaluation test", description="evaluation test",
coefficient=1.0, coefficient=1.0,
) )
@ -104,7 +106,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Saisie toutes les notes de l'évaluation # --- Saisie toutes les notes de l'évaluation
for idx, etud in enumerate(etuds): for idx, etud in enumerate(etuds):
etudids_changed, nb_suppress, existing_decisions = G.create_note( etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e["id"], evaluation_id=e["evaluation_id"],
etudid=etud["etudid"], etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)], note=NOTES_T[idx % len(NOTES_T)],
) )
@ -130,14 +132,14 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# --- Une autre évaluation # --- Une autre évaluation
e2 = G.create_evaluation( e2 = G.create_evaluation(
moduleimpl_id=moduleimpl_id, moduleimpl_id=moduleimpl_id,
jour="02/01/2020", date_debut=datetime.datetime(2020, 1, 2),
description="evaluation test 2", description="evaluation test 2",
coefficient=1.0, coefficient=1.0,
) )
# Saisie les notes des 5 premiers étudiants: # Saisie les notes des 5 premiers étudiants:
for idx, etud in enumerate(etuds[:5]): for idx, etud in enumerate(etuds[:5]):
etudids_changed, nb_suppress, existing_decisions = G.create_note( etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e2["id"], evaluation_id=e2["evaluation_id"],
etudid=etud["etudid"], etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)], note=NOTES_T[idx % len(NOTES_T)],
) )
@ -159,7 +161,7 @@ def run_sco_basic(verbose=False) -> FormSemestre:
# Saisie des notes qui manquent: # Saisie des notes qui manquent:
for idx, etud in enumerate(etuds[5:]): for idx, etud in enumerate(etuds[5:]):
etudids_changed, nb_suppress, existing_decisions = G.create_note( etudids_changed, nb_suppress, existing_decisions = G.create_note(
evaluation_id=e2["id"], evaluation_id=e2["evaluation_id"],
etudid=etud["etudid"], etudid=etud["etudid"],
note=NOTES_T[idx % len(NOTES_T)], 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: for modimpl in formsemestre.modimpls:
evaluation = Evaluation( evaluation = Evaluation(
moduleimpl=modimpl, moduleimpl=modimpl,
jour=formsemestre.date_debut, date_debut=formsemestre.date_debut,
description=f"Exam {modimpl.module.titre}", description=f"Exam {modimpl.module.titre}",
coefficient=1.0, coefficient=1.0,
note_max=20.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" "Création d'une evaluation dans chaque modimpl du semestre"
for moduleimpl in formsemestre.modimpls: for moduleimpl in formsemestre.modimpls:
args = { 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_debut": "8h00",
"heure_fin": "9h00", "heure_fin": "9h00",
"description": f"Evaluation-{moduleimpl.module.code}", "description": f"Evaluation-{moduleimpl.module.code}",