2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2021-01-01 17:51:08 +01:00
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# 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
) :
2021-01-01 18:40:47 +01:00
""" Initialisation des notes manquantes """
2020-09-26 16:19:37 +02:00
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 ) :
2021-01-01 18:40:47 +01:00
""" Saisie des notes via un fichier Excel """
2020-09-26 16:19:37 +02:00
authuser = REQUEST . AUTHENTICATED_USER
2021-01-01 18:40:47 +01:00
authusername = str ( authuser )
2020-09-26 16:19:37 +02:00
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 >
& nbsp ; & nbsp ; & nbsp ;
< 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 ) :
2021-01-01 18:40:47 +01:00
""" Document Excel pour saisie notes dans l ' évaluation et les groupes indiqués """
2020-09-26 16:19:37 +02:00
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 ) :
2021-01-01 18:40:47 +01:00
""" Formulaire saisie notes d ' une évaluation pour un groupe """
2020-09-26 16:19:37 +02:00
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
) :
2021-01-01 18:40:47 +01:00
""" Enregistre une note (ajax) """
2020-09-26 16:19:37 +02:00
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 )