# -*- 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 semestres et modules """ import collections import flask from flask import flash, url_for, g, render_template, request from app import db from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.models import ( Formation, FormSemestre, FormSemestreInscription, ModuleImpl, Scolog, ) from app.models.etudiants import Identite from app.models.groups import Partition, GroupDescr from app.models.scolar_event import ScolarEvent import app.scodoc.sco_utils as scu from app import log from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.codes_cursus import UE_STANDARD, UE_SPORT, UE_TYPE_NAME import app.scodoc.notesdb as ndb from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc import sco_find_etud from app.scodoc import sco_formsemestre from app.scodoc import sco_moduleimpl from app.scodoc import sco_groups from app.scodoc import sco_etud from app.scodoc import sco_cache # --- Gestion des inscriptions aux semestres _formsemestre_inscriptionEditor = ndb.EditableTable( "notes_formsemestre_inscription", "formsemestre_inscription_id", ("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat", "etape"), sortkey="formsemestre_id", insert_ignore_conflicts=True, ) def do_formsemestre_inscription_list(*args, **kw): "list formsemestre_inscriptions" cnx = ndb.GetDBConnexion() return _formsemestre_inscriptionEditor.list(cnx, *args, **kw) def do_formsemestre_inscription_listinscrits(formsemestre_id): """Liste les inscrits (état I) à ce semestre et cache le résultat. Result: [ { "etudid":, "formsemestre_id": , "etat": , "etape": }] """ r = sco_cache.SemInscriptionsCache.get(formsemestre_id) if r is None: # retreive list r = do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id, "etat": scu.INSCRIT} ) sco_cache.SemInscriptionsCache.set(formsemestre_id, r) return r def do_formsemestre_inscription_delete(oid, formsemestre_id=None): "delete formsemestre_inscription" cnx = ndb.GetDBConnexion() _formsemestre_inscriptionEditor.delete(cnx, oid) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) def do_formsemestre_demission( etudid, formsemestre_id, event_date=None, etat_new=scu.DEMISSION, # DEMISSION or DEF operation_method="dem_etudiant", event_type="DEMISSION", ): "Démission ou défaillance d'un étudiant" # marque 'D' ou DEF dans l'inscription au semestre et ajoute # un "evenement" scolarite if etat_new not in (scu.DEF, scu.DEMISSION): raise ScoValueError("nouveau code d'état invalide") try: event_date_iso = ndb.DateDMYtoISO(event_date) except ValueError as exc: raise ScoValueError("format de date invalide") from exc etud = Identite.get_etud(etudid) # check lock formsemestre: FormSemestre = FormSemestre.query.filter_by( id=formsemestre_id, dept_id=g.scodoc_dept_id ).first_or_404() if not formsemestre.etat: raise ScoValueError("Modification impossible: semestre verrouille") # if formsemestre_id not in (inscr.formsemestre_id for inscr in etud.inscriptions()): raise ScoValueError("étudiant non inscrit dans ce semestre !") inscr = next( inscr for inscr in etud.inscriptions() if inscr.formsemestre_id == formsemestre_id ) inscr.etat = etat_new db.session.add(inscr) Scolog.logdb(method=operation_method, etudid=etudid) event = ScolarEvent( etudid=etudid, event_date=event_date_iso, formsemestre_id=formsemestre_id, event_type=event_type, ) db.session.add(event) db.session.commit() sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) if etat_new == scu.DEMISSION: flash("Démission enregistrée") elif etat_new == scu.DEF: flash("Défaillance enregistrée") def do_formsemestre_inscription_edit(args=None, formsemestre_id=None): "edit a formsemestre_inscription" cnx = ndb.GetDBConnexion() _formsemestre_inscriptionEditor.edit(cnx, args) sco_cache.invalidate_formsemestre( formsemestre_id=formsemestre_id ) # > modif inscription semestre def check_if_has_decision_jury( formsemestre: FormSemestre, etudids: list[int] | set[int] ): "raise exception if one of the etuds has a decision in formsemestre" nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) for etudid in etudids: if nt.etud_has_decision(etudid): etud = db.session.get(Identite, etudid) raise ScoValueError( f"""désinscription impossible: l'étudiant {etud.nomprenom} a une décision de jury (la supprimer avant si nécessaire)""" ) def do_formsemestre_desinscription( etudid, formsemestre_id: int, check_has_dec_jury=True ): """Désinscription d'un étudiant. Si semestre extérieur et dernier inscrit, suppression de ce semestre. """ formsemestre = FormSemestre.get_formsemestre(formsemestre_id) etud = Identite.get_etud(etudid) # -- check lock if not formsemestre.etat: raise ScoValueError("désinscription impossible: semestre verrouille") # -- Si decisions de jury, désinscription interdite if check_has_dec_jury: check_if_has_decision_jury(formsemestre, [etudid]) inscr_sem = FormSemestreInscription.query.filter_by( etudid=etudid, formsemestre_id=formsemestre_id ).first() if not inscr_sem: raise ScoValueError(f"{etud.nomprenom} n'est pas inscrit au semestre !") # -- desinscription de tous les modules cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( """SELECT Im.id AS moduleimpl_inscription_id FROM notes_moduleimpl_inscription Im, notes_moduleimpl M WHERE Im.etudid=%(etudid)s and Im.moduleimpl_id = M.id and M.formsemestre_id = %(formsemestre_id)s """, {"etudid": etudid, "formsemestre_id": formsemestre_id}, ) res = cursor.fetchall() moduleimpl_inscription_ids = [x[0] for x in res] for moduleimpl_inscription_id in moduleimpl_inscription_ids: sco_moduleimpl.do_moduleimpl_inscription_delete( moduleimpl_inscription_id, formsemestre_id=formsemestre_id ) # -- désincription de tous les groupes des partitions de ce semestre Partition.formsemestre_remove_etud(formsemestre_id, etud) # -- désincription du semestre formsemestre.desinscrit_etudiant(etud) # --- Semestre extérieur if formsemestre.modalite == "EXT": if 0 == len(formsemestre.inscriptions): log( f"""do_formsemestre_desinscription: suppression du semestre extérieur {formsemestre}""" ) db.session.delete(formsemestre) db.session.commit() flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}") def do_formsemestre_inscription_with_modules( formsemestre_id, etudid, group_ids: list = None, etat=scu.INSCRIT, etape=None, method="inscription_with_modules", dept_id: int = None, ): """Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS (donc sauf le sport) Si dept_id est spécifié, utilise ce département au lieu du courant. Vérifie la capacité d'accueil. """ etud = Identite.get_etud(etudid) group_ids = group_ids or [] if isinstance(group_ids, int): group_ids = [group_ids] # Check that all groups exist before creating the inscription groups = [ GroupDescr.query.get_or_404(group_id) for group_id in group_ids if group_id != "" ] formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id) # Inscription au semestre formsemestre.inscrit_etudiant(etud, etat=etat, etape=etape, method=method) log( f"""do_formsemestre_inscription_with_modules: etudid={ etudid} formsemestre_id={formsemestre_id}""" ) # inscriptions aux groupes # 1- inscrit au groupe 'tous' group_id = sco_groups.get_default_group(formsemestre_id) sco_groups.set_group(etudid, group_id) gdone = {group_id} # empeche doublons # 2- inscrit aux groupes for group in groups: if group.id not in gdone: sco_groups.set_group(etudid, group.id) gdone.add(group.id) # Inscription à tous les modules de ce semestre for modimpl in formsemestre.modimpls: if modimpl.module.ue.type != UE_SPORT: sco_moduleimpl.do_moduleimpl_inscription_create( {"moduleimpl_id": modimpl.id, "etudid": etudid}, formsemestre_id=formsemestre_id, ) # Mise à jour des inscriptions aux parcours: formsemestre.update_inscriptions_parcours_from_groups(etudid=etudid) def formsemestre_inscription_with_modules_etud( formsemestre_id, etudid=None, group_ids=None ): """Form. inscription d'un étudiant au semestre. Si etudid n'est pas specifié, form. choix etudiant. """ if etudid is None: return sco_find_etud.form_search_etud( title="Choix de l'étudiant à inscrire dans ce semestre", add_headers=True, dest_url="notes.formsemestre_inscription_with_modules_etud", parameters={"formsemestre_id": formsemestre_id}, parameters_keys="formsemestre_id", ) return formsemestre_inscription_with_modules( etudid, formsemestre_id, group_ids=group_ids ) def formsemestre_inscription_with_modules_form(etudid, only_ext=False): """Formulaire inscription de l'etud dans l'un des semestres existants. Si only_ext, ne montre que les semestre extérieurs. """ etud: Identite = Identite.query.filter_by( id=etudid, dept_id=g.scodoc_dept_id ).first_or_404() H = [ f"
L'étudiant sera inscrit à tous les modules du semestre choisi (sauf Sport & Culture).
aucune session de formation !
") H.append( f"""{etud.nomprenom} est déjà inscrit dans le semestre {formsemestre.titre_mois()}
""" ) return render_template( "sco_page.j2", title=f"Inscription de {etud.nomprenom} dans ce semestre", content=f"Attention: {etud.nomprenom} est déjà inscrit sur la même période dans: {", ".join(l)}.
""" ) H.append("Continuer quand même l'inscription
""" ) return render_template( "sco_page.j2", title=f"Inscription de {etud.nomprenom} dans ce semestre", content=f"Voici la liste des modules du semestre choisi.
Les modules cochés sont ceux dans lesquels l'étudiant est inscrit. Vous pouvez l'inscrire ou le désincrire d'un ou plusieurs modules.
Attention: cette méthode ne devrait être utilisée que pour les modules optionnels (ou les activités culturelles et sportives) et pour désinscrire les étudiants dispensés (UE validées).
""" ) return render_template("sco_page.j2", content="\n".join(H) + "\n" + tf[1]) if tf[0] == -1: return flask.redirect( url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid) ) # Inscriptions aux modules choisis # il faut desinscrire des modules qui ne figurent pas # et inscrire aux autres, sauf si deja inscrit a_desinscrire = {}.fromkeys([x.id for x in formsemestre.modimpls]) insdict = {} for ins in inscr: insdict[ins["moduleimpl_id"]] = ins for ue in ues: for moduleimpl_id in [int(x) for x in tf[2][f"moduleimpls_{ue.id}"]]: if moduleimpl_id in a_desinscrire: del a_desinscrire[moduleimpl_id] # supprime ceux auxquel pas inscrit moduleimpls_a_desinscrire = list(a_desinscrire.keys()) for moduleimpl_id in moduleimpls_a_desinscrire: if moduleimpl_id not in insdict: del a_desinscrire[moduleimpl_id] a_inscrire = set() for ue in ues: a_inscrire.update( int(x) for x in tf[2][f"moduleimpls_{ue.id}"] ) # conversion en int ! # supprime ceux auquel deja inscrit: for ins in inscr: if ins["moduleimpl_id"] in a_inscrire: a_inscrire.remove(ins["moduleimpl_id"]) # dict des modules: modimpls_by_id = {modimpl.id: modimpl for modimpl in formsemestre.modimpls} # if (not a_inscrire) and (not a_desinscrire): H.append( f"""{etud.nomprenom} va être désinscrit{etud.e} des modules:
{etud.nomprenom} va être inscrit{etud.e} aux modules:
Total: {len(etudlist)} étudiants concernés.
Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps !
Sauf exception, cette situation est anormale:
Aucun étudiant en inscription multiple (c'est normal) !
""") return render_template( "sco_page.j2", title="Inscriptions multiples parmi les étudiants du semestre", content="