👉💡 vous pourriez essayer le nouvel éditeur
""" ) H.append( """Les partitions sont des découpages de l'ensemble des étudiants. Par exemple, les "groupes de TD" sont une partition. On peut créer autant de partitions que nécessaire.
Les groupes %s de cette partition seront supprimés
""" % (partition["partition_name"], grnames), dest_url="", cancel_url="edit_partition_form?formsemestre_id=%s" % formsemestre_id, parameters={"redirect": redirect, "partition_id": partition_id}, ) log("partition_delete: partition_id=%s" % partition_id) # 1- groups for group in groups: group_delete(group["group_id"]) # 2- partition partitionEditor.delete(cnx, partition_id) formsemestre.update_inscriptions_parcours_from_groups() # redirect to partition edit page: if redirect: return flask.redirect( "edit_partition_form?formsemestre_id=" + str(formsemestre_id) ) def partition_move(partition_id, after=0, redirect=1): """Move before/after previous one (decrement/increment numero)""" partition = get_partition(partition_id) formsemestre_id = partition["formsemestre_id"] formsemestre = FormSemestre.get_formsemestre(formsemestre_id) if not formsemestre.can_change_groups(): raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") # redirect = int(redirect) after = int(after) # 0: deplace avant, 1 deplace apres if after not in (0, 1): raise ValueError('invalid value for "after"') others = get_partitions_list(formsemestre_id) objs = ( Partition.query.filter_by(formsemestre_id=formsemestre_id) .order_by(Partition.numero, Partition.partition_name) .all() ) if len({o.numero for o in objs}) != len(objs): # il y a des numeros identiques ! scu.objects_renumber(db, objs) if len(others) > 1: pidx = [p["partition_id"] for p in others].index(partition_id) # log("partition_move: after=%s pidx=%s" % (after, pidx)) neigh = None # partition to swap with if after == 0 and pidx > 0: neigh = others[pidx - 1] elif after == 1 and pidx < len(others) - 1: neigh = others[pidx + 1] if neigh: # # swap numero between partition and its neighbor # log("moving partition %s" % partition_id) cnx = ndb.GetDBConnexion() # Si aucun numéro n'a été affecté, le met au minimum min_numero = ( ndb.SimpleQuery( "SELECT MIN(numero) FROM partition WHERE formsemestre_id=%(formsemestre_id)s", {"formsemestre_id": formsemestre_id}, ).fetchone()[0] or 0 ) if neigh["numero"] is None: neigh["numero"] = min_numero - 1 if partition["numero"] is None: partition["numero"] = min_numero - 1 - after partition["numero"], neigh["numero"] = neigh["numero"], partition["numero"] partitionEditor.edit(cnx, partition) partitionEditor.edit(cnx, neigh) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) # redirect to partition edit page: if redirect: return flask.redirect( "edit_partition_form?formsemestre_id=" + str(formsemestre_id) ) def partition_rename(partition_id): """Form to rename a partition""" partition = get_partition(partition_id) formsemestre_id = partition["formsemestre_id"] formsemestre = FormSemestre.get_formsemestre(formsemestre_id) if not formsemestre.can_change_groups(): raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") H = ["Semestre {formsemestre.titre_annee()}
Les groupes existants seront effacés et remplacés par ceux créés ici. La répartition aléatoire tente d'uniformiser le niveau des groupes (en utilisant la dernière moyenne générale disponible pour chaque étudiant) et de maximiser la mixité de chaque groupe.
""", ] tf = TrivialFormulator( request.base_url, scu.get_request_args(), descr, {}, cancelbutton="Annuler", submitlabel="Créer et peupler les groupes", name="tf", ) if tf[0] == 0: return render_template( "sco_page.j2", title="Répartition des groupes", content="\n".join(H) + "\n" + tf[1], sco=ScoData(formsemestre=formsemestre), ) elif tf[0] == -1: return flask.redirect(dest_url) else: # form submission log(f"groups_auto_repartition({partition})") group_names = tf[2]["groupNames"] group_names = sorted({x.strip() for x in group_names.split(",")}) # Détruit les groupes existant de cette partition for group in partition.groups: db.session.delete(group) db.session.commit() # Crée les nouveaux groupes groups = [] for group_name in group_names: if group_name.strip(): groups.append(partition.create_group(group_name)) # nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) identdict = nt.identdict # build: { civilite : liste etudids trie par niveau croissant } civilites = {x["civilite"] for x in identdict.values()} listes = {} for civilite in civilites: listes[civilite] = [ (_get_prev_moy(x["etudid"], formsemestre.id), x["etudid"]) for x in identdict.values() if x["civilite"] == civilite ] listes[civilite].sort() log("listes[%s] = %s" % (civilite, listes[civilite])) # affect aux groupes: n = len(identdict) igroup = 0 nbgroups = len(groups) while n > 0: log(f"n={n}") for civilite in civilites: log(f"civilite={civilite}") if len(listes[civilite]): n -= 1 etudid = listes[civilite].pop()[1] group = groups[igroup] igroup = (igroup + 1) % nbgroups log(f"in {etudid} in group {group.id}") change_etud_group_in_partition(etudid, group) log(f"{etudid} in group {group.id}") return flask.redirect(dest_url) def _get_prev_moy(etudid: int, formsemestre_id: int) -> float | str: """Donne la derniere moyenne generale calculee pour cette étudiant, ou 0 si on n'en trouve pas (nouvel inscrit,...). """ etud = Identite.get_etud(etudid) Se = sco_cursus.get_situation_etud_cursus(etud, formsemestre_id) if Se.prev_formsemestre: prev_sem = db.session.get(FormSemestre, Se.prev_formsemestre.id) nt: NotesTableCompat = res_sem.load_formsemestre_results(prev_sem) return nt.get_etud_moy_gen(etud.id) return 0.0 def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"): """Crée une partition "apo_etapes" avec un groupe par étape Apogée. Cette partition n'est crée que si plusieurs étapes différentes existent dans ce semestre. Si la partition existe déjà, ses groupes sont mis à jour (les groupes devenant vides ne sont pas supprimés). """ # A RE-ECRIRE pour utiliser les modèles. from app.scodoc import sco_formsemestre_inscriptions partition_name = str(partition_name) log(f"create_etapes_partition({formsemestre_id})") ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( args={"formsemestre_id": formsemestre_id} ) etapes = {i["etape"] for i in ins if i["etape"]} partitions = get_partitions_list(formsemestre_id, with_default=False) partition = None for p in partitions: if p["partition_name"] == partition_name: partition = p break if len(etapes) < 2 and not partition: return # moins de deux étapes, pas de création if partition: pid = partition["partition_id"] else: pid = partition_create( formsemestre_id, partition_name=partition_name, redirect=False ) partition: Partition = db.session.get(Partition, pid) groups = partition.groups groups_by_names = {g.group_name: g for g in groups} for etape in etapes: if etape not in groups_by_names: new_group = create_group(pid, etape) groups_by_names[etape] = new_group # Place les etudiants dans les groupes for i in ins: if i["etape"]: change_etud_group_in_partition(i["etudid"], groups_by_names[i["etape"]]) def do_evaluation_listeetuds_groups( evaluation_id: int, groups=None, getallstudents: bool = False, include_demdef: bool = False, ) -> list[tuple[int, str]]: """Donne la liste non triée des etudids inscrits à cette évaluation dans les groupes indiqués (donc inscrits au modimpl ET au formsemestre). Si getallstudents==True, donne tous les étudiants inscrits à cette evaluation. Si include_demdef, compte aussi les etudiants démissionnaires et défaillants (sinon, par défaut, seulement les 'I') Résultat: [ (etudid, etat) ], où etat='I', 'D', 'DEF' """ # nb: pour notes_table / do_evaluation_etat, getallstudents est vrai et # include_demdef faux fromtables = [ "notes_moduleimpl_inscription Im", "notes_formsemestre_inscription Isem", "notes_moduleimpl M", "notes_evaluation E", ] # construit condition sur les groupes if not getallstudents: if not groups: return [] # no groups, so no students rg = ["gm.group_id = '%(group_id)s'" % g for g in groups] rq = """and Isem.etudid = gm.etudid and gd.partition_id = p.id and p.formsemestre_id = Isem.formsemestre_id """ r = rq + " AND (" + " or ".join(rg) + " )" fromtables += ["group_membership gm", "group_descr gd", "partition p"] else: r = "" # requete complete req = ( "SELECT distinct Im.etudid, Isem.etat FROM " + ", ".join(fromtables) + """ WHERE Isem.etudid = Im.etudid and Im.moduleimpl_id = M.id and Isem.formsemestre_id = M.formsemestre_id and E.moduleimpl_id = M.id and E.id = %(evaluation_id)s """ ) if not include_demdef: req += " and Isem.etat='I'" req += r cnx = ndb.GetDBConnexion() cursor = cnx.cursor() cursor.execute(req, {"evaluation_id": evaluation_id}) return cursor.fetchall() def do_evaluation_listegroupes(evaluation_id, include_default=False): """Donne la liste des groupes dans lesquels figurent des etudiants inscrits au module/semestre auquel appartient cette evaluation. Si include_default, inclue aussi le groupe par defaut ('tous') [ group ] """ if include_default: c = "" else: c = " AND p.partition_name is not NULL" cnx = ndb.GetDBConnexion() cursor = cnx.cursor() cursor.execute( """SELECT DISTINCT gd.id AS group_id FROM group_descr gd, group_membership gm, partition p, notes_moduleimpl m, notes_evaluation e WHERE gm.group_id = gd.id and gd.partition_id = p.id and p.formsemestre_id = m.formsemestre_id and m.id = e.moduleimpl_id and e.id = %(evaluation_id)s """ + c, {"evaluation_id": evaluation_id}, ) group_ids = [x[0] for x in cursor] return listgroups(group_ids) def listgroups(group_ids): cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) groups = [] for group_id in group_ids: cursor.execute( """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* FROM group_descr gd, partition p WHERE p.id = gd.partition_id AND gd.id = %(group_id)s """, {"group_id": group_id}, ) r = cursor.dictfetchall() if r: groups.append(r[0]) return _sortgroups(groups) def _sortgroups(groups): # Tri: place 'all' en tête, puis groupe par partition / nom de groupe R = [g for g in groups if g["partition_name"] is None] o = [g for g in groups if g["partition_name"] != None] o.sort(key=lambda x: (x["numero"] or 0, x["group_name"])) return R + o def listgroups_filename(groups): """Build a filename representing groups""" return "gr" + "+".join([g["group_name"] or "tous" for g in groups]) def listgroups_abbrev(groups): """Human readable abbreviation descring groups (eg "A / AB / B3") Ne retient que les partitions avec show_in_lists """ return " / ".join( [g["group_name"] for g in groups if g["group_name"] and g["show_in_lists"]] ) # form_group_choice replaces formChoixGroupe def form_group_choice( formsemestre_id, allow_none=True, # offre un choix vide dans chaque partition select_default=True, # Le groupe par defaut est mentionné (hidden). display_sem_title=False, ): """Partie de formulaire pour le choix d'un ou plusieurs groupes. Variable : group_ids """ from app.scodoc import sco_formsemestre sem = sco_formsemestre.get_formsemestre(formsemestre_id) if display_sem_title: sem_title = "%s: " % sem["titremois"] else: sem_title = "" # H = ["""Groupe de %(partition_name)s | " % p) H.append(' |