# -*- 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["formsemestre_id"] if prev_formsemestre else None, ) 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: if partition_group["groups_editable"]: 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, "ignore_jury": ignore_jury, "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", 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"/> <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> ') 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"])