# -*- 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()}

Opération non autorisée

Modification évaluation impossible pour {current_user.get_nomplogin()}

Revenir

{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("%d/%m/%Y", 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("%d/%m/%Y") 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", }, ), ( "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)" ) ), ), }, ), ] 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": """
les poids ne sont pas modifiables (voir réglage paramétrage)
""", }, ) ) 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""" ({ "coef. mod.:" +str(coef_ue) if coef_ue else "ce module n'a pas de coef. dans cette UE" }) {ue.titre or ''} """, "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"""

{action} en {scu.MODULE_TYPE_NAMES[module.module_type]} {module.code or "module sans code"} { module.titre or module.abbrev or "(sans titre)" } {link}

""" ] 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, 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) 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["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"], "%d/%m/%Y" ) 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)