forked from ScoDoc/ScoDoc
438 lines
16 KiB
Python
438 lines
16 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@gmail.com
|
|
#
|
|
##############################################################################
|
|
|
|
"""Formulaire ajout/édition d'une évaluation
|
|
"""
|
|
import datetime
|
|
import time
|
|
|
|
import flask
|
|
from flask import g, render_template, request, url_for
|
|
from flask_login import current_user
|
|
|
|
from app import db
|
|
from app.models import Evaluation, Module, ModuleImpl
|
|
from app.models.evaluations import heure_to_time, check_and_convert_evaluation_args
|
|
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.sco_utils import ModuleType
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc import sco_evaluations
|
|
from app.scodoc import sco_preferences
|
|
|
|
|
|
def evaluation_create_form(
|
|
moduleimpl_id=None,
|
|
evaluation_id=None,
|
|
edit=False,
|
|
page_title="Évaluation",
|
|
):
|
|
"Formulaire création/édition d'une évaluation (pas de ses notes)"
|
|
evaluation: Evaluation
|
|
if evaluation_id is not None:
|
|
evaluation = db.session.get(Evaluation, evaluation_id)
|
|
if evaluation is None:
|
|
raise ScoValueError("Cette évaluation n'existe pas ou plus !")
|
|
moduleimpl_id = evaluation.moduleimpl_id
|
|
#
|
|
modimpl = ModuleImpl.get_modimpl(moduleimpl_id)
|
|
formsemestre_id = modimpl.formsemestre_id
|
|
formsemestre = modimpl.formsemestre
|
|
module: Module = modimpl.module
|
|
sem_ues = formsemestre.get_ues(with_sport=False)
|
|
is_malus = module.module_type == ModuleType.MALUS
|
|
is_apc = module.module_type in (ModuleType.RESSOURCE, ModuleType.SAE)
|
|
preferences = sco_preferences.SemPreferences(formsemestre.id)
|
|
can_edit_poids = not preferences["but_disable_edit_poids_evaluations"]
|
|
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible
|
|
#
|
|
if not modimpl.can_edit_evaluation(current_user):
|
|
return f"""
|
|
{html_sco_header.sco_header()}
|
|
<h2>Opération non autorisée</h2>
|
|
<p>Modification évaluation impossible pour {current_user.get_nomplogin()}</p>
|
|
<p><a href="{url_for('notes.moduleimpl_status',
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}" class="stdlink">Revenir</a>
|
|
</p>
|
|
{html_sco_header.sco_footer()}
|
|
"""
|
|
if not edit:
|
|
# création nouvel
|
|
if moduleimpl_id is None:
|
|
raise ValueError("missing moduleimpl_id parameter")
|
|
numeros = [(e.numero or 0) for e in modimpl.evaluations]
|
|
initvalues = {
|
|
"jour": time.strftime(scu.DATE_FMT, time.localtime()),
|
|
"note_max": 20,
|
|
"numero": (max(numeros) + 1) if numeros else 0,
|
|
"publish_incomplete": is_malus,
|
|
"visibulletin": 1,
|
|
}
|
|
submitlabel = "Créer cette évaluation"
|
|
action = "Création d'une évaluation"
|
|
link = ""
|
|
else:
|
|
# édition données existantes
|
|
# setup form init values
|
|
if evaluation_id is None:
|
|
raise ValueError("missing evaluation_id parameter")
|
|
initvalues = evaluation.to_dict()
|
|
moduleimpl_id = initvalues["moduleimpl_id"]
|
|
submitlabel = "Modifier l'évaluation"
|
|
action = "Modification d'une évaluation"
|
|
link = ""
|
|
# Note maximale actuelle dans cette éval ?
|
|
etat = sco_evaluations.do_evaluation_etat(evaluation_id)
|
|
if etat["maxi_num"] is not None:
|
|
min_note_max = max(scu.NOTES_PRECISION, etat["maxi_num"])
|
|
else:
|
|
min_note_max = scu.NOTES_PRECISION
|
|
#
|
|
if min_note_max > scu.NOTES_PRECISION:
|
|
min_note_max_str = scu.fmt_note(min_note_max)
|
|
else:
|
|
min_note_max_str = "0"
|
|
|
|
initvalues["coefficient"] = initvalues.get("coefficient", 1.0)
|
|
vals = scu.get_request_args()
|
|
#
|
|
ue_coef_dict = modimpl.module.get_ue_coef_dict()
|
|
if is_apc: # BUT: poids vers les UE
|
|
if edit:
|
|
if evaluation.set_default_poids():
|
|
db.session.commit()
|
|
ue_poids_dict = evaluation.get_ue_poids_dict()
|
|
for ue in sem_ues:
|
|
initvalues[f"poids_{ue.id}"] = ue_poids_dict[ue.id]
|
|
else:
|
|
for ue in sem_ues:
|
|
coef_ue = ue_coef_dict.get(ue.id, 0.0) or 0.0
|
|
if coef_ue > 0:
|
|
poids = 1.0 # par défaut au départ
|
|
else:
|
|
poids = 0.0
|
|
initvalues[f"poids_{ue.id}"] = poids
|
|
# Blocage
|
|
if edit:
|
|
initvalues["blocked"] = evaluation.is_blocked()
|
|
initvalues["blocked_until"] = (
|
|
evaluation.blocked_until.strftime(scu.DATE_FMT)
|
|
if evaluation.blocked_until
|
|
and evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
|
|
else ""
|
|
)
|
|
#
|
|
form = [
|
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
|
("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
|
|
("moduleimpl_id", {"default": moduleimpl_id, "input_type": "hidden"}),
|
|
("numero", {"default": initvalues["numero"], "input_type": "hidden"}),
|
|
(
|
|
"jour",
|
|
{
|
|
"input_type": "datedmy",
|
|
"title": "Date",
|
|
"size": 12,
|
|
"explanation": "date de l'examen, devoir ou contrôle",
|
|
},
|
|
),
|
|
(
|
|
"heure_debut",
|
|
{
|
|
"title": "Heure de début",
|
|
"explanation": "heure du début de l'épreuve",
|
|
"input_type": "time",
|
|
},
|
|
),
|
|
(
|
|
"heure_fin",
|
|
{
|
|
"title": "Heure de fin",
|
|
"explanation": "heure de fin de l'épreuve",
|
|
"input_type": "time",
|
|
},
|
|
),
|
|
]
|
|
if is_malus: # pas de coefficient
|
|
form.append(("coefficient", {"input_type": "hidden", "default": "1."}))
|
|
elif not is_apc: # modules standard hors BUT
|
|
form.append(
|
|
(
|
|
"coefficient",
|
|
{
|
|
"size": 6,
|
|
"type": "float", # peut être négatif (!)
|
|
"explanation": """coef. dans le module (choisi librement par
|
|
l'enseignant, non utilisé pour rattrapage, 2ème session et bonus)""",
|
|
"allow_null": False,
|
|
},
|
|
)
|
|
)
|
|
form += [
|
|
(
|
|
"note_max",
|
|
{
|
|
"size": 4,
|
|
"type": "float",
|
|
"title": "Notes de 0 à",
|
|
"explanation": f"""barème (note max actuelle: {min_note_max_str}).""",
|
|
"allow_null": False,
|
|
"max_value": scu.NOTES_MAX,
|
|
"min_value": min_note_max,
|
|
},
|
|
),
|
|
(
|
|
"description",
|
|
{
|
|
"size": 36,
|
|
"type": "text",
|
|
"explanation": """type d'évaluation, apparait sur le bulletins longs.
|
|
Exemples: "contrôle court", "examen de TP", "examen final".""",
|
|
},
|
|
),
|
|
(
|
|
"visibulletin",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Visible sur bulletins",
|
|
"explanation": "(pour les bulletins en version intermédiaire/passerelle)",
|
|
},
|
|
),
|
|
(
|
|
"publish_incomplete",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Prise en compte immédiate",
|
|
"explanation": """notes utilisées même si incomplètes (dangereux,
|
|
à n'utiliser que dans des cas particuliers
|
|
<a target="_blank" rel="noopener noreferrer"
|
|
href="https://scodoc.org/Evaluation/#pourquoi-eviter-dutiliser-prise-en-compte-immediate"
|
|
>voir la documentation</a>
|
|
)
|
|
""",
|
|
},
|
|
),
|
|
(
|
|
"evaluation_type",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Modalité",
|
|
"allowed_values": Evaluation.VALID_EVALUATION_TYPES,
|
|
"type": "int",
|
|
"labels": (
|
|
"Normale",
|
|
"Rattrapage (remplace si meilleure note)",
|
|
"Deuxième session (remplace toujours)",
|
|
(
|
|
"Bonus "
|
|
+ (
|
|
"(pondéré par poids et ajouté aux moyennes de ce module)"
|
|
if is_apc
|
|
else "(ajouté à la moyenne de ce module, différent du bonus sport !)"
|
|
)
|
|
),
|
|
),
|
|
},
|
|
),
|
|
]
|
|
if is_apc: # ressources et SAÉs
|
|
form += [
|
|
(
|
|
"coefficient",
|
|
{
|
|
"size": 6,
|
|
"type": "float",
|
|
"explanation": """importance de l'évaluation (multiplie les poids ci-dessous).
|
|
Non utilisé pour les bonus.""",
|
|
"allow_null": False,
|
|
"dom_id": "evaluation-edit-coef",
|
|
},
|
|
),
|
|
]
|
|
# Liste des UE utilisées dans des modules de ce semestre:
|
|
if sem_ues and not can_edit_poids:
|
|
form.append(
|
|
(
|
|
"sep_poids",
|
|
{
|
|
"input_type": "separator",
|
|
"title": """
|
|
<div class="warning-light">les poids ne sont pas modifiables (voir réglage paramétrage)
|
|
</div>""",
|
|
},
|
|
)
|
|
)
|
|
for ue in sem_ues:
|
|
coef_ue = ue_coef_dict.get(ue.id, 0.0)
|
|
form.append(
|
|
(
|
|
f"poids_{ue.id}",
|
|
{
|
|
"title": f"Poids {ue.acronyme}",
|
|
"size": 2,
|
|
"type": "float",
|
|
"explanation": f"""
|
|
<span class="eval_coef_ue" title="coef. du module dans cette UE">({
|
|
"coef. mod.:" +str(coef_ue) if coef_ue
|
|
else "ce module n'a pas de coef. dans cette UE"
|
|
})</span>
|
|
<span class="eval_coef_ue_titre">{ue.titre or ''}</span>
|
|
""",
|
|
"allow_null": False,
|
|
# ok si poids nul ou coef vers l'UE nul:
|
|
"validator": lambda val, field: (not val)
|
|
or ue_coef_dict.get(int(field[len("poids_") :]), 0.0) != 0,
|
|
"enabled": (coef_ue != 0 or initvalues[f"poids_{ue.id}"] != 0.0)
|
|
and can_edit_poids,
|
|
},
|
|
),
|
|
)
|
|
# Bloquage / date prise en compte
|
|
form += [
|
|
(
|
|
"blocked",
|
|
{
|
|
"input_type": "boolcheckbox",
|
|
"title": "Bloquer la prise en compte",
|
|
"explanation": """empêche la prise en compte
|
|
(ne sera pas visible sur les bulletins ni dans les tableaux)""",
|
|
"dom_id": "evaluation-edit-blocked",
|
|
},
|
|
),
|
|
(
|
|
"blocked_until",
|
|
{
|
|
"input_type": "datedmy",
|
|
"title": "Date déblocage",
|
|
"size": 12,
|
|
"explanation": "sera débloquée à partir de cette date",
|
|
},
|
|
),
|
|
]
|
|
tf = TrivialFormulator(
|
|
request.base_url,
|
|
vals,
|
|
form,
|
|
cancelbutton="Annuler",
|
|
submitlabel=submitlabel,
|
|
initvalues=initvalues,
|
|
readonly=False,
|
|
)
|
|
|
|
dest_url = url_for(
|
|
"notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id
|
|
)
|
|
H = [
|
|
f"""<h3>{action} en
|
|
{scu.MODULE_TYPE_NAMES[module.module_type]} <a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}">{module.code or "module sans code"} {
|
|
module.titre or module.abbrev or "(sans titre)"
|
|
}</a> {link}</h3>
|
|
"""
|
|
]
|
|
if tf[0] == 0:
|
|
head = html_sco_header.sco_header(page_title=page_title)
|
|
return (
|
|
head
|
|
+ "\n".join(H)
|
|
+ "\n"
|
|
+ tf[1]
|
|
+ render_template(
|
|
"scodoc/forms/evaluation_edit.j2",
|
|
)
|
|
+ render_template(
|
|
"scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl
|
|
)
|
|
+ render_template("sco_timepicker.j2")
|
|
+ html_sco_header.sco_footer()
|
|
)
|
|
elif tf[0] == -1:
|
|
return flask.redirect(dest_url)
|
|
else:
|
|
# form submission
|
|
args = tf[2]
|
|
# modifie le codage des dates
|
|
# (nb: ce formulaire ne permet de créer que des évaluation sur la même journée)
|
|
date_debut = scu.convert_fr_date(args["jour"]) if args.get("jour") else None
|
|
args["date_debut"] = date_debut
|
|
args["date_fin"] = date_debut # même jour
|
|
args.pop("jour", None)
|
|
if date_debut and 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)
|
|
# note: ce formulaire ne permet de créer que des évaluations
|
|
# avec debut et fin sur le même jour.
|
|
if date_debut and 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)
|
|
# Blocage:
|
|
if args.get("blocked"):
|
|
if args.get("blocked_until"):
|
|
try:
|
|
args["blocked_until"] = datetime.datetime.strptime(
|
|
args["blocked_until"], scu.DATE_FMT
|
|
)
|
|
except ValueError as exc:
|
|
raise ScoValueError("Date déblocage (j/m/a) invalide") from exc
|
|
else: # bloquage coché sans date
|
|
args["blocked_until"] = Evaluation.BLOCKED_FOREVER
|
|
else: # si pas coché, efface date déblocage
|
|
args["blocked_until"] = None
|
|
#
|
|
if edit:
|
|
check_and_convert_evaluation_args(args, modimpl)
|
|
evaluation.from_dict(args)
|
|
else:
|
|
# création d'une evaluation
|
|
evaluation = Evaluation.create(moduleimpl=modimpl, **args)
|
|
db.session.add(evaluation)
|
|
db.session.commit()
|
|
evaluation_id = evaluation.id
|
|
if is_apc:
|
|
# Set poids
|
|
evaluation = db.session.get(Evaluation, evaluation_id)
|
|
for ue in sem_ues:
|
|
evaluation.set_ue_poids(ue, tf[2][f"poids_{ue.id}"])
|
|
db.session.add(evaluation)
|
|
db.session.commit()
|
|
sco_cache.invalidate_formsemestre(evaluation.moduleimpl.formsemestre.id)
|
|
return flask.redirect(dest_url)
|