# -*- 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 g, render_template, request, url_for from app import db from app.auth.models import User from app.models import Evaluation, FormSemestre, ModuleImpl, NotesNotes, NotesNotesLog 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) -> str: "le commentaire ou une chaine vide" if self["notes"]: return self["notes"][0]["comment"] return "" def comp_values(self): "compute keys: comment, nb_notes" self["comment"] = self.get_comment() self["nb_notes"] = len(self["notes"]) self["date_str"] = self["date"].strftime("%a %d/%m/%y %Hh%M") self["_date_str_order"] = self["date"].isoformat() 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 notes_dates = {} # { uid : intervalmap } for note in notes + notes_log: if note["uid"] not in notes_dates: notes_dates[note["uid"]] = intervalmap() nd = notes_dates[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 operations = [] for uid, note_date in notes_dates.items(): user_name = "{prenomnom} ({user_name})".format(**sco_users.user_info(uid)) for (t0, _), notes in note_date.items(): operation = NotesOperation( evaluation_id=evaluation_id, date=t0, uid=uid, user_name=user_name, notes=note_date[t0], current_notes_by_etud=current_notes_by_etud, ) operation.comp_values() operations.append(operation) return operations def evaluation_list_operations(evaluation_id: int): """Page listing operations on evaluation""" evaluation = Evaluation.get_evaluation(evaluation_id) operations = list_operations(evaluation_id) columns_ids = ("date_str", "user_name", "nb_notes", "comment") titles = { "date_str": "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"""

Opérations sur l'évaluation {evaluation.description} {evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"}

""", preferences=sco_preferences.SemPreferences( evaluation.moduleimpl.formsemestre_id ), table_id="evaluation_list_operations", ) return tab.make_page() def formsemestre_list_notes_intervenants(formsemestre: FormSemestre) -> list[User]: "Liste des comptes ayant saisi au moins une note dans le semestre" q1 = ( User.query.join(NotesNotes) .join(Evaluation) .join(ModuleImpl) .filter_by(formsemestre_id=formsemestre.id) .distinct() ) q2 = ( User.query.join(NotesNotesLog) .join(Evaluation, Evaluation.id == NotesNotesLog.evaluation_id) .join(ModuleImpl) .filter_by(formsemestre_id=formsemestre.id) .distinct() ) return sorted(q1.union(q2).all(), key=lambda x: x.sort_key()) def formsemestre_list_saisies_notes( formsemestre_id, only_modifs=False, user_name: str | None = None, fmt="html" ): """Table listant toutes les opérations de saisies de notes, dans toutes les évaluations du semestre. """ formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) only_modifs = scu.to_bool(only_modifs) model = NotesNotesLog if only_modifs else NotesNotes notes_query = ( db.session.query(model) .join(Evaluation, Evaluation.id == model.evaluation_id) .join(ModuleImpl) .filter_by(formsemestre_id=formsemestre.id) .order_by(model.date.desc()) ) if user_name: user = db.session.query(User).filter_by(user_name=user_name).first() if user: notes_query = notes_query.join(User).filter(model.uid == user.id) # Formate les notes keep_numeric = fmt in scu.FORMATS_NUMERIQUES rows = [] for note in notes_query: ens = User.get_user(note.uid) evaluation = note.evaluation row = { "date": note.date.strftime(scu.DATEATIME_FMT), "_date_order": note.date.isoformat(), "code_nip": note.etudiant.code_nip, "nom": note.etudiant.nom_disp(), "prenom": note.etudiant.prenom_str, "date_evaluation": ( evaluation.date_debut.strftime(scu.DATEATIME_FMT) if evaluation and note.evaluation.date_debut else "" ), "_date_evaluation_order": ( note.evaluation.date_debut.isoformat() if evaluation and note.evaluation.date_debut else "" ), "value": scu.fmt_note(note.value, keep_numeric=keep_numeric), "module": ( ( note.evaluation.moduleimpl.module.code or note.evaluation.moduleimpl.module.titre ) if evaluation else "" ), "evaluation": note.evaluation.description if evaluation else "", "_evaluation_target": ( url_for( "notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, evaluation_id=note.evaluation_id, ) if evaluation else "" ), "user_name": ens.user_name if ens else "", } if only_modifs: # si c'est une modif de note, ajoute une colonne avec la nouvelle valeur new = NotesNotes.query.filter_by( evaluation_id=note.evaluation_id, etudid=note.etudid ).first() if new: row["new_value"] = scu.fmt_note(new.value, keep_numeric=keep_numeric) row["old_date"] = row["date"] row["_old_date_order"] = row["_date_order"] row["date"] = new.date.strftime(scu.DATEATIME_FMT) row["_date_order"] = new.date.isoformat() rows.append(row) columns_ids = ( "date", "code_nip", "nom", "prenom", "value", ) if only_modifs: columns_ids += ( "new_value", "old_date", ) columns_ids += ( "user_name", "module", "evaluation", "date_evaluation", "comment", ) titles = { "code_nip": "NIP", "nom": "nom", "prenom": "prenom", "date": "Date modif." if only_modifs else "Date saisie", "old_date": "Date saisie précédente", "value": "Note", "comment": "Remarque", "user_name": "Enseignant", "evaluation_id": "evaluation_id", "module": "Module", "evaluation": "Evaluation", "date_evaluation": "Date éval.", } if only_modifs: titles["value"] = "Ancienne note" titles["new_value"] = "Nouvelle note" table = GenTable( titles=titles, columns_ids=columns_ids, rows=rows, html_title=f"

Saisies de notes dans {formsemestre.titre_annee()}

", 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=f"""{request.base_url}?formsemestre_id={ formsemestre_id}&only_modifs={int(only_modifs)}""" + (f"&user_name={user_name}" if user_name else ""), origin=f"Généré par {sco_version.SCONAME} le " + scu.timedate_human_repr() + "", table_id="formsemestre_list_saisies_notes", filename=( ( f"modifs_notes-S{formsemestre.semestre_id}" if only_modifs else f"saisies_notes_S{formsemestre.semestre_id}" ) + ("-" + user_name if user_name else "") ), ) if fmt == "html": return render_template( "formsemestre/list_saisies_notes.j2", table=table, title="Opérations de saisies de notes", only_modifs=only_modifs, formsemestre_id=formsemestre.id, intervenants=formsemestre_list_notes_intervenants(formsemestre), user_name=user_name, ) return table.make_page(fmt=fmt, page_title="Opérations de saisies de notes") 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') """