# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Affichage étudiants d'un ou plusieurs groupes sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf) """ # Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code) import datetime from urllib.parse import parse_qs from flask import url_for, g, request from flask_login import current_user from app import db from app.models import FormSemestre import app.scodoc.sco_utils as scu from app.scodoc import html_sco_header from app.scodoc import sco_excel from app.scodoc import sco_formsemestre from app.scodoc import sco_groups from app.scodoc import sco_moduleimpl from app.scodoc import sco_cursus from app.scodoc import sco_portal_apogee from app.scodoc import sco_preferences from app.scodoc import sco_etud from app.scodoc.sco_etud import etud_sort_key from app.scodoc.gen_tables import GenTable from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied from app.scodoc.sco_permissions import Permission JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ "js/etud_info.js", "js/groups_view.js", ] CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS # view: def groups_view( group_ids=(), fmt="html", with_codes=0, etat=None, with_paiement=0, with_archives=0, with_annotations=0, with_bourse=0, formsemestre_id=None, ): """Affichage des étudiants des groupes indiqués group_ids: liste de group_id fmt: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf Options pour listes: with_paiement: si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail) with_archives: ajoute colonne avec noms fichiers archivés formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes. """ # Informations sur les groupes à afficher: groups_infos = DisplayedGroupsInfos( group_ids, formsemestre_id=formsemestre_id, etat=etat, select_all_when_unspecified=True, ) # Formats spéciaux: download direct if fmt != "html": return groups_table( groups_infos=groups_infos, fmt=fmt, with_codes=with_codes, etat=etat, with_paiement=with_paiement, with_archives=with_archives, with_annotations=with_annotations, with_bourse=with_bourse, ) # Note: le formulaire est soumis a chaque modif des groupes # on pourrait faire comme pour le form de saisie des notes. Il faudrait pour cela: # - charger tous les etudiants au debut, quels que soient les groupes selectionnés # - ajouter du JS pour modifier les liens (arguments group_ids) quand le menu change return f""" { html_sco_header.sco_header( javascripts=JAVASCRIPTS, cssstyles=CSSSTYLES, init_qtip=True, ) } <style> div.multiselect-container.dropdown-menu {{ min-width: 180px; }} span.warning_unauthorized {{ color: pink; font-style: italic; margin-left: 12px; }} </style> <div id="group-tabs"> <!-- Menu choix groupe --> {form_groups_choice(groups_infos, submit_on_change=True)} <ul class="nav nav-tabs"> <li class="active"><a href="#tab-listes" data-toggle="tab">Listes</a></li> <li><a href="#tab-photos" data-toggle="tab">Photos</a></li> <li><a href="#tab-abs" data-toggle="tab">Absences et feuilles...</a></li> </ul> </div> <!-- Tab panes --> <div class="tab-content"> <div class="tab-pane active" id="tab-listes"> { groups_table( groups_infos=groups_infos, fmt=fmt, with_codes=with_codes, etat=etat, with_paiement=with_paiement, with_archives=with_archives, with_annotations=with_annotations, with_bourse=with_bourse, ) } </div> <div class="tab-pane" id="tab-photos"> { tab_photos_html(groups_infos, etat=etat) } </div> <div class="tab-pane" id="tab-abs"> { tab_absences_html(groups_infos, etat=etat) } </div> </div> { html_sco_header.sco_footer() } """ def form_groups_choice( groups_infos, with_selectall_butt=False, with_deselect_butt=False, submit_on_change=False, default_deselect_others=True, ): """form pour selection groupes group_ids est la liste des groupes actuellement sélectionnés et doit comporter au moins un élément, sauf si formsemestre_id est spécifié. (utilisé pour retrouver le semestre et proposer la liste des autres groupes) Si submit_on_change, soumet (recharge la page) à chaque modif. Si default_deselect_others, désélectionne le groupe "Tous" quand on sélectionne un autre groupe. Ces deux options ajoutent des classes utilisées en JS pour la gestion du formulaire. """ default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id) H = [ f""" <form id="group_selector" method="get"> <input type="hidden" name="formsemestre_id" id="formsemestre_id" value="{groups_infos.formsemestre_id}"/> <input type="hidden" name="default_group_id" id="default_group_id" value="{default_group_id}"/> Groupes: { menu_groups_choice( groups_infos, submit_on_change=submit_on_change, default_deselect_others=default_deselect_others, ) } """ ] if with_selectall_butt: H.append( """<input type="button" value="sélectionner tous" onmousedown="select_groupe_tous();"/>""" ) if with_deselect_butt: H.append( """<input type="button" value="ne pas filtrer" onmousedown="remove_group_filter();"/>""" ) H.append("</form>") return "\n".join(H) def menu_groups_choice( groups_infos, submit_on_change=False, default_deselect_others=True ): """menu pour selection groupes group_ids est la liste des groupes actuellement sélectionnés et doit comporter au moins un élément, sauf si formsemestre_id est spécifié. (utilisé pour retrouver le semestre et proposer la liste des autres groupes) """ default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id) n_members = len(sco_groups.get_group_members(default_group_id)) H = [ f"""<select name="group_ids" id="group_ids_sel" class="multiselect {'submit_on_change' if submit_on_change else ''} {'default_deselect_others' if default_deselect_others else ''} " multiple="multiple"> <option class="default_group" value="{default_group_id}" {'selected' if default_group_id in groups_infos.group_ids else ''} >Tous ({n_members})</option> """ ] for partition in groups_infos.partitions: H.append('<optgroup label="%s">' % partition["partition_name"]) # Les groupes dans cette partition: for g in sco_groups.get_partition_groups(partition): if g["group_id"] in groups_infos.group_ids: selected = "selected" else: selected = "" if g["group_name"]: n_members = len(sco_groups.get_group_members(g["group_id"])) H.append( '<option value="%s" %s>%s (%s)</option>' % (g["group_id"], selected, g["group_name"], n_members) ) H.append("</optgroup>") H.append("</select> ") return "\n".join(H) def menu_group_choice(group_id=None, formsemestre_id=None): """Un bête menu pour choisir un seul groupe group_id est le groupe actuellement sélectionné. Si aucun groupe selectionné, utilise formsemestre_id pour lister les groupes. """ if group_id: group = sco_groups.get_group(group_id) formsemestre_id = group["formsemestre_id"] elif not formsemestre_id: raise ValueError("missing formsemestre_id") H = [ """ <select id="group_selector_u" name="group_id" onchange="reload_selected_group();"> """ ] if not group_id: H.append('<option value="">choisir...</option>') for partition in sco_groups.get_partitions_list(formsemestre_id): if partition["partition_name"]: H.append('<optgroup label="%s">' % partition["partition_name"]) groups = sco_groups.get_partition_groups(partition) for group in groups: if group["group_id"] == group_id: selected = "selected" else: selected = "" name = group["group_name"] or "Tous" n_members = len(sco_groups.get_group_members(group["group_id"])) H.append( '<option value="%s" %s>%s (%s)</option>' % (group["group_id"], selected, name, n_members) ) if partition["partition_name"]: H.append("</optgroup>") H.append( """</select> <script> function reload_selected_group() { var url = $.url(); var group_id = $("#group_selector_u").val(); if (group_id) { url.param()['group_id'] = group_id; var query_string = $.param(url.param(), traditional=true ); window.location = url.attr('base') + url.attr('path') + '?' + query_string; } } </script> """ ) return "\n".join(H) class DisplayedGroupsInfos: """Container with attributes describing groups to display in the page .groups_query_args : 'group_ids=xxx&group_ids=yyy' .base_url : url de la requete, avec les groupes, sans les autres paramètres .formsemestre_id : semestre "principal" (en fait celui du 1er groupe de la liste) .members : .groups_titles etat: filtrage selon l'état de l'inscription select_all_when_unspecified : sélectionne le groupe "tous" si aucun groupe n'est indiqué. empty_list_select_all: si vrai (défaut) on sélectionne le groupe tous si aucun groupe indiqué. """ def __init__( self, group_ids=(), # groupes specifies dans l'URL, ou un seul int formsemestre_id: int | None = None, etat: str | None = None, select_all_when_unspecified=False, empty_list_select_all=True, moduleimpl_id=None, # used to find formsemestre when unspecified ): if isinstance(group_ids, int): if group_ids: group_ids = [group_ids] # cas ou un seul parametre, pas de liste else: try: group_ids = [int(g) for g in group_ids] except ValueError as exc: raise ScoValueError( "identifiant de groupe invalide (mettre à jour vos bookmarks ?)" ) from exc if not formsemestre_id and moduleimpl_id: mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) if len(mods) != 1: raise ValueError("invalid moduleimpl_id") formsemestre_id = mods[0]["formsemestre_id"] if not group_ids: # appel sans groupe (eg page accueil) if not formsemestre_id: raise ValueError("missing parameter formsemestre_id or group_ids") if empty_list_select_all: if select_all_when_unspecified: group_ids = [ sco_groups.get_default_group( formsemestre_id, fix_if_missing=True ) ] else: # selectionne le premier groupe trouvé, s'il y en a un partition = sco_groups.get_partitions_list( formsemestre_id, with_default=True )[0] groups = sco_groups.get_partition_groups(partition) if groups: group_ids = [groups[0]["group_id"]] else: group_ids = [sco_groups.get_default_group(formsemestre_id)] else: group_ids = [] gq = [] for group_id in group_ids: gq.append("group_ids=" + str(group_id)) self.groups_query_args = "&".join(gq) self.base_url = request.base_url + "?" + self.groups_query_args self.group_ids = group_ids self.groups = [] groups_titles = [] self.members = [] self.tous_les_etuds_du_sem = ( False # affiche tous les etuds du semestre ? (si un seul semestre) ) self.sems = {} # formsemestre_id : sem self.formsemestre = None self.formsemestre_id = formsemestre_id self.nbdem = 0 # nombre d'étudiants démissionnaires en tout sem = None selected_partitions = set() for group_id in group_ids: group_members, group, group_tit, sem, nbdem = sco_groups.get_group_infos( group_id, etat=etat ) self.groups.append(group) self.nbdem += nbdem self.sems[sem["formsemestre_id"]] = sem if not self.formsemestre_id: self.formsemestre_id = sem["formsemestre_id"] self.formsemestre = sem self.members.extend(group_members) groups_titles.append(group_tit) if group["partition_name"] is None: self.tous_les_etuds_du_sem = True else: # liste les partitions explicitement sélectionnés (= des groupes de group_ids) selected_partitions.add((group["numero"] or 0, group["partition_id"])) self.selected_partitions = [ x[1] for x in sorted(list(selected_partitions)) ] # -> [ partition_id ] if not self.formsemestre: # aucun groupe selectionne self.formsemestre = sco_formsemestre.get_formsemestre(formsemestre_id) if formsemestre_id not in self.sems: self.sems[formsemestre_id] = self.formsemestre self.sortuniq() if len(self.sems) > 1: self.tous_les_etuds_du_sem = False # plusieurs semestres if self.tous_les_etuds_du_sem: if sem and sem["semestre_id"] >= 0: self.groups_titles = "S%d" % sem["semestre_id"] else: self.groups_titles = "tous" self.groups_filename = self.groups_titles else: self.groups_titles = ", ".join(groups_titles) self.groups_filename = "_".join(groups_titles).replace(" ", "_") # Sanitize filename: self.groups_filename = scu.make_filename(self.groups_filename) # colonnes pour affichages nom des groupes: # gère le cas où les étudiants appartiennent à des semestres différents self.partitions = [] # les partitions, sans celle par defaut for formsemestre_id in self.sems: for partition in sco_groups.get_partitions_list(formsemestre_id): if partition["partition_name"]: self.partitions.append(partition) def sortuniq(self): "Trie les étudiants (de plusieurs groupes) et élimine les doublons" if (len(self.group_ids) <= 1) or len(self.members) <= 1: return # on suppose que les etudiants d'un groupe sont deja triés # tri selon nom_usuel ou nom, sans accents self.members.sort(key=etud_sort_key) to_remove = [] T = self.members for i in range(len(T) - 1, 0, -1): if T[i - 1]["etudid"] == T[i]["etudid"]: to_remove.append(i) for i in to_remove: del T[i] def get_form_elem(self): """html hidden input with groups""" H = [] for group_id in self.group_ids: H.append(f'<input type="hidden" name="group_ids" value="{group_id}"/>') return "\n".join(H) def get_formsemestre(self) -> FormSemestre: return ( db.session.get(FormSemestre, self.formsemestre_id) if self.formsemestre_id else None ) # Ancien ZScolar.group_list renommé ici en group_table def groups_table( groups_infos: DisplayedGroupsInfos = None, with_codes=0, etat=None, fmt="html", with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail) with_archives=0, # ajoute colonne avec noms fichiers archivés with_annotations=0, with_bourse=0, ): """liste etudiants inscrits dans ce semestre fmt: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape """ from app.scodoc import sco_report can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData)) with_codes = int(with_codes) with_paiement = int(with_paiement) and can_view_etud_data with_archives = int(with_archives) and can_view_etud_data with_annotations = int(with_annotations) and can_view_etud_data with_bourse = int(with_bourse) and can_view_etud_data base_url_np = groups_infos.base_url + f"&with_codes={with_codes}" base_url = ( base_url_np + f"""&with_paiement={with_paiement}&with_archives={ with_archives}&with_annotations={with_annotations }&with_bourse={with_bourse}""" ) # columns_ids = ["civilite_str", "nom_disp", "prenom"] # colonnes a inclure titles = { "civilite_str": "Civ.", "nom_disp": "Nom", "prenom": "Prénom", "email": "Mail", "emailperso": "Personnel", "etat": "Etat", "etudid": "etudid", "code_nip": "code_nip", "code_ine": "code_ine", "datefinalisationinscription_str": "Finalisation inscr.", "paiementinscription_str": "Paiement", "etudarchive": "Fichiers", "annotations_str": "Annotations", "bourse_str": "Boursier", "etape": "Etape", "semestre_groupe": "Semestre-Groupe", # pour Moodle "annee": "annee_admission", } # ajoute colonnes pour groupes columns_ids.extend([p["partition_id"] for p in groups_infos.partitions]) titles.update( dict( [(p["partition_id"], p["partition_name"]) for p in groups_infos.partitions] ) ) partitions_name = { p["partition_id"]: p["partition_name"] for p in groups_infos.partitions } if fmt != "html": # ne mentionne l'état que en Excel (style en html) columns_ids.append("etat") columns_ids.append("email") if can_view_etud_data: columns_ids.append("emailperso") if fmt == "moodlecsv": columns_ids = ["email", "semestre_groupe"] if with_codes: columns_ids += ["etape", "etudid", "code_nip", "code_ine"] if with_paiement: columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"] if with_paiement: # or with_codes: sco_portal_apogee.check_paiement_etuds(groups_infos.members) if with_archives: from app.scodoc import sco_archives_etud sco_archives_etud.add_archives_info_to_etud_list(groups_infos.members) columns_ids += ["etudarchive"] if with_annotations: sco_etud.add_annotations_to_etud_list(groups_infos.members) columns_ids += ["annotations_str"] if with_bourse: columns_ids += ["bourse_str"] moodle_sem_name = groups_infos.formsemestre["session_id"] moodle_groupenames = set() # ajoute liens for etud in groups_infos.members: if etud["email"]: etud["_email_target"] = "mailto:" + etud["email"] else: etud["_email_target"] = "" if etud["emailperso"]: etud["_emailperso_target"] = "mailto:" + etud["emailperso"] else: etud["_emailperso_target"] = "" fiche_url = url_for( "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"] ) etud["_nom_disp_target"] = fiche_url etud["_nom_disp_order"] = etud_sort_key(etud) etud["_prenom_target"] = fiche_url etud["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"]) etud["bourse_str"] = "oui" if etud["boursier"] else "non" if etud["etat"] == "D": etud["_css_row_class"] = "etuddem" # et groupes: for partition_id in etud["partitions"]: etud[partition_id] = etud["partitions"][partition_id]["group_name"] # Ajoute colonne pour moodle: semestre_groupe, de la forme # RT-DUT-FI-S3-2021-PARTITION-GROUPE moodle_groupename = [] if groups_infos.selected_partitions: # il y a des groupes selectionnes, utilise leurs partitions for partition_id in groups_infos.selected_partitions: if partition_id in etud["partitions"]: moodle_groupename.append( partitions_name[partition_id] + "-" + etud["partitions"][partition_id]["group_name"] ) else: # pas de groupes sélectionnés: prend le premier s'il y en a un moodle_groupename = ["tous"] if etud["partitions"]: for p in etud["partitions"].items(): # partitions is an OrderedDict moodle_groupename = [ partitions_name[p[0]] + "-" + p[1]["group_name"] ] break moodle_groupenames |= set(moodle_groupename) etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename) if groups_infos.nbdem > 1: s = "s" else: s = "" if fmt == "moodlecsv": # de la forme S1-[FI][FA]-groupe.csv if not moodle_groupenames: moodle_groupenames = {"tous"} filename = ( moodle_sem_name + "-" + groups_infos.formsemestre["modalite"] + "-" + "+".join(sorted(moodle_groupenames)) ) else: filename = f"etudiants_{groups_infos.groups_filename}" prefs = sco_preferences.SemPreferences(groups_infos.formsemestre_id) tab = GenTable( rows=groups_infos.members, columns_ids=columns_ids, titles=titles, caption="soit %d étudiants inscrits et %d démissionaire%s." % (len(groups_infos.members) - groups_infos.nbdem, groups_infos.nbdem, s), base_url=base_url, filename=filename, pdf_link=False, # pas d'export pdf html_sortable=True, html_class="table_leftalign table_listegroupe", xml_outer_tag="group_list", xml_row_tag="etud", text_fields_separator=prefs["moodle_csv_separator"], text_with_titles=prefs["moodle_csv_with_headerline"], preferences=prefs, ) # if fmt == "html": amail_inst = [ x["email"] for x in groups_infos.members if x["email"] and x["etat"] != "D" ] amail_perso = [ x["emailperso"] for x in groups_infos.members if x["emailperso"] and x["etat"] != "D" ] if len(groups_infos.members): if groups_infos.tous_les_etuds_du_sem: htitle = "Les %d étudiants inscrits" % len(groups_infos.members) else: htitle = "Groupe %s (%d étudiants)" % ( groups_infos.groups_titles, len(groups_infos.members), ) else: htitle = "Aucun étudiant !" H = [ f"""<div class="tab-content"> <form> <span style="font-weight:bold; font-size:120%;">{htitle}</span> """ ] if groups_infos.members: menu_options = [] options = { "with_codes": "Affiche codes", } if can_view_etud_data: options.update( { "with_paiement": "Paiement inscription", "with_archives": "Fichiers archivés", "with_annotations": "Annotations", "with_bourse": "Statut boursier", } ) for option, label in options.items(): if locals().get(option, False): selected = "selected" else: selected = "" menu_options.append( f"""<option value="{option}" {selected}>{label}</option>""" ) H.extend( [ """<span style="margin-left: 2em;"> <select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""", "\n".join(menu_options), """</select></span> <script type="text/javascript"> $(document).ready(function() { $('#group_list_options').multiselect( { includeSelectAllOption: false, nonSelectedText:'Options...', onChange: function(element, checked){ change_list_options(); } } ); }); </script> """, """<span class="warning_unauthorized">accès aux données personnelles interdit</span>""" if not can_view_etud_data else "", ] ) H.append("</div></form>") if groups_infos.members: H.extend( [ tab.html(), f""" <ul> <li><a class="stdlink" href="{tab.base_url}&fmt=xlsappel">Feuille d'appel Excel</a> </li> <li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel</a> </li> <li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a> </li> <li> <a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id={groups_infos.formsemestre_id}"> Fichier CSV pour Moodle (tous les groupes)</a> <em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em> </li>""", ] ) if amail_inst: H.append( f"""<li> <a class="stdlink" href="mailto:?bcc={','.join(amail_inst) }">Envoyer un mail collectif au groupe de {groups_infos.groups_titles} (via {len(amail_inst)} adresses institutionnelles)</a> </li>""" ) if can_view_etud_data: if amail_perso: H.append( f"""<li> <a class="stdlink" href="mailto:?bcc={','.join(amail_perso) }">Envoyer un mail collectif au groupe de {groups_infos.groups_titles} (via {len(amail_perso)} adresses personnelles)</a> </li>""" ) else: H.append("<li><em>Adresses personnelles non renseignées</em></li>") else: H.append( """<li class="unauthorized">adresses mail personnelles protégées</li>""" ) H.append("</ul>") return "".join(H) elif ( fmt == "pdf" or fmt == "xml" or fmt == "json" or fmt == "xls" or fmt == "moodlecsv" ): if fmt == "moodlecsv": fmt = "csv" return tab.make_page(fmt=fmt) elif fmt == "xlsappel": xls = sco_excel.excel_feuille_listeappel( groups_infos.formsemestre, groups_infos.groups_titles, groups_infos.members, partitions=groups_infos.partitions, with_codes=with_codes, with_paiement=with_paiement, server_name=request.url_root, ) filename = "liste_%s" % groups_infos.groups_filename return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) elif fmt == "allxls": if not can_view_etud_data: raise ScoPermissionDenied( "Vous n'avez pas la permission requise (ViewEtudData)" ) # feuille Excel avec toutes les infos etudiants if not groups_infos.members: return "" keys = [ "etudid", "code_nip", "etat", "civilite_str", "nom", "nom_usuel", "prenom", "inscriptionstr", ] if with_paiement: keys.append("paiementinscription") keys += [ "email", "emailperso", "domicile", "villedomicile", "codepostaldomicile", "paysdomicile", "telephone", "telephonemobile", "fax", "date_naissance", "lieu_naissance", "bac", "specialite", "annee_bac", "nomlycee", "villelycee", "codepostallycee", "codelycee", "annee", "type_admission", "boursier_prec", "boursier", "debouche", "parcours", "code_cursus", ] titles = keys[:] other_partitions = sco_groups.get_group_other_partitions(groups_infos.groups[0]) keys += [p["partition_id"] for p in other_partitions] titles += [p["partition_name"] for p in other_partitions] # remplis infos lycee si on a que le code lycée # et ajoute infos inscription for m in groups_infos.members: etud = sco_etud.get_etud_info(m["etudid"], filled=True)[0] m.update(etud) sco_etud.etud_add_lycee_infos(etud) # et ajoute le parcours Se = sco_cursus.get_situation_etud_cursus( etud, groups_infos.formsemestre_id ) m["parcours"] = Se.get_cursus_descr() m["code_cursus"], _ = sco_report.get_code_cursus_etud( etud["etudid"], sems=etud["sems"] ) rows = [[m.get(k, "") for k in keys] for m in groups_infos.members] title = "etudiants_%s" % groups_infos.groups_filename xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title) filename = title return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE) else: raise ScoValueError("unsupported format") def tab_absences_html(groups_infos, etat=None): """contenu du tab "absences et feuilles diverses" """ authuser = current_user H = ['<div class="tab-content">'] if not groups_infos.members: return "".join(H) + "<h3>Aucun étudiant !</h3></div>" group_ids: str = ",".join(map(str, groups_infos.group_ids)) formsemestre: FormSemestre = groups_infos.get_formsemestre() H.extend( [ "<h3>Assiduité</h3>", '<ul class="ul_abs">', "<li>", form_choix_saisie_semaine(groups_infos), # Ajout Le Havre "</li>", "<li>", form_choix_jour_saisie_hebdo(groups_infos), "</li>", f"""<li><a class="stdlink" href="{ url_for("assiduites.visu_assi_group", scodoc_dept=g.scodoc_dept, group_ids=group_ids, date_debut=formsemestre.date_debut.isoformat(), date_fin=formsemestre.date_fin.isoformat() ) }">État de l'assiduité du groupe</a></li>""", "</ul>", "<h3>Feuilles</h3>", '<ul class="ul_feuilles">', """<li><a class="stdlink" href="%s&fmt=xlsappel">Feuille d'émargement %s (Excel)</a></li>""" % (groups_infos.base_url, groups_infos.groups_titles), """<li><a class="stdlink" href="trombino?%s&fmt=pdf">Trombinoscope en PDF</a></li>""" % groups_infos.groups_query_args, """<li><a class="stdlink" href="pdf_trombino_tours?%s&fmt=pdf">Trombinoscope en PDF (format "IUT de Tours", beta)</a></li>""" % groups_infos.groups_query_args, """<li><a class="stdlink" href="pdf_feuille_releve_absences?%s&fmt=pdf">Feuille relevé absences hebdomadaire (beta)</a></li>""" % groups_infos.groups_query_args, """<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>""" % groups_infos.groups_query_args, f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>""" if authuser.has_permission(Permission.ViewEtudData) else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>""", "</ul>", ] ) H.append('<h3>Opérations diverses</h3><ul class="ul_misc">') # Lien pour verif codes INE/NIP # (pour tous les etudiants du semestre) group_id = sco_groups.get_default_group(groups_infos.formsemestre_id) if authuser.has_permission(Permission.EtudInscrit): H.append( f"""<li><a class="stdlink" href="{ url_for('scolar.check_group_apogee', scodoc_dept=g.scodoc_dept, group_id=group_id, etat=etat or "") }">Vérifier codes Apogée</a> (de tous les groupes)</li> """ ) # Lien pour ajout fichiers étudiants text = "Télécharger des fichiers associés aux étudiants (e.g. dossiers d'admission)" if authuser.has_permission( Permission.EtudAddAnnotations ) and authuser.has_permission(Permission.ViewEtudData): H.append( f"""<li><a class="stdlink" href="{ url_for('scolar.etudarchive_import_files_form', scodoc_dept=g.scodoc_dept, group_id=group_id )}">{text}</a></li>""" ) else: H.append(f"""<li class="unauthorized" title="non autorisé">{text}</li>""") H.append("</ul></div>") return "".join(H) def tab_photos_html(groups_infos, etat=None): """contenu du tab "photos" """ from app.scodoc import sco_trombino if not groups_infos.members: return '<div class="tab-content"><h3>Aucun étudiant !</h3></div>' return sco_trombino.trombino_html(groups_infos) def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None): """Formulaire choix jour semaine pour saisie.""" authuser = current_user if not authuser.has_permission(Permission.AbsChange): return "" return f""" <button onclick="window.location='{ url_for( "assiduites.signal_assiduites_group", scodoc_dept=g.scodoc_dept, group_ids=",".join(map(str,groups_infos.group_ids)), jour=datetime.date.today().isoformat(), formsemestre_id=groups_infos.formsemestre_id, moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id ) }';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button> """ # Saisie de l'assiduité par semaine def form_choix_saisie_semaine(groups_infos): authuser = current_user if not authuser.has_permission(Permission.AbsChange): return "" query_args = parse_qs(request.query_string) moduleimpl_id = query_args.get("moduleimpl_id", [None])[0] semaine = datetime.date.today().isocalendar().week return f""" <button onclick="window.location='{url_for( "assiduites.signal_assiduites_diff", group_ids=",".join(map(str,groups_infos.group_ids)), semaine=semaine, scodoc_dept=g.scodoc_dept, formsemestre_id=groups_infos.formsemestre_id, moduleimpl_id=moduleimpl_id )}';">Saisie à la semaine</button> """ def export_groups_as_moodle_csv(formsemestre_id=None): """Export all students/groups, in a CSV format suitable for Moodle Each (student,group) will be listed on a separate line jo@univ.fr,S3-A jo@univ.fr,S3-B1 if jo belongs to group A in a partition, and B1 in another one. Caution: if groups in different partitions share the same name, there will be duplicates... should we prefix the group names with the partition's name ? """ if not formsemestre_id: raise ScoValueError("missing parameter: formsemestre_id") _, partitions_etud_groups = sco_groups.get_formsemestre_groups( formsemestre_id, with_default=True ) sem = sco_formsemestre.get_formsemestre(formsemestre_id) moodle_sem_name = sem["session_id"] columns_ids = ("email", "semestre_groupe") T = [] for partition_id in partitions_etud_groups: partition = sco_groups.get_partition(partition_id) members = partitions_etud_groups[partition_id] for etudid in members: etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0] group_name = members[etudid]["group_name"] elts = [moodle_sem_name] if partition["partition_name"]: elts.append(partition["partition_name"]) if group_name: elts.append(group_name) T.append({"email": etud["email"], "semestre_groupe": "-".join(elts)}) # Make table prefs = sco_preferences.SemPreferences(formsemestre_id) tab = GenTable( rows=T, columns_ids=("email", "semestre_groupe"), filename=moodle_sem_name + "-moodle", titles={x: x for x in columns_ids}, text_fields_separator=prefs["moodle_csv_separator"], text_with_titles=prefs["moodle_csv_with_headerline"], preferences=prefs, ) return tab.make_page(fmt="csv")