diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index 658bbbea..ba57c67a 100644 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -40,7 +40,7 @@ from app.scodoc.sco_permissions import Permission def sidebar_common(): "partie commune à toutes les sidebar" H = [ - f"""ScoDoc 9 + f"""ScoDoc 9
") return "".join(H) + + +""" +HTML <-> text conversions. +http://stackoverflow.com/questions/328356/extracting-text-from-html-file-using-python +""" + + +class _HTMLToText(HTMLParser): + def __init__(self): + HTMLParser.__init__(self) + self._buf = [] + self.hide_output = False + + def handle_starttag(self, tag, attrs): + if tag in ("p", "br") and not self.hide_output: + self._buf.append("\n") + elif tag in ("script", "style"): + self.hide_output = True + + def handle_startendtag(self, tag, attrs): + if tag == "br": + self._buf.append("\n") + + def handle_endtag(self, tag): + if tag == "p": + self._buf.append("\n") + elif tag in ("script", "style"): + self.hide_output = False + + def handle_data(self, text): + if text and not self.hide_output: + self._buf.append(re.sub(r"\s+", " ", text)) + + def handle_entityref(self, name): + if name in name2codepoint and not self.hide_output: + c = chr(name2codepoint[name]) + self._buf.append(c) + + def handle_charref(self, name): + if not self.hide_output: + n = int(name[1:], 16) if name.startswith("x") else int(name) + self._buf.append(chr(n)) + + def get_text(self): + return re.sub(r" +", " ", "".join(self._buf)) + + +def html_to_text(html): + """ + Given a piece of HTML, return the plain text it contains. + This handles entities and char refs, but not javascript and stylesheets. + """ + parser = _HTMLToText() + try: + parser.feed(html) + parser.close() + except: # HTMLParseError: No good replacement? + pass + return parser.get_text() diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index e3d6e419..27617c00 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -630,7 +630,7 @@ class NotesTable(object): matiere_sum_notes += val * coef matiere_sum_coefs += coef matiere_id_last = matiere_id - except: # val == "NI" "NA" + except TypeError: # val == "NI" "NA" assert val == "NI" or val == "NA" nb_missing = nb_missing + 1 coefs.append(0) diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py index 6fa29fb9..b03b5427 100644 --- a/app/scodoc/notesdb.py +++ b/app/scodoc/notesdb.py @@ -597,6 +597,22 @@ def float_null_is_null(x): return float(x) +BOOL_STR = { + "": False, + "false": False, + "0": False, + "1": True, + "true": "true", +} + + +def bool_or_str(x): + """a boolean, may also be encoded as a string "0", "False", "1", "True" """ + if isinstance(x, str): + return BOOL_STR[x.lower()] + return x + + # post filtering # def UniqListofDicts(L, key): diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py index 1805c085..3a47995a 100644 --- a/app/scodoc/sco_archives_etud.py +++ b/app/scodoc/sco_archives_etud.py @@ -30,7 +30,8 @@ les dossiers d'admission et autres pièces utiles. """ import flask -from flask import url_for, g, request +from flask import url_for, render_template +from flask import g, request from flask_login import current_user import app.scodoc.sco_utils as scu @@ -328,9 +329,9 @@ def etudarchive_import_files_form(group_id): if tf[0] == 0: return "\n".join(H) + tf[1] + "" + F - elif tf[0] == -1: - # retrouve le semestre à partir du groupe: - group = sco_groups.get_group(group_id) + # retrouve le semestre à partir du groupe: + group = sco_groups.get_group(group_id) + if tf[0] == -1: return flask.redirect( url_for( "notes.formsemestre_status", @@ -340,21 +341,41 @@ def etudarchive_import_files_form(group_id): ) else: return etudarchive_import_files( - group_id=tf[2]["group_id"], + formsemestre_id=group["formsemestre_id"], xlsfile=tf[2]["xlsfile"], zipfile=tf[2]["zipfile"], description=tf[2]["description"], ) -def etudarchive_import_files(group_id=None, xlsfile=None, zipfile=None, description=""): +def etudarchive_import_files( + formsemestre_id=None, xlsfile=None, zipfile=None, description="" +): + "Importe des fichiers" + def callback(etud, data, filename): _store_etud_file_to_new_archive(etud["etudid"], data, filename, description) - filename_title = "fichier_a_charger" - page_title = "Téléchargement de fichiers associés aux étudiants" - # Utilise la fontion au depart developpee pour les photos - r = sco_trombino.zip_excel_import_files( - xlsfile, zipfile, callback, filename_title, page_title + # Utilise la fontion developpée au depart pour les photos + ( + ignored_zipfiles, + unmatched_files, + stored_etud_filename, + ) = sco_trombino.zip_excel_import_files( + xlsfile=xlsfile, + zipfile=zipfile, + callback=callback, + filename_title="fichier_a_charger", + ) + return render_template( + "scolar/photos_import_files.html", + page_title="Téléchargement de fichiers associés aux étudiants", + ignored_zipfiles=ignored_zipfiles, + unmatched_files=unmatched_files, + stored_etud_filename=stored_etud_filename, + next_page=url_for( + "scolar.groups_view", + scodoc_dept=g.scodoc_dept, + formsemestre_id=formsemestre_id, + ), ) - return r + html_sco_header.sco_footer() diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py index 1cbd199d..f07b9b58 100644 --- a/app/scodoc/sco_edit_matiere.py +++ b/app/scodoc/sco_edit_matiere.py @@ -190,7 +190,7 @@ def do_matiere_delete(oid): def matiere_delete(matiere_id=None): - """Delete an UE""" + """Delete matière""" from app.scodoc import sco_edit_ue M = matiere_list(args={"matiere_id": matiere_id})[0] @@ -200,7 +200,11 @@ def matiere_delete(matiere_id=None): "

Suppression de la matière %(titre)s" % M, " dans l'UE (%(acronyme)s))

" % UE, ] - dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(UE["formation_id"]) + dest_url = url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(UE["formation_id"]), + ) tf = TrivialFormulator( request.base_url, scu.get_request_args(), @@ -227,13 +231,13 @@ def matiere_edit(matiere_id=None): if not F: raise ScoValueError("Matière inexistante !") F = F[0] - U = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]}) - if not F: + ues = sco_edit_ue.ue_list(args={"ue_id": F["ue_id"]}) + if not ues: raise ScoValueError("UE inexistante !") - U = U[0] - Fo = sco_formations.formation_list(args={"formation_id": U["formation_id"]})[0] + ue = ues[0] + Fo = sco_formations.formation_list(args={"formation_id": ue["formation_id"]})[0] - ues = sco_edit_ue.ue_list(args={"formation_id": U["formation_id"]}) + ues = sco_edit_ue.ue_list(args={"formation_id": ue["formation_id"]}) ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues] ue_ids = [u["ue_id"] for u in ues] H = [ @@ -278,8 +282,11 @@ associé. submitlabel="Modifier les valeurs", ) - dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(U["formation_id"]) - + dest_url = url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(ue["formation_id"]), + ) if tf[0] == 0: return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer() elif tf[0] == -1: diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index a23ddf8c..54d7fc84 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -285,21 +285,25 @@ def module_delete(module_id=None): """Delete a module""" if not module_id: raise ScoValueError("invalid module !") - Mods = module_list(args={"module_id": module_id}) - if not Mods: + modules = module_list(args={"module_id": module_id}) + if not modules: raise ScoValueError("Module inexistant !") - Mod = Mods[0] + mod = modules[0] H = [ html_sco_header.sco_header(page_title="Suppression d'un module"), - """

Suppression du module %(titre)s (%(code)s)

""" % Mod, + """

Suppression du module %(titre)s (%(code)s)

""" % mod, ] - dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"]) + dest_url = url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(mod["formation_id"]), + ) tf = TrivialFormulator( request.base_url, scu.get_request_args(), (("module_id", {"input_type": "hidden"}),), - initvalues=Mod, + initvalues=mod, submitlabel="Confirmer la suppression", cancelbutton="Annuler", ) @@ -367,9 +371,11 @@ def module_edit(module_id=None): Mod["ue_matiere_id"] = "%s!%s" % (Mod["ue_id"], Mod["matiere_id"]) semestres_indices = list(range(1, parcours.NB_SEM + 1)) - - dest_url = scu.NotesURL() + "/ue_list?formation_id=" + str(Mod["formation_id"]) - + dest_url = url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(Mod["formation_id"]), + ) H = [ html_sco_header.sco_header( page_title="Modification du module %(titre)s" % Mod, @@ -588,9 +594,9 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True): """Création d'un module de "malus" dans chaque UE d'une formation""" from app.scodoc import sco_edit_ue - ue_list = sco_edit_ue.ue_list(args={"formation_id": formation_id}) + ues = sco_edit_ue.ue_list(args={"formation_id": formation_id}) - for ue in ue_list: + for ue in ues: # Un seul module de malus par UE: nb_mod_malus = len( [ @@ -603,7 +609,11 @@ def formation_add_malus_modules(formation_id, titre=None, redirect=True): ue_add_malus_module(ue["ue_id"], titre=titre) if redirect: - return flask.redirect("ue_list?formation_id=" + str(formation_id)) + return flask.redirect( + url_for( + "notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id + ) + ) def ue_add_malus_module(ue_id, titre=None, code=None): diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index cc7d7557..66d8777d 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -75,7 +75,7 @@ _ueEditor = ndb.EditableTable( sortkey="numero", input_formators={ "type": ndb.int_null_is_zero, - "is_external": bool, + "is_external": ndb.bool_or_str, }, output_formators={ "numero": ndb.int_null_is_zero, @@ -139,7 +139,11 @@ def do_ue_delete(ue_id, delete_validations=False, force=False): % (len(validations), ue["acronyme"], ue["titre"]), dest_url="", target_variable="delete_validations", - cancel_url="ue_list?formation_id=%s" % ue["formation_id"], + cancel_url=url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(ue["formation_id"]), + ), parameters={"ue_id": ue_id, "dialog_confirmed": 1}, ) if delete_validations: @@ -294,6 +298,14 @@ def ue_edit(ue_id=None, create=False, formation_id=None): "explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules", }, ), + ( + "is_external", + { + "input_type": "boolcheckbox", + "title": "UE externe", + "explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement", + }, + ), ] if parcours.UE_IS_MODULE: # demande le semestre pour creer le module immediatement: @@ -374,12 +386,12 @@ def ue_edit(ue_id=None, create=False, formation_id=None): ) -def _add_ue_semestre_id(ue_list): +def _add_ue_semestre_id(ues): """ajoute semestre_id dans les ue, en regardant le premier module de chacune. Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000), qui les place à la fin de la liste. """ - for ue in ue_list: + for ue in ues: Modlist = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]}) if Modlist: ue["semestre_id"] = Modlist[0]["semestre_id"] @@ -391,34 +403,38 @@ def next_ue_numero(formation_id, semestre_id=None): """Numero d'une nouvelle UE dans cette formation. Si le semestre est specifie, cherche les UE ayant des modules de ce semestre """ - ue_list = ue_list(args={"formation_id": formation_id}) - if not ue_list: + ues = ue_list(args={"formation_id": formation_id}) + if not ues: return 0 if semestre_id is None: - return ue_list[-1]["numero"] + 1000 + return ues[-1]["numero"] + 1000 else: # Avec semestre: (prend le semestre du 1er module de l'UE) - _add_ue_semestre_id(ue_list) - ue_list_semestre = [ue for ue in ue_list if ue["semestre_id"] == semestre_id] + _add_ue_semestre_id(ues) + ue_list_semestre = [ue for ue in ues if ue["semestre_id"] == semestre_id] if ue_list_semestre: return ue_list_semestre[-1]["numero"] + 10 else: - return ue_list[-1]["numero"] + 1000 + return ues[-1]["numero"] + 1000 def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False): """Delete an UE""" - ue = ue_list(args={"ue_id": ue_id}) - if not ue: + ues = ue_list(args={"ue_id": ue_id}) + if not ues: raise ScoValueError("UE inexistante !") - ue = ue[0] + ue = ues[0] if not dialog_confirmed: return scu.confirm_dialog( "

Suppression de l'UE %(titre)s (%(acronyme)s))

" % ue, dest_url="", parameters={"ue_id": ue_id}, - cancel_url="ue_list?formation_id=%s" % ue["formation_id"], + cancel_url=url_for( + "notes.ue_table", + scodoc_dept=g.scodoc_dept, + formation_id=str(ue["formation_id"]), + ), ) return do_ue_delete(ue_id, delete_validations=delete_validations) @@ -438,21 +454,24 @@ def ue_table(formation_id=None, msg=""): # was ue_list parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"]) locked = sco_formations.formation_has_locked_sems(formation_id) - ue_list = ue_list(args={"formation_id": formation_id}) + ues = ue_list(args={"formation_id": formation_id, "is_external": False}) + ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True}) # tri par semestre et numero: - _add_ue_semestre_id(ue_list) - ue_list.sort(key=lambda u: (u["semestre_id"], u["numero"])) - has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ue_list])) != len(ue_list) + _add_ue_semestre_id(ues) + _add_ue_semestre_id(ues_externes) + ues.sort(key=lambda u: (u["semestre_id"], u["numero"])) + ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"])) + has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues) - perm_change = current_user.has_permission(Permission.ScoChangeFormation) - # editable = (not locked) and perm_change + has_perm_change = current_user.has_permission(Permission.ScoChangeFormation) + # editable = (not locked) and has_perm_change # On autorise maintanant la modification des formations qui ont des semestres verrouillés, # sauf si cela affect les notes passées (verrouillées): # - pas de modif des modules utilisés dans des semestres verrouillés # - pas de changement des codes d'UE utilisés dans des semestres verrouillés - editable = perm_change + editable = has_perm_change tag_editable = ( - current_user.has_permission(Permission.ScoEditFormationTags) or perm_change + current_user.has_permission(Permission.ScoEditFormationTags) or has_perm_change ) if locked: lockicon = scu.icontag("lock32_img", title="verrouillé") @@ -556,213 +575,20 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); H.append( '
montrer les tags
' ) - - cur_ue_semestre_id = None - iue = 0 - for UE in ue_list: - if UE["ects"]: - UE["ects_str"] = ", %g ECTS" % UE["ects"] - else: - UE["ects_str"] = "" - if editable: - klass = "span_apo_edit" - else: - klass = "" - UE["code_apogee_str"] = ( - """, Apo: """ - % (klass, UE["ue_id"], scu.APO_MISSING_CODE_STR) - + (UE["code_apogee"] or "") - + "" + H.append( + _ue_table_ues( + parcours, + ues, + editable, + tag_editable, + has_perm_change, + arrow_up, + arrow_down, + arrow_none, + delete_icon, + delete_disabled_icon, ) - - if cur_ue_semestre_id != UE["semestre_id"]: - cur_ue_semestre_id = UE["semestre_id"] - if iue > 0: - H.append("") - if UE["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT: - lab = "Pas d'indication de semestre:" - else: - lab = "Semestre %s:" % UE["semestre_id"] - H.append('
%s
' % lab) - H.append('
") + ) if editable: H.append( '
") # formation_ue_list + if ues_externes: + H.append('
') + H.append( + '
UE externes déclarées (pour information):
' + ) + H.append( + _ue_table_ues( + parcours, + ues_externes, + editable, + tag_editable, + has_perm_change, + arrow_up, + arrow_down, + arrow_none, + delete_icon, + delete_disabled_icon, + ) + ) + H.append("
") # formation_ue_list + H.append("

") + if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT: + lab = "Pas d'indication de semestre:" + else: + lab = "Semestre %s:" % ue["semestre_id"] + H.append('
%s
' % lab) + H.append('