# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 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 # ############################################################################## """Importation de notes depuis Moodle Contrib. Pierre-Alain Jacquot, mai 2021 """ # QUESTION: un (long) commentaire expliquant le principe de base de ce module import requests import re def cleanhtml(raw_html): cleanr = re.compile("<.*?>") cleantext = re.sub(cleanr, " ", raw_html) cleantext = cleantext.strip() cleantext = cleantext.encode("utf-8") return cleantext # def get_moodle_course_id(moodle_serveur, moodle_token, courses_short_name): param_cours = { "wstoken": moodle_token, "moodlewsrestformat": "json", "wsfunction": "core_course_get_courses_by_field", "field": "shortname", "value": courses_short_name, } try: r = requests.post(url=moodle_serveur, data=param_cours).json() except ValueError: raise ValueError("Erreur de connexion vérifiez l'URL de Moodle") if "exception" in r: raise ValueError( "Connexion au service web de Moodle impossible %s : Vérifiez votre paramétrage" % r["message"] ) if len(r["courses"]) == 0: courseid = 0 else: courseid = r["courses"][0]["id"] return courseid def has_student_role(user): """ Retourne vrai si l'utilisateur a le role 5 : «etudiant» ou «student» dans le cours i.e. il a des notes """ # QUESTION: ce nombre "5" est une constante universelle dans Moodle ? est_etudiant = False for role in user["roles"]: # print "role : "+str(role['roleid'] ) if role["roleid"] == 5: # print "est_etudiant "+str(role['roleid'] ) est_etudiant = 1 return est_etudiant def get_etudiants_from_course(moodle_serveur, moodle_token, courses_short_name): """ Extrait la liste des étudiants des utilisateurs inscrit dans le cours. Cette liste contient les informations suivante : - id moodle - email - idnumber (numéro d'identification) s'il existe : celui ci peut servir a stocker le EID ou le nip """ courseid = get_moodle_course_id(moodle_serveur, moodle_token, courses_short_name) param_cours = { "wstoken": moodle_token, "moodlewsrestformat": "json", "wsfunction": "core_enrol_get_enrolled_users", "options[0][name]": "onlyactive", "options[0][value]": "1", "options[1][name]": "userfields", "options[1][value]": "id,email,idnumber,roles", "courseid": courseid, } r = requests.post(url=moodle_serveur, data=param_cours).json() etudiants = [user for user in r if has_student_role(user)] # le role n'est plus une information pertinente : suppression for etudiant in etudiants: del etudiant["roles"] etudiant["email"] = etudiant["email"].encode("ascii").lower() return etudiants def get_evaluation_list(moodle_serveur, moodle_token, courses_short_name): """ Récupère la liste des evaluations du cours Moodle On recherche les notes d'un seul etudiant pour gagner du temps. """ # QUESTION: documenter les valeurs résultats etudiants = get_etudiants_from_course( moodle_serveur, moodle_token, courses_short_name ) a_userid = etudiants[0]["id"] courseid = get_moodle_course_id(moodle_serveur, moodle_token, courses_short_name) param_notes = { "wstoken": moodle_token, "moodlewsrestformat": "json", "wsfunction": "gradereport_user_get_grades_table", "courseid": courseid, "userid": a_userid, } r = requests.post(url=moodle_serveur, data=param_notes) notes = r.json() bareme = {} liste_evals = [] for etu_notes in notes["tables"][0]["tabledata"]: if "grade" in etu_notes: nom_eval = cleanhtml(etu_notes["itemname"]["content"]) liste_evals.append(nom_eval) bareme_min, bareme_max = etu_notes["range"]["content"].split("–") bareme[nom_eval] = { "min": float(bareme_min.replace(",", ".")), "max": float(bareme_max.replace(",", ".")), } return liste_evals, bareme def get_grades_from_moodle_course(moodle_serveur, moodle_token, courses_short_name): """ Récupère toutes les notes du cours et les remet en forme dans un dictionnaire indexé par le userid de moodle {userid: { nom_eval:note, ...}} """ courseid = get_moodle_course_id(moodle_serveur, moodle_token, courses_short_name) param_notes = { "wstoken": moodle_token, "moodlewsrestformat": "json", "wsfunction": "gradereport_user_get_grades_table", "courseid": courseid, } r = requests.post(url=moodle_serveur, data=param_notes) notes = r.json() notes_evals = {} for etudiant in notes["tables"]: # remise en forme des notes dans un dictionnaire indexe par le nom de la note tab_notes = {} for etu_notes in etudiant["tabledata"]: if "grade" in etu_notes: if etu_notes["grade"]["content"] == "-": etu_notes["grade"]["content"] = "SUPR" tab_notes[cleanhtml(etu_notes["itemname"]["content"])] = etu_notes[ "grade" ]["content"] notes_evals[etudiant["userid"]] = tab_notes return notes_evals # QUESTION: j'ai l'impression qu'il y a trop de code en commun entre cette fonction et # sco_saisie_notes._form_saisie_notes # QUESTION: manque vérification de la présence de décisions de jury ?? (qui devrait bloquer l'import amha) def import_eval_notes_from_moodle(context, evaluation_id, group_ids=[], REQUEST=None): """Récuperation des notes sur moodle""" moodle_token = context.get_preference("moodle_ws_token", formsemestre_id) moodle_serveur = context.get_preference("moodle_server_url", formsemestre_id) # Désactive si l'interface n'est pas configurée: if not moodle_serveur or not moodle_token: return "Interface Moodle non paramétrée !" 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] # M = context.do_moduleimpl_list( args={ 'moduleimpl_id' : E['moduleimpl_id'] } )[0] formsemestre_id = M["formsemestre_id"] if not can_edit_notes(context, authuser, E["moduleimpl_id"]): return ( context.sco_header(REQUEST) + "

Modification des notes impossible pour %s

" % authusername + """

(vérifiez que le semestre n'est pas verrouillé et que vous avez l'autorisation d'effectuer cette opération)

Continuer

""" % 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 ), """Import des notes depuis Moodle""", ] H.append( """
Nom abrégé du cours sur Moodle
""" % (evaluation_id) ) if "course_short_name" in REQUEST.form: course_short_name = REQUEST.form["course_short_name"] courseid = get_moodle_course_id(moodle_serveur, moodle_token, course_short_name) if courseid == 0: H.append( """

" %s " n'est pas un nom abrégé de cours connu sur ce Moodle

""" % course_short_name ) else: list_evaluations, bareme = get_evaluation_list( moodle_serveur, moodle_token, course_short_name ) msg = "

Remarque : Si l'étudiant n'a pas de note sur Moodle la note dans cette évaluation sera supprimée

" if len(list_evaluations) > 5: msg += "

ATTENTION : Le chargement des notes peut prendre beaucoup de temps

" H.append( """
liste des évaluations du cours %s
""" % course_short_name ) pbplage = False for ev in range(0, len(list_evaluations)): # verification du bareme marque = "" if ( bareme[list_evaluations[ev]]["min"] != scu.NOTES_MIN or bareme[list_evaluations[ev]]["max"] != E["note_max"] ): marque = ( """note entre %.2f et %.2f""" % ( bareme[list_evaluations[ev]]["min"], bareme[list_evaluations[ev]]["max"], ) ) pbplage = True H.append( """ %s %s
""" % (ev, list_evaluations[ev], marque) ) if pbplage: msg += ( '

ATTENTION : certaines évaluations ne sont pas dans la plage %.2f - %.2f il faudrait modifier cette cette évaluation pour pouvoir les importer !

' % (scu.NOTES_MIN, E["note_max"]) ) H.append( """
%s
""" % (course_short_name, evaluation_id, msg) ) if "num_eval" in REQUEST.form: nom_eval = list_evaluations[int(REQUEST.form["num_eval"])] etudiant_info = get_etudiants_from_course( moodle_serveur, moodle_token, course_short_name ) moodle_notes = get_grades_from_moodle_course( moodle_serveur, moodle_token, course_short_name ) email_id = {} nip_id = {} for etu in groups_infos.members: email = str(etu["email"]).lower() email_id[email] = etu["etudid"] nip_id[etu["code_nip"]] = etu["etudid"] nouvelles_notes = [] for etu in etudiant_info: # La présence d'un code nip est prioritaire sur l'adresse mail if "idnumber" in etu: nouvelles_notes.append( (nip_id[etu["idnumber"]], moodle_notes[etu["id"]][nom_eval]) ) elif etu["email"] in email_id: email = str(etu["email"]).lower() nouvelles_notes.append( (email_id[email], moodle_notes[etu["id"]][nom_eval]) ) updiag = do_moodle_import( context, REQUEST, nouvelles_notes, "Moodle/%s/%s" % (course_short_name, nom_eval), ) # updiag=[0,"en test: merci de patienter"] if updiag[0]: H.append(updiag[1]) H.append( """

Notes chargées.    Revenir au tableau de bord du module     Charger d'autres notes dans cette évaluation

""" % E ) else: H.append( """

Notes non chargées !

""" + updiag[1] ) H.append( """

Reprendre

""" % E ) # H.append("""

Autres opérations

""" % E ) H.append(context.sco_footer(REQUEST)) return "\n".join(H) # QUESTION: Beaucoup de code dupliqué de sco-saisie_notes => maintenance trop difficile à terme # => refactoring nécessaire def do_moodle_import(context, REQUEST, notes, comment): """import moodle""" authuser = REQUEST.AUTHENTICATED_USER evaluation_id = REQUEST.form["evaluation_id"] # comment = "Importée de moodle"#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] # M = context.do_moduleimpl_withmodule_list( args={ 'moduleimpl_id' : E['moduleimpl_id'] } )[0] # Check access # (admin, respformation, and responsable_id) # if not context.can_edit_notes( authuser, E['moduleimpl_id'] ): if not can_edit_notes(context, authuser, E["moduleimpl_id"]): # XXX imaginer un redirect + msg erreur raise AccessDenied("Modification des notes impossible pour %s" % authuser) # diag = [] try: # -- check values L, invalids, withoutnotes, absents, tosuppress = _check_notes( notes, E, M["module"] ) if len(invalids): diag.append( "Erreur: Moodle fournit %d notes invalides vérifiez que la note maximale est bien la même sur scodoc et sur Moodle

" % 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] # M = context.do_moduleimpl_list( args={ '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 %(titre)s' % mod, url=mod["url"], ) msg = ( "

%d notes changées (%d sans notes, %d absents, %d note supprimées)

" % (nb_changed, len(withoutnotes), len(absents), nb_suppress) ) if existing_decisions: msg += """

Important: il y avait déjà des décisions de jury enregistrées, qui sont potentiellement à revoir suite à cette modification !

""" # msg += '

' + str(notes) # debug return 1, msg except InvalidNoteValue: if diag: msg = ( '

" ) else: msg = '' return 0, msg + "

(pas de notes modifiées)

"