Divers correctifs et modernisation du code (commence #381)

This commit is contained in:
Emmanuel Viennet 2022-09-03 11:41:56 +02:00
parent 71a15fed2f
commit 227b94ac6a
20 changed files with 103 additions and 93 deletions

View File

@ -289,7 +289,7 @@ def formsemestre_programme(formsemestre_id: int):
def formsemestre_etudiants(
formsemestre_id: int, with_query: bool = False, long: bool = False
):
"""Etudiants d'un formsemestre."""
"""Étudiants d'un formsemestre."""
query = FormSemestre.query.filter_by(id=formsemestre_id)
if g.scodoc_dept:
query = query.filter_by(dept_id=g.scodoc_dept_id)

View File

@ -134,7 +134,7 @@ def etud_in_group(group_id: int):
@scodoc
@permission_required(Permission.ScoView)
def etud_in_group_query(group_id: int):
"""Etudiants du groupe, filtrés par état"""
"""Étudiants du groupe, filtrés par état"""
etat = request.args.get("etat")
if etat not in {None, scu.INSCRIT, scu.DEMISSION, scu.DEF}:
return json_error(404, "etat: valeur invalide")

View File

@ -66,7 +66,7 @@ class Identite(db.Model):
@classmethod
def from_request(cls, etudid=None, code_nip=None):
"""Etudiant à partir de l'etudid ou du code_nip, soit
"""Étudiant à partir de l'etudid ou du code_nip, soit
passés en argument soit retrouvés directement dans la requête web.
Erreur 404 si inexistant.
"""

View File

@ -241,7 +241,7 @@ VOID_APO_RES = dict(N="", B="", J="", R="", M="")
class ApoEtud(dict):
"""Etudiant Apogee:"""
"""Étudiant Apogee:"""
def __init__(
self,

View File

@ -143,7 +143,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if not nt.get_etud_etat(etudid):
raise ScoValueError("Etudiant non inscrit à ce semestre")
raise ScoValueError("Étudiant non inscrit à ce semestre")
I = scu.DictDefault(defaultvalue="")
I["etudid"] = etudid
I["formsemestre_id"] = formsemestre_id

View File

@ -191,7 +191,7 @@ def apo_semset_maq_status(
"notes.view_scodoc_etuds",
scodoc_dept=g.scodoc_dept,
semset_id=semset_id,
title="Etudiants ScoDoc non listés dans les maquettes Apogée chargées",
title="Étudiants ScoDoc non listés dans les maquettes Apogée chargées",
nip_list=",".join(nips_no_apo),
)
H.append(
@ -204,7 +204,7 @@ def apo_semset_maq_status(
"notes.view_apo_etuds",
scodoc_dept=g.scodoc_dept,
semset_id=semset_id,
title="Etudiants présents dans maquettes Apogée mais pas dans les semestres ScoDoc",
title="Étudiants présents dans maquettes Apogée mais pas dans les semestres ScoDoc",
nip_list=",".join(nips_no_sco),
)
H.append(
@ -747,7 +747,7 @@ def view_apo_csv(etape_apo="", semset_id="", format="html"):
base_url="%s?etape_apo=%s&semset_id=%s"
% (request.base_url, etape_apo, semset_id),
filename="students_" + etape_apo,
caption="Etudiants Apogée en " + etape_apo,
caption="Étudiants Apogée en " + etape_apo,
preferences=sco_preferences.SemPreferences(),
)

View File

@ -211,7 +211,7 @@ def etud_sort_key(etud: dict) -> tuple:
"""
return (
scu.sanitize_string(
etud["nom_usuel"] or etud["nom"] or "", remove_spaces=False
etud.get("nom_usuel") or etud["nom"] or "", remove_spaces=False
).lower(),
scu.sanitize_string(etud["prenom"] or "", remove_spaces=False).lower(),
)

View File

@ -185,7 +185,7 @@ def search_etud_in_dept(expnom=""):
tab = GenTable(
columns_ids=("nomprenom", "code_nip", "inscription", "groupes"),
titles={
"nomprenom": "Etudiant",
"nomprenom": "Étudiant",
"code_nip": "NIP",
"inscription": "Inscription",
"groupes": "Groupes",
@ -335,7 +335,7 @@ def table_etud_in_accessible_depts(expnom=None):
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
tab = GenTable(
titles={"nomprenom": "Etudiants en " + dept_id},
titles={"nomprenom": "Étudiants en " + dept_id},
columns_ids=("nomprenom",),
rows=etuds,
html_sortable=True,

View File

@ -741,7 +741,7 @@ def do_formsemestre_createwithmodules(edit=False):
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
if sco_preferences.get_preference("always_require_apo_sem_codes") and not any(
[tf[2]["etape_apo" + str(n)] for n in range(1, scu.EDIT_NB_ETAPES + 1)]
[tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)]
):
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'

View File

@ -90,8 +90,8 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
f"""<h2>Enregistrement d'une inscription antérieure dans un autre
établissement</h2>
<p class="help">
Cette opération crée un semestre extérieur ("ancien") et y inscrit
juste cet étudiant.
Cette opération crée un semestre extérieur ("ancien") de la même
formation que le semestre courant, et y inscrit juste cet étudiant.
La décision de jury peut ensuite y être saisie.
</p>
<p class="help">
@ -128,7 +128,11 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
"""
)
if not semestre_ids:
H.append("""<p class="warning">pas de semestres extérieurs possibles</p>""")
H.append(
f"""<p class="warning">pas de semestres extérieurs possibles
(indices entre {min_semestre_id} et {max_semestre_id}, semestre courant.)
</p>"""
)
return "\n".join(H) + F
# Formulaire
semestre_ids_list = sorted(semestre_ids)
@ -150,8 +154,9 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
{
"size": 40,
"title": "Nom de ce semestre extérieur",
"explanation": """par exemple: établissement. N'indiquez pas les dates, ni le semestre, ni la modalité dans
le titre: ils seront automatiquement ajoutés""",
"explanation": """par exemple: établissement.
N'indiquez pas les dates, ni le semestre, ni la modalité dans
le titre: ils seront automatiquement ajoutés""",
},
),
(
@ -211,6 +216,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
)
)
else:
# Le semestre extérieur est créé dans la même formation que le semestre courant
tf[2]["formation_id"] = orig_sem["formation_id"]
formsemestre_ext_create(etudid, tf[2])
return flask.redirect(

View File

@ -30,12 +30,13 @@
import time
import flask
from flask import url_for, g, request
from flask import flash, url_for, g, request
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models.groups import GroupDescr, Partition
from app.models.etudiants import Identite
from app.models.groups import GroupDescr
import app.scodoc.sco_utils as scu
from app import log
from app.scodoc.scolog import logdb
@ -85,12 +86,12 @@ def do_formsemestre_inscription_listinscrits(formsemestre_id):
def do_formsemestre_inscription_create(args, method=None):
"create a formsemestre_inscription (and sco event)"
cnx = ndb.GetDBConnexion()
log("do_formsemestre_inscription_create: args=%s" % str(args))
log(f"do_formsemestre_inscription_create: args={args}")
sems = sco_formsemestre.do_formsemestre_list(
{"formsemestre_id": args["formsemestre_id"]}
)
if len(sems) != 1:
raise ScoValueError("code de semestre invalide: %s" % args["formsemestre_id"])
raise ScoValueError(f"code de semestre invalide: {args['formsemestre_id']}")
sem = sems[0]
# check lock
if not sem["etat"]:
@ -112,13 +113,11 @@ def do_formsemestre_inscription_create(args, method=None):
cnx,
method=method,
etudid=args["etudid"],
msg="inscription en semestre %s" % args["formsemestre_id"],
msg=f"inscription en semestre {args['formsemestre_id']}",
commit=False,
)
#
sco_cache.invalidate_formsemestre(
formsemestre_id=args["formsemestre_id"]
) # > inscription au semestre
sco_cache.invalidate_formsemestre(formsemestre_id=args["formsemestre_id"])
return r
@ -127,9 +126,7 @@ def do_formsemestre_inscription_delete(oid, formsemestre_id=None):
cnx = ndb.GetDBConnexion()
_formsemestre_inscriptionEditor.delete(cnx, oid)
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
) # > desinscription du semestre
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
def do_formsemestre_demission(
@ -183,25 +180,26 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
"""
from app.scodoc import sco_formsemestre_edit
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
# -- check lock
if not sem["etat"]:
raise ScoValueError("desinscription impossible: semestre verrouille")
# -- Si decisions de jury, desinscription interdite
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
etud = Identite.query.get_or_404(etudid)
# -- check lock
if not formsemestre.etat:
raise ScoValueError("désinscription impossible: semestre verrouille")
# -- Si decisions de jury, désinscription interdite
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if nt.etud_has_decision(etudid):
raise ScoValueError(
"desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)"
"""désinscription impossible: l'étudiant {etud.nomprenom} a
une décision de jury (la supprimer avant si nécessaire)"""
)
insem = do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
)
if not insem:
raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid)
raise ScoValueError(f"{etud.nomprenom} n'est pas inscrit au semestre !")
insem = insem[0]
# -- desinscription de tous les modules
cnx = ndb.GetDBConnexion()
@ -221,21 +219,22 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
sco_moduleimpl.do_moduleimpl_inscription_delete(
moduleimpl_inscription_id, formsemestre_id=formsemestre_id
)
# -- desincription du semestre
# -- désincription du semestre
do_formsemestre_inscription_delete(
insem["formsemestre_inscription_id"], formsemestre_id=formsemestre_id
)
# --- Semestre extérieur
if sem["modalite"] == "EXT":
if formsemestre.modalite == "EXT":
inscrits = do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id}
)
nbinscrits = len(inscrits)
if nbinscrits == 0:
log(
"do_formsemestre_desinscription: suppression du semestre extérieur %s"
% formsemestre_id
f"""do_formsemestre_desinscription:
suppression du semestre extérieur {formsemestre}"""
)
flash("Semestre exterieur supprimé")
sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id)
logdb(

View File

@ -322,7 +322,7 @@ def formsemestre_validation_etud_form(
# Décisions possibles
rows_assidu = decisions_possible_rows(
Se, True, subtitle="Etudiant assidu:", trclass="sfv_ass"
Se, True, subtitle="Étudiant assidu:", trclass="sfv_ass"
)
rows_non_assidu = decisions_possible_rows(
Se, False, subtitle="Si problème d'assiduité:", trclass="sfv_pbass"

View File

@ -213,7 +213,7 @@ def sco_import_generate_excel_sample(
else:
lines = [[]] # empty content, titles only
return sco_excel.excel_simple_table(
titles=titles, titles_styles=titles_styles, sheet_name="Etudiants", lines=lines
titles=titles, titles_styles=titles_styles, sheet_name="Étudiants", lines=lines
)

View File

@ -115,7 +115,7 @@ def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
def list_inscrits(formsemestre_id, with_dems=False):
"""Etudiants déjà inscrits à ce semestre
"""Étudiants déjà inscrits à ce semestre
{ etudid : etud }
"""
if not with_dems:

View File

@ -46,11 +46,11 @@ from app.scodoc import htmlutils
from app.scodoc import sco_cache
from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_etud
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
@ -106,14 +106,13 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1)
if not etuds_info:
log(
"moduleimpl_inscriptions_edit: incoherency for etudid=%s !"
% ins["etudid"]
f"""moduleimpl_inscriptions_edit: inconsistency for etudid={ins['etudid']} !"""
)
raise ScoValueError(
"Etudiant %s inscrit mais inconnu dans la base !!!!!" % ins["etudid"]
f"""Étudiant {ins['etudid']} inscrit mais inconnu dans la base !"""
)
ins["etud"] = etuds_info[0]
inscrits.sort(key=lambda x: x["etud"]["nom"])
inscrits.sort(key=lambda inscr: sco_etud.etud_sort_key(inscr["etud"]))
in_m = sco_moduleimpl.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"]
)
@ -144,24 +143,20 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
</script>"""
)
H.append("""<form method="post" id="mi_form" action="%s">""" % request.base_url)
H.append(
"""
<input type="hidden" name="moduleimpl_id" value="%(moduleimpl_id)s"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/><p></p>
"""
% M
)
H.append("<table><tr>")
H.append(_make_menu(partitions, "Ajouter", "true"))
H.append(_make_menu(partitions, "Enlever", "false"))
H.append("</tr></table>")
H.append(
"""
f"""<form method="post" id="mi_form" action="{request.base_url}">
<input type="hidden" name="moduleimpl_id" value="{M['moduleimpl_id']}"/>
<input type="submit" name="submitted" value="Appliquer les modifications"/>
<p></p>
<table><tr>
{ _make_menu(partitions, "Ajouter", "true") }
{ _make_menu(partitions, "Enlever", "false")}
</tr></table>
<p><br/></p>
<table class="sortable" id="mi_table"><tr>
<th>Nom</th>"""
% sem
<table class="sortable" id="mi_table">
<tr>
<th>Nom</th>
"""
)
for partition in partitions:
if partition["partition_name"]:
@ -201,14 +196,20 @@ def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
gr_name = group["group_name"]
break
# gr_name == '' si etud non inscrit dans un groupe de cette partition
H.append("<td>%s</td>" % gr_name)
H.append(f"<td>{gr_name}</td>")
H.append("""</table></form>""")
else: # SUBMISSION
# inscrit a ce module tous les etuds selectionnes
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl_id, formsemestre_id, etuds, reset=True
)
return flask.redirect("moduleimpl_status?moduleimpl_id=%s" % (moduleimpl_id))
return flask.redirect(
url_for(
"notes.moduleimpl_status",
scodoc_dept=g.scodoc_dept,
moduleimpl_id=moduleimpl_id,
)
)
#
H.append(footer)
return "\n".join(H)

View File

@ -162,7 +162,7 @@ def ficheEtud(etudid=None):
etuds = sco_etud.etudident_list(cnx, args)
if not etuds:
log(f"ficheEtud: etudid={etudid!r} request.args={request.args!r}")
raise ScoValueError("Etudiant inexistant !")
raise ScoValueError("Étudiant inexistant !")
etud = etuds[0]
etudid = etud["etudid"]
sco_etud.fill_etuds_info([etud])
@ -573,7 +573,7 @@ def menus_etud(etudid):
},
]
return htmlutils.make_menu("Etudiant", menuEtud, alone=True)
return htmlutils.make_menu("Étudiant", menuEtud, alone=True)
def etud_info_html(etudid, with_photo="1", debug=False):

View File

@ -447,7 +447,7 @@ def list_synch(sem, anneeapogee=None):
"etuds": set_to_sorted_list(a_importer, is_inscrit=True, etud_apo=True),
"infos": {
"id": "etuds_a_importer",
"title": "Etudiants dans Apogée à importer",
"title": "Étudiants dans Apogée à importer",
"help": """Ces étudiants sont inscrits dans cette étape Apogée mais ne sont pas connus par ScoDoc:
cocher les noms à importer et inscrire puis appuyer sur le bouton "Appliquer".""",
"title_target": "",
@ -461,7 +461,7 @@ def list_synch(sem, anneeapogee=None):
"etuds": set_to_sorted_list(etuds_noninscrits, is_inscrit=True),
"infos": {
"id": "etuds_noninscrits",
"title": "Etudiants non inscrits dans ce semestre",
"title": "Étudiants non inscrits dans ce semestre",
"help": """Ces étudiants sont déjà connus par ScoDoc, sont inscrits dans cette étape Apogée mais ne sont pas inscrits à ce semestre ScoDoc. Cochez les étudiants à inscrire.""",
"comment": """ dans ScoDoc et Apogée, <br/>mais pas inscrits
dans ce semestre""",
@ -475,7 +475,7 @@ def list_synch(sem, anneeapogee=None):
"etuds": set_to_sorted_list(etuds_nonapogee, is_inscrit=True),
"infos": {
"id": "etuds_nonapogee",
"title": "Etudiants ScoDoc inconnus dans cette étape Apogée",
"title": "Étudiants ScoDoc inconnus dans cette étape Apogée",
"help": """Ces étudiants sont inscrits dans ce semestre ScoDoc, ont un code NIP, mais ne sont pas inscrits dans cette étape Apogée. Soit ils sont en retard pour leur inscription, soit il s'agit d'une erreur: vérifiez avec le service Scolarité de votre établissement. Autre possibilité: votre code étape semestre (%s) est incorrect ou vous n'avez pas choisi la bonne année d'inscription."""
% sem["etape_apo_str"],
"comment": " à vérifier avec la Scolarité",
@ -489,7 +489,7 @@ def list_synch(sem, anneeapogee=None):
"etuds": list(inscrits_without_key.values()),
"infos": {
"id": "inscrits_without_key",
"title": "Etudiants ScoDoc sans clé Apogée (NIP)",
"title": "Étudiants ScoDoc sans clé Apogée (NIP)",
"help": """Ces étudiants sont inscrits dans ce semestre ScoDoc, mais n'ont pas de code NIP: on ne peut pas les mettre en correspondance avec Apogée. Utiliser le lien 'Changer les données identité' dans le menu 'Etudiant' sur leur fiche pour ajouter cette information.""",
"title_target": "",
"with_checkbox": True,
@ -501,7 +501,7 @@ def list_synch(sem, anneeapogee=None):
"etuds": set_to_sorted_list(etuds_ok, is_inscrit=True),
"infos": {
"id": "etuds_ok",
"title": "Etudiants dans Apogée et déjà inscrits",
"title": "Étudiants dans Apogée et déjà inscrits",
"help": """Ces etudiants sont inscrits dans le semestre ScoDoc et sont présents dans Apogée:
tout est donc correct. Décocher les étudiants que vous souhaitez désinscrire.""",
"title_target": "",

View File

@ -503,7 +503,7 @@ def photos_import_files_form(group_ids=()):
html_sco_header.sco_header(page_title="Import des photos des étudiants"),
f"""<h2 class="formsemestre">Téléchargement des photos des étudiants</h2>
<p><b>Vous pouvez aussi charger les photos individuellement via la fiche
de chaque étudiant (menu "Etudiant" / "Changer la photo").</b>
de chaque étudiant (menu "Étudiant" / "Changer la photo").</b>
</p>
<p class="help">Cette page permet de charger en une seule fois les photos
de plusieurs étudiants.<br/>

View File

@ -1491,7 +1491,7 @@ def do_formsemestre_inscription_listinscrits(formsemestre_id, format=None):
@permission_required(Permission.ScoImplement)
@scodoc7func
def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False):
"""desinscrit l'etudiant de ce semestre (et donc de tous les modules).
"""désinscrit l'etudiant de ce semestre (et donc de tous les modules).
A n'utiliser qu'en cas d'erreur de saisie.
S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit,
le semestre sera supprimé.
@ -1506,13 +1506,15 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if nt.etud_has_decision(etudid):
raise ScoValueError(
"""Désinscription impossible: l'étudiant a une décision de jury
f"""Désinscription impossible: l'étudiant a une décision de jury
(la supprimer avant si nécessaire:
<a href="formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s">
supprimer décision jury</a>
<a href="{
url_for("notes.formsemestre_validation_suppress_etud",
scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=formsemestre_id)
}">supprimer décision jury</a>
)
"""
% (etudid, formsemestre_id)
)
if not dialog_confirmed:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
@ -1542,14 +1544,18 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
)
nbinscrits = len(inscrits)
if nbinscrits <= 1:
msg_ext = """<p class="warning">Attention: le semestre extérieur sera supprimé
car il n'a pas d'autre étudiant inscrit.
msg_ext = """<p class="warning">Attention: le semestre extérieur
sera supprimé car il n'a pas d'autre étudiant inscrit.
</p>
"""
return scu.confirm_dialog(
"""<h2>Confirmer la demande de desinscription ?</h2>""" + msg_ext,
"""<h2>Confirmer la demande de désinscription ?</h2>""" + msg_ext,
dest_url="",
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
cancel_url=url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
),
parameters={"etudid": etudid, "formsemestre_id": formsemestre_id},
)
@ -1557,11 +1563,9 @@ def formsemestre_desinscription(etudid, formsemestre_id, dialog_confirmed=False)
etudid, formsemestre_id
)
return (
html_sco_header.sco_header()
+ '<p>Etudiant désinscrit !</p><p><a class="stdlink" href="%s">retour à la fiche</a>'
% url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
+ html_sco_header.sco_footer()
flash("Étudiant désinscrit")
return redirect(
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
)

View File

@ -1708,7 +1708,7 @@ def etudident_delete(etudid, dialog_confirmed=False):
cnx = ndb.GetDBConnexion()
etuds = sco_etud.etudident_list(cnx, {"etudid": etudid})
if not etuds:
raise ScoValueError("Etudiant inexistant !")
raise ScoValueError("Étudiant inexistant !")
else:
etud = etuds[0]
sco_etud.fill_etuds_info([etud])
@ -1789,7 +1789,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
cnx = ndb.GetDBConnexion()
H = [
html_sco_header.html_sem_header(
"Etudiants du %s" % (group["group_name"] or "semestre")
"Étudiants du %s" % (group["group_name"] or "semestre")
),
'<table class="sortable" id="listegroupe">',
"<tr><th>Nom</th><th>Nom usuel</th><th>Prénom</th><th>Mail</th><th>NIP (ScoDoc)</th><th>Apogée</th></tr>",