# -*- 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 # ############################################################################## """Gestion évaluations (ScoDoc7, code en voie de modernisation) """ import pprint import flask from flask import url_for, g from flask_login import current_user from app import db, log from app.models import Evaluation, ModuleImpl, ScolarNews from app.models.evaluations import evaluation_enrich_dict, check_convert_evaluation_args import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc import sco_cache from app.scodoc import sco_moduleimpl from app.scodoc import sco_permissions_check _evaluationEditor = ndb.EditableTable( "notes_evaluation", "evaluation_id", ( "evaluation_id", "moduleimpl_id", "jour", "heure_debut", "heure_fin", "description", "note_max", "coefficient", "visibulletin", "publish_incomplete", "evaluation_type", "numero", ), sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord output_formators={ "jour": ndb.DateISOtoDMY, "numero": ndb.int_null_is_zero, }, input_formators={ "jour": ndb.DateDMYtoISO, "heure_debut": ndb.TimetoISO8601, # converti par evaluation_enrich_dict "heure_fin": ndb.TimetoISO8601, # converti par evaluation_enrich_dict "visibulletin": bool, "publish_incomplete": bool, "evaluation_type": int, }, ) def do_evaluation_list(args, sortkey=None): """List evaluations, sorted by numero (or most recent date first). Ajoute les champs: 'duree' : '2h30' 'matin' : 1 (commence avant 12:00) ou 0 'apresmidi' : 1 (termine après 12:00) ou 0 'descrheure' : ' de 15h00 à 16h30' """ cnx = ndb.GetDBConnexion() evals = _evaluationEditor.list(cnx, args, sortkey=sortkey) # calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi for e in evals: evaluation_enrich_dict(e) return evals def do_evaluation_list_in_formsemestre(formsemestre_id): "list evaluations in this formsemestre" mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id) evals = [] for modimpl in mods: evals += do_evaluation_list(args={"moduleimpl_id": modimpl["moduleimpl_id"]}) return evals def do_evaluation_edit(args): "edit an evaluation" evaluation_id = args["evaluation_id"] evaluation: Evaluation = db.session.get(Evaluation, evaluation_id) if evaluation is None: raise ValueError("evaluation inexistante !") if not evaluation.moduleimpl.can_edit_evaluation(current_user): raise AccessDenied( f"Modification évaluation impossible pour {current_user.get_nomplogin()}" ) args["moduleimpl_id"] = evaluation.moduleimpl.id check_convert_evaluation_args(evaluation.moduleimpl, args) cnx = ndb.GetDBConnexion() _evaluationEditor.edit(cnx, args) # inval cache pour ce semestre sco_cache.invalidate_formsemestre( formsemestre_id=evaluation.moduleimpl.formsemestre_id ) def do_evaluation_delete(evaluation_id): "delete evaluation" evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id) modimpl: ModuleImpl = evaluation.moduleimpl if not modimpl.can_edit_evaluation(current_user): raise AccessDenied( f"Modification évaluation impossible pour {current_user.get_nomplogin()}" ) notes_db = do_evaluation_get_all_notes(evaluation_id) # { etudid : value } notes = [x["value"] for x in notes_db.values()] if notes: raise ScoValueError( "Impossible de supprimer cette évaluation: il reste des notes" ) db.session.delete(evaluation) db.session.commit() # inval cache pour ce semestre sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id) # news url = url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id, ) ScolarNews.add( typ=ScolarNews.NEWS_NOTE, obj=modimpl.id, text=f"""Suppression d'une évaluation dans {modimpl.module.titre}""", url=url, ) # ancien _notes_getall def do_evaluation_get_all_notes( evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None ): """Toutes les notes pour une évaluation: { etudid : { 'value' : value, 'date' : date ... }} Attention: inclut aussi les notes des étudiants qui ne sont plus inscrits au module. """ # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant do_cache = filter_suppressed and table == "notes_notes" and (by_uid is None) if do_cache: r = sco_cache.EvaluationCache.get(evaluation_id) if r is not None: return r cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cond = " where evaluation_id=%(evaluation_id)s" if by_uid: cond += " and uid=%(by_uid)s" cursor.execute( "select * from " + table + cond, {"evaluation_id": evaluation_id, "by_uid": by_uid}, ) res = cursor.dictfetchall() d = {} if filter_suppressed: for x in res: if x["value"] != scu.NOTES_SUPPRESS: d[x["etudid"]] = x else: for x in res: d[x["etudid"]] = x if do_cache: status = sco_cache.EvaluationCache.set(evaluation_id, d) if not status: log(f"Warning: EvaluationCache.set: {evaluation_id}\t{status}") return d def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1): """Move before/after previous one (decrement/increment numero) (published) """ evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id) moduleimpl_id = evaluation.moduleimpl_id redirect = int(redirect) # access: can change eval ? if not evaluation.moduleimpl.can_edit_evaluation(current_user): raise AccessDenied( f"Modification évaluation impossible pour {current_user.get_nomplogin()}" ) Evaluation.moduleimpl_evaluation_renumber( evaluation.moduleimpl, only_if_unumbered=True ) e = do_evaluation_list(args={"evaluation_id": evaluation_id})[0] after = int(after) # 0: deplace avant, 1 deplace apres if after not in (0, 1): raise ValueError('invalid value for "after"') mod_evals = do_evaluation_list({"moduleimpl_id": e["moduleimpl_id"]}) if len(mod_evals) > 1: idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id) neigh = None # object to swap with if after == 0 and idx > 0: neigh = mod_evals[idx - 1] elif after == 1 and idx < len(mod_evals) - 1: neigh = mod_evals[idx + 1] if neigh: # if neigh["numero"] == e["numero"]: log("Warning: moduleimpl_evaluation_move: forcing renumber") Evaluation.moduleimpl_evaluation_renumber( evaluation.moduleimpl, only_if_unumbered=False ) else: # swap numero with neighbor e["numero"], neigh["numero"] = neigh["numero"], e["numero"] do_evaluation_edit(e) do_evaluation_edit(neigh) # redirect to moduleimpl page: if redirect: return flask.redirect( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=e["moduleimpl_id"], ) )