# -*- 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 # ############################################################################## """Opérations d'inscriptions aux modules (interface pour gérer options ou parcours) """ import collections from operator import attrgetter import flask from flask import url_for, g, request from flask_login import current_user from app import db, log from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import ( FormSemestre, Identite, ModuleImpl, Partition, ScolarFormSemestreValidation, UniteEns, ) from app.scodoc.scolog import logdb from app.scodoc import html_sco_header from app.scodoc import htmlutils 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_moduleimpl import app.scodoc.notesdb as ndb from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission import app.scodoc.sco_utils as scu from app.tables import list_etuds def moduleimpl_inscriptions_edit( moduleimpl_id, etudids: list[int] | None = None, submitted=False ): """Formulaire inscription des etudiants a ce module * Gestion des inscriptions Nom TD TA TP (triable) [x] M. XXX YYY - - - ajouter TD A, TD B, TP 1, TP 2 ... supprimer TD A, TD B, TP 1, TP 2 ... * Si pas les droits: idem en readonly """ etudids = etudids or [] modimpl = ModuleImpl.get_modimpl(moduleimpl_id) module = modimpl.module formsemestre = modimpl.formsemestre # -- check lock if not formsemestre.etat: raise ScoValueError("opération impossible: semestre verrouille") header = html_sco_header.sco_header( page_title="Inscription au module", init_qtip=True, javascripts=["js/etud_info.js"], ) footer = html_sco_header.sco_footer() H = [ header, f"""<h2>Inscriptions au module <a class="stdlink" href="{ url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id) }">{module.titre or "(module sans titre)"}</a> ({module.code})</a></h2> <p class="help">Cette page permet d'éditer les étudiants inscrits à ce module (ils doivent évidemment être inscrits au semestre). Les étudiants cochés sont (ou seront) inscrits. Vous pouvez inscrire ou désinscrire tous les étudiants d'un groupe à l'aide des menus "Ajouter" et "Enlever". </p> <p class="help">Aucune modification n'est prise en compte tant que l'on n'appuie pas sur le bouton "Appliquer les modifications". </p> """, ] # Liste des inscrits à ce semestre inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits( formsemestre.id ) for ins in inscrits: etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1) if not etuds_info: log( f"""moduleimpl_inscriptions_edit: inconsistency for etudid={ins['etudid']} !""" ) raise ScoValueError( f"""Étudiant {ins['etudid']} inscrit mais inconnu dans la base !""" ) ins["etud"] = etuds_info[0] inscrits.sort(key=lambda inscr: sco_etud.etud_sort_key(inscr["etud"])) in_m = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=modimpl.id) in_module = {x["etudid"] for x in in_m} # partitions = sco_groups.get_partitions_list(formsemestre.id) # if not submitted: H.append( """<script type="text/javascript"> function group_select(groupName, partitionIdx, check) { var nb_inputs_to_skip = 2; // nb d'input avant les checkbox !!! var elems = document.getElementById("mi_form").getElementsByTagName("input"); if (partitionIdx==-1) { for (var i =nb_inputs_to_skip; i < elems.length; i++) { elems[i].checked=check; } } else { for (var i =nb_inputs_to_skip; i < elems.length; i++) { var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes; if (cells.length && cells[0].nodeValue == groupName) { elems[i].checked=check; } } } } </script> <style> table.mi_table td, table.mi_table th { text-align: left; } </style> """ ) H.append( f"""<form method="post" id="mi_form" action="{request.base_url}"> <input type="hidden" name="moduleimpl_id" value="{modimpl.id}"/> <input type="submit" name="submitted" value="Appliquer les modifications"/> <div> { _make_menu(partitions, "Ajouter", "true") } { _make_menu(partitions, "Enlever", "false")} </div> <table class="gt_table mi_table"> <thead> <tr> <th class="etud">Nom</th> """ ) for partition in partitions: if partition["partition_name"]: H.append(f"<th>{partition['partition_name']}</th>") H.append("</tr></thead><tbody>") for ins in inscrits: etud = ins["etud"] if etud["etudid"] in in_module: checked = 'checked="checked"' else: checked = "" H.append( f"""<tr><td class="etud"><input type="checkbox" name="etudids:list" value="{etud['etudid']}" {checked}>""" ) H.append( f"""<a class="discretelink etudinfo" href="{ url_for( "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"], ) }" id="{etud['etudid']}">{etud['nomprenom']}</a>""" ) H.append("""</input></td>""") groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre.id) for partition in partitions: if partition["partition_name"]: gr_name = "" for group in groups: if group["partition_id"] == partition["partition_id"]: gr_name = group["group_name"] break # gr_name == '' si etud non inscrit dans un groupe de cette partition H.append(f"<td>{gr_name}</td>") H.append("""</tbody></table></form>""") else: # SUBMISSION # inscrit a ce module tous les etuds selectionnes sco_moduleimpl.do_moduleimpl_inscrit_etuds( moduleimpl_id, formsemestre.id, etudids, reset=True ) return flask.redirect( url_for( "notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=moduleimpl_id, ) ) # H.append(footer) return "\n".join(H) def _make_menu(partitions: list[dict], title="", check="true") -> str: """Menu with list of all groups""" items = [{"title": "Tous", "attr": f"onclick=\"group_select('', -1, {check})\""}] p_idx = 0 for partition in partitions: if partition["partition_name"] is not None: p_idx += 1 for group in sco_groups.get_partition_groups(partition): items.append( { "title": "%s %s" % (partition["partition_name"], group["group_name"]), "attr": "onclick=\"group_select('%s', %s, %s)\"" % (group["group_name"], p_idx, check), } ) return ( '<div class="inscr_addremove_menu">' + htmlutils.make_menu(title, items, alone=True) + "</div>" ) def moduleimpl_inscriptions_stats(formsemestre_id): """Affiche quelques informations sur les inscriptions aux modules de ce semestre. Inscrits au semestre: <nb> Modules communs (tous inscrits): <liste des modules (codes) Autres modules: (regroupés par UE) UE 1 <code du module>: <nb inscrits> (<description en termes de groupes>) ... En APC, n'affiche pas la colonne UE, car le rattachement n'a pas d'importance pédagogique. descriptions: groupes de TD A, B et C tous sauf groupe de TP Z (?) tous sauf <liste d'au plus 7 noms> """ authuser = current_user formsemestre = FormSemestre.get_formsemestre(formsemestre_id) res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) is_apc = formsemestre.formation.is_apc() inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) set_all = set([x["etudid"] for x in inscrits]) partitions, _ = sco_groups.get_formsemestre_groups(formsemestre_id) can_change = authuser.has_permission(Permission.EtudInscrit) and formsemestre.etat # Décrit les inscriptions aux modules: commons = [] # modules communs a tous les etuds du semestre options = [] # modules ou seuls quelques etudiants sont inscrits mod_description = {} # modimplid : str mod_nb_inscrits = {} # modimplid : int if is_apc: modimpls = sorted(formsemestre.modimpls, key=lambda m: m.module.sort_key_apc()) else: modimpls = formsemestre.modimpls_sorted for modimpl in modimpls: tous_inscrits, nb_inscrits, descr = descr_inscrs_module( modimpl.id, set_all, partitions, ) if tous_inscrits: commons.append(modimpl) else: mod_description[modimpl.id] = descr mod_nb_inscrits[modimpl.id] = nb_inscrits options.append(modimpl) # Page HTML: H = [ html_sco_header.html_sem_header( "Inscriptions aux modules et UE du semestre", javascripts=["js/etud_info.js", "js/moduleimpl_inscriptions_stats.js"], init_qtip=True, ) ] H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>") if options: H.append("<h3>Modules auxquels tous les étudiants ne sont pas inscrits:</h3>") H.append( f"""<table class="formsemestre_status formsemestre_inscr"> <tr> {'<th>UE</th>' if not is_apc else ""} <th>Code</th> <th>Module</th> <th>Inscrits</th> <th></th> </tr> """ ) for modimpl in options: if can_change: c_link = f"""<a class="discretelink" href="{url_for( 'notes.moduleimpl_inscriptions_edit', scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id) }">{mod_description[modimpl.id] or "<i>(inscrire des étudiants)</i>"}</a> """ else: c_link = mod_description[modimpl.id] H.append("""<tr class="formsemestre_status">""") if not is_apc: H.append( f""" <td>{ modimpl.module.ue.acronyme or "" }</td> """ ) H.append( f""" <td class="formsemestre_status_code">{ modimpl.module.code or "(module sans code)" }</td> <td class="formsemestre_status_module">{modimpl.module.titre or ""}</td> <td class="formsemestre_status_inscrits">{ mod_nb_inscrits[modimpl.id]}</td><td>{c_link}</td> </tr> """ ) H.append("</table>") else: H.append( """<span style="font-size:110%; font-style:italic; color: red;" >Tous les étudiants sont inscrits à tous les modules.</span>""" ) if commons: H.append( f"""<h3>Modules communs (auxquels tous les étudiants sont inscrits):</h3> <table class="formsemestre_status formsemestre_inscr"> <tr> {'<th>UE</th>' if not is_apc else ""} <th>Code</th> <th>Module</th>""" ) if is_apc: H.append("<th>Parcours</th>") H.append("""</tr>""") for modimpl in commons: if can_change: c_link = f"""<a class="discretelink" href="{ url_for("notes.moduleimpl_inscriptions_edit", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id) }">{modimpl.module.titre}</a>""" else: c_link = modimpl.module.titre H.append("""<tr class="formsemestre_status_green">""") if not is_apc: H.append( f""" <td>{modimpl.module.ue.acronyme or ""}</td> """ ) H.append( f""" <td class="formsemestre_status_code">{ modimpl.module.code or "(module sans code)" }</td><td>{c_link}</td>""" ) if is_apc: H.append( f"""<td><em>{', '.join(p.code for p in modimpl.module.parcours)}</em></td>""" ) H.append("</tr>") H.append("</table>") # Etudiants "dispensés" d'une UE (capitalisée) ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id) if ues_cap_info: H.append( '<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">' ) ues = [UniteEns.query.get_or_404(ue_id) for ue_id in ues_cap_info.keys()] ues.sort(key=lambda u: u.numero) for ue in ues: H.append( f"""<li class="tit"><span class="tit">{ue.acronyme}: {ue.titre or ''}</span>""" ) H.append("<ul>") for info in ues_cap_info[ue.id]: etud = Identite.get_etud(info["etudid"]) H.append( f"""<li class="etud"><a class="discretelink etudinfo" id="{etud.id}" href="{ url_for( "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id, ) }">{etud.nomprenom}</a>""" ) if info["ue_status"]["event_date"]: H.append( f"""(cap. le {info["ue_status"]["event_date"].strftime(scu.DATE_FMT)})""" ) if is_apc: is_inscrit_ue = (etud.id, ue.id) not in res.dispense_ues else: # CLASSIQUE is_inscrit_ue = info["is_ins"] if is_inscrit_ue: dm = ", ".join( [ m["code"] or m["abbrev"] or "pas_de_code" for m in info["is_ins"] ] ) H.append( f"""actuellement inscrit dans <a title="{dm}" class="discretelink" >{len(info["is_ins"])} modules</a>""" ) if is_inscrit_ue: if info["ue_status"]["is_capitalized"]: H.append( """<div><em style="font-size: 70%">UE actuelle moins bonne que l'UE capitalisée</em> </div>""" ) else: H.append( """<div><em style="font-size: 70%">UE actuelle meilleure que l'UE capitalisée</em> </div>""" ) if can_change: H.append( f"""<div><a class="stdlink" href="{ url_for("notes.etud_desinscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud.id, formsemestre_id=formsemestre_id, ue_id=ue.id) }">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div> """ ) else: H.append("(non réinscrit dans cette UE)") if can_change: H.append( f"""<div><a class="stdlink" href="{ url_for("notes.etud_inscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud.id, formsemestre_id=formsemestre_id, ue_id=ue.id) }">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div> """ ) H.append("</li>") H.append("</ul></li>") H.append("</ul>") # BUT: propose dispense de toutes UEs if is_apc: H.append(_list_but_ue_inscriptions(res, read_only=not can_change)) H.append( """<hr/><p class="help">Cette page décrit les inscriptions actuelles. Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en cliquant sur la ligne du module.</p> <p class="help">Note: la déinscription d'un module ne perd pas les notes. Ainsi, si l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.</p> """ ) H.append(html_sco_header.sco_footer()) return "\n".join(H) def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> str: """HTML pour dispenser/reinscrire chaque étudiant à chaque UE du BUT""" H = [ """ <div class="list_but_ue_inscriptions"> <h3>Inscriptions/déinscription aux UEs du BUT</h3> <form class="list_but_ue_inscriptions"> """ ] table_inscr = _table_but_ue_inscriptions(res) ue_ids = ( set.union(*(set(x.keys()) for x in table_inscr.values())) if table_inscr else set() ) ues = sorted( (db.session.get(UniteEns, ue_id) for ue_id in ue_ids), key=lambda u: (u.numero or 0, u.acronyme), ) H.append( """<table id="but_ue_inscriptions" class="stripe compact"> <thead> <tr><th>Nom</th><th>Parcours</th> """ ) for ue in ues: H.append(f"""<th title="{ue.titre or ''}">{ue.acronyme}</th>""") H.append( """</tr> </thead> <tbody> """ ) partition_parcours: Partition = Partition.query.filter_by( formsemestre=res.formsemestre, partition_name=scu.PARTITION_PARCOURS ).first() etuds = list_etuds.etuds_sorted_from_ids(table_inscr.keys()) for etud in etuds: ues_etud = table_inscr[etud.id] H.append( f"""<tr><td><a class="discretelink etudinfo" id={etud.id} href="{url_for( "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id, )}" >{etud.nomprenom}</a></td>""" ) # Parcours: if partition_parcours: group = partition_parcours.get_etud_group(etud.id) parcours_name = group.group_name if group else "" else: parcours_name = "" H.append(f"""<td class="parcours">{parcours_name}</td>""") # UEs: for ue in ues: td_class = "" est_inscr = ues_etud.get(ue.id) # None si pas concerné if est_inscr is None: content = "" else: # Validations d'UE déjà enregistrées dans d'autres semestres validations_ue = ( ScolarFormSemestreValidation.query.filter_by(etudid=etud.id) .filter( ScolarFormSemestreValidation.formsemestre_id != res.formsemestre.id, ScolarFormSemestreValidation.code.in_( codes_cursus.CODES_UE_VALIDES ), ) .join(UniteEns) .filter_by(ue_code=ue.ue_code) .all() ) validations_ue.sort( key=lambda v: codes_cursus.BUT_CODES_ORDER.get(v.code, 0) ) validation = validations_ue[-1] if validations_ue else None expl_validation = ( f"""Validée ({validation.code}) le { validation.event_date.strftime(scu.DATE_FMT)}""" if validation else "" ) td_class = ' class="ue_validee"' if validation else "" content = f"""<input type="checkbox" {'checked' if est_inscr else ''} {'disabled' if read_only else ''} title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}. {expl_validation}" onchange="change_ue_inscr(this);" data-url_inscr="{ url_for("notes.etud_inscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud.id, formsemestre_id=res.formsemestre.id, ue_id=ue.id) }" data-url_desinscr="{ url_for("notes.etud_desinscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud.id, formsemestre_id=res.formsemestre.id, ue_id=ue.id) }" /> """ H.append(f"""<td{td_class}>{content}</td>""") H.append( """ </tbody> </table> </form> <div class="help"> <p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours. </p> <p>Il peut s'agir d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres cas particuliers. </p> <p>La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre) et n'affecte pas les notes saisies. </p> </div> </div> """ ) return "\n".join(H) def _table_but_ue_inscriptions(res: NotesTableCompat) -> dict[int, dict]: """ "table" avec les inscriptions aux UEs de chaque étudiant { etudid : { ue_id : True | False } } """ return { etudid: { ue_id: (etudid, ue_id) not in res.dispense_ues for ue_id in res.etud_ues_ids(etudid) } for etudid, inscr in res.formsemestre.etuds_inscriptions.items() if inscr.etat == scu.INSCRIT } def descr_inscrs_module(moduleimpl_id, set_all, partitions): """returns tous_inscrits, nb_inscrits, descr""" ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id) set_m = set([x["etudid"] for x in ins]) # ens. des inscrits au module non_inscrits = set_all - set_m if len(non_inscrits) == 0: return True, len(ins), "" # tous inscrits if len(non_inscrits) <= 7: # seuil arbitraire return False, len(ins), "tous sauf " + _fmt_etud_set(non_inscrits) # Cherche les groupes: gr = [] # [ ( partition_name , [ group_names ] ) ] for partition in partitions: grp = [] # groupe de cette partition for group in sco_groups.get_partition_groups(partition): members = sco_groups.get_group_members(group["group_id"]) set_g = set([m["etudid"] for m in members]) if set_g.issubset(set_m): grp.append(group["group_name"]) set_m = set_m - set_g gr.append((partition["partition_name"], grp)) # d = [] for partition_name, grp in gr: if grp: d.append("groupes de %s: %s" % (partition_name, ", ".join(grp))) r = [] if d: r.append(", ".join(d)) if set_m: r.append(_fmt_etud_set(set_m)) # return False, len(ins), " et ".join(r) def _fmt_etud_set(etudids, max_list_size=7) -> str: # max_list_size est le nombre max de noms d'etudiants listés # au delà, on indique juste le nombre, sans les noms. if len(etudids) > max_list_size: return f"{len(etudids)} étudiants" etuds = [] for etudid in etudids: etud = db.session.get(Identite, etudid) if etud: etuds.append(etud) return ", ".join( [ f"""<a class="discretelink" href="{ url_for( "scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id ) }">{etud.nomprenom}</a>""" for etud in sorted(etuds, key=attrgetter("sort_key")) ] ) def get_etuds_with_capitalized_ue(formsemestre_id: int) -> list[dict]: """For each UE, computes list of students capitalizing the UE. returns { ue_id : [ { infos } ] } """ ues_cap_info = collections.defaultdict(list) formsemestre = FormSemestre.get_formsemestre(formsemestre_id) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) ues = nt.get_ues_stat_dict() for ue in ues: for etud in inscrits: ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"]) if ue_status and ue_status["was_capitalized"]: ues_cap_info[ue["ue_id"]].append( { "etudid": etud["etudid"], "ue_status": ue_status, "is_ins": etud_modules_ue_inscr( etud["etudid"], formsemestre_id, ue["ue_id"] ), } ) return ues_cap_info def etud_modules_ue_inscr(etudid, formsemestre_id, ue_id) -> list[int]: """Modules de cette UE dans ce semestre auxquels l'étudiant est inscrit. Utile pour formations classiques seulement. """ r = ndb.SimpleDictFetch( """SELECT mod.id AS module_id, mod.* FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem, notes_moduleimpl_inscription i WHERE sem.id = %(formsemestre_id)s AND mi.formsemestre_id = sem.id AND mod.id = mi.module_id AND mod.ue_id = %(ue_id)s AND i.moduleimpl_id = mi.id AND i.etudid = %(etudid)s ORDER BY mod.numero """, {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id}, ) return r def do_etud_desinscrit_ue_classic(etudid, formsemestre_id, ue_id): """Désinscrit l'etudiant de tous les modules de cette UE dans ce semestre. N'utiliser que pour les formations classiques, pas APC. """ cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( """DELETE FROM notes_moduleimpl_inscription WHERE id IN ( SELECT i.id FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem, notes_moduleimpl_inscription i WHERE sem.id = %(formsemestre_id)s AND mi.formsemestre_id = sem.id AND mod.id = mi.module_id AND mod.ue_id = %(ue_id)s AND i.moduleimpl_id = mi.id AND i.etudid = %(etudid)s ) """, {"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id}, ) logdb( cnx, method="etud_desinscrit_ue", etudid=etudid, msg=f"desinscription UE {ue_id}", commit=False, ) sco_cache.invalidate_formsemestre( formsemestre_id=formsemestre_id ) # > desinscription etudiant des modules def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id): """Incrit l'etudiant de tous les modules de cette UE dans ce semestre.""" # Verifie qu'il est bien inscrit au semestre insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id, "etudid": etudid} ) if not insem: raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid) cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( """SELECT mi.id FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem WHERE sem.id = %(formsemestre_id)s AND mi.formsemestre_id = sem.id AND mod.id = mi.module_id AND mod.ue_id = %(ue_id)s """, {"formsemestre_id": formsemestre_id, "ue_id": ue_id}, ) res = cursor.dictfetchall() for moduleimpl_id in [x["id"] for x in res]: sco_moduleimpl.do_moduleimpl_inscription_create( {"moduleimpl_id": moduleimpl_id, "etudid": etudid}, formsemestre_id=formsemestre_id, )