# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 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 db, log from app.models import Formation, FormSemestre, GroupDescr from app.scodoc.gen_tables import GenTable from app.scodoc import html_sco_header from app.scodoc import sco_cache from app.scodoc import codes_cursus 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_preferences from app.scodoc import sco_pv_dict 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 comme 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): """Étudiants 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_pv_dict.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_parcours=False): """Inscrit ces etudiants dans ce semestre (la liste doit avoir été vérifiée au préalable) En option: - Si inscrit_groupes, inscrit aux mêmes groupes que dans le semestre origine (toutes partitions, y compris parcours) - Si inscrit_parcours, inscrit au même groupe de parcours (mais ignore les autres partitions) (si les deux sont vrais, inscrit_parcours n'a pas d'effet) """ # TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"]) formsemestre.setup_parcours_groups() log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}") for etudid in etudids: sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules( sem["formsemestre_id"], etudid, etat=scu.INSCRIT, method="formsemestre_inscr_passage", ) if inscrit_groupes or inscrit_parcours: # 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] # 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 = { 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: group: GroupDescr = db.session.get( GroupDescr, partition_group["group_id"] ) if inscrit_groupes or ( group.partition.partition_name == scu.PARTITION_PARCOURS and inscrit_parcours ): sco_groups.change_etud_group_in_partition(etudid, group) def do_desinscrit(sem: dict, etudids: list[int]): "désinscrit les étudiants indiqués du formsemestre" log(f"do_desinscrit: {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"] == codes_cursus.NO_SEMESTRE_ID: continue # formation: Formation = Formation.query.get_or_404(s["formation_id"]) parcours = codes_cursus.get_cursus_from_code(formation.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, inscrit_parcours=False, submitted=False, dialog_confirmed=False, ignore_jury=False, ) -> str: """Page 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 étudiants non inscrits changent (de groupe) - les étudiants 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) inscrit_parcours = int(inscrit_parcours) 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 = [] if not submitted: H += _build_page( sem, auth_etuds_by_sem, inscrits, candidats_non_inscrits, inscrits_ailleurs, inscrit_groupes=inscrit_groupes, inscrit_parcours=inscrit_parcours, ignore_jury=ignore_jury, ) else: if not dialog_confirmed: # Confirmation if a_inscrire: H.append("
Confirmer ?
" 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, "inscrit_parcours": inscrit_parcours, "ignore_jury": ignore_jury, "submitted": 1, }, ) ) else: with sco_cache.DeferredSemCacheManager(): # Inscription des étudiants au nouveau semestre: do_inscrit( sem, a_inscrire, inscrit_groupes=inscrit_groupes, inscrit_parcours=inscrit_parcours, ) # Désinscriptions: do_desinscrit(sem, a_desinscrire) H.append( f"""Cette page permet d'inscrire des étudiants dans le semestre destination {sem['titreannee']}, et d'en désincrire si besoin.
Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères gras sont déjà inscrits dans le semestre destination. Ceux qui sont en gras et en rouge sont inscrits dans un autre semestre.
Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter d'autres étudiants à inscrire dans le semestre destination.
Si vous dé-selectionnez un étudiant déjà inscrit (en gras), il sera désinscrit.
Le bouton inscrire aux mêmes groupes ne prend en compte que les groupes qui existent dans les deux semestres: pensez à créer les partitions et groupes que vous souhaitez conserver avant d'inscrire les étudiants.
Les parcours de BUT sont gérés comme des groupes de la partition parcours: si on conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans modifier le semestre avant de faire passer les étudiants).
Aucune action ne sera effectuée si vous n'appuyez pas sur le bouton "Appliquer les modifications" !