forked from ScoDoc/ScoDoc
400 lines
13 KiB
Python
400 lines
13 KiB
Python
# -*- 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"""<h2>Opérations sur l'évaluation
|
|
<a class="stdlink" href="{
|
|
url_for("notes.evaluation_listenotes", scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
|
}">{evaluation.description}</a>
|
|
{evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"}
|
|
</h2>""",
|
|
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"<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=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')
|
|
"""
|