forked from ScoDoc/DocScoDoc
1271 lines
47 KiB
Python
1271 lines
47 KiB
Python
|
# -*- mode: python -*-
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
##############################################################################
|
||
|
#
|
||
|
# Gestion scolarite IUT
|
||
|
#
|
||
|
# Copyright (c) 1999 - 2020 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
|
||
|
#
|
||
|
##############################################################################
|
||
|
|
||
|
"""Saisie des notes
|
||
|
|
||
|
Formulaire revu en juillet 2016
|
||
|
"""
|
||
|
import datetime
|
||
|
|
||
|
from notesdb import *
|
||
|
from sco_utils import *
|
||
|
from notes_log import log
|
||
|
from TrivialFormulator import TrivialFormulator, TF
|
||
|
from notes_table import *
|
||
|
import sco_formsemestre
|
||
|
import sco_groups
|
||
|
import sco_groups_view
|
||
|
from sco_formsemestre_status import makeMenu
|
||
|
import sco_evaluations
|
||
|
import sco_undo_notes
|
||
|
import htmlutils
|
||
|
import sco_excel
|
||
|
import scolars
|
||
|
import sco_news
|
||
|
from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC
|
||
|
|
||
|
|
||
|
def convert_note_from_string(
|
||
|
note,
|
||
|
note_max,
|
||
|
note_min=NOTES_MIN,
|
||
|
etudid=None,
|
||
|
absents=[],
|
||
|
tosuppress=[],
|
||
|
invalids=[],
|
||
|
):
|
||
|
"""converti une valeur (chaine saisie) vers une note numérique (float)
|
||
|
Les listes absents, tosuppress et invalids sont modifiées
|
||
|
"""
|
||
|
invalid = False
|
||
|
note_value = None
|
||
|
note = note.replace(",", ".")
|
||
|
if note[:3] == "ABS":
|
||
|
note_value = None
|
||
|
absents.append(etudid)
|
||
|
elif note[:3] == "NEU" or note[:3] == "EXC":
|
||
|
note_value = NOTES_NEUTRALISE
|
||
|
elif note[:3] == "ATT":
|
||
|
note_value = NOTES_ATTENTE
|
||
|
elif note[:3] == "SUP":
|
||
|
note_value = NOTES_SUPPRESS
|
||
|
tosuppress.append(etudid)
|
||
|
else:
|
||
|
try:
|
||
|
note_value = float(note)
|
||
|
if (note_value < note_min) or (note_value > note_max):
|
||
|
raise ValueError
|
||
|
except:
|
||
|
invalids.append(etudid)
|
||
|
invalid = True
|
||
|
|
||
|
return note_value, invalid
|
||
|
|
||
|
|
||
|
def _displayNote(val):
|
||
|
"""Convert note from DB to viewable string.
|
||
|
Utilisé seulement pour I/O vers formulaires (sans perte de precision)
|
||
|
(Utiliser fmt_note pour les affichages)
|
||
|
"""
|
||
|
if val is None:
|
||
|
val = "ABS"
|
||
|
elif val == NOTES_NEUTRALISE:
|
||
|
val = "EXC" # excuse, note neutralise
|
||
|
elif val == NOTES_ATTENTE:
|
||
|
val = "ATT" # attente, note neutralise
|
||
|
elif val == NOTES_SUPPRESS:
|
||
|
val = "SUPR"
|
||
|
else:
|
||
|
val = "%g" % val
|
||
|
return val
|
||
|
|
||
|
|
||
|
def _check_notes(notes, evaluation, mod):
|
||
|
"""notes is a list of tuples (etudid, value)
|
||
|
mod is the module (used to ckeck type, for malus)
|
||
|
returns list of valid notes (etudid, float value)
|
||
|
and 4 lists of etudid: invalids, withoutnotes, absents, tosuppress, existingjury
|
||
|
"""
|
||
|
note_max = evaluation["note_max"]
|
||
|
if mod["module_type"] == MODULE_STANDARD:
|
||
|
note_min = NOTES_MIN
|
||
|
elif mod["module_type"] == MODULE_MALUS:
|
||
|
note_min = -20.0
|
||
|
else:
|
||
|
raise ValueError("Invalid module type") # bug
|
||
|
L = [] # liste (etudid, note) des notes ok (ou absent)
|
||
|
invalids = [] # etudid avec notes invalides
|
||
|
withoutnotes = [] # etudid sans notes (champs vides)
|
||
|
absents = [] # etudid absents
|
||
|
tosuppress = [] # etudids avec ancienne note à supprimer
|
||
|
|
||
|
for (etudid, note) in notes:
|
||
|
note = str(note).strip().upper()
|
||
|
if note[:3] == "DEM":
|
||
|
continue # skip !
|
||
|
if note:
|
||
|
value, invalid = convert_note_from_string(
|
||
|
note,
|
||
|
note_max,
|
||
|
note_min=note_min,
|
||
|
etudid=etudid,
|
||
|
absents=absents,
|
||
|
tosuppress=tosuppress,
|
||
|
invalids=invalids,
|
||
|
)
|
||
|
if not invalid:
|
||
|
L.append((etudid, value))
|
||
|
else:
|
||
|
withoutnotes.append(etudid)
|
||
|
|
||
|
return L, invalids, withoutnotes, absents, tosuppress
|
||
|
|
||
|
|
||
|
def do_evaluation_upload_xls(context, REQUEST):
|
||
|
"""
|
||
|
Soumission d'un fichier XLS (evaluation_id, notefile)
|
||
|
"""
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
evaluation_id = REQUEST.form["evaluation_id"]
|
||
|
comment = REQUEST.form["comment"]
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
M = context.do_moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
# Check access
|
||
|
# (admin, respformation, and responsable_id)
|
||
|
if not context.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||
|
# XXX imaginer un redirect + msg erreur
|
||
|
raise AccessDenied("Modification des notes impossible pour %s" % authuser)
|
||
|
#
|
||
|
data = REQUEST.form["notefile"].read()
|
||
|
diag, lines = sco_excel.Excel_to_list(data)
|
||
|
try:
|
||
|
if not lines:
|
||
|
raise InvalidNoteValue()
|
||
|
# -- search eval code
|
||
|
n = len(lines)
|
||
|
i = 0
|
||
|
ok = True
|
||
|
while i < n:
|
||
|
if not lines[i]:
|
||
|
diag.append("Erreur: format invalide (ligne vide ?)")
|
||
|
raise InvalidNoteValue()
|
||
|
f0 = lines[i][0].strip()
|
||
|
if f0 and f0[0] == "!":
|
||
|
break
|
||
|
i = i + 1
|
||
|
if i == n:
|
||
|
diag.append("Erreur: format invalide ! (pas de ligne evaluation_id)")
|
||
|
raise InvalidNoteValue()
|
||
|
|
||
|
eval_id = lines[i][0].strip()[1:]
|
||
|
if eval_id != evaluation_id:
|
||
|
diag.append(
|
||
|
"Erreur: fichier invalide: le code d'évaluation de correspond pas ! ('%s' != '%s')"
|
||
|
% (eval_id, evaluation_id)
|
||
|
)
|
||
|
raise InvalidNoteValue()
|
||
|
# --- get notes -> list (etudid, value)
|
||
|
# ignore toutes les lignes ne commençant pas par !
|
||
|
notes = []
|
||
|
ni = i + 1
|
||
|
try:
|
||
|
for line in lines[i + 1 :]:
|
||
|
if line:
|
||
|
cell0 = line[0].strip()
|
||
|
if cell0 and cell0[0] == "!":
|
||
|
etudid = cell0[1:]
|
||
|
if len(line) > 4:
|
||
|
val = line[4].strip()
|
||
|
else:
|
||
|
val = "" # ligne courte: cellule vide
|
||
|
if etudid:
|
||
|
notes.append((etudid, val))
|
||
|
ni += 1
|
||
|
except:
|
||
|
diag.append(
|
||
|
'Erreur: feuille invalide ! (erreur ligne %d)<br/>"%s"'
|
||
|
% (ni, str(lines[ni]))
|
||
|
)
|
||
|
raise InvalidNoteValue()
|
||
|
# -- check values
|
||
|
L, invalids, withoutnotes, absents, tosuppress = _check_notes(
|
||
|
notes, E, M["module"]
|
||
|
)
|
||
|
if len(invalids):
|
||
|
diag.append(
|
||
|
"Erreur: la feuille contient %d notes invalides</p>" % len(invalids)
|
||
|
)
|
||
|
if len(invalids) < 25:
|
||
|
etudsnames = [
|
||
|
context.getEtudInfo(etudid=etudid, filled=True)[0]["nomprenom"]
|
||
|
for etudid in invalids
|
||
|
]
|
||
|
diag.append("Notes invalides pour: " + ", ".join(etudsnames))
|
||
|
raise InvalidNoteValue()
|
||
|
else:
|
||
|
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||
|
context, authuser, evaluation_id, L, comment
|
||
|
)
|
||
|
# news
|
||
|
cnx = context.GetDBConnexion()
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
|
||
|
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||
|
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||
|
sco_news.add(
|
||
|
context,
|
||
|
REQUEST,
|
||
|
typ=NEWS_NOTE,
|
||
|
object=M["moduleimpl_id"],
|
||
|
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % mod,
|
||
|
url=mod["url"],
|
||
|
)
|
||
|
|
||
|
msg = (
|
||
|
"<p>%d notes changées (%d sans notes, %d absents, %d note supprimées)</p>"
|
||
|
% (nb_changed, len(withoutnotes), len(absents), nb_suppress)
|
||
|
)
|
||
|
if existing_decisions:
|
||
|
msg += """<p class="warning">Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !</p>"""
|
||
|
# msg += '<p>' + str(notes) # debug
|
||
|
return 1, msg
|
||
|
|
||
|
except InvalidNoteValue:
|
||
|
if diag:
|
||
|
msg = (
|
||
|
'<ul class="tf-msg"><li class="tf_msg">'
|
||
|
+ '</li><li class="tf_msg">'.join(diag)
|
||
|
+ "</li></ul>"
|
||
|
)
|
||
|
else:
|
||
|
msg = '<ul class="tf-msg"><li class="tf_msg">Une erreur est survenue</li></ul>'
|
||
|
return 0, msg + "<p>(pas de notes modifiées)</p>"
|
||
|
|
||
|
|
||
|
def do_evaluation_set_missing(
|
||
|
context, evaluation_id, value, REQUEST=None, dialog_confirmed=False
|
||
|
):
|
||
|
"""Initialisation des notes manquantes
|
||
|
"""
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
evaluation_id = REQUEST.form["evaluation_id"]
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
M = context.do_moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
# Check access
|
||
|
# (admin, respformation, and responsable_id)
|
||
|
if not context.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||
|
# XXX imaginer un redirect + msg erreur
|
||
|
raise AccessDenied("Modification des notes impossible pour %s" % authuser)
|
||
|
#
|
||
|
NotesDB = context._notes_getall(evaluation_id)
|
||
|
etudids = sco_groups.do_evaluation_listeetuds_groups(
|
||
|
context, evaluation_id, getallstudents=True, include_dems=False
|
||
|
)
|
||
|
notes = []
|
||
|
for etudid in etudids: # pour tous les inscrits
|
||
|
if not NotesDB.has_key(etudid): # pas de note
|
||
|
notes.append((etudid, value))
|
||
|
# Check value
|
||
|
L, invalids, withoutnotes, absents, tosuppress = _check_notes(notes, E, M["module"])
|
||
|
diag = ""
|
||
|
if len(invalids):
|
||
|
diag = "Valeur %s invalide" % value
|
||
|
if diag:
|
||
|
return (
|
||
|
context.sco_header(REQUEST)
|
||
|
+ '<h2>%s</h2><p><a href="saisie_notes?evaluation_id=%s">Recommencer</a>'
|
||
|
% (diag, evaluation_id)
|
||
|
+ context.sco_footer(REQUEST)
|
||
|
)
|
||
|
# Confirm action
|
||
|
if not dialog_confirmed:
|
||
|
return context.confirmDialog(
|
||
|
"""<h2>Mettre toutes les notes manquantes de l'évaluation
|
||
|
à la valeur %s ?</h2>
|
||
|
<p>Seuls les étudiants pour lesquels aucune note (ni valeur, ni ABS, ni EXC)
|
||
|
n'a été rentrée seront affectés.</p>
|
||
|
<p><b>%d étudiants concernés par ce changement de note.</b></p>
|
||
|
<p class="warning">Attention, les étudiants sans notes de tous les groupes de ce semestre seront affectés.</p>
|
||
|
"""
|
||
|
% (value, len(L)),
|
||
|
dest_url="",
|
||
|
REQUEST=REQUEST,
|
||
|
cancel_url="saisie_notes?evaluation_id=%s" % evaluation_id,
|
||
|
parameters={"evaluation_id": evaluation_id, "value": value},
|
||
|
)
|
||
|
# ok
|
||
|
comment = "Initialisation notes manquantes"
|
||
|
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||
|
context, authuser, evaluation_id, L, comment
|
||
|
)
|
||
|
# news
|
||
|
cnx = context.GetDBConnexion()
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
|
||
|
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||
|
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||
|
sco_news.add(
|
||
|
context,
|
||
|
REQUEST,
|
||
|
typ=NEWS_NOTE,
|
||
|
object=M["moduleimpl_id"],
|
||
|
text='Initialisation notes dans <a href="%(url)s">%(titre)s</a>' % mod,
|
||
|
url=mod["url"],
|
||
|
)
|
||
|
return (
|
||
|
context.sco_header(REQUEST)
|
||
|
+ """<h2>%d notes changées</h2>
|
||
|
<ul>
|
||
|
<li><a class="stdlink" href="saisie_notes?evaluation_id=%s">
|
||
|
Revenir au formulaire de saisie des notes</a></li>
|
||
|
<li><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">
|
||
|
Tableau de bord du module</a></li>
|
||
|
</ul>
|
||
|
"""
|
||
|
% (nb_changed, evaluation_id, M["moduleimpl_id"])
|
||
|
+ context.sco_footer(REQUEST)
|
||
|
)
|
||
|
|
||
|
|
||
|
def evaluation_suppress_alln(context, evaluation_id, REQUEST, dialog_confirmed=False):
|
||
|
"suppress all notes in this eval"
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
|
||
|
if context.can_edit_notes(authuser, E["moduleimpl_id"], allow_ens=False):
|
||
|
# On a le droit de modifier toutes les notes
|
||
|
# recupere les etuds ayant une note
|
||
|
NotesDB = context._notes_getall(evaluation_id)
|
||
|
elif context.can_edit_notes(authuser, E["moduleimpl_id"], allow_ens=True):
|
||
|
# Enseignant associé au module: ne peut supprimer que les notes qu'il a saisi
|
||
|
NotesDB = context._notes_getall(evaluation_id, by_uid=str(authuser))
|
||
|
else:
|
||
|
raise AccessDenied("Modification des notes impossible pour %s" % authuser)
|
||
|
|
||
|
notes = [(etudid, NOTES_SUPPRESS) for etudid in NotesDB.keys()]
|
||
|
|
||
|
if not dialog_confirmed:
|
||
|
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||
|
context, authuser, evaluation_id, notes, do_it=False
|
||
|
)
|
||
|
msg = "<p>Confirmer la suppression des %d notes ?</p>" % nb_suppress
|
||
|
if existing_decisions:
|
||
|
msg += """<p class="warning">Important: il y a déjà des décisions de jury enregistrées, qui seront potentiellement à revoir suite à cette modification !</p>"""
|
||
|
return context.confirmDialog(
|
||
|
msg,
|
||
|
dest_url="",
|
||
|
REQUEST=REQUEST,
|
||
|
OK="Supprimer les notes",
|
||
|
cancel_url="moduleimpl_status?moduleimpl_id=%s" % E["moduleimpl_id"],
|
||
|
parameters={"evaluation_id": evaluation_id},
|
||
|
)
|
||
|
|
||
|
# modif
|
||
|
nb_changed, nb_suppress, existing_decisions = _notes_add(
|
||
|
context, authuser, evaluation_id, notes, comment="effacer tout"
|
||
|
)
|
||
|
assert nb_changed == nb_suppress
|
||
|
H = ["<p>%s notes supprimées</p>" % nb_suppress]
|
||
|
if existing_decisions:
|
||
|
H.append(
|
||
|
"""<p class="warning">Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !</p>"""
|
||
|
)
|
||
|
H += [
|
||
|
'<p><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%s">continuer</a>'
|
||
|
% E["moduleimpl_id"]
|
||
|
]
|
||
|
# news
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
|
||
|
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||
|
cnx = context.GetDBConnexion()
|
||
|
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||
|
sco_news.add(
|
||
|
context,
|
||
|
REQUEST,
|
||
|
typ=NEWS_NOTE,
|
||
|
object=M["moduleimpl_id"],
|
||
|
text='Suppression des notes d\'une évaluation dans <a href="%(url)s">%(titre)s</a>'
|
||
|
% mod,
|
||
|
url=mod["url"],
|
||
|
)
|
||
|
|
||
|
return context.sco_header(REQUEST) + "\n".join(H) + context.sco_footer(REQUEST)
|
||
|
|
||
|
|
||
|
def _notes_add(context, uid, evaluation_id, notes, comment=None, do_it=True):
|
||
|
"""
|
||
|
Insert or update notes
|
||
|
notes is a list of tuples (etudid,value)
|
||
|
If do_it is False, simulate the process and returns the number of values that
|
||
|
WOULD be changed or suppressed.
|
||
|
Nota:
|
||
|
- si la note existe deja avec valeur distincte, ajoute une entree au log (notes_notes_log)
|
||
|
Return number of changed notes
|
||
|
"""
|
||
|
uid = str(uid)
|
||
|
now = apply(
|
||
|
psycopg2.Timestamp, time.localtime()[:6]
|
||
|
) # datetime.datetime.now().isoformat()
|
||
|
# Verifie inscription et valeur note
|
||
|
inscrits = {}.fromkeys(
|
||
|
sco_groups.do_evaluation_listeetuds_groups(
|
||
|
context, evaluation_id, getallstudents=True, include_dems=True
|
||
|
)
|
||
|
)
|
||
|
for (etudid, value) in notes:
|
||
|
if not ((value is None) or (type(value) == type(1.0))):
|
||
|
raise NoteProcessError(
|
||
|
"etudiant %s: valeur de note invalide (%s)" % (etudid, value)
|
||
|
)
|
||
|
# Recherche notes existantes
|
||
|
NotesDB = context._notes_getall(evaluation_id)
|
||
|
# Met a jour la base
|
||
|
cnx = context.GetDBConnexion(autocommit=False)
|
||
|
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
|
||
|
nb_changed = 0
|
||
|
nb_suppress = 0
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
existing_decisions = (
|
||
|
[]
|
||
|
) # etudids pour lesquels il y a une decision de jury et que la note change
|
||
|
try:
|
||
|
for (etudid, value) in notes:
|
||
|
changed = False
|
||
|
if not NotesDB.has_key(etudid):
|
||
|
# nouvelle note
|
||
|
if value != NOTES_SUPPRESS:
|
||
|
if do_it:
|
||
|
aa = {
|
||
|
"etudid": etudid,
|
||
|
"evaluation_id": evaluation_id,
|
||
|
"value": value,
|
||
|
"comment": comment,
|
||
|
"uid": uid,
|
||
|
"date": now,
|
||
|
}
|
||
|
quote_dict(aa)
|
||
|
cursor.execute(
|
||
|
"insert into notes_notes (etudid,evaluation_id,value,comment,date,uid) values (%(etudid)s,%(evaluation_id)s,%(value)s,%(comment)s,%(date)s,%(uid)s)",
|
||
|
aa,
|
||
|
)
|
||
|
changed = True
|
||
|
else:
|
||
|
# il y a deja une note
|
||
|
oldval = NotesDB[etudid]["value"]
|
||
|
if type(value) != type(oldval):
|
||
|
changed = True
|
||
|
elif type(value) == type(1.0) and (
|
||
|
abs(value - oldval) > NOTES_PRECISION
|
||
|
):
|
||
|
changed = True
|
||
|
elif value != oldval:
|
||
|
changed = True
|
||
|
if changed:
|
||
|
# recopie l'ancienne note dans notes_notes_log, puis update
|
||
|
if do_it:
|
||
|
cursor.execute(
|
||
|
"insert into notes_notes_log (etudid,evaluation_id,value,comment,date,uid) select etudid,evaluation_id,value,comment,date,uid from notes_notes where etudid=%(etudid)s and evaluation_id=%(evaluation_id)s",
|
||
|
{"etudid": etudid, "evaluation_id": evaluation_id},
|
||
|
)
|
||
|
aa = {
|
||
|
"etudid": etudid,
|
||
|
"evaluation_id": evaluation_id,
|
||
|
"value": value,
|
||
|
"date": now,
|
||
|
"comment": comment,
|
||
|
"uid": uid,
|
||
|
}
|
||
|
quote_dict(aa)
|
||
|
if value != NOTES_SUPPRESS:
|
||
|
if do_it:
|
||
|
cursor.execute(
|
||
|
"update notes_notes set value=%(value)s, comment=%(comment)s, date=%(date)s, uid=%(uid)s where etudid=%(etudid)s and evaluation_id=%(evaluation_id)s",
|
||
|
aa,
|
||
|
)
|
||
|
else: # suppression ancienne note
|
||
|
if do_it:
|
||
|
log(
|
||
|
"_notes_add, suppress, evaluation_id=%s, etudid=%s, oldval=%s"
|
||
|
% (evaluation_id, etudid, oldval)
|
||
|
)
|
||
|
cursor.execute(
|
||
|
"delete from notes_notes where etudid=%(etudid)s and evaluation_id=%(evaluation_id)s",
|
||
|
aa,
|
||
|
)
|
||
|
# garde trace de la suppression dans l'historique:
|
||
|
aa["value"] = NOTES_SUPPRESS
|
||
|
cursor.execute(
|
||
|
"insert into notes_notes_log (etudid,evaluation_id,value,comment,date,uid) values (%(etudid)s, %(evaluation_id)s, %(value)s, %(comment)s, %(date)s, %(uid)s)",
|
||
|
aa,
|
||
|
)
|
||
|
nb_suppress += 1
|
||
|
if changed:
|
||
|
nb_changed += 1
|
||
|
if has_existing_decision(context, M, E, etudid):
|
||
|
existing_decisions.append(etudid)
|
||
|
except:
|
||
|
log("*** exception in _notes_add")
|
||
|
if do_it:
|
||
|
# inval cache
|
||
|
context._inval_cache(
|
||
|
formsemestre_id=M["formsemestre_id"]
|
||
|
) # > modif notes (exception)
|
||
|
cnx.rollback() # abort
|
||
|
raise # re-raise exception
|
||
|
if do_it:
|
||
|
cnx.commit()
|
||
|
context._inval_cache(formsemestre_id=M["formsemestre_id"]) # > modif notes
|
||
|
context.get_evaluations_cache().inval_cache(key=evaluation_id)
|
||
|
return nb_changed, nb_suppress, existing_decisions
|
||
|
|
||
|
|
||
|
def saisie_notes_tableur(context, evaluation_id, group_ids=[], REQUEST=None):
|
||
|
"""Saisie des notes via un fichier Excel
|
||
|
"""
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
evals = context.do_evaluation_list({"evaluation_id": evaluation_id})
|
||
|
if not evals:
|
||
|
raise ScoValueError("invalid evaluation_id")
|
||
|
E = evals[0]
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
formsemestre_id = M["formsemestre_id"]
|
||
|
if not context.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||
|
return (
|
||
|
context.sco_header(REQUEST)
|
||
|
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
||
|
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||
|
avez l'autorisation d'effectuer cette opération)</p>
|
||
|
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||
|
"""
|
||
|
% E["moduleimpl_id"]
|
||
|
+ context.sco_footer(REQUEST)
|
||
|
)
|
||
|
|
||
|
if E["description"]:
|
||
|
page_title = 'Saisie des notes de "%s"' % E["description"]
|
||
|
else:
|
||
|
page_title = "Saisie des notes"
|
||
|
|
||
|
# Informations sur les groupes à afficher:
|
||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||
|
context,
|
||
|
group_ids=group_ids,
|
||
|
formsemestre_id=formsemestre_id,
|
||
|
select_all_when_unspecified=True,
|
||
|
etat=None,
|
||
|
REQUEST=REQUEST,
|
||
|
)
|
||
|
|
||
|
H = [
|
||
|
context.sco_header(
|
||
|
REQUEST,
|
||
|
page_title=page_title,
|
||
|
javascripts=sco_groups_view.JAVASCRIPTS,
|
||
|
cssstyles=sco_groups_view.CSSSTYLES,
|
||
|
init_qtip=True,
|
||
|
),
|
||
|
sco_evaluations.evaluation_describe(
|
||
|
context, evaluation_id=evaluation_id, REQUEST=REQUEST
|
||
|
),
|
||
|
"""<span class="eval_title">Saisie des notes par fichier</span>""",
|
||
|
]
|
||
|
|
||
|
# Menu choix groupe:
|
||
|
H.append("""<div id="group-tabs"><table><tr><td>""")
|
||
|
H.append(sco_groups_view.form_groups_choice(context, groups_infos))
|
||
|
H.append("</td></tr></table></div>")
|
||
|
|
||
|
H.append(
|
||
|
"""<div class="saisienote_etape1">
|
||
|
<span class="titredivsaisienote">Etape 1 : </span>
|
||
|
<ul>
|
||
|
<li><a href="feuille_saisie_notes?evaluation_id=%s&%s" class="stdlink" id="lnk_feuille_saisie">obtenir le fichier tableur à remplir</a></li>
|
||
|
<li>ou <a class="stdlink" href="saisie_notes?evaluation_id=%s">aller au formulaire de saisie</a></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
<form><input type="hidden" name="evaluation_id" id="formnotes_evaluation_id" value="%s"/></form>
|
||
|
"""
|
||
|
% (evaluation_id, groups_infos.groups_query_args, evaluation_id, evaluation_id)
|
||
|
)
|
||
|
|
||
|
H.append(
|
||
|
"""<div class="saisienote_etape2">
|
||
|
<span class="titredivsaisienote">Etape 2 : chargement d'un fichier de notes</span>""" #'
|
||
|
)
|
||
|
|
||
|
nf = TrivialFormulator(
|
||
|
REQUEST.URL0,
|
||
|
REQUEST.form,
|
||
|
(
|
||
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||
|
(
|
||
|
"notefile",
|
||
|
{"input_type": "file", "title": "Fichier de note (.xls)", "size": 44},
|
||
|
),
|
||
|
(
|
||
|
"comment",
|
||
|
{
|
||
|
"size": 44,
|
||
|
"title": "Commentaire",
|
||
|
"explanation": "(la colonne remarque du fichier excel est ignorée)",
|
||
|
},
|
||
|
),
|
||
|
),
|
||
|
formid="notesfile",
|
||
|
submitlabel="Télécharger",
|
||
|
)
|
||
|
if nf[0] == 0:
|
||
|
H.append(
|
||
|
"""<p>Le fichier doit être un fichier tableur obtenu via
|
||
|
l'étape 1 ci-dessus, puis complété et enregistré au format Excel.
|
||
|
</p>"""
|
||
|
)
|
||
|
H.append(nf[1])
|
||
|
elif nf[0] == -1:
|
||
|
H.append("<p>Annulation</p>")
|
||
|
elif nf[0] == 1:
|
||
|
updiag = do_evaluation_upload_xls(context, REQUEST)
|
||
|
if updiag[0]:
|
||
|
H.append(updiag[1])
|
||
|
H.append(
|
||
|
"""<p>Notes chargées.
|
||
|
<a class="stdlink" href="moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s">
|
||
|
Revenir au tableau de bord du module</a>
|
||
|
|
||
|
<a class="stdlink" href="saisie_notes?evaluation_id=%(evaluation_id)s">Charger d'autres notes dans cette évaluation</a>
|
||
|
</p>"""
|
||
|
% E
|
||
|
)
|
||
|
else:
|
||
|
H.append("""<p class="redboldtext">Notes non chargées !</p>""" + updiag[1])
|
||
|
H.append(
|
||
|
"""
|
||
|
<p><a class="stdlink" href="saisie_notes_tableur?evaluation_id=%(evaluation_id)s">
|
||
|
Reprendre</a>
|
||
|
</p>"""
|
||
|
% E
|
||
|
)
|
||
|
#
|
||
|
H.append("""</div><h3>Autres opérations</h3><ul>""")
|
||
|
if context.can_edit_notes(
|
||
|
REQUEST.AUTHENTICATED_USER, E["moduleimpl_id"], allow_ens=False
|
||
|
):
|
||
|
H.append(
|
||
|
"""
|
||
|
<li>
|
||
|
<form action="do_evaluation_set_missing" method="get">
|
||
|
Mettre toutes les notes manquantes à <input type="text" size="5" name="value"/>
|
||
|
<input type="submit" value="OK"/>
|
||
|
<input type="hidden" name="evaluation_id" value="%s"/>
|
||
|
<em>ABS indique "absent" (zéro), EXC "excusé" (neutralisées), ATT "attente"</em>
|
||
|
</form>
|
||
|
</li>
|
||
|
<li><a class="stdlink" href="evaluation_suppress_alln?evaluation_id=%s">Effacer toutes les notes de cette évaluation</a> (ceci permet ensuite de supprimer l'évaluation si besoin)
|
||
|
</li>"""
|
||
|
% (evaluation_id, evaluation_id)
|
||
|
) #'
|
||
|
H.append(
|
||
|
"""<li><a class="stdlink" href="moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s">Revenir au module</a></li>
|
||
|
<li><a class="stdlink" href="saisie_notes?evaluation_id=%(evaluation_id)s">Revenir au formulaire de saisie</a></li>
|
||
|
</ul>"""
|
||
|
% E
|
||
|
)
|
||
|
|
||
|
H.append(
|
||
|
"""<h3>Explications</h3>
|
||
|
<ol>
|
||
|
<li>Etape 1:
|
||
|
<ol><li>choisir le ou les groupes d'étudiants;</li>
|
||
|
<li>télécharger le fichier Excel à remplir.</li>
|
||
|
</ol>
|
||
|
</li>
|
||
|
<li>Etape 2 (cadre vert): Indiquer le fichier Excel <em>téléchargé à l'étape 1</em> et dans lequel on a saisi des notes. Remarques:
|
||
|
<ul>
|
||
|
<li>le fichier Excel peut être incomplet: on peut ne saisir que quelques notes et répéter l'opération (en téléchargeant un nouveau fichier) plus tard;</li>
|
||
|
<li>seules les valeurs des notes modifiées sont prises en compte;</li>
|
||
|
<li>seules les notes sont extraites du fichier Excel;</li>
|
||
|
<li>on peut optionnellement ajouter un commentaire (type "copies corrigées par Dupont", ou "Modif. suite à contestation") dans la case "Commentaire".
|
||
|
</li>
|
||
|
<li>le fichier Excel <em>doit impérativement être celui chargé à l'étape 1 pour cette évaluation</em>. Il n'est pas possible d'utiliser une liste d'appel ou autre document Excel téléchargé d'une autre page.</li>
|
||
|
</ul>
|
||
|
</li>
|
||
|
</ol>
|
||
|
"""
|
||
|
)
|
||
|
H.append(context.sco_footer(REQUEST))
|
||
|
return "\n".join(H)
|
||
|
|
||
|
|
||
|
def feuille_saisie_notes(context, evaluation_id, group_ids=[], REQUEST=None):
|
||
|
"""Document Excel pour saisie notes dans l'évaluation et les groupes indiqués
|
||
|
"""
|
||
|
evals = context.do_evaluation_list({"evaluation_id": evaluation_id})
|
||
|
if not evals:
|
||
|
raise ScoValueError("invalid evaluation_id")
|
||
|
E = evals[0]
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
formsemestre_id = M["formsemestre_id"]
|
||
|
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
|
||
|
sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"])
|
||
|
if E["jour"]:
|
||
|
indication_date = DateDMYtoISO(E["jour"])
|
||
|
else:
|
||
|
indication_date = sanitize_filename(E["description"])[:12]
|
||
|
evalname = "%s-%s" % (Mod["code"], indication_date)
|
||
|
|
||
|
if E["description"]:
|
||
|
evaltitre = "%s du %s" % (E["description"], E["jour"])
|
||
|
else:
|
||
|
evaltitre = "évaluation du %s" % E["jour"]
|
||
|
description = "%s en %s (%s) resp. %s" % (
|
||
|
evaltitre,
|
||
|
Mod["abbrev"],
|
||
|
Mod["code"],
|
||
|
strcapitalize(M["responsable_id"]),
|
||
|
)
|
||
|
|
||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||
|
context,
|
||
|
group_ids=group_ids,
|
||
|
formsemestre_id=formsemestre_id,
|
||
|
select_all_when_unspecified=True,
|
||
|
etat=None,
|
||
|
REQUEST=REQUEST,
|
||
|
)
|
||
|
groups = sco_groups.listgroups(context, groups_infos.group_ids)
|
||
|
gr_title_filename = sco_groups.listgroups_filename(groups)
|
||
|
gr_title = sco_groups.listgroups_abbrev(groups)
|
||
|
if None in [g["group_name"] for g in groups]: # tous les etudiants
|
||
|
getallstudents = True
|
||
|
gr_title = "tous"
|
||
|
gr_title_filename = "tous"
|
||
|
else:
|
||
|
getallstudents = False
|
||
|
etudids = sco_groups.do_evaluation_listeetuds_groups(
|
||
|
context, evaluation_id, groups, getallstudents=getallstudents, include_dems=True
|
||
|
)
|
||
|
# Notes existantes
|
||
|
NotesDB = context._notes_getall(evaluation_id)
|
||
|
|
||
|
# une liste de liste de chaines: lignes de la feuille de calcul
|
||
|
L = []
|
||
|
|
||
|
etuds = _get_sorted_etuds(context, E, etudids, formsemestre_id)
|
||
|
for e in etuds:
|
||
|
etudid = e["etudid"]
|
||
|
groups = sco_groups.get_etud_groups(context, etudid, sem)
|
||
|
grc = sco_groups.listgroups_abbrev(groups)
|
||
|
|
||
|
L.append(
|
||
|
[
|
||
|
"%s" % etudid,
|
||
|
strupper(e["nom"]),
|
||
|
strcapitalize(strlower(e["prenom"])),
|
||
|
e["inscr"]["etat"],
|
||
|
grc,
|
||
|
e["val"],
|
||
|
e["explanation"],
|
||
|
]
|
||
|
)
|
||
|
|
||
|
filename = "notes_%s_%s.xls" % (evalname, gr_title_filename)
|
||
|
xls = sco_excel.Excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
|
||
|
return sco_excel.sendExcelFile(REQUEST, xls, filename)
|
||
|
|
||
|
|
||
|
def has_existing_decision(context, M, E, etudid):
|
||
|
"""Verifie s'il y a une validation pour cet etudiant dans ce semestre ou UE
|
||
|
Si oui, return True
|
||
|
"""
|
||
|
formsemestre_id = M["formsemestre_id"]
|
||
|
nt = context._getNotesCache().get_NotesTable(
|
||
|
context, formsemestre_id
|
||
|
) # > get_etud_decision_sem, get_etud_decision_ues
|
||
|
if nt.get_etud_decision_sem(etudid):
|
||
|
return True
|
||
|
dec_ues = nt.get_etud_decision_ues(etudid)
|
||
|
if dec_ues:
|
||
|
mod = context.do_module_list({"module_id": M["module_id"]})[0]
|
||
|
ue_id = mod["ue_id"]
|
||
|
if ue_id in dec_ues:
|
||
|
return True # decision pour l'UE a laquelle appartient cette evaluation
|
||
|
|
||
|
return False # pas de decision de jury affectee par cette note
|
||
|
|
||
|
|
||
|
# -----------------------------
|
||
|
# Nouveau formulaire saisie notes (2016)
|
||
|
|
||
|
|
||
|
def saisie_notes(context, evaluation_id, group_ids=[], REQUEST=None):
|
||
|
"""Formulaire saisie notes d'une évaluation pour un groupe
|
||
|
"""
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
authusername = str(authuser)
|
||
|
|
||
|
evals = context.do_evaluation_list({"evaluation_id": evaluation_id})
|
||
|
if not evals:
|
||
|
raise ScoValueError("invalid evaluation_id")
|
||
|
E = evals[0]
|
||
|
M = context.do_moduleimpl_withmodule_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
formsemestre_id = M["formsemestre_id"]
|
||
|
# Check access
|
||
|
# (admin, respformation, and responsable_id)
|
||
|
if not context.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||
|
return (
|
||
|
context.sco_header(REQUEST)
|
||
|
+ "<h2>Modification des notes impossible pour %s</h2>" % authusername
|
||
|
+ """<p>(vérifiez que le semestre n'est pas verrouillé et que vous
|
||
|
avez l'autorisation d'effectuer cette opération)</p>
|
||
|
<p><a href="moduleimpl_status?moduleimpl_id=%s">Continuer</a></p>
|
||
|
"""
|
||
|
% E["moduleimpl_id"]
|
||
|
+ context.sco_footer(REQUEST)
|
||
|
)
|
||
|
|
||
|
# Informations sur les groupes à afficher:
|
||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||
|
context,
|
||
|
group_ids=group_ids,
|
||
|
formsemestre_id=formsemestre_id,
|
||
|
select_all_when_unspecified=True,
|
||
|
etat=None,
|
||
|
REQUEST=REQUEST,
|
||
|
)
|
||
|
|
||
|
if E["description"]:
|
||
|
page_title = 'Saisie "%s"' % E["description"]
|
||
|
else:
|
||
|
page_title = "Saisie des notes"
|
||
|
|
||
|
# HTML page:
|
||
|
H = [
|
||
|
context.sco_header(
|
||
|
REQUEST,
|
||
|
page_title=page_title,
|
||
|
javascripts=sco_groups_view.JAVASCRIPTS + ["js/saisie_notes.js"],
|
||
|
cssstyles=sco_groups_view.CSSSTYLES,
|
||
|
init_qtip=True,
|
||
|
),
|
||
|
sco_evaluations.evaluation_describe(
|
||
|
context, evaluation_id=evaluation_id, REQUEST=REQUEST
|
||
|
),
|
||
|
'<div id="saisie_notes"><span class="eval_title">Saisie des notes</span>',
|
||
|
]
|
||
|
H.append("""<div id="group-tabs"><table><tr><td>""")
|
||
|
H.append(sco_groups_view.form_groups_choice(context, groups_infos))
|
||
|
H.append('</td><td style="padding-left: 35px;">')
|
||
|
H.append(
|
||
|
makeMenu(
|
||
|
"Autres opérations",
|
||
|
[
|
||
|
{
|
||
|
"title": "Saisie par fichier tableur",
|
||
|
"id": "menu_saisie_tableur",
|
||
|
"url": "/saisie_notes_tableur?evaluation_id=%s&%s"
|
||
|
% (E["evaluation_id"], groups_infos.groups_query_args),
|
||
|
},
|
||
|
{
|
||
|
"title": "Voir toutes les notes du module",
|
||
|
"url": "/evaluation_listenotes?moduleimpl_id=%s"
|
||
|
% E["moduleimpl_id"],
|
||
|
},
|
||
|
{
|
||
|
"title": "Effacer toutes les notes de cette évaluation",
|
||
|
"url": "/evaluation_suppress_alln?evaluation_id=%s"
|
||
|
% (E["evaluation_id"],),
|
||
|
},
|
||
|
],
|
||
|
base_url=context.absolute_url(),
|
||
|
alone=True,
|
||
|
)
|
||
|
)
|
||
|
H.append("""</td></tr></table></div>""")
|
||
|
|
||
|
# Le formulaire de saisie des notes:
|
||
|
form = _form_saisie_notes(context, E, M, groups_infos.group_ids, REQUEST=REQUEST)
|
||
|
if form is None:
|
||
|
return "" # redirect
|
||
|
H.append(form)
|
||
|
#
|
||
|
H.append("</div>") # /saisie_notes
|
||
|
|
||
|
H.append(
|
||
|
"""<div class="sco_help">
|
||
|
<p>Les modifications sont enregistrées au fur et à mesure.</p>
|
||
|
<h4>Codes spéciaux:</h4>
|
||
|
<ul>
|
||
|
<li>ABS: absent (compte comme un zéro)</li>
|
||
|
<li>EXC: excusé (note neutralisée)</li>
|
||
|
<li>SUPR: pour supprimer une note existante</li>
|
||
|
<li>ATT: note en attente (permet de publier une évaluation avec des notes manquantes)</li>
|
||
|
</ul>
|
||
|
</div>"""
|
||
|
)
|
||
|
|
||
|
H.append(context.sco_footer(REQUEST))
|
||
|
return "\n".join(H)
|
||
|
|
||
|
|
||
|
def _get_sorted_etuds(context, E, etudids, formsemestre_id):
|
||
|
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
|
||
|
NotesDB = context._notes_getall(E["evaluation_id"]) # Notes existantes
|
||
|
cnx = context.GetDBConnexion()
|
||
|
etuds = []
|
||
|
for etudid in etudids:
|
||
|
# infos identite etudiant
|
||
|
e = scolars.etudident_list(cnx, {"etudid": etudid})[0]
|
||
|
scolars.format_etud_ident(e)
|
||
|
etuds.append(e)
|
||
|
# infos inscription dans ce semestre
|
||
|
e["inscr"] = context.do_formsemestre_inscription_list(
|
||
|
{"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||
|
)[0]
|
||
|
# Groupes auxquels appartient cet étudiant:
|
||
|
e["groups"] = sco_groups.get_etud_groups(context, etudid, sem)
|
||
|
|
||
|
# Information sur absence (tenant compte de la demi-journée)
|
||
|
jour_iso = DateDMYtoISO(E["jour"])
|
||
|
warn_abs_lst = []
|
||
|
if E["matin"]:
|
||
|
nbabs = context.Absences.CountAbs(etudid, jour_iso, jour_iso, matin=1)
|
||
|
nbabsjust = context.Absences.CountAbsJust(
|
||
|
etudid, jour_iso, jour_iso, matin=1
|
||
|
)
|
||
|
if nbabs:
|
||
|
if nbabsjust:
|
||
|
warn_abs_lst.append("absent justifié le matin !")
|
||
|
else:
|
||
|
warn_abs_lst.append("absent le matin !")
|
||
|
if E["apresmidi"]:
|
||
|
nbabs = context.Absences.CountAbs(etudid, jour_iso, jour_iso, matin=0)
|
||
|
nbabsjust = context.Absences.CountAbsJust(
|
||
|
etudid, jour_iso, jour_iso, matin=0
|
||
|
)
|
||
|
if nbabs:
|
||
|
if nbabsjust:
|
||
|
warn_abs_lst.append("absent justifié l'après-midi !")
|
||
|
else:
|
||
|
warn_abs_lst.append("absent l'après-midi !")
|
||
|
|
||
|
e["absinfo"] = '<span class="sn_abs">' + " ".join(warn_abs_lst) + "</span> "
|
||
|
|
||
|
# Note actuelle de l'étudiant:
|
||
|
if NotesDB.has_key(etudid):
|
||
|
e["val"] = _displayNote(NotesDB[etudid]["value"])
|
||
|
comment = NotesDB[etudid]["comment"]
|
||
|
if comment is None:
|
||
|
comment = ""
|
||
|
e["explanation"] = "%s (%s) %s" % (
|
||
|
NotesDB[etudid]["date"].strftime("%d/%m/%y %Hh%M"),
|
||
|
NotesDB[etudid]["uid"],
|
||
|
comment,
|
||
|
)
|
||
|
else:
|
||
|
e["val"] = ""
|
||
|
e["explanation"] = ""
|
||
|
# Démission ?
|
||
|
if e["inscr"]["etat"] == "D":
|
||
|
# if not e['val']:
|
||
|
e["val"] = "DEM"
|
||
|
e["explanation"] = "Démission"
|
||
|
|
||
|
etuds.sort(key=lambda x: (x["nom"], x["prenom"]))
|
||
|
|
||
|
return etuds
|
||
|
|
||
|
|
||
|
def _form_saisie_notes(context, E, M, group_ids, REQUEST=None):
|
||
|
"""Formulaire HTML saisie des notes dans l'évaluation E du moduleimpl M
|
||
|
pour les groupes indiqués.
|
||
|
|
||
|
On charge tous les étudiants, ne seront montrés que ceux
|
||
|
des groupes sélectionnés grace a un filtre en javascript.
|
||
|
"""
|
||
|
evaluation_id = E["evaluation_id"]
|
||
|
formsemestre_id = M["formsemestre_id"]
|
||
|
groups = sco_groups.listgroups(context, group_ids)
|
||
|
|
||
|
etudids = sco_groups.do_evaluation_listeetuds_groups(
|
||
|
context, evaluation_id, getallstudents=True, include_dems=True
|
||
|
)
|
||
|
if not etudids:
|
||
|
return '<div class="ue_warning"><span>Aucun étudiant sélectionné !</span></div>'
|
||
|
|
||
|
# Decisions de jury existantes ?
|
||
|
decisions_jury = {
|
||
|
etudid: has_existing_decision(context, M, E, etudid) for etudid in etudids
|
||
|
}
|
||
|
nb_decisions = sum(
|
||
|
decisions_jury.values()
|
||
|
) # Nb de decisions de jury (pour les inscrits à l'évaluation)
|
||
|
|
||
|
etuds = _get_sorted_etuds(context, E, etudids, formsemestre_id)
|
||
|
|
||
|
# Build form:
|
||
|
descr = [
|
||
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||
|
("formsemestre_id", {"default": formsemestre_id, "input_type": "hidden"}),
|
||
|
("group_ids", {"default": group_ids, "input_type": "hidden", "type": "list"}),
|
||
|
# ('note_method', { 'default' : note_method, 'input_type' : 'hidden'}),
|
||
|
("comment", {"size": 44, "title": "Commentaire", "return_focus_next": True}),
|
||
|
("changed", {"default": "0", "input_type": "hidden"}), # changed in JS
|
||
|
]
|
||
|
if M["module"]["module_type"] == MODULE_STANDARD:
|
||
|
descr.append(
|
||
|
(
|
||
|
"s3",
|
||
|
{
|
||
|
"input_type": "text", # affiche le barème
|
||
|
"title": "Notes ",
|
||
|
"cssclass": "formnote_bareme",
|
||
|
"readonly": True,
|
||
|
"default": " / %g" % E["note_max"],
|
||
|
},
|
||
|
)
|
||
|
)
|
||
|
elif M["module"]["module_type"] == MODULE_MALUS:
|
||
|
descr.append(
|
||
|
(
|
||
|
"s3",
|
||
|
{
|
||
|
"input_type": "text", # affiche le barème
|
||
|
"title": "",
|
||
|
"cssclass": "formnote_bareme",
|
||
|
"readonly": True,
|
||
|
"default": "Points de malus (soustraits à la moyenne de l'UE, entre -20 et 20)",
|
||
|
},
|
||
|
)
|
||
|
)
|
||
|
else:
|
||
|
raise ValueError("invalid module type (%s)" % M["module"]["module_type"]) # bug
|
||
|
|
||
|
initvalues = {}
|
||
|
for e in etuds:
|
||
|
etudid = e["etudid"]
|
||
|
disabled = e["val"] == "DEM"
|
||
|
etud_classes = []
|
||
|
if disabled:
|
||
|
classdem = " etud_dem"
|
||
|
etud_classes.append("etud_dem")
|
||
|
disabled_attr = 'disabled="%d"' % disabled
|
||
|
else:
|
||
|
classdem = ""
|
||
|
disabled_attr = ""
|
||
|
# attribue a chaque element une classe css par groupe:
|
||
|
for group_info in e["groups"]:
|
||
|
etud_classes.append(group_info["group_id"])
|
||
|
|
||
|
label = '<span class="%s">' % classdem + e["nomprenom"] + "</span>"
|
||
|
|
||
|
# Historique des saisies de notes:
|
||
|
if not disabled:
|
||
|
explanation = (
|
||
|
'<span id="hist_%s">' % etudid
|
||
|
+ get_note_history_menu(context, evaluation_id, etudid)
|
||
|
+ "</span>"
|
||
|
)
|
||
|
else:
|
||
|
explanation = ""
|
||
|
explanation = e["absinfo"] + explanation
|
||
|
|
||
|
# Lien modif decision de jury:
|
||
|
explanation += '<span id="jurylink_%s" class="jurylink"></span>' % etudid
|
||
|
|
||
|
# Valeur actuelle du champ:
|
||
|
initvalues["note_" + etudid] = e["val"]
|
||
|
label_link = '<a class="etudinfo" id="%s">%s</a>' % (etudid, label)
|
||
|
|
||
|
# Element de formulaire:
|
||
|
descr.append(
|
||
|
(
|
||
|
"note_" + etudid,
|
||
|
{
|
||
|
"size": 5,
|
||
|
"title": label_link,
|
||
|
"explanation": explanation,
|
||
|
"return_focus_next": True,
|
||
|
"attributes": [
|
||
|
'class="note%s"' % classdem,
|
||
|
disabled_attr,
|
||
|
"data-last-saved-value=%s" % e["val"],
|
||
|
"data-orig-value=%s" % e["val"],
|
||
|
"data-etudid=%s" % etudid,
|
||
|
],
|
||
|
"template": """<tr%(item_dom_attr)s class="etud_elem """
|
||
|
+ " ".join(etud_classes)
|
||
|
+ """"><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>""",
|
||
|
},
|
||
|
)
|
||
|
)
|
||
|
#
|
||
|
H = []
|
||
|
if nb_decisions > 0:
|
||
|
H.append(
|
||
|
"""<div class="saisie_warn">
|
||
|
<ul class="tf-msg">
|
||
|
<li class="tf-msg">Attention: il y a déjà des <b>décisions de jury</b> enregistrées pour %d étudiants. Après changement des notes, vérifiez la situation !</li>
|
||
|
</ul>
|
||
|
</div>"""
|
||
|
% nb_decisions
|
||
|
)
|
||
|
# H.append('''<div id="sco_msg" class="head_message"></div>''')
|
||
|
|
||
|
destination = "%s/Notes/moduleimpl_status?moduleimpl_id=%s" % (
|
||
|
context.ScoURL(),
|
||
|
M["moduleimpl_id"],
|
||
|
)
|
||
|
|
||
|
tf = TF(
|
||
|
destination,
|
||
|
REQUEST.form,
|
||
|
descr,
|
||
|
initvalues=initvalues,
|
||
|
submitlabel="Terminer",
|
||
|
formid="formnotes",
|
||
|
)
|
||
|
H.append(tf.getform()) # check and init
|
||
|
if tf.canceled():
|
||
|
REQUEST.RESPONSE.redirect(destination)
|
||
|
return None
|
||
|
elif (not tf.submitted()) or not tf.result:
|
||
|
# ajout formularie saisie notes manquantes
|
||
|
H.append(
|
||
|
"""
|
||
|
<div>
|
||
|
<form action="do_evaluation_set_missing" method="get">
|
||
|
Mettre <em>toutes</em> les notes manquantes à <input type="text" size="5" name="value"/>
|
||
|
<input type="submit" value="OK"/>
|
||
|
<input type="hidden" name="evaluation_id" value="%s"/>
|
||
|
<em>affecte tous les groupes. ABS indique "absent" (zéro), EXC "excusé" (neutralisées), ATT "attente"</em>
|
||
|
</form>
|
||
|
</div>
|
||
|
"""
|
||
|
% evaluation_id
|
||
|
)
|
||
|
# affiche formulaire
|
||
|
return "\n".join(H)
|
||
|
else:
|
||
|
# form submission
|
||
|
# rien à faire
|
||
|
REQUEST.RESPONSE.redirect(destination)
|
||
|
return None
|
||
|
|
||
|
|
||
|
def save_note(
|
||
|
context, etudid=None, evaluation_id=None, value=None, comment="", REQUEST=None
|
||
|
):
|
||
|
"""Enregistre une note (ajax)
|
||
|
"""
|
||
|
authuser = REQUEST.AUTHENTICATED_USER
|
||
|
log(
|
||
|
"save_note: evaluation_id=%s etudid=%s uid=%s value=%s"
|
||
|
% (evaluation_id, etudid, authuser, value)
|
||
|
)
|
||
|
E = context.do_evaluation_list({"evaluation_id": evaluation_id})[0]
|
||
|
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||
|
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
|
||
|
Mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % M
|
||
|
result = {"nbchanged": 0} # JSON
|
||
|
# Check access: admin, respformation, or responsable_id
|
||
|
if not context.can_edit_notes(authuser, E["moduleimpl_id"]):
|
||
|
result["status"] = "unauthorized"
|
||
|
else:
|
||
|
L, invalids, withoutnotes, absents, tosuppress = _check_notes(
|
||
|
[(etudid, value)], E, Mod
|
||
|
)
|
||
|
if L:
|
||
|
nbchanged, nbsuppress, existing_decisions = _notes_add(
|
||
|
context, authuser, evaluation_id, L, comment=comment, do_it=True
|
||
|
)
|
||
|
sco_news.add(
|
||
|
context,
|
||
|
REQUEST,
|
||
|
typ=NEWS_NOTE,
|
||
|
object=M["moduleimpl_id"],
|
||
|
text='Chargement notes dans <a href="%(url)s">%(titre)s</a>' % Mod,
|
||
|
url=Mod["url"],
|
||
|
max_frequency=30 * 60, # 30 minutes
|
||
|
)
|
||
|
result["nbchanged"] = nbchanged
|
||
|
result["existing_decisions"] = existing_decisions
|
||
|
if nbchanged > 0:
|
||
|
result["history_menu"] = get_note_history_menu(
|
||
|
context, evaluation_id, etudid
|
||
|
)
|
||
|
else:
|
||
|
result["history_menu"] = "" # no update needed
|
||
|
result["status"] = "ok"
|
||
|
|
||
|
# time.sleep(5)
|
||
|
return sendJSON(REQUEST, result)
|
||
|
|
||
|
|
||
|
def get_note_history_menu(context, evaluation_id, etudid):
|
||
|
"""Menu HTML historique de la note"""
|
||
|
history = sco_undo_notes.get_note_history(context, evaluation_id, etudid)
|
||
|
if not history:
|
||
|
return ""
|
||
|
|
||
|
H = []
|
||
|
if len(history) > 1:
|
||
|
H.append(
|
||
|
'<select data-etudid="%s" class="note_history" onchange="change_history(this);">'
|
||
|
% etudid
|
||
|
)
|
||
|
envir = "select"
|
||
|
item = "option"
|
||
|
else:
|
||
|
# pas de menu
|
||
|
H.append('<span class="history">')
|
||
|
envir = "span"
|
||
|
item = "span"
|
||
|
|
||
|
first = True
|
||
|
for i in history:
|
||
|
jt = i["date"].strftime("le %d/%m/%Y à %H:%M") + " (%s)" % i["user_name"]
|
||
|
dispnote = _displayNote(i["value"])
|
||
|
if first:
|
||
|
nv = "" # ne repete pas la valeur de la note courante
|
||
|
else:
|
||
|
# ancienne valeur
|
||
|
nv = '<span class="histvalue">: %s</span>' % dispnote
|
||
|
first = False
|
||
|
if i["comment"]:
|
||
|
comment = ' <span class="histcomment">%s</span>' % i["comment"]
|
||
|
else:
|
||
|
comment = ""
|
||
|
H.append(
|
||
|
'<%s data-note="%s">%s %s%s</%s>' % (item, dispnote, jt, nv, comment, item)
|
||
|
)
|
||
|
|
||
|
H.append("</%s>" % envir)
|
||
|
return "\n".join(H)
|