ScoDoc/app/scodoc/sco_inscr_passage.py

834 lines
31 KiB
Python

# -*- 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
#
##############################################################################
"""Form. pour inscription rapide des etudiants d'un semestre dans un autre
Utilise les autorisations d'inscription délivrées en jury.
"""
import datetime
from operator import itemgetter
from flask import url_for, g, render_template, request
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import db, log
from app.models import Formation, FormSemestre, GroupDescr, Identite
from app.scodoc.gen_tables import GenTable
from app.scodoc import sco_cache
from app.scodoc import codes_cursus
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_preferences
from app.scodoc import sco_pv_dict
from app.scodoc.sco_exceptions import ScoValueError
def _list_authorized_etuds_by_sem(
formsemestre: FormSemestre, ignore_jury=False
) -> tuple[dict[int, dict], list[dict], dict[int, Identite]]:
"""Liste des etudiants autorisés à s'inscrire dans sem.
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
ignore_jury: si vrai, considère tous les étudiants comme autorisés, même
s'ils n'ont pas de décision de jury.
"""
src_sems = _list_source_sems(formsemestre)
inscrits = list_inscrits(formsemestre.id)
r = {}
candidats = {} # etudid : etud (tous les etudiants candidats)
nb = 0 # debug
src_formsemestre: FormSemestre
for src_formsemestre in src_sems:
if ignore_jury:
# liste de tous les inscrits au semestre (sans dems)
etud_list = list_inscrits(src_formsemestre.id).values()
else:
# liste des étudiants autorisés par le jury à s'inscrire ici
etud_list = _list_etuds_from_sem(src_formsemestre, formsemestre)
liste_filtree = []
for e in etud_list:
# Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src
auth_used = False # autorisation deja utilisée ?
etud = Identite.get_etud(e["etudid"])
for inscription in etud.inscriptions():
if inscription.formsemestre.date_debut >= src_formsemestre.date_fin:
auth_used = True
if not auth_used:
candidats[e["etudid"]] = etud
liste_filtree.append(e)
nb += 1
r[src_formsemestre.id] = {
"etuds": liste_filtree,
"infos": {
"id": src_formsemestre.id,
"title": src_formsemestre.titre_annee(),
"title_target": url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=src_formsemestre.id,
),
"filename": "etud_autorises",
},
}
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
for e in r[src_formsemestre.id]["etuds"]:
e["inscrit"] = e["etudid"] in inscrits
# Ajoute liste des etudiants actuellement inscrits
for e in inscrits.values():
e["inscrit"] = True
r[formsemestre.id] = {
"etuds": list(inscrits.values()),
"infos": {
"id": formsemestre.id,
"title": "Semestre cible: " + formsemestre.titre_annee(),
"title_target": url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
),
"comment": " actuellement inscrits dans ce semestre",
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
"filename": "etud_inscrits",
},
}
return r, inscrits, candidats
def list_inscrits(formsemestre_id: int, with_dems=False) -> list[dict]:
"""Étudiants déjà inscrits à ce semestre
{ etudid : etud }
"""
if not with_dems:
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
formsemestre_id
) # optimized
else:
args = {"formsemestre_id": formsemestre_id}
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(args=args)
inscr = {}
for i in ins:
etudid = i["etudid"]
inscr[etudid] = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
return inscr
def _list_etuds_from_sem(src: FormSemestre, dst: FormSemestre) -> list[dict]:
"""Liste des étudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
target_semestre_id = dst.semestre_id
dpv = sco_pv_dict.dict_pvjury(src.id)
if not dpv:
return []
etuds = [
x["identite"]
for x in dpv["decisions"]
if target_semestre_id in [a["semestre_id"] for a in x["autorisations"]]
]
return etuds
def list_inscrits_date(formsemestre: FormSemestre):
"""Liste les etudiants inscrits à la date de début de formsemestre
dans n'importe quel semestre du même département
SAUF formsemestre
"""
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""SELECT ins.etudid
FROM
notes_formsemestre_inscription ins,
notes_formsemestre S
WHERE ins.formsemestre_id = S.id
AND S.id != %(formsemestre_id)s
AND S.date_debut <= %(date_debut_iso)s
AND S.date_fin >= %(date_debut_iso)s
AND S.dept_id = %(dept_id)s
""",
{
"formsemestre_id": formsemestre.id,
"date_debut_iso": formsemestre.date_debut.isoformat(),
"dept_id": formsemestre.dept_id,
},
)
return [x[0] for x in cursor.fetchall()]
def do_inscrit(
formsemestre: FormSemestre, etudids, inscrit_groupes=False, inscrit_parcours=False
):
"""Inscrit ces etudiants dans ce semestre
(la liste doit avoir été vérifiée au préalable)
En option:
- Si inscrit_groupes, inscrit aux mêmes groupes que dans le semestre origine
(toutes partitions, y compris parcours)
- Si inscrit_parcours, inscrit au même groupe de parcours (mais ignore les autres partitions)
(si les deux sont vrais, inscrit_parcours n'a pas d'effet)
"""
# TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr
formsemestre.setup_parcours_groups()
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
for etudid in etudids:
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
formsemestre.id,
etudid,
etat=scu.INSCRIT,
method="formsemestre_inscr_passage",
)
if inscrit_groupes or inscrit_parcours:
# Inscription dans les mêmes groupes que ceux du semestre d'origine,
# s'ils existent.
# (mise en correspondance à partir du nom du groupe, sans tenir compte
# du nom de la partition: évidemment, cela ne marche pas si on a les
# même noms de groupes dans des partitions différentes)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
# recherche le semestre origine (il serait plus propre de l'avoir conservé!)
if len(etud["sems"]) < 2:
continue
prev_formsemestre = etud["sems"][1]
sco_groups.etud_add_group_infos(
etud,
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
)
cursem_groups_by_name = {
g["group_name"]: g
for g in sco_groups.get_sem_groups(formsemestre.id)
if g["group_name"]
}
# forme la liste des groupes présents dans les deux semestres:
partition_groups = [] # [ partition+group ] (ds nouveau sem.)
for partition_id in etud["partitions"]:
prev_group_name = etud["partitions"][partition_id]["group_name"]
if prev_group_name in cursem_groups_by_name:
new_group = cursem_groups_by_name[prev_group_name]
partition_groups.append(new_group)
# Inscrit aux groupes
for partition_group in partition_groups:
group: GroupDescr = db.session.get(
GroupDescr, partition_group["group_id"]
)
if inscrit_groupes or (
group.partition.partition_name == scu.PARTITION_PARCOURS
and inscrit_parcours
):
sco_groups.change_etud_group_in_partition(etudid, group)
def do_desinscrit(
formsemestre: FormSemestre, etudids: list[int], check_has_dec_jury=True
):
"désinscrit les étudiants indiqués du formsemestre"
log(f"do_desinscrit: {etudids}")
for etudid in etudids:
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
etudid, formsemestre.id, check_has_dec_jury=check_has_dec_jury
)
def _list_source_sems(formsemestre: FormSemestre) -> list[FormSemestre]:
"""Liste des semestres sources
formsemestre est le semestre destination
"""
# liste des semestres du même type de cursus terminant
# pas trop loin de la date de début du semestre destination
date_fin_min = formsemestre.date_debut - datetime.timedelta(days=275)
date_fin_max = formsemestre.date_debut + datetime.timedelta(days=45)
return (
FormSemestre.query.filter(
FormSemestre.dept_id == formsemestre.dept_id,
# saute le semestre destination:
FormSemestre.id != formsemestre.id,
# et les semestres de formations speciales (monosemestres):
FormSemestre.semestre_id != codes_cursus.NO_SEMESTRE_ID,
# semestre pas trop dans le futur
FormSemestre.date_fin <= date_fin_max,
# ni trop loin dans le passé
FormSemestre.date_fin >= date_fin_min,
)
.join(Formation)
.filter_by(type_parcours=formsemestre.formation.type_parcours)
).all()
# view, GET, POST
def formsemestre_inscr_passage(
formsemestre_id,
etuds: str | list[int] | list[str] | int | None = None,
inscrit_groupes=False,
inscrit_parcours=False,
submitted=False,
dialog_confirmed=False,
ignore_jury=False,
) -> str:
"""Page Form. pour inscription des etudiants d'un semestre dans un autre
(donné par formsemestre_id).
Permet de selectionner parmi les etudiants autorisés à s'inscrire.
Principe:
- trouver liste d'etud, par semestre
- afficher chaque semestre "boites" avec cases à cocher
- si l'étudiant est déjà inscrit, le signaler (gras, nom de groupes): il peut être désinscrit
- on peut choisir les groupes TD, TP, TA
- seuls les étudiants non inscrits changent (de groupe)
- les étudiants inscrit qui se trouvent décochés sont désinscrits
- Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant.
"""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
inscrit_groupes = int(inscrit_groupes)
inscrit_parcours = int(inscrit_parcours)
ignore_jury = int(ignore_jury)
# -- check lock
if not formsemestre.etat:
raise ScoValueError("opération impossible: semestre verrouille")
H = []
etuds = [] if etuds is None else etuds
if isinstance(etuds, str):
# string, vient du form de confirmation
etuds = [int(x) for x in etuds.split(",") if x]
elif isinstance(etuds, int):
etuds = [etuds]
elif etuds and isinstance(etuds[0], str):
etuds = [int(x) for x in etuds]
auth_etuds_by_sem, inscrits, candidats = _list_authorized_etuds_by_sem(
formsemestre, ignore_jury=ignore_jury
)
etuds_set = set(etuds)
candidats_set = set(candidats)
inscrits_set = set(inscrits)
candidats_non_inscrits = candidats_set - inscrits_set
inscrits_ailleurs = set(list_inscrits_date(formsemestre))
def set_to_sorted_etud_list(etudset) -> list[Identite]:
etuds = [candidats[etudid] for etudid in etudset]
etuds.sort(key=lambda e: e.sort_key)
return etuds
if submitted:
a_inscrire = etuds_set.intersection(candidats_set) - inscrits_set
a_desinscrire = inscrits_set - etuds_set
else:
a_inscrire = a_desinscrire = []
if not submitted:
H += _build_page(
formsemestre,
auth_etuds_by_sem,
inscrits,
candidats_non_inscrits,
inscrits_ailleurs,
inscrit_groupes=inscrit_groupes,
inscrit_parcours=inscrit_parcours,
ignore_jury=ignore_jury,
)
else:
if not dialog_confirmed:
# Confirmation
if a_inscrire:
H.append("<h3>Étudiants à inscrire</h3><ol>")
for etud in set_to_sorted_etud_list(a_inscrire):
H.append(f"<li>{etud.nomprenom}</li>")
H.append("</ol>")
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
if a_inscrire_en_double:
H.append("<h3>dont étudiants déjà inscrits:</h3><ul>")
for etud in set_to_sorted_etud_list(a_inscrire_en_double):
H.append(f'<li class="inscrit-ailleurs">{etud.nomprenom}</li>')
H.append("</ul>")
if a_desinscrire:
H.append("<h3>Étudiants à désinscrire</h3><ol>")
a_desinscrire_ident = sorted(
(db.session.get(Identite, eid) for eid in a_desinscrire),
key=lambda x: x.sort_key,
)
for etud in a_desinscrire_ident:
H.append(f'<li class="desinscription">{etud.nomprenom}</li>')
H.append("</ol>")
todo = a_inscrire or a_desinscrire
if not todo:
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
H.append(
scu.confirm_dialog(
dest_url=(
"formsemestre_inscr_passage" if todo else "formsemestre_status"
),
message="<p>Confirmer ?</p>" if todo else "",
add_headers=False,
cancel_url="formsemestre_inscr_passage?formsemestre_id="
+ str(formsemestre_id),
OK="Effectuer l'opération" if todo else "",
parameters={
"formsemestre_id": formsemestre_id,
"etuds": ",".join([str(x) for x in etuds]),
"inscrit_groupes": inscrit_groupes,
"inscrit_parcours": inscrit_parcours,
"ignore_jury": ignore_jury,
"submitted": 1,
},
)
)
else:
# check decisions jury ici pour éviter de recontruire le cache
# après chaque desinscription
sco_formsemestre_inscriptions.check_if_has_decision_jury(
formsemestre, a_desinscrire
)
# check decisions jury ici pour éviter de recontruire le cache
# après chaque desinscription
sco_formsemestre_inscriptions.check_if_has_decision_jury(
formsemestre, a_desinscrire
)
with sco_cache.DeferredSemCacheManager():
# Inscription des étudiants au nouveau semestre:
do_inscrit(
formsemestre,
a_inscrire,
inscrit_groupes=inscrit_groupes,
inscrit_parcours=inscrit_parcours,
)
# Désinscriptions:
do_desinscrit(formsemestre, a_desinscrire, check_has_dec_jury=False)
H.append(
f"""<h3>Opération effectuée</h3>
<ul>
<li><a class="stdlink" href="{
url_for("notes.formsemestre_inscr_passage",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
}">Continuer les inscriptions</a>
</li>
<li><a class="stdlink" href="{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
}">Tableau de bord du semestre</a>
</li>"""
)
partition = sco_groups.formsemestre_get_main_partition(formsemestre_id)
if (
partition["partition_id"]
!= sco_groups.formsemestre_get_main_partition(formsemestre_id)[
"partition_id"
]
): # il y a au moins une vraie partition
H.append(
f"""<li><a class="stdlink" href="{
url_for("scolar.partition_editor", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id)
}">Répartir les groupes de {partition["partition_name"]}</a></li>
"""
)
#
return render_template(
"sco_page.j2", title="Passage des étudiants", content="\n".join(H)
)
def _build_page(
formsemestre: FormSemestre,
auth_etuds_by_sem,
inscrits,
candidats_non_inscrits,
inscrits_ailleurs,
inscrit_groupes=False,
inscrit_parcours=False,
ignore_jury=False,
):
inscrit_groupes = int(inscrit_groupes)
inscrit_parcours = int(inscrit_parcours)
ignore_jury = int(ignore_jury)
if inscrit_groupes:
inscrit_groupes_checked = " checked"
else:
inscrit_groupes_checked = ""
if inscrit_parcours:
inscrit_parcours_checked = " checked"
else:
inscrit_parcours_checked = ""
if ignore_jury:
ignore_jury_checked = " checked"
else:
ignore_jury_checked = ""
H = [
f"""
<h2 class="formsemestre">Passages dans le semestre</h2>
<form name="f" method="post" action="{request.base_url}">
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/>
&nbsp;<a href="#help">aide</a>
<input name="inscrit_groupes" type="checkbox" value="1"
{inscrit_groupes_checked}>inscrire aux mêmes groupes (y compris parcours)</input>
<input name="inscrit_parcours" type="checkbox" value="1"
{inscrit_parcours_checked}>inscrire aux mêmes parcours</input>
<input name="ignore_jury" type="checkbox" value="1" onchange="document.f.submit()"
{ignore_jury_checked}>inclure tous les étudiants (même sans décision de jury)</input>
<div class="pas_recap">Actuellement <span id="nbinscrits">{len(inscrits)}</span>
inscrits et {len(candidats_non_inscrits)} candidats supplémentaires.
</div>
<div>{scu.EMO_WARNING}
<em>Seuls les semestres dont la date de fin est proche de la date de début
de ce semestre ({formsemestre.date_debut.strftime(scu.DATE_FMT)}) sont pris en
compte.</em>
</div>
{etuds_select_boxes(auth_etuds_by_sem, inscrits_ailleurs)}
<input type="submit" name="submitted" value="Appliquer les modifications"/>
{formsemestre_inscr_passage_help(formsemestre)}
</form>
""",
]
# Semestres sans étudiants autorisés
empty_sems = []
for formsemestre_id in auth_etuds_by_sem.keys():
if not auth_etuds_by_sem[formsemestre_id]["etuds"]:
empty_sems.append(auth_etuds_by_sem[formsemestre_id]["infos"])
if empty_sems:
H.append(
"""<div class="pas_empty_sems"><h3>Autres semestres sans candidats :</h3><ul>"""
)
for infos in empty_sems:
H.append(
"""<li><a class="stdlink" href="%(title_target)s">%(title)s</a></li>"""
% infos
)
H.append("""</ul></div>""")
return H
def formsemestre_inscr_passage_help(formsemestre: FormSemestre):
"texte d'aide en bas de la page passage des étudiants"
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
<a class="stdlink"
href="{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
}">{formsemestre.titre_annee()}</a>,
et d'en désincrire si besoin.
</p>
<p>Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères
<span class="deja-inscrit">gras</span> sont déjà inscrits dans le semestre destination.
Ceux qui sont en <span class="inscrit-ailleurs">gras et en rouge</span> sont inscrits
dans un <em>autre</em> semestre.
</p>
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter
d'autres étudiants à inscrire dans le semestre destination.
</p>
<p>Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.
</p>
<p>Le bouton <em>inscrire aux mêmes groupes</em> ne prend en compte que les groupes
qui existent dans les deux semestres: pensez à créer les partitions et groupes que
vous souhaitez conserver <b>avant</b> d'inscrire les étudiants.
</p>
<p>Les parcours de BUT sont gérés comme des groupes de la partition parcours: si on
conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans
<a class="stdlink" href="{
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id )
}">modifier le semestre</a> avant de faire passer les étudiants).
</a>
<p class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton
"Appliquer les modifications" !
</p>
</div>
"""
def etuds_select_boxes(
auth_etuds_by_cat,
inscrits_ailleurs: dict = None,
sel_inscrits=True,
show_empty_boxes=False,
export_cat_xls=None,
base_url="",
read_only=False,
):
"""Boites pour selection étudiants par catégorie
auth_etuds_by_cat = { category : { 'info' : {}, 'etuds' : ... }
inscrits_ailleurs =
sel_inscrits=
export_cat_xls =
"""
inscrits_ailleurs = inscrits_ailleurs or {}
if export_cat_xls:
return etuds_select_box_xls(auth_etuds_by_cat[export_cat_xls])
H = [
"""<script type="text/javascript">
function sem_select(formsemestre_id, state) {
var elems = document.getElementById(formsemestre_id).getElementsByTagName("input");
for (var i =0; i < elems.length; i++) { elems[i].checked=state; }
}
function sem_select_inscrits(formsemestre_id) {
var elems = document.getElementById(formsemestre_id).getElementsByTagName("input");
for (var i =0; i < elems.length; i++) {
if (elems[i].parentNode.className.indexOf('inscrit') >= 0) {
elems[i].checked=true;
} else {
elems[i].checked=false;
}
}
}
</script>
<div class="etuds_select_boxes">"""
] # "
# Élimine les boites vides:
auth_etuds_by_cat = {
k: auth_etuds_by_cat[k]
for k in auth_etuds_by_cat
if auth_etuds_by_cat[k]["etuds"]
}
for src_cat in auth_etuds_by_cat.keys():
infos = auth_etuds_by_cat[src_cat]["infos"]
infos["comment"] = infos.get("comment", "") # commentaire dans sous-titre boite
etuds = auth_etuds_by_cat[src_cat]["etuds"]
etuds.sort(key=itemgetter("nom"))
with_checkbox = (not read_only) and auth_etuds_by_cat[src_cat]["infos"].get(
"with_checkbox", True
)
if etuds or show_empty_boxes:
infos["nbetuds"] = len(etuds)
xls_url = (
scu.build_url_query(base_url, export_cat_xls=src_cat)
if base_url and etuds
else ""
)
H.append(
etuds_select_box(
etuds,
infos,
with_checkbox=with_checkbox,
sel_inscrits=sel_inscrits,
xls_url=xls_url,
inscrits_ailleurs=inscrits_ailleurs,
)
)
H.append("</div>")
return "\n".join(H)
def etuds_select_box(
etuds: list[dict],
infos: dict,
with_checkbox: bool = True,
sel_inscrits: bool = True,
xls_url: str = "",
inscrits_ailleurs: set = None,
) -> str:
"""HTML pour une "boite" avec une liste d'étudiants à sélectionner"""
inscrits_ailleurs = inscrits_ailleurs or {}
box_id = infos["id"]
H = []
H.append(
f"""<div class="pas_sembox" id="{box_id}">
<div class="pas_sembox_title"><a href="{infos['title_target']}" """
)
help_txt = infos.get("help")
if help_txt: # bubble
H.append(f'title="{help_txt}"')
H.append(
""">%(title)s</a></div>
<div class="pas_sembox_subtitle">(%(nbetuds)d étudiants%(comment)s)"""
% infos
)
if with_checkbox:
H.append(
f""" (Select.
<a href="#" class="stdlink" onclick="sem_select('{box_id}', true);">tous</a>
<a href="#" class="stdlink" onclick="sem_select('{box_id}', false );">aucun</a>"""
)
if sel_inscrits:
H.append(
f"""<a href="#"
class="stdlink" onclick="sem_select_inscrits('{box_id}');"
>inscrits</a>"""
)
if with_checkbox or sel_inscrits:
H.append(")")
if xls_url:
H.append(f'<a href="{xls_url}">{scu.ICON_XLS}</a>&nbsp;')
H.append("</div>")
checkbox_title = "<th></th>" if with_checkbox else ""
H.append(
f"""<table class="etuds-box">
<thead>
<tr>
<th>Étape</th>
{checkbox_title}
<th>Nom</th>
<th>Paiement</th>
<th>Finalisé</th>
</tr>
</thead>
<tbody>
"""
)
for etud in etuds:
is_inscrit = etud.get("inscrit", False)
extra_class = (
"deja-inscrit"
if is_inscrit
else ("inscrit-ailleurs" if etud["etudid"] in inscrits_ailleurs else "")
)
H.append(
_etud_row(
etud,
with_checkbox=with_checkbox,
checkbox_name=infos.get("checkbox_name", "etuds"),
etud_key=infos.get("etud_key", "etudid"),
is_inscrit=is_inscrit,
extra_class=extra_class,
)
)
H.append(
"""</tbody>
</table>"""
)
H.append("</div>")
return "\n".join(H)
def _etud_row(
etud: dict,
with_checkbox: bool = True,
checkbox_name: str = "etuds",
etud_key: str = "",
is_inscrit: bool = False,
extra_class: str = "",
) -> str:
"""HTML 'row' for this etud"""
H = []
nomprenom = scu.format_nomprenom(etud)
if etud["etudid"]:
elink = f"""<a id="{etud['etudid']}" class="discretelink etudinfo {extra_class}"
href="{ url_for(
'scolar.fiche_etud',
scodoc_dept=g.scodoc_dept,
etudid=etud['etudid'],
)
}">{nomprenom}</a>
"""
else:
# ce n'est pas un etudiant ScoDoc
elink = nomprenom
checkbox_cell = (
f"""<td><input type="checkbox" name="{checkbox_name}:list"
value="{etud[etud_key]}" {'checked="checked"' if is_inscrit else ''}>
</td>
"""
if with_checkbox
else ""
)
paiement = etud.get("paiementinscription", True)
datefinalisation = etud.get("datefinalisationinscription")
H.append(
f"""
<tr class="{extra_class}">
<td class="etape">{etud.get("etape", "") or ""}</td>
{checkbox_cell}
<td data-order="{etud.get("nom", "").upper()}">{elink}</td>
<td class="paiement {'' if paiement else 'paspaye'}">{
'' if paiement else 'non paiement'
}</td>
<td class="finalise" data-order="{
datefinalisation.isoformat() if datefinalisation else ''
}">{"inscription finalisée le " + datefinalisation.strftime(scu.DATE_FMT)
if datefinalisation else "" }
</td>
</tr>
"""
)
return "\n".join(H)
def etuds_select_box_xls(src_cat):
"export a box to excel"
etuds = src_cat["etuds"]
columns_ids = [
"etudid",
"ine",
"nip",
"civilite_str",
"nom",
"prenom",
"etape",
"paiementinscription_str",
"datefinalisationinscription",
]
titles = {x: x for x in columns_ids} | {
"paiementinscription_str": "Paiement inscr.",
"datefinalisationinscription": "Finalisation inscr.",
}
for e in etuds:
if not e.get("paiementinscription", True):
e["paiementinscription_str"] = "NON"
else:
e["paiementinscription_str"] = "-"
# si e est un étudiant Apo, on a nip et ine
# mais si e est ScoDoc, on a code_nip et code_ine:
e["nip"] = e.get("nip", e.get("code_nip"))
e["ine"] = e.get("ine", e.get("code_ine"))
# Pour excel, datefinalisationinscription doit être datetime
dat = e.get("datefinalisationinscription")
if isinstance(dat, datetime.date):
e["datefinalisationinscription"] = datetime.datetime.combine(
dat, datetime.time.min
)
tab = GenTable(
caption="%(title)s. %(help)s" % src_cat["infos"],
columns_ids=columns_ids,
preferences=sco_preferences.SemPreferences(),
rows=etuds,
table_id="etuds_select_box_xls",
titles=titles,
)
return tab.excel()