Skip to content
Snippets Groups Projects
Select Git revision
  • b53969dbddd964878cea7c2858cafca68ce2ea4a
  • master default protected
2 results

sco_inscr_passage.py

Blame
  • Forked from Jean-Marie Place / SCODOC_R6A06
    Source project has a limited visibility.
    sco_inscr_passage.py 25.51 KiB
    # -*- mode: python -*-
    # -*- coding: utf-8 -*-
    
    ##############################################################################
    #
    # Gestion scolarite IUT
    #
    # Copyright (c) 1999 - 2022 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, request
    
    import app.scodoc.notesdb as ndb
    import app.scodoc.sco_utils as scu
    from app import log
    from app.scodoc.gen_tables import GenTable
    from app.scodoc import html_sco_header
    from app.scodoc import sco_codes_parcours
    from app.scodoc import sco_preferences
    from app.scodoc import sco_pvjury
    from app.scodoc import sco_formsemestre
    from app.scodoc import sco_formsemestre_inscriptions
    from app.scodoc import sco_formations
    from app.scodoc import sco_groups
    from app.scodoc import sco_etud
    from app.scodoc.sco_exceptions import ScoValueError
    
    
    def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
        """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 comem autorisés, même
        s'ils n'ont pas de décision de jury.
        """
        src_sems = list_source_sems(sem, delai=delai)
        inscrits = list_inscrits(sem["formsemestre_id"])
        r = {}
        candidats = {}  # etudid : etud (tous les etudiants candidats)
        nb = 0  # debug
        for src in src_sems:
            if ignore_jury:
                # liste de tous les inscrits au semestre (sans dems)
                liste = list_inscrits(src["formsemestre_id"]).values()
            else:
                # liste des étudiants autorisés par le jury à s'inscrire ici
                liste = list_etuds_from_sem(src, sem)
            liste_filtree = []
            for e in liste:
                # Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src
                auth_used = False  # autorisation deja utilisée ?
                etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0]
                for isem in etud["sems"]:
                    if ndb.DateDMYtoISO(isem["date_debut"]) >= ndb.DateDMYtoISO(
                        src["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["titreannee"],
                    "title_target": "formsemestre_status?formsemestre_id=%s"
                    % 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[sem["formsemestre_id"]] = {
            "etuds": list(inscrits.values()),
            "infos": {
                "id": sem["formsemestre_id"],
                "title": "Semestre cible: " + sem["titreannee"],
                "title_target": "formsemestre_status?formsemestre_id=%s"
                % sem["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, with_dems=False):
        """Etudiants 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, dst) -> list[dict]:
        """Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
        target = dst["semestre_id"]
        dpv = sco_pvjury.dict_pvjury(src["formsemestre_id"])
        if not dpv:
            return []
        etuds = [
            x["identite"]
            for x in dpv["decisions"]
            if target in [a["semestre_id"] for a in x["autorisations"]]
        ]
        return etuds
    
    
    def list_inscrits_date(sem):
        """Liste les etudiants inscrits dans n'importe quel semestre
        du même département
        SAUF sem à la date de début de sem.
        """
        cnx = ndb.GetDBConnexion()
        cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
        sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
        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
            """,
            sem,
        )
        return [x[0] for x in cursor.fetchall()]
    
    
    def do_inscrit(sem, etudids, inscrit_groupes=False):
        """Inscrit ces etudiants dans ce semestre
        (la liste doit avoir été vérifiée au préalable)
        En option: inscrit aux mêmes groupes que dans le semestre origine
        """
        log("do_inscrit (inscrit_groupes=%s): %s" % (inscrit_groupes, etudids))
        for etudid in etudids:
            sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
                sem["formsemestre_id"],
                etudid,
                etat="I",
                method="formsemestre_inscr_passage",
            )
            if inscrit_groupes:
                # 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]
                log("cherche groupes de %(nom)s" % etud)
    
                # 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)
    
                cursem_groups_by_name = dict(
                    [
                        (g["group_name"], g)
                        for g in sco_groups.get_sem_groups(sem["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:
                    sco_groups.change_etud_group_in_partition(
                        etudid,
                        partition_group["group_id"],
                        partition_group,
                    )
    
    
    def do_desinscrit(sem, etudids):
        log("do_desinscrit: %s" % etudids)
        for etudid in etudids:
            sco_formsemestre_inscriptions.do_formsemestre_desinscription(
                etudid, sem["formsemestre_id"]
            )
    
    
    def list_source_sems(sem, delai=None) -> list[dict]:
        """Liste des semestres sources
        sem est le semestre destination
        """
        # liste des semestres débutant a moins
        # de delai (en jours) de la date de fin du semestre d'origine.
        sems = sco_formsemestre.do_formsemestre_list()
        othersems = []
        d, m, y = [int(x) for x in sem["date_debut"].split("/")]
        date_debut_dst = datetime.date(y, m, d)
    
        delais = datetime.timedelta(delai)
        for s in sems:
            if s["formsemestre_id"] == sem["formsemestre_id"]:
                continue  # saute le semestre destination
            if s["date_fin"]:
                d, m, y = [int(x) for x in s["date_fin"].split("/")]
                date_fin = datetime.date(y, m, d)
                if date_debut_dst - date_fin > delais:
                    continue  # semestre trop ancien
                if date_fin > date_debut_dst:
                    continue  # semestre trop récent
            # Elimine les semestres de formations speciales (sans parcours)
            if s["semestre_id"] == sco_codes_parcours.NO_SEMESTRE_ID:
                continue
            #
            F = sco_formations.formation_list(args={"formation_id": s["formation_id"]})[0]
            parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
            if not parcours.ALLOW_SEM_SKIP:
                if s["semestre_id"] < (sem["semestre_id"] - 1):
                    continue
            othersems.append(s)
        return othersems
    
    
    def formsemestre_inscr_passage(
        formsemestre_id,
        etuds=[],
        inscrit_groupes=False,
        submitted=False,
        dialog_confirmed=False,
        ignore_jury=False,
    ):
        """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 etudiants non inscrits changent (de groupe)
        - les etudiants inscrit qui se trouvent décochés sont désinscrits
        - Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant.
    
        """
        inscrit_groupes = int(inscrit_groupes)
        ignore_jury = int(ignore_jury)
        sem = sco_formsemestre.get_formsemestre(formsemestre_id)
        # -- check lock
        if not sem["etat"]:
            raise ScoValueError("opération impossible: semestre verrouille")
        header = html_sco_header.sco_header(page_title="Passage des étudiants")
        footer = html_sco_header.sco_footer()
        H = [header]
        if isinstance(etuds, str):
            # list de strings, 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(
            sem, 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(sem))
    
        def set_to_sorted_etud_list(etudset):
            etuds = [candidats[etudid] for etudid in etudset]
            etuds.sort(key=itemgetter("nom"))
            return etuds
    
        if submitted:
            a_inscrire = etuds_set.intersection(candidats_set) - inscrits_set
            a_desinscrire = inscrits_set - etuds_set
        else:
            a_inscrire = a_desinscrire = []
        # log('formsemestre_inscr_passage: a_inscrire=%s' % str(a_inscrire) )
        # log('formsemestre_inscr_passage: a_desinscrire=%s' % str(a_desinscrire) )
    
        if not submitted:
            H += build_page(
                sem,
                auth_etuds_by_sem,
                inscrits,
                candidats_non_inscrits,
                inscrits_ailleurs,
                inscrit_groupes=inscrit_groupes,
                ignore_jury=ignore_jury,
            )
        else:
            if not dialog_confirmed:
                # Confirmation
                if a_inscrire:
                    H.append("<h3>Etudiants à inscrire</h3><ol>")
                    for etud in set_to_sorted_etud_list(a_inscrire):
                        H.append("<li>%(nomprenom)s</li>" % etud)
                    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('<li class="inscrailleurs">%(nomprenom)s</li>' % etud)
                    H.append("</ul>")
                if a_desinscrire:
                    H.append("<h3>Etudiants à désinscrire</h3><ol>")
                    for etudid in a_desinscrire:
                        H.append(
                            '<li class="desinscription">%(nomprenom)s</li>'
                            % inscrits[etudid]
                        )
                    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,
                            "submitted": 1,
                        },
                    )
                )
            else:
                # Inscription des étudiants au nouveau semestre:
                do_inscrit(
                    sem,
                    a_inscrire,
                    inscrit_groupes=inscrit_groupes,
                )
    
                # Desincriptions:
                do_desinscrit(sem, a_desinscrire)
    
                H.append(
                    """<h3>Opération effectuée</h3>
                <ul><li><a class="stdlink" href="formsemestre_inscr_passage?formsemestre_id=%s">Continuer les inscriptions</a></li>
                    <li><a class="stdlink" href="formsemestre_status?formsemestre_id=%s">Tableau de bord du semestre</a></li>"""
                    % (formsemestre_id, formsemestre_id)
                )
                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.affect_groups",
                    scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
                    }">Répartir les groupes de {partition["partition_name"]}</a></li>
                    """
                    )
    
        #
        H.append(footer)
        return "\n".join(H)
    
    
    def build_page(
        sem,
        auth_etuds_by_sem,
        inscrits,
        candidats_non_inscrits,
        inscrits_ailleurs,
        inscrit_groupes=False,
        ignore_jury=False,
    ):
        inscrit_groupes = int(inscrit_groupes)
        ignore_jury = int(ignore_jury)
        if inscrit_groupes:
            inscrit_groupes_checked = " checked"
        else:
            inscrit_groupes_checked = ""
        if ignore_jury:
            ignore_jury_checked = " checked"
        else:
            ignore_jury_checked = ""
        H = [
            html_sco_header.html_sem_header(
                "Passages dans le semestre", sem, with_page_header=False
            ),
            """<form name="f" method="post" action="%s">""" % request.base_url,
            """<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
        <input type="submit" name="submitted" value="Appliquer les modifications"/>
        &nbsp;<a href="#help">aide</a>
        """
            % sem,  # "
            """<input name="inscrit_groupes" type="checkbox" value="1" %s>inscrire aux mêmes groupes</input>"""
            % inscrit_groupes_checked,
            """<input name="ignore_jury" type="checkbox" value="1" onchange="document.f.submit()" %s>inclure tous les étudiants (même sans décision de jury)</input>"""
            % ignore_jury_checked,
            """<div class="pas_recap">Actuellement <span id="nbinscrits">%s</span> inscrits
            et %d candidats supplémentaires
            </div>"""
            % (len(inscrits), len(candidats_non_inscrits)),
            etuds_select_boxes(auth_etuds_by_sem, inscrits_ailleurs),
            """<p/><input type="submit" name="submitted" value="Appliquer les modifications"/>""",
            formsemestre_inscr_passage_help(sem),
            """</form>""",
        ]
    
        # Semestres sans etudiants 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 href="%(title_target)s">%(title)s</a></li>""" % infos)
            H.append("""</ul></div>""")
    
        return H
    
    
    def formsemestre_inscr_passage_help(sem):
        return (
            """<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="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>, 
        et d'en désincrire si besoin.
        </p>
        <p>Les étudiants sont groupés par semestres d'origines. Ceux qui sont en caractères
        <span class="inscrit">gras</span> sont déjà inscrits dans le semestre destination.
        Ceux qui sont en <span class"inscrailleurs">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 class="help">Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !</p>
        </div>"""
            % sem
        )
    
    
    def etuds_select_boxes(
        auth_etuds_by_cat,
        inscrits_ailleurs={},
        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 =
        """
        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
            help = infos.get("help", "")
            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
            )
            checkbox_name = auth_etuds_by_cat[src_cat]["infos"].get(
                "checkbox_name", "etuds"
            )
            etud_key = auth_etuds_by_cat[src_cat]["infos"].get("etud_key", "etudid")
            if etuds or show_empty_boxes:
                infos["nbetuds"] = len(etuds)
                H.append(
                    """<div class="pas_sembox" id="%(id)s">
                    <div class="pas_sembox_title"><a href="%(title_target)s" """
                    % infos
                )
                if help:  # bubble
                    H.append('title="%s"' % help)
                H.append(
                    """>%(title)s</a></div>
                    <div class="pas_sembox_subtitle">(%(nbetuds)d étudiants%(comment)s)"""
                    % infos
                )
                if with_checkbox:
                    H.append(
                        """ (Select.
                    <a href="#" onclick="sem_select('%(id)s', true);">tous</a>
                    <a href="#" onclick="sem_select('%(id)s', false );">aucun</a>"""  # "
                        % infos
                    )
                if sel_inscrits:
                    H.append(
                        """<a href="#" onclick="sem_select_inscrits('%(id)s');">inscrits</a>"""
                        % infos
                    )
                if with_checkbox or sel_inscrits:
                    H.append(")")
                if base_url and etuds:
                    url = scu.build_url_query(base_url, export_cat_xls=src_cat)
                    H.append(f'<a href="{url}">{scu.ICON_XLS}</a>&nbsp;')
                H.append("</div>")
                for etud in etuds:
                    if etud.get("inscrit", False):
                        c = " inscrit"
                        checked = 'checked="checked"'
                    else:
                        checked = ""
                        if etud["etudid"] in inscrits_ailleurs:
                            c = " inscrailleurs"
                        else:
                            c = ""
                    sco_etud.format_etud_ident(etud)
                    if etud["etudid"]:
                        elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
                            c,
                            url_for(
                                "scolar.ficheEtud",
                                scodoc_dept=g.scodoc_dept,
                                etudid=etud["etudid"],
                            ),
                            etud["nomprenom"],
                        )
                    else:
                        # ce n'est pas un etudiant ScoDoc
                        elink = etud["nomprenom"]
    
                    if etud.get("datefinalisationinscription", None):
                        elink += (
                            '<span class="finalisationinscription">'
                            + " : inscription finalisée le "
                            + etud["datefinalisationinscription"].strftime("%d/%m/%Y")
                            + "</span>"
                        )
    
                    if not etud.get("paiementinscription", True):
                        elink += '<span class="paspaye"> (non paiement)</span>'
    
                    H.append("""<div class="pas_etud%s">""" % c)
                    if "etape" in etud:
                        etape_str = etud["etape"] or ""
                    else:
                        etape_str = ""
                    H.append("""<span class="sp_etape">%s</span>""" % etape_str)
                    if with_checkbox:
                        H.append(
                            """<input type="checkbox" name="%s:list" value="%s" %s>"""
                            % (checkbox_name, etud[etud_key], checked)
                        )
                    H.append(elink)
                    if with_checkbox:
                        H.append("""</input>""")
                    H.append("</div>")
                H.append("</div>")
    
        H.append("</div>")
        return "\n".join(H)
    
    
    def etuds_select_box_xls(src_cat):
        "export a box to excel"
        etuds = src_cat["etuds"]
        columns_ids = ["etudid", "civilite_str", "nom", "prenom", "etape"]
        titles = {x: x for x in columns_ids}
    
        # Ajoute colonne paiement inscription
        columns_ids.append("paiementinscription_str")
        titles["paiementinscription_str"] = "paiement inscription"
        for e in etuds:
            if not e.get("paiementinscription", True):
                e["paiementinscription_str"] = "NON"
            else:
                e["paiementinscription_str"] = "-"
        tab = GenTable(
            titles=titles,
            columns_ids=columns_ids,
            rows=etuds,
            caption="%(title)s. %(help)s" % src_cat["infos"],
            preferences=sco_preferences.SemPreferences(),
        )
        return tab.excel()  # tab.make_page(filename=src_cat["infos"]["filename"])