# -*- 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@viennet.net # ############################################################################## """ScoDoc : annulation des saisies de notes note = {evaluation_id, etudid, value, date, uid, comment} Pour une évaluation: - notes actuelles: table notes_notes - historique: table notes_notes_log saisie de notes == saisir ou supprimer une ou plusieurs notes (mêmes date et uid) ! tolérance sur les dates (200ms ?) Chaque saisie affecte ou remplace une ou plusieurs notes. Opérations: - lister les saisies de notes - annuler une saisie complète - lister les modifs d'une seule note - annuler une modif d'une note """ import datetime from flask import request from app.models import Evaluation, FormSemestre from app.scodoc.intervals import intervalmap import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc import sco_evaluation_db from app.scodoc import sco_preferences from app.scodoc import sco_users from app.scodoc.gen_tables import GenTable import sco_version # deux notes (de même uid) sont considérées comme de la même opération si # elles sont séparées de moins de 2*tolerance: OPERATION_DATE_TOLERANCE = datetime.timedelta(seconds=0.1) class NotesOperation(dict): """Represents an operation on an evaluation Keys: evaluation_id, date, uid, notes """ def get_comment(self): if self["notes"]: return self["notes"][0]["comment"] else: return "" def comp_values(self): "compute keys: comment, nb_notes" self["comment"] = self.get_comment() self["nb_notes"] = len(self["notes"]) self["datestr"] = self["date"].strftime("%a %d/%m/%y %Hh%M") def undo(self): "undo operation" pass # replace notes by last found in notes_log # and suppress log entry # select * from notes_notes_log where evaluation_id= and etudid= and date < # # verrouille tables notes, notes_log # pour chaque note qui n'est pas plus recente que l'operation: # recupere valeurs precedentes dans log # affecte valeurs notes # suppr log # deverrouille tablesj # for note in self['notes']: # # il y a-t-il une modif plus recente ? # if self['current_notes_by_etud']['date'] <= self['date'] + OPERATION_DATE_TOLERANCE: # # + invalider cache sco_cache.EvaluationCache.delete(evaluation_id) def list_operations(evaluation_id): """returns list of NotesOperation for this evaluation""" notes = list( sco_evaluation_db.do_evaluation_get_all_notes( evaluation_id, filter_suppressed=False ).values() ) notes_log = list( sco_evaluation_db.do_evaluation_get_all_notes( evaluation_id, filter_suppressed=False, table="notes_notes_log" ).values() ) dt = OPERATION_DATE_TOLERANCE NotesDates = {} # { uid : intervalmap } for note in notes + notes_log: if note["uid"] not in NotesDates: NotesDates[note["uid"]] = intervalmap() nd = NotesDates[note["uid"]] if nd[note["date"]] is None: nd[note["date"] - dt : note["date"] + dt] = [note] else: nd[note["date"]].append(note) current_notes_by_etud = {} # { etudid : note } for note in notes: current_notes_by_etud[note["etudid"]] = note Ops = [] for uid in NotesDates.keys(): user_name = "{prenomnom} ({user_name})".format(**sco_users.user_info(uid)) for (t0, _), notes in NotesDates[uid].items(): Op = NotesOperation( evaluation_id=evaluation_id, date=t0, uid=uid, user_name=user_name, notes=NotesDates[uid][t0], current_notes_by_etud=current_notes_by_etud, ) Op.comp_values() Ops.append(Op) return Ops def evaluation_list_operations(evaluation_id): """Page listing operations on evaluation""" evaluation = Evaluation.get_evaluation(evaluation_id) operations = list_operations(evaluation_id) columns_ids = ("datestr", "user_name", "nb_notes", "comment") titles = { "datestr": "Date", "user_name": "Enseignant", "nb_notes": "Nb de notes", "comment": "Commentaire", } tab = GenTable( titles=titles, columns_ids=columns_ids, rows=operations, html_sortable=False, html_title=f"""<h2>Opérations sur l'évaluation {evaluation.description} { evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)" }</h2>""", preferences=sco_preferences.SemPreferences( evaluation.moduleimpl.formsemestre_id ), ) return tab.make_page() def formsemestre_list_saisies_notes(formsemestre_id, fmt="html"): """Table listant toutes les opérations de saisies de notes, dans toutes les évaluations du semestre. """ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) rows = ndb.SimpleDictFetch( """SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.date_debut, u.user_name, e.id as evaluation_id FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi, notes_modules mod, identite i, "user" u WHERE mi.id = e.moduleimpl_id and mi.module_id = mod.id and e.id = n.evaluation_id and i.id = n.etudid and u.id = n.uid and mi.formsemestre_id = %(formsemestre_id)s ORDER BY date desc """, {"formsemestre_id": formsemestre_id}, ) # Formate les notes keep_numeric = fmt in scu.FORMATS_NUMERIQUES for row in rows: row["value"] = scu.fmt_note(row["value"], keep_numeric=keep_numeric) row["date_evaluation"] = ( row["date_debut"].strftime("%d/%m/%Y %H:%M") if row["date_debut"] else "" ) row["_date_evaluation_order"] = ( row["date_debut"].isoformat() if row["date_debut"] else "" ) columns_ids = ( "date", "code_nip", "nom", "prenom", "value", "user_name", "titre", "evaluation_id", "description", "date_evaluation", "comment", ) titles = { "code_nip": "NIP", "nom": "nom", "prenom": "prenom", "date": "Date", "value": "Note", "comment": "Remarque", "user_name": "Enseignant", "evaluation_id": "evaluation_id", "titre": "Module", "description": "Evaluation", "date_evaluation": "Date éval.", } tab = GenTable( titles=titles, columns_ids=columns_ids, rows=rows, html_title=f"<h2>Saisies de notes dans {formsemestre.titre_annee()}</h2>", html_class="table_leftalign table_coldate gt_table_searchable", html_class_ignore_default=True, html_sortable=True, caption=f"Saisies de notes dans {formsemestre.titre_annee()}", preferences=sco_preferences.SemPreferences(formsemestre_id), base_url="%s?formsemestre_id=%s" % (request.base_url, formsemestre_id), origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "", ) return tab.make_page(fmt=fmt) def get_note_history(evaluation_id, etudid, fmt=""): """Historique d'une note = liste chronologique d'opérations, la plus récente d'abord [ { 'value', 'date', 'comment', 'uid' } ] """ cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) # Valeur courante cursor.execute( """ SELECT * FROM notes_notes WHERE evaluation_id=%(evaluation_id)s AND etudid=%(etudid)s """, {"evaluation_id": evaluation_id, "etudid": etudid}, ) history = cursor.dictfetchall() # Historique cursor.execute( """ SELECT * FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s AND etudid=%(etudid)s ORDER BY date DESC""", {"evaluation_id": evaluation_id, "etudid": etudid}, ) history += cursor.dictfetchall() # Replace None comments by '' # et cherche nom complet de l'enseignant: for x in history: x["comment"] = x["comment"] or "" x["user_name"] = sco_users.user_info(x["uid"])["nomcomplet"] if fmt == "json": return scu.sendJSON(history) else: return history """ from debug import * from app.scodoc.sco_undo_notes import * _ = go_dept(app, 'RT').Notes get_note_history( 'EVAL29740', 'EID28403') """