############################################################################## # ScoDoc # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """ ScoDoc 9 API : accès aux évaluations """ from flask import g, request from flask_json import as_json from flask_login import current_user, login_required import app from app import log, db from app.api import api_bp as bp, api_web_bp from app.decorators import scodoc, permission_required from app.models import Evaluation, ModuleImpl, FormSemestre from app.scodoc import sco_evaluation_db, sco_saisie_notes from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_permissions import Permission import app.scodoc.sco_utils as scu @bp.route("/evaluation/") @api_web_bp.route("/evaluation/") @login_required @scodoc @permission_required(Permission.ScoView) @as_json def get_evaluation(evaluation_id: int): """Description d'une évaluation. { 'coefficient': 1.0, 'date_debut': '2016-01-04T08:30:00', 'date_fin': '2016-01-04T12:30:00', 'description': 'TP NI9219 Température', 'evaluation_type': 0, 'id': 15797, 'moduleimpl_id': 1234, 'note_max': 20.0, 'numero': 3, 'poids': { 'UE1.1': 1.0, 'UE1.2': 1.0, 'UE1.3': 1.0 }, 'publish_incomplete': False, 'visibulletin': True } """ query = Evaluation.query.filter_by(id=evaluation_id) if g.scodoc_dept: query = ( query.join(ModuleImpl) .join(FormSemestre) .filter_by(dept_id=g.scodoc_dept_id) ) e = query.first_or_404() return e.to_dict_api() @bp.route("/moduleimpl//evaluations") @api_web_bp.route("/moduleimpl//evaluations") @login_required @scodoc @permission_required(Permission.ScoView) @as_json def moduleimpl_evaluations(moduleimpl_id: int): """ Retourne la liste des évaluations d'un moduleimpl moduleimpl_id : l'id d'un moduleimpl Exemple de résultat : voir /evaluation """ modimpl = ModuleImpl.get_modimpl(moduleimpl_id) return [evaluation.to_dict_api() for evaluation in modimpl.evaluations] @bp.route("/evaluation//notes") @api_web_bp.route("/evaluation//notes") @login_required @scodoc @permission_required(Permission.ScoView) @as_json def evaluation_notes(evaluation_id: int): """ Retourne la liste des notes de l'évaluation evaluation_id : l'id de l'évaluation Exemple de résultat : { "11": { "etudid": 11, "evaluation_id": 1, "value": 15.0, "comment": "", "date": "Wed, 20 Apr 2022 06:49:05 GMT", "uid": 2 }, "12": { "etudid": 12, "evaluation_id": 1, "value": 12.0, "comment": "", "date": "Wed, 20 Apr 2022 06:49:06 GMT", "uid": 2 }, ... } """ query = Evaluation.query.filter_by(id=evaluation_id) if g.scodoc_dept: query = ( query.join(ModuleImpl) .join(FormSemestre) .filter_by(dept_id=g.scodoc_dept_id) ) evaluation = query.first_or_404() dept = evaluation.moduleimpl.formsemestre.departement app.set_sco_dept(dept.acronym) notes = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) for etudid in notes: # "ABS", "EXC", etc mais laisse les notes sur le barème de l'éval. note = notes[etudid] note["value"] = scu.fmt_note(note["value"], keep_numeric=True) note["note_max"] = evaluation.note_max del note["id"] # in JS, keys must be string, not integers return {str(etudid): note for etudid, note in notes.items()} @bp.route("/evaluation//notes/set", methods=["POST"]) @api_web_bp.route("/evaluation//notes/set", methods=["POST"]) @login_required @scodoc @permission_required(Permission.EnsView) @as_json def evaluation_set_notes(evaluation_id: int): # evaluation-notes-set """Écriture de notes dans une évaluation. The request content type should be "application/json", and contains: { 'notes' : [ [etudid, value], ... ], 'comment' : optional string } Result: - nb_changed: nombre de notes changées - nb_suppress: nombre de notes effacées - etudids_with_decision: liste des etudiants dont la note a changé alors qu'ils ont une décision de jury enregistrée. """ query = Evaluation.query.filter_by(id=evaluation_id) if g.scodoc_dept: query = ( query.join(ModuleImpl) .join(FormSemestre) .filter_by(dept_id=g.scodoc_dept_id) ) evaluation = query.first_or_404() dept = evaluation.moduleimpl.formsemestre.departement app.set_sco_dept(dept.acronym) data = request.get_json(force=True) # may raise 400 Bad Request notes = data.get("notes") if notes is None: return scu.json_error(404, "no notes") if not isinstance(notes, list): return scu.json_error(404, "invalid notes argument (must be a list)") return sco_saisie_notes.save_notes( evaluation, notes, comment=data.get("comment", "") ) @bp.route("/moduleimpl//evaluation/create", methods=["POST"]) @api_web_bp.route("/moduleimpl//evaluation/create", methods=["POST"]) @login_required @scodoc @permission_required(Permission.EnsView) # permission gérée dans la fonction @as_json def evaluation_create(moduleimpl_id: int): """Création d'une évaluation. The request content type should be "application/json", and contains: { "description" : str, "evaluation_type" : int, // {0,1,2} default 0 (normale) "date_debut" : date_iso, // optionnel "date_fin" : date_iso, // optionnel "note_max" : float, // si non spécifié, 20.0 "numero" : int, // ordre de présentation, default tri sur date "visibulletin" : boolean , //default true "publish_incomplete" : boolean , //default false "coefficient" : float, // si non spécifié, 1.0 "poids" : { ue_id : poids } // optionnel } Result: l'évaluation créée. """ moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(moduleimpl_id) if not moduleimpl.can_edit_evaluation(current_user): return scu.json_error(403, "opération non autorisée") data = request.get_json(force=True) # may raise 400 Bad Request try: evaluation = Evaluation.create(moduleimpl=moduleimpl, **data) except ValueError: return scu.json_error(400, "paramètre incorrect") except ScoValueError as exc: return scu.json_error( 400, f"paramètre de type incorrect ({exc.args[0] if exc.args else ''})" ) db.session.add(evaluation) db.session.commit() # Les poids vers les UEs: poids = data.get("poids") if poids is not None: if not isinstance(poids, dict): log("API error: canceling evaluation creation") db.session.delete(evaluation) db.session.commit() return scu.json_error( 400, "paramètre de type incorrect (poids must be a dict)" ) try: evaluation.set_ue_poids_dict(data["poids"]) except ScoValueError as exc: log("API error: canceling evaluation creation") db.session.delete(evaluation) db.session.commit() return scu.json_error( 400, f"erreur enregistrement des poids ({exc.args[0] if exc.args else ''})", ) db.session.commit() return evaluation.to_dict_api() @bp.route("/evaluation//delete", methods=["POST"]) @api_web_bp.route("/evaluation//delete", methods=["POST"]) @login_required @scodoc @permission_required(Permission.EnsView) # permission gérée dans la fonction @as_json def evaluation_delete(evaluation_id: int): """Suppression d'une évaluation. Efface aussi toutes ses notes """ query = Evaluation.query.filter_by(id=evaluation_id) if g.scodoc_dept: query = ( query.join(ModuleImpl) .join(FormSemestre) .filter_by(dept_id=g.scodoc_dept_id) ) evaluation = query.first_or_404() dept = evaluation.moduleimpl.formsemestre.departement app.set_sco_dept(dept.acronym) if not evaluation.moduleimpl.can_edit_evaluation(current_user): raise AccessDenied("evaluation_delete") sco_saisie_notes.evaluation_suppress_alln( evaluation_id=evaluation_id, dialog_confirmed=True ) evaluation.delete() return "ok"