forked from ScoDoc/ScoDoc
388 lines
14 KiB
Python
388 lines
14 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2023 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 url_for, render_template
|
|
from flask import g
|
|
from flask_login import current_user
|
|
from flask import request
|
|
|
|
from app import db
|
|
from app.models import Evaluation, FormSemestre, ModuleImpl
|
|
from app.models.evaluations import heure_to_time
|
|
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.sco_utils import ModuleType
|
|
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_moduleimpl
|
|
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)"
|
|
if evaluation_id is not None:
|
|
evaluation: 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 = (
|
|
ModuleImpl.query.filter_by(id=moduleimpl_id)
|
|
.join(FormSemestre)
|
|
.filter_by(dept_id=g.scodoc_dept_id)
|
|
.first_or_404()
|
|
)
|
|
modimpl_o = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[
|
|
0
|
|
]
|
|
mod = modimpl_o["module"]
|
|
formsemestre_id = modimpl_o["formsemestre_id"]
|
|
formsemestre = modimpl.formsemestre
|
|
sem_ues = formsemestre.get_ues(with_sport=False)
|
|
is_malus = mod["module_type"] == ModuleType.MALUS
|
|
is_apc = mod["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")
|
|
initvalues = {
|
|
"note_max": 20,
|
|
"jour": time.strftime("%d/%m/%Y", time.localtime()),
|
|
"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 les données"
|
|
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"
|
|
#
|
|
H = [
|
|
f"""<h3>{action} en
|
|
{scu.MODULE_TYPE_NAMES[mod["module_type"]]} <a class="stdlink" href="{
|
|
url_for("notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id)
|
|
}">{mod["code"] or "module sans code"} {mod["titre"]}</a> {link}</h3>
|
|
"""
|
|
]
|
|
|
|
heures = [f"{h:02d}h{m:02d}" for h in range(8, 19) for m in (0, 30)]
|
|
#
|
|
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
|
|
#
|
|
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"}),
|
|
(
|
|
"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": "menu",
|
|
"allowed_values": heures,
|
|
"labels": heures,
|
|
},
|
|
),
|
|
(
|
|
"heure_fin",
|
|
{
|
|
"title": "Heure de fin",
|
|
"explanation": "heure de fin de l'épreuve",
|
|
"input_type": "menu",
|
|
"allowed_values": heures,
|
|
"labels": heures,
|
|
},
|
|
),
|
|
]
|
|
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 et 2ème session)",
|
|
"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",
|
|
},
|
|
),
|
|
(
|
|
"evaluation_type",
|
|
{
|
|
"input_type": "menu",
|
|
"title": "Modalité",
|
|
"allowed_values": (
|
|
scu.EVALUATION_NORMALE,
|
|
scu.EVALUATION_RATTRAPAGE,
|
|
scu.EVALUATION_SESSION2,
|
|
),
|
|
"type": "int",
|
|
"labels": (
|
|
"Normale",
|
|
"Rattrapage (remplace si meilleure note)",
|
|
"Deuxième session (remplace toujours)",
|
|
),
|
|
},
|
|
),
|
|
]
|
|
if is_apc: # ressources et SAÉs
|
|
form += [
|
|
(
|
|
"coefficient",
|
|
{
|
|
"size": 6,
|
|
"type": "float",
|
|
"explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
|
|
"allow_null": False,
|
|
},
|
|
),
|
|
]
|
|
# 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}</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,
|
|
},
|
|
),
|
|
)
|
|
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
|
|
)
|
|
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/help/evaluations.j2", is_apc=is_apc)
|
|
+ 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)
|
|
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:
|
|
date_debut = None
|
|
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 évaluation 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)
|
|
#
|
|
if edit:
|
|
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)
|