ScoDoc/app/scodoc/sco_groups_view.py

1126 lines
42 KiB
Python
Raw Normal View History

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-12-31 23:04:06 +01:00
# 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
2024-05-29 16:29:34 +02:00
sous forme: de liste html (table exportable), de trombinoscope (exportable en pdf)
"""
# Re-ecriture en 2014 (re-organisation de l'interface, modernisation du code)
# Modif en 2024 (9.7/revamp, abandon des tabs bootstrap)
import datetime
from urllib.parse import parse_qs
from flask import url_for, g, render_template, request
2021-09-18 14:03:36 +02:00
from flask_login import current_user
from app import db, log
from app.models import FormSemestre, Identite, ScolarEvent
import app.scodoc.sco_utils as scu
from app.scodoc import sco_assiduites as scass
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
2022-07-07 16:24:52 +02:00
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
2022-09-02 22:00:34 +02:00
from app.scodoc.sco_etud import etud_sort_key
from app.scodoc.gen_tables import GenTable
2024-01-20 17:37:24 +01:00
from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied
from app.scodoc.sco_permissions import Permission
# view
def groups_lists(
group_ids=(),
fmt="html",
with_codes=0,
with_date_inscription=0,
etat=None,
with_paiement=0,
with_archives=0,
with_annotations=0,
2022-12-07 15:52:50 +01:00
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.
"""
# version sans tabs: juste la liste des étudiants
# 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,
with_date_inscription=with_date_inscription,
etat=etat,
with_paiement=with_paiement,
with_archives=with_archives,
with_annotations=with_annotations,
2022-12-07 15:52:50 +01:00
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 render_template(
"formsemestre/groups_lists.j2",
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
groups_table=groups_table(
groups_infos=groups_infos,
fmt=fmt,
with_codes=with_codes,
with_date_inscription=with_date_inscription,
etat=etat,
with_paiement=with_paiement,
with_archives=with_archives,
with_annotations=with_annotations,
with_bourse=with_bourse,
),
groups_titles=groups_infos.groups_titles,
)
# view
def groups_photos(group_ids=(), etat=None, formsemestre_id=None):
"""Affichage des photos des étudiants (trombi) des groupes indiqués
group_ids: liste de group_id
formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes.
2023-11-17 17:00:15 +01:00
"""
groups_infos = DisplayedGroupsInfos(
group_ids,
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
)
return render_template(
"formsemestre/groups_photos.j2",
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
tab_photos_html=tab_photos_html(groups_infos, etat=etat),
groups_titles=groups_infos.groups_titles,
)
def groups_feuilles(group_ids=(), etat=None, formsemestre_id=None):
"""Affichage des feuilles d'appel des groupes indiqués
group_ids: liste de group_id
formsemestre_id est utilisé si aucun groupe selectionné pour construire la liste des groupes.
"""
groups_infos = DisplayedGroupsInfos(
group_ids,
formsemestre_id=formsemestre_id,
select_all_when_unspecified=True,
)
return render_template(
"formsemestre/groups_feuilles.j2",
form_groups_choice=form_groups_choice(groups_infos, submit_on_change=True),
tab_absences_html=tab_absences_html(groups_infos, etat=etat),
groups_titles=groups_infos.groups_titles,
)
2023-11-16 23:34:47 +01:00
def form_groups_choice(
groups_infos,
with_selectall_butt=False,
with_deselect_butt=False,
submit_on_change=False,
default_deselect_others=True,
2024-08-26 12:03:37 +02:00
args: dict | None = None,
title="Groupes :",
2023-11-16 23:34:47 +01:00
):
2024-08-26 12:03:37 +02:00
"""Formulaire complet pour choix de groupe.
2023-11-16 23:34:47 +01:00
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)
2024-08-26 12:03:37 +02:00
args = args or {}
args_wdg = "\n".join(
[
f"""<input type="hidden" name="{k}" value="{v}"/>"""
for k, v in args.items()
if k not in ("formsemestre_id", "group_ids")
]
)
H = [
2023-11-16 23:34:47 +01:00
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}"/>
2024-08-26 12:03:37 +02:00
{args_wdg}
{title}
2023-11-16 23:34:47 +01:00
{
menu_groups_choice(
groups_infos,
submit_on_change=submit_on_change,
default_deselect_others=default_deselect_others,
)
}
"""
]
if with_selectall_butt:
H.append(
2023-11-16 23:34:47 +01:00
"""<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)
2023-11-16 23:34:47 +01:00
def menu_groups_choice(
2024-07-31 13:43:39 +02:00
groups_infos,
submit_on_change=False,
default_deselect_others=True,
2023-11-16 23:34:47 +01:00
):
"""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)
2024-07-31 13:43:39 +02:00
2024-08-25 08:05:40 +02:00
Si url_export :
2024-07-31 13:43:39 +02:00
selecteur.value = &group_ids=xxx&group_ids=yyy...
sinon :
selecteur.value = [xxx, yyy, ...]
"""
default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
2023-11-16 23:34:47 +01:00
n_members = len(sco_groups.get_group_members(default_group_id))
2024-07-31 13:43:39 +02:00
values: dict = {
# Choix : Tous (tous les groupes)
"": [
{
"value": default_group_id,
"label": f"Tous ({n_members})",
"selected": default_group_id in groups_infos.group_ids,
"single": default_deselect_others,
}
]
}
for partition in groups_infos.partitions:
2024-07-31 13:43:39 +02:00
p_name: str = partition["partition_name"]
vals: list[tuple[str, str, bool]] = []
# Les groupes dans cette partition:
2024-07-31 13:43:39 +02:00
for grp in sco_groups.get_partition_groups(partition):
selected: bool = grp["group_id"] in groups_infos.group_ids
if grp["group_name"]:
vals.append(
{
"value": grp["group_id"],
"label": f"{grp['group_name']} ({len(sco_groups.get_group_members(grp['group_id']))})",
"selected": selected,
}
)
2024-07-31 13:43:39 +02:00
values[p_name] = vals
multi_select: scu.MultiSelect = scu.MultiSelect(
values=values, name="group_ids", html_id="group_ids_sel"
)
if submit_on_change:
multi_select.change_event("submit_group_selector();")
return multi_select.html()
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)
2023-07-26 16:00:23 +02:00
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)
2023-12-09 15:53:45 +01:00
.members :
.groups_titles
2023-11-16 23:34:47 +01:00
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
2023-11-16 23:34:47 +01:00
formsemestre_id: int | None = None,
etat: str | None = None,
select_all_when_unspecified=False,
2023-11-16 23:34:47 +01:00
empty_list_select_all=True,
moduleimpl_id=None, # used to find formsemestre when unspecified
):
group_ids = [] if group_ids is None else group_ids
if isinstance(group_ids, int):
if group_ids:
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
2021-09-23 07:33:28 +02:00
else:
try:
group_ids = [int(g) for g in group_ids]
except ValueError as exc:
log(f"DisplayedGroupsInfos: invalid group_id '{group_ids}'")
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:
2023-11-16 23:34:47 +01:00
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:
2023-11-16 23:34:47 +01:00
# 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)
)
2023-07-26 16:00:23 +02:00
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)
2023-01-02 13:03:42 +01:00
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)
2023-01-02 13:03:42 +01:00
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)
2023-11-16 23:34:47 +01:00
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):
2022-09-02 22:00:34 +02:00
"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
2022-09-02 22:00:34 +02:00
# 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_etudids(self) -> set[int]:
"Les etudids des groupes choisis"
return {member["etudid"] for member in self.members}
def get_form_elem(self):
"""html hidden input with groups"""
H = []
for group_id in self.group_ids:
2022-09-30 16:20:51 +02:00
H.append(f'<input type="hidden" name="group_ids" value="{group_id}"/>')
return "\n".join(H)
def get_formsemestre(self) -> FormSemestre:
2024-08-26 12:03:37 +02:00
"le formsemestre"
return (
db.session.get(FormSemestre, self.formsemestre_id)
if self.formsemestre_id
else None
)
def get_groups_key(self) -> str:
"clé identifiant les groupes sélectionnés, utile pour cache"
return "-".join(str(x) for x in sorted(self.group_ids))
# Ancien ZScolar.group_list renommé ici en group_table
def groups_table(
2022-09-02 22:00:34 +02:00
groups_infos: DisplayedGroupsInfos = None,
with_codes=0,
with_date_inscription=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,
2022-12-07 15:52:50 +01:00
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
2024-01-20 17:37:24 +01:00
can_view_etud_data = int(current_user.has_permission(Permission.ViewEtudData))
with_codes = int(with_codes)
with_date_inscription = int(with_date_inscription)
2024-01-20 17:37:24 +01:00
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 = (
groups_infos.base_url
+ f"""&with_codes={with_codes}&with_date_inscription={
with_date_inscription}&with_paiement={with_paiement}&with_archives={
2022-12-07 15:52:50 +01:00
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",
"date_inscription": "Date inscription",
"datefinalisationinscription_str": "Finalisation inscr.",
"paiementinscription_str": "Paiement",
"etudarchive": "Fichiers",
"annotations_str": "Annotations",
2024-02-19 14:10:55 +01:00
"bourse_str": "Boursier", # requière ViewEtudData
"etape": "Etape",
"semestre_groupe": "Semestre-Groupe", # pour Moodle
2023-02-22 00:28:33 +01:00
"annee": "annee_admission",
2024-02-19 14:10:55 +01:00
"nationalite": "nationalite", # requière ViewEtudData
}
# 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")
2024-01-20 17:37:24 +01:00
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_date_inscription:
columns_ids += ["date_inscription"]
if with_paiement:
columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
if with_paiement:
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"]
2022-12-07 15:52:50 +01:00
if with_bourse:
columns_ids += ["bourse_str"]
moodle_sem_name = groups_infos.formsemestre["session_id"]
moodle_groupenames = set()
# ajoute liens
2024-02-19 14:10:55 +01:00
for etud_info in groups_infos.members:
if with_date_inscription:
event = ScolarEvent.query.filter_by(
etudid=etud_info["etudid"],
event_type="INSCRIPTION",
formsemestre_id=groups_infos.formsemestre_id,
).first()
if event:
etud_info["date_inscription"] = event.event_date.strftime(scu.DATE_FMT)
etud_info["_date_inscription_xls"] = event.event_date
etud_info["_date_inscription_order"] = event.event_date.isoformat
2024-02-19 14:10:55 +01:00
if etud_info["email"]:
etud_info["_email_target"] = "mailto:" + etud_info["email"]
else:
2024-02-19 14:10:55 +01:00
etud_info["_email_target"] = ""
if etud_info["emailperso"]:
etud_info["_emailperso_target"] = "mailto:" + etud_info["emailperso"]
else:
2024-02-19 14:10:55 +01:00
etud_info["_emailperso_target"] = ""
fiche_url = url_for(
2024-02-19 14:10:55 +01:00
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud_info["etudid"]
)
2024-02-19 14:10:55 +01:00
etud_info["_nom_disp_target"] = fiche_url
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
etud_info["_prenom_target"] = fiche_url
etud_info["_nom_disp_td_attrs"] = (
f"""id="{etud_info['etudid']}" class="etudinfo" """
2024-02-19 14:10:55 +01:00
)
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
if etud_info["etat"] == "D":
etud_info["_css_row_class"] = "etuddem"
# et groupes:
2024-02-19 14:10:55 +01:00
for partition_id in etud_info["partitions"]:
etud_info[partition_id] = etud_info["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:
2024-02-19 14:10:55 +01:00
if partition_id in etud_info["partitions"]:
moodle_groupename.append(
partitions_name[partition_id]
+ "-"
2024-02-19 14:10:55 +01:00
+ etud_info["partitions"][partition_id]["group_name"]
)
else:
# pas de groupes sélectionnés: prend le premier s'il y en a un
moodle_groupename = ["tous"]
2024-02-19 14:10:55 +01:00
if etud_info["partitions"]:
for p in etud_info[
"partitions"
].items(): # partitions is an OrderedDict
moodle_groupename = [
partitions_name[p[0]] + "-" + p[1]["group_name"]
]
break
moodle_groupenames |= set(moodle_groupename)
2024-02-19 14:10:55 +01:00
etud_info["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:
2024-01-20 17:37:24 +01:00
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,
table_id="groups_table",
)
#
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:
options = {
2024-01-20 17:37:24 +01:00
"with_codes": "Affiche codes",
"with_date_inscription": "Date inscription",
}
2024-01-20 17:37:24 +01:00
if can_view_etud_data:
options.update(
{
"with_paiement": "Paiement inscription",
"with_archives": "Fichiers archivés",
"with_annotations": "Annotations",
"with_bourse": "Statut boursier",
}
)
2024-07-31 13:43:39 +02:00
valeurs: list[tuple[str, str]] = []
2024-01-20 17:37:24 +01:00
for option, label in options.items():
2024-07-31 13:43:39 +02:00
selected = locals().get(option, False)
valeurs.append(
{
"value": option,
"label": label,
"selected": selected,
}
)
2024-07-31 13:43:39 +02:00
multi_select: scu.MultiSelect = scu.MultiSelect(
values={"": valeurs},
label="Options",
name="options",
html_id="group_list_options",
)
2024-08-24 18:24:46 +02:00
multi_select.change_event("change_list_options(values);")
H.extend(
2024-07-31 13:43:39 +02:00
# ;
[
2024-07-31 13:43:39 +02:00
f"""
<span style="margin-left: 2em;">
{multi_select.html()}
</span>
""",
2024-02-19 14:10:55 +01:00
(
"""<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(),
2024-01-20 17:37:24 +01:00
f"""
2024-08-24 08:06:46 +02:00
<ul>
<li><a class="stdlink" href="{tab.base_url}&fmt=xls">Table Excel
groupe(s) {groups_infos.groups_titles}</a>
</li>
<li><a class="stdlink" href="{tab.base_url}&fmt=moodlecsv">Fichier CSV pour
Moodle groupe(s) {groups_infos.groups_titles}</a>
</li>
<li>
<a class="stdlink" href="{
url_for('scolar.export_groups_as_moodle_csv',
2024-08-24 08:06:46 +02:00
scodoc_dept=g.scodoc_dept, formsemestre_id=groups_infos.formsemestre_id)
}">
2024-08-24 08:06:46 +02:00
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(
2024-01-20 17:37:24 +01:00
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>"""
)
2024-01-20 17:37:24 +01:00
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>"""
)
2024-01-20 17:37:24 +01:00
else:
H.append("<li><em>Adresses personnelles non renseignées</em></li>")
else:
2024-01-20 17:37:24 +01:00
H.append(
"""<li class="unauthorized">adresses mail personnelles protégées</li>"""
)
H.append("</ul>")
2023-11-17 17:00:15 +01:00
return "".join(H)
2024-02-19 14:10:55 +01:00
elif fmt in {"pdf", "xml", "json", "xls", "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_date_inscription=with_date_inscription,
with_paiement=with_paiement,
server_name=request.url_root,
)
2024-02-19 14:10:55 +01:00
filename = f"liste_{groups_infos.groups_filename}"
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
elif fmt == "allxls":
2024-01-20 17:37:24 +01:00
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",
2024-02-19 14:10:55 +01:00
"nationalite",
"bac",
"specialite",
"annee_bac",
"nomlycee",
"villelycee",
"codepostallycee",
"codelycee",
2023-02-22 00:28:33 +01:00
"annee",
"type_admission",
"boursier_prec",
2022-12-07 15:52:50 +01:00
"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:
2024-02-19 14:10:55 +01:00
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
2024-06-07 17:58:02 +02:00
# TODO utiliser Identite
etud = Identite.get_etud(m["etudid"])
2024-02-19 14:10:55 +01:00
m.update(etud_info)
sco_etud.etud_add_lycee_infos(etud_info)
# et ajoute le parcours
2022-07-07 16:24:52 +02:00
Se = sco_cursus.get_situation_etud_cursus(
2024-06-07 17:58:02 +02:00
etud, groups_infos.formsemestre_id
)
m["parcours"] = Se.get_cursus_descr()
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
2024-06-07 17:58:02 +02:00
etud.id, formsemestres=etud.get_formsemestres()
)
2024-06-07 17:58:02 +02:00
# TODO utiliser Identite:
2023-02-22 00:28:33 +01:00
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
2024-06-07 17:58:02 +02:00
title = f"etudiants_{groups_infos.groups_filename}"
2023-02-22 00:28:33 +01:00
xls = sco_excel.excel_simple_table(titles=titles, lines=rows, sheet_name=title)
2024-06-07 17:58:02 +02:00
return scu.send_file(
xls, filename=title, suffix=scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE
)
else:
raise ScoValueError("unsupported format")
def tab_absences_html(groups_infos, etat=None):
"""contenu du tab "absences et feuilles diverses" """
2021-09-18 14:03:36 +02:00
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()
disable_abs: str | bool = scass.has_assiduites_disable_pref(formsemestre)
liens_abs: list = [
'<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>",
]
if disable_abs:
liens_abs = [
f"""
2024-07-10 21:31:36 +02:00
<div class="scobox" style="width:fit-content; font-style:italic;">
La gestion des absences est désactivée dans ScoDoc pour ce semestre:
{disable_abs}
</div>
"""
]
2024-07-31 13:43:39 +02:00
url_feuille_appel: str = (
url_for(
"scolar.formulaire_feuille_appel",
scodoc_dept=g.scodoc_dept,
formsemestre_id=groups_infos.formsemestre_id,
)
+ "?"
2024-07-31 13:43:39 +02:00
+ groups_infos.groups_query_args
2024-07-12 16:40:46 +02:00
)
H.extend(
[
"<h3>Assiduité</h3>",
*liens_abs,
"<h3>Feuilles</h3>",
'<ul class="ul_feuilles">',
2024-07-12 16:40:46 +02:00
"""<li><a class="stdlink" href="%s">Feuille d'émargement %s (Excel)</a></li>"""
% (url_feuille_appel, 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,
2024-02-19 14:10:55 +01:00
(
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
2024-01-20 17:37:24 +01:00
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
2024-01-20 17:37:24 +01:00
)}">{text}</a></li>"""
)
2024-01-20 17:37:24 +01:00
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."""
2021-09-18 14:03:36 +02:00
authuser = current_user
if not authuser.has_permission(Permission.AbsChange):
return ""
2023-09-06 15:09:43 +02:00
return f"""
<a class="stdlink" href="{
2023-09-06 15:09:43 +02:00
url_for(
"assiduites.signal_assiduites_group",
scodoc_dept=g.scodoc_dept,
group_ids=",".join(map(str,groups_infos.group_ids)),
day=datetime.date.today().isoformat(),
2023-09-06 15:09:43 +02:00
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')})</a>
2023-09-06 15:09:43 +02:00
"""
2023-09-06 15:09:43 +02:00
# Saisie de l'assiduité par semaine
def form_choix_saisie_semaine(groups_infos):
2021-09-18 14:03:36 +02:00
authuser = current_user
if not authuser.has_permission(Permission.AbsChange):
return ""
query_args = parse_qs(request.query_string)
2023-09-06 15:09:43 +02:00
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
2024-05-29 16:29:34 +02:00
semaine = datetime.datetime.now().strftime("%G-W%V")
2023-09-06 15:09:43 +02:00
return f"""
<a class="stdlink" href="{url_for(
2024-05-29 16:29:34 +02:00
"assiduites.signal_assiduites_hebdo",
2023-09-06 15:09:43 +02:00
group_ids=",".join(map(str,groups_infos.group_ids)),
2024-05-29 16:29:34 +02:00
week=semaine,
2023-09-06 15:09:43 +02:00
scodoc_dept=g.scodoc_dept,
formsemestre_id=groups_infos.formsemestre_id,
moduleimpl_id=moduleimpl_id
)}">Saisie à la semaine (semaine {''.join(semaine[-2:])})</a>
2023-09-06 15:09:43 +02:00
"""
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")
rows = []
for partition_id, members in partitions_etud_groups.items():
partition = sco_groups.get_partition(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)
rows.append({"email": etud["email"], "semestre_groupe": "-".join(elts)})
# Make table
prefs = sco_preferences.SemPreferences(formsemestre_id)
tab = GenTable(
columns_ids=("email", "semestre_groupe"),
filename=moodle_sem_name + "-moodle",
preferences=prefs,
rows=rows,
text_fields_separator=prefs["moodle_csv_separator"],
text_with_titles=prefs["moodle_csv_with_headerline"],
table_id="export_groups_as_moodle_csv",
titles={x: x for x in columns_ids},
)
return tab.make_page(fmt="csv")