BUT: Partition de parcours et inscriptions

This commit is contained in:
Emmanuel Viennet 2022-05-28 11:38:22 +02:00
parent 5af4b5bed6
commit 45449f0465
9 changed files with 151 additions and 41 deletions

View File

@ -76,5 +76,6 @@ from app.models.but_refcomp import (
ApcCompetence,
ApcSituationPro,
ApcAppCritique,
ApcParcours,
)
from app.models.config import ScoDocSiteConfig

View File

@ -5,6 +5,7 @@
import datetime
from functools import cached_property
from flask import flash
import flask_sqlalchemy
from sqlalchemy.sql import text
@ -13,6 +14,7 @@ from app import log
from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models.groups import GroupDescr, Partition
import app.scodoc.sco_utils as scu
from app.models.but_refcomp import ApcParcours
@ -480,6 +482,85 @@ class FormSemestre(db.Model):
"""Map { etudid : inscription } (incluant DEM et DEF)"""
return {ins.etud.id: ins for ins in self.inscriptions}
def setup_parcours_groups(self) -> None:
"""Vérifie et créee si besoin la partition et les groupes de parcours BUT."""
if not self.formation.is_apc():
return
partition = Partition.query.filter_by(
formsemestre_id=self.id, partition_name=scu.PARTITION_PARCOURS
).first()
if partition is None:
# Création de la partition de parcours
partition = Partition(
formsemestre_id=self.id,
partition_name=scu.PARTITION_PARCOURS,
numero=-1,
)
db.session.add(partition)
db.session.flush() # pour avoir un id
flash(f"Partition Parcours créée.")
for parcour in self.parcours:
if parcour.code:
group = GroupDescr.query.filter_by(
partition_id=partition.id, group_name=parcour.code
).first()
if not group:
partition.groups.append(GroupDescr(group_name=parcour.code))
db.session.commit()
def update_inscriptions_parcours_from_groups(self) -> None:
"""Met à jour les inscriptions dans les parcours du semestres en
fonction des groupes de parcours.
Les groupes de parcours sont ceux de la partition scu.PARTITION_PARCOURS
et leur nom est le code du parcours (eg "Cyber").
"""
partition = Partition.query.filter_by(
formsemestre_id=self.id, partition_name=scu.PARTITION_PARCOURS
).first()
if partition is None: # pas de partition de parcours
return
# Efface les inscriptions aux parcours:
db.session.execute(
text(
"""UPDATE notes_formsemestre_inscription
SET parcour_id=NULL
WHERE formsemestre_id=:formsemestre_id
"""
),
{
"formsemestre_id": self.id,
},
)
# Inscrit les étudiants des groupes de parcours:
for group in partition.groups:
query = ApcParcours.query.filter_by(code=group.group_name)
if query.count() != 1:
log(
f"""update_inscriptions_parcours_from_groups: {
query.count()} parcours with code {group.group_name}"""
)
continue
parcour = query.first()
db.session.execute(
text(
"""UPDATE notes_formsemestre_inscription ins
SET parcour_id=:parcour_id
FROM group_membership gm
WHERE formsemestre_id=:formsemestre_id
AND gm.etudid = ins.etudid
AND gm.group_id = :group_id
"""
),
{
"formsemestre_id": self.id,
"parcour_id": parcour.id,
"group_id": group.id,
},
)
db.session.commit()
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table(

View File

@ -141,7 +141,9 @@ def do_formsemestre_list(*a, **kw):
def _formsemestre_enrich(sem):
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris).
XXX obsolete: préférer formsemestre.to_dict() ou, mieux, les méthodes de FormSemestre.
"""
# imports ici pour eviter refs circulaires
from app.scodoc import sco_formsemestre_edit

View File

@ -548,7 +548,8 @@ def do_formsemestre_createwithmodules(edit=False):
"allowed_values": [
str(parcour.id) for parcour in ref_comp.parcours
],
"explanation": "Parcours proposés dans ce semestre.",
"explanation": """Parcours proposés dans ce semestre.
S'il s'agit d'un semestre de "tronc commun", ne pas indiquer de parcours.""",
},
)
]
@ -905,7 +906,7 @@ def do_formsemestre_createwithmodules(edit=False):
modargs, formsemestre_id=formsemestre_id
)
mod = sco_edit_module.module_list({"module_id": module_id})[0]
# --- Assocation des parcours
# --- Association des parcours
formsemestre = FormSemestre.query.get(formsemestre_id)
if "parcours" in tf[2]:
formsemestre.parcours = [
@ -914,6 +915,8 @@ def do_formsemestre_createwithmodules(edit=False):
]
db.session.add(formsemestre)
db.session.commit()
# --- Crée ou met à jour les groupes de parcours BUT
formsemestre.setup_parcours_groups()
# --- Fin
if edit:
if msg:

View File

@ -581,14 +581,20 @@ def formsemestre_recap_parcours_table(
else:
pm = plusminus % sem["formsemestre_id"]
H.append(
'<td class="rcp_type_sem" style="background-color:%s;">%s%s</td>'
% (bgcolor, num_sem, pm)
inscr = formsemestre.etuds_inscriptions.get(etudid)
parcours_name = (
f' <span class="code_parcours">{inscr.parcour.code}</span>'
if (inscr and inscr.parcour)
else ""
)
H.append('<td class="datedebut">%(mois_debut)s</td>' % sem)
H.append(
'<td class="rcp_titre_sem"><a class="formsemestre_status_link" href="%sformsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="Bulletin de notes">%s</a></td>'
% (a_url, sem["formsemestre_id"], etudid, sem["titreannee"])
f"""
<td class="rcp_type_sem" style="background-color:{bgcolor};">{num_sem}{pm}</td>
<td class="datedebut">{sem['mois_debut']}</td>
<td class="rcp_titre_sem"><a class="formsemestre_status_link"
href="{a_url}formsemestre_bulletinetud?formsemestre_id={formsemestre.id}&etudid={etudid}"
title="Bulletin de notes">{formsemestre.titre_annee()}{parcours_name}</a></td>
"""
)
if decision_sem:
H.append('<td class="rcp_dec">%s</td>' % decision_sem["code"])

View File

@ -107,14 +107,19 @@ def get_group(group_id: int):
return r[0]
def group_delete(group, force=False):
def group_delete(group_id: int):
"""Delete a group."""
# if not group['group_name'] and not force:
# raise ValueError('cannot suppress this group')
# remove memberships:
ndb.SimpleQuery("DELETE FROM group_membership WHERE group_id=%(group_id)s", group)
ndb.SimpleQuery(
"DELETE FROM group_membership WHERE group_id=%(group_id)s",
{"group_id": group_id},
)
# delete group:
ndb.SimpleQuery("DELETE FROM group_descr WHERE id=%(group_id)s", group)
ndb.SimpleQuery(
"DELETE FROM group_descr WHERE id=%(group_id)s", {"group_id": group_id}
)
def get_partition(partition_id):
@ -690,7 +695,12 @@ def change_etud_group_in_partition(etudid, group_id, partition=None):
% (formsemestre_id, partition["partition_name"], group["group_name"]),
)
cnx.commit()
# 4- invalidate cache
# 5- Update parcours
formsemestre = FormSemestre.query.get(formsemestre_id)
formsemestre.update_inscriptions_parcours_from_groups()
# 6- invalidate cache
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
) # > change etud group
@ -720,7 +730,7 @@ def setGroups(
return response
partition = get_partition(partition_id)
if not partition["group_editable"]:
if not partition["groups_editable"]:
msg = "setGroups: partition non editable"
log(msg)
return xml_error(msg, code=403)
@ -796,6 +806,10 @@ def setGroups(
for etudid in fs[1:-1]:
change_etud_group_in_partition(etudid, group_id, partition)
# Update parcours
formsemestre = FormSemestre.query.get(formsemestre_id)
formsemestre.update_inscriptions_parcours_from_groups()
data = (
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
)
@ -835,21 +849,18 @@ def delete_group(group_id, partition_id=None):
affectation aux groupes)
partition_id est optionnel et ne sert que pour verifier que le groupe
est bien dans cette partition.
S'il s'agit d'un groupe de parcours, affecte l'inscription des étudiants aux parcours.
"""
group = get_group(group_id)
group = GroupDescr.query.get_or_404(group_id)
if partition_id:
if partition_id != group["partition_id"]:
if partition_id != group.partition_id:
raise ValueError("inconsistent partition/group")
else:
partition_id = group["partition_id"]
partition = get_partition(partition_id)
if not sco_permissions_check.can_change_groups(partition["formsemestre_id"]):
if not sco_permissions_check.can_change_groups(group.partition.formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
log(
"delete_group: group_id=%s group_name=%s partition_name=%s"
% (group_id, group["group_name"], partition["partition_name"])
)
group_delete(group)
log(f"delete_group: group={group:r} partition={group.partition}")
formsemestre = group.partition.formsemestre
group_delete(group.id)
formsemestre.update_inscriptions_parcours_from_groups()
def partition_create(
@ -1097,11 +1108,14 @@ def partition_set_attr(partition_id, attr, value):
def partition_delete(partition_id, force=False, redirect=1, dialog_confirmed=False):
"""Suppress a partition (and all groups within).
default partition cannot be suppressed (unless force)"""
The default partition cannot be suppressed (unless force).
Si la partition de parcours est supprimée, les étudiants sont désinscrits des parcours.
"""
partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"]
if not sco_permissions_check.can_change_groups(formsemestre_id):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if not partition["partition_name"] and not force:
raise ValueError("cannot suppress this partition")
@ -1127,10 +1141,12 @@ def partition_delete(partition_id, force=False, redirect=1, dialog_confirmed=Fal
log("partition_delete: partition_id=%s" % partition_id)
# 1- groups
for group in groups:
group_delete(group, force=force)
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(
@ -1214,7 +1230,8 @@ def partition_rename(partition_id):
"default": partition["partition_name"],
"allow_null": False,
"size": 12,
"validator": lambda val, _: len(val) < SHORT_STR_LEN,
"validator": lambda val, _: (len(val) < SHORT_STR_LEN)
and (val != scu.PARTITION_PARCOURS),
},
),
),
@ -1246,6 +1263,8 @@ def partition_set_name(partition_id, partition_name, redirect=1):
partition = get_partition(partition_id)
if partition["partition_name"] is None:
raise ValueError("can't set a name to default partition")
if partition_name == scu.PARTITION_PARCOURS:
raise ScoValueError(f"nom de partition {scu.PARTITION_PARCOURS} réservé.")
formsemestre_id = partition["formsemestre_id"]
# check unicity
@ -1415,7 +1434,7 @@ def groups_auto_repartition(partition_id=None):
group_names = sorted(set([x.strip() for x in groupNames.split(",")]))
# Détruit les groupes existant de cette partition
for old_group in get_partition_groups(partition):
group_delete(old_group)
group_delete(old_group["group_id"])
# Crée les nouveaux groupes
group_ids = []
for group_name in group_names:

View File

@ -43,6 +43,7 @@ def affect_groups(partition_id):
formsemestre_id = partition["formsemestre_id"]
if not sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
raise AccessDenied("vous n'avez pas la permission de modifier les groupes")
partition.formsemestre.setup_parcours_groups()
return render_template(
"scolar/affect_groups.html",
sco_header=html_sco_header.sco_header(

View File

@ -2267,6 +2267,14 @@ span.missing_value {
color: red;
}
span.code_parcours {
color: white;
background-color: rgb(254, 95, 246);
padding-left: 4px;
padding-right: 4px;
border-radius: 2px;
}
tr#tf_module_parcours>td {
background-color: rgb(229, 229, 229);
}

View File

@ -918,18 +918,7 @@ def create_partition_parcours(formsemestre_id):
"""Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
avec un groupe par parcours."""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if scu.PARTITION_PARCOURS in (p.partition_name for p in formsemestre.partitions):
flash(f"""Partition "{scu.PARTITION_PARCOURS}" déjà existante""")
else:
partition_id = sco_groups.partition_create(
formsemestre_id, partition_name=scu.PARTITION_PARCOURS, redirect=False
)
n = 0
for parcour in formsemestre.parcours:
if parcour.code:
_ = sco_groups.create_group(partition_id, group_name=parcour.code)
n += 1
flash(f"Partition Parcours créée avec {n} groupes.")
formsemestre.setup_parcours_groups()
return flask.redirect(
url_for(
"scolar.edit_partition_form",