Editeur de partitions: liens vers outil de répartition

This commit is contained in:
Emmanuel Viennet 2024-08-26 14:46:28 +02:00
parent 6c6d1103e6
commit 04a8177d12
7 changed files with 150 additions and 73 deletions

View File

@ -1332,17 +1332,6 @@ def groups_auto_repartition(partition: Partition):
), ),
] ]
H = [
f"""<h2>Répartition des groupes de {partition.partition_name}</h2>
<p>Semestre {formsemestre.titre_annee()}</p>
<p class="help">Les groupes existants seront <b>effacés</b> 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.
</p>
""",
]
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
@ -1354,58 +1343,59 @@ def groups_auto_repartition(partition: Partition):
) )
if tf[0] == 0: if tf[0] == 0:
return render_template( return render_template(
"sco_page.j2", "formsemestre/groups_auto_repartition.j2",
title="Répartition des groupes", title="Répartition dans des groupes",
content="\n".join(H) + "\n" + tf[1], form_html=tf[1],
partition=partition,
sco=ScoData(formsemestre=formsemestre), sco=ScoData(formsemestre=formsemestre),
) )
elif tf[0] == -1: if tf[0] == -1:
return flask.redirect(dest_url) return flask.redirect(dest_url)
else:
# form submission # form submission
log(f"groups_auto_repartition({partition})") log(f"groups_auto_repartition({partition})")
group_names = tf[2]["groupNames"] group_names = tf[2]["groupNames"]
group_names = sorted({x.strip() for x in group_names.split(",")}) group_names = sorted({x.strip() for x in group_names.split(",")})
# Détruit les groupes existant de cette partition # Détruit les groupes existant de cette partition
for group in partition.groups: for group in partition.groups:
db.session.delete(group) db.session.delete(group)
db.session.commit() db.session.commit()
# Crée les nouveaux groupes # Crée les nouveaux groupes
groups = [] groups = []
for group_name in group_names: for group_name in group_names:
if group_name.strip(): if group_name.strip():
groups.append(partition.create_group(group_name)) groups.append(partition.create_group(group_name))
# #
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
identdict = nt.identdict identdict = nt.identdict
# build: { civilite : liste etudids trie par niveau croissant } # build: { civilite : liste etudids trie par niveau croissant }
civilites = {x["civilite"] for x in identdict.values()} civilites = {x["civilite"] for x in identdict.values()}
listes = {} 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: for civilite in civilites:
listes[civilite] = [ log(f"civilite={civilite}")
(_get_prev_moy(x["etudid"], formsemestre.id), x["etudid"]) if len(listes[civilite]):
for x in identdict.values() n -= 1
if x["civilite"] == civilite etudid = listes[civilite].pop()[1]
] group = groups[igroup]
listes[civilite].sort() igroup = (igroup + 1) % nbgroups
log("listes[%s] = %s" % (civilite, listes[civilite])) log(f"in {etudid} in group {group.id}")
# affect aux groupes: change_etud_group_in_partition(etudid, group)
n = len(identdict) log(f"{etudid} in group {group.id}")
igroup = 0 return flask.redirect(dest_url)
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: def _get_prev_moy(etudid: int, formsemestre_id: int) -> float | str:

View File

@ -309,7 +309,7 @@ body.editionActivated .filtres>div>div>div>div {
display: none; display: none;
} }
#zonePartitions span.editing a { #zonePartitions span.editing a {
text-decoration: none; text-decoration: none;
} }
@ -400,7 +400,7 @@ body.editionActivated .filtres .nonEditable .move {
/*****************************/ /*****************************/
/* Zone Etudiants */ /* Zone Etudiants */
/*****************************/ /*****************************/
#zoneChoix summary{ #zoneChoix summary {
margin: 0 0 16px; margin: 0 0 16px;
cursor: pointer; cursor: pointer;
} }
@ -450,6 +450,7 @@ body.editionActivated .filtres .nonEditable .move {
margin-bottom: 4px; margin-bottom: 4px;
width: fit-content; width: fit-content;
} }
#zoneChoix .autoAffectation .progress { #zoneChoix .autoAffectation .progress {
position: absolute; position: absolute;
top: 100%; top: 100%;
@ -524,7 +525,8 @@ body.editionActivated .filtres .nonEditable .move {
} }
#zoneChoix .etudiants .partition>div, #zoneChoix .etudiants .partition>div,
#zoneChoix .etudiants .partition span { #zoneChoix .etudiants .partition span,
div.partition-name {
display: block; display: block;
padding: 4px 8px; padding: 4px 8px;
border: 1px solid #aaa; border: 1px solid #aaa;
@ -541,12 +543,22 @@ body.editionActivated .filtres .nonEditable .move {
color: #fff; color: #fff;
} }
#zoneChoix .etudiants .partition>:nth-child(1) { #zoneChoix .etudiants .partition>:nth-child(1),
div.partition-name {
background: #09c; background: #09c;
border-color: #09c; border-color: #09c;
color: #fff; color: #fff;
} }
div.partition-name {
display: inline-block;
width: fit-content;
}
div.partition-name a {
color: #fff !important;
}
section:not(#zonePartitions) .hide { section:not(#zonePartitions) .hide {
display: none !important; display: none !important;
} }

View File

@ -0,0 +1,30 @@
{# Formulaire répartition auto dans les groupes #}
{% extends "sco_page.j2" %}
{% block app_content %}
<h2>Répartition des groupes de {{partition.partition_name}}</h2>
<div class="scobox">
<div class="scobox-title">Semestre {{sco.formsemestre.titre_annee()}}</div>
<div class="help space-after-24 space-before-24">
💡Les groupes existants dans cette partition seront <b>effacés</b> 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.
</div>
{{form_html|safe}}
</div>
<div>
<a class="stdlink" href="{{
url_for('scolar.partition_editor',
scodoc_dept=g.scodoc_dept, formsemestre_id=sco.formsemestre.id)
}}">Retour à l'éditeur de partitions</a>
</div>
{% endblock %}

View File

@ -52,7 +52,23 @@
<details> <details>
<summary>Outils d'affectation</summary> <summary>Outils d'affectation</summary>
<div class="autoAffectation"> <div class="autoAffectation">
<a href="students_groups_auto_assignment?formsemestre_id={{formsemestre.id}}"><svg <svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="#0b0b0b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M13.8 12H3" />
</svg> Répartir les étudiants dans les groupes
<div id="partitionsARepartir">
{# peuplé en js par listeGroupesRepartition()
<div class="partition-name"><a href="">TD</a></div>
<div class="partition-name"><a href="">groupes de projets</a></div>
#}
</div>
</div>
<div class="autoAffectation">
<a href="{{
url_for('scolar.students_groups_auto_assignment',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id
)}}"><svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="#0b0b0b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> stroke="#0b0b0b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M13.8 12H3" /> <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M13.8 12H3" />
@ -134,6 +150,7 @@
processDatas(partitions, etudiants); processDatas(partitions, etudiants);
processEvents(); processEvents();
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
document.querySelector("body").classList.add("loaded"); document.querySelector("body").classList.add("loaded");
document.querySelector('.wait').style.display = "none"; document.querySelector('.wait').style.display = "none";
@ -354,6 +371,19 @@
} }
function listeGroupesRepartition() { // peuple la liste des partitions pour répartition auto
let output = '';
document.querySelectorAll('#zonePartitions .filtres>[data-idpartition]').forEach(partition => {
if (!partition.classList.contains('nonEditable')) {
output += `
<div class="partition-name">
<a href="groups_auto_repartition/${partition.dataset.idpartition}">${partition.children[0].children[1].innerText}
</a></div>`;
}
});
document.querySelector("#partitionsARepartir").innerHTML = output;
}
/******************************/ /******************************/
/* Gestionnaire d'événements */ /* Gestionnaire d'événements */
/******************************/ /******************************/
@ -677,6 +707,7 @@
</div>`; </div>`;
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
}) })
.catch(error => { .catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (1).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (1).</h2>";
@ -730,6 +761,7 @@
// divGroupe.querySelector(".modif").click(); // divGroupe.querySelector(".modif").click();
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
}) })
.catch(error => { .catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (4).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (4).</h2>";
@ -811,6 +843,7 @@
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (2).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (2).</h2>";
} }
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
}) })
.catch(error => { .catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (3).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (3).</h2>";
@ -924,6 +957,7 @@
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>";
} }
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
}) })
} }
@ -1038,6 +1072,7 @@
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>";
} }
listeGroupesAutoaffectation(); listeGroupesAutoaffectation();
listeGroupesRepartition();
}) })
.catch(error => { .catch(error => {
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (7).</h2>"; document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (7).</h2>";

View File

@ -1,3 +1,7 @@
{%- extends 'sco_page.j2' -%}
{% block styles %}
{{super()}}
<style> <style>
.wait { .wait {
position: fixed; position: fixed;
@ -212,7 +216,10 @@
z-index: 1; z-index: 1;
} }
</style> </style>
{% endblock %}
{% block app_content %}
<main class="moitemoite"> <main class="moitemoite">
<div class="wait"></div> <div class="wait"></div>
<section> <section>
@ -249,6 +256,11 @@
</main> </main>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{scu.STATIC_DIR}}/libjs/xlsx-populate-1.21.0.min.js"></script> <script src="{{scu.STATIC_DIR}}/libjs/xlsx-populate-1.21.0.min.js"></script>
<script> <script>
/************************/ /************************/
@ -858,3 +870,4 @@
} }
</script> </script>
{% endblock %}

View File

@ -1003,15 +1003,12 @@ def partition_editor(formsemestre_id: int, edit_partition=False):
@scodoc7func @scodoc7func
def students_groups_auto_assignment(formsemestre_id: int): def students_groups_auto_assignment(formsemestre_id: int):
"""Répartition auto des groupes""" """Répartition auto des groupes"""
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
H = [
render_template(
"scolar/students_groups_auto_assignment.j2",
formsemestre=formsemestre,
),
]
return render_template( return render_template(
"sco_page.j2", title="Répartition des groupes", content="\n".join(H) "scolar/students_groups_auto_assignment.j2",
formsemestre=formsemestre,
title="Répartition des groupes",
sco=ScoData(formsemestre=formsemestre),
) )

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.7.13" SCOVERSION = "9.7.14"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"