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, ApcCompetence,
ApcSituationPro, ApcSituationPro,
ApcAppCritique, ApcAppCritique,
ApcParcours,
) )
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig

View File

@ -5,6 +5,7 @@
import datetime import datetime
from functools import cached_property from functools import cached_property
from flask import flash
import flask_sqlalchemy import flask_sqlalchemy
from sqlalchemy.sql import text 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 APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
from app.models.groups import GroupDescr, Partition
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.models.but_refcomp import ApcParcours from app.models.but_refcomp import ApcParcours
@ -480,6 +482,85 @@ class FormSemestre(db.Model):
"""Map { etudid : inscription } (incluant DEM et DEF)""" """Map { etudid : inscription } (incluant DEM et DEF)"""
return {ins.etud.id: ins for ins in self.inscriptions} 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 # Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
notes_formsemestre_responsables = db.Table( notes_formsemestre_responsables = db.Table(

View File

@ -141,7 +141,9 @@ def do_formsemestre_list(*a, **kw):
def _formsemestre_enrich(sem): 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 # imports ici pour eviter refs circulaires
from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_edit

View File

@ -548,7 +548,8 @@ def do_formsemestre_createwithmodules(edit=False):
"allowed_values": [ "allowed_values": [
str(parcour.id) for parcour in ref_comp.parcours 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 modargs, formsemestre_id=formsemestre_id
) )
mod = sco_edit_module.module_list({"module_id": module_id})[0] mod = sco_edit_module.module_list({"module_id": module_id})[0]
# --- Assocation des parcours # --- Association des parcours
formsemestre = FormSemestre.query.get(formsemestre_id) formsemestre = FormSemestre.query.get(formsemestre_id)
if "parcours" in tf[2]: if "parcours" in tf[2]:
formsemestre.parcours = [ formsemestre.parcours = [
@ -914,6 +915,8 @@ def do_formsemestre_createwithmodules(edit=False):
] ]
db.session.add(formsemestre) db.session.add(formsemestre)
db.session.commit() db.session.commit()
# --- Crée ou met à jour les groupes de parcours BUT
formsemestre.setup_parcours_groups()
# --- Fin # --- Fin
if edit: if edit:
if msg: if msg:

View File

@ -581,14 +581,20 @@ def formsemestre_recap_parcours_table(
else: else:
pm = plusminus % sem["formsemestre_id"] pm = plusminus % sem["formsemestre_id"]
H.append( inscr = formsemestre.etuds_inscriptions.get(etudid)
'<td class="rcp_type_sem" style="background-color:%s;">%s%s</td>' parcours_name = (
% (bgcolor, num_sem, pm) 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( 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>' f"""
% (a_url, sem["formsemestre_id"], etudid, sem["titreannee"]) <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: if decision_sem:
H.append('<td class="rcp_dec">%s</td>' % decision_sem["code"]) 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] return r[0]
def group_delete(group, force=False): def group_delete(group_id: int):
"""Delete a group.""" """Delete a group."""
# if not group['group_name'] and not force: # if not group['group_name'] and not force:
# raise ValueError('cannot suppress this group') # raise ValueError('cannot suppress this group')
# remove memberships: # 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: # 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): 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"]), % (formsemestre_id, partition["partition_name"], group["group_name"]),
) )
cnx.commit() 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( sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id formsemestre_id=formsemestre_id
) # > change etud group ) # > change etud group
@ -720,7 +730,7 @@ def setGroups(
return response return response
partition = get_partition(partition_id) partition = get_partition(partition_id)
if not partition["group_editable"]: if not partition["groups_editable"]:
msg = "setGroups: partition non editable" msg = "setGroups: partition non editable"
log(msg) log(msg)
return xml_error(msg, code=403) return xml_error(msg, code=403)
@ -796,6 +806,10 @@ def setGroups(
for etudid in fs[1:-1]: for etudid in fs[1:-1]:
change_etud_group_in_partition(etudid, group_id, partition) change_etud_group_in_partition(etudid, group_id, partition)
# Update parcours
formsemestre = FormSemestre.query.get(formsemestre_id)
formsemestre.update_inscriptions_parcours_from_groups()
data = ( data = (
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>' '<?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) affectation aux groupes)
partition_id est optionnel et ne sert que pour verifier que le groupe partition_id est optionnel et ne sert que pour verifier que le groupe
est bien dans cette partition. 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:
if partition_id != group["partition_id"]: if partition_id != group.partition_id:
raise ValueError("inconsistent partition/group") raise ValueError("inconsistent partition/group")
else: if not sco_permissions_check.can_change_groups(group.partition.formsemestre_id):
partition_id = group["partition_id"]
partition = get_partition(partition_id)
if not sco_permissions_check.can_change_groups(partition["formsemestre_id"]):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
log( log(f"delete_group: group={group:r} partition={group.partition}")
"delete_group: group_id=%s group_name=%s partition_name=%s" formsemestre = group.partition.formsemestre
% (group_id, group["group_name"], partition["partition_name"]) group_delete(group.id)
) formsemestre.update_inscriptions_parcours_from_groups()
group_delete(group)
def partition_create( 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): def partition_delete(partition_id, force=False, redirect=1, dialog_confirmed=False):
"""Suppress a partition (and all groups within). """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) partition = get_partition(partition_id)
formsemestre_id = partition["formsemestre_id"] formsemestre_id = partition["formsemestre_id"]
if not sco_permissions_check.can_change_groups(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 !") 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: if not partition["partition_name"] and not force:
raise ValueError("cannot suppress this partition") 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) log("partition_delete: partition_id=%s" % partition_id)
# 1- groups # 1- groups
for group in groups: for group in groups:
group_delete(group, force=force) group_delete(group["group_id"])
# 2- partition # 2- partition
partitionEditor.delete(cnx, partition_id) partitionEditor.delete(cnx, partition_id)
formsemestre.update_inscriptions_parcours_from_groups()
# redirect to partition edit page: # redirect to partition edit page:
if redirect: if redirect:
return flask.redirect( return flask.redirect(
@ -1214,7 +1230,8 @@ def partition_rename(partition_id):
"default": partition["partition_name"], "default": partition["partition_name"],
"allow_null": False, "allow_null": False,
"size": 12, "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) partition = get_partition(partition_id)
if partition["partition_name"] is None: if partition["partition_name"] is None:
raise ValueError("can't set a name to default partition") 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"] formsemestre_id = partition["formsemestre_id"]
# check unicity # check unicity
@ -1415,7 +1434,7 @@ def groups_auto_repartition(partition_id=None):
group_names = sorted(set([x.strip() for x in groupNames.split(",")])) group_names = sorted(set([x.strip() for x in groupNames.split(",")]))
# Détruit les groupes existant de cette partition # Détruit les groupes existant de cette partition
for old_group in get_partition_groups(partition): for old_group in get_partition_groups(partition):
group_delete(old_group) group_delete(old_group["group_id"])
# Crée les nouveaux groupes # Crée les nouveaux groupes
group_ids = [] group_ids = []
for group_name in group_names: for group_name in group_names:

View File

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

View File

@ -2267,6 +2267,14 @@ span.missing_value {
color: red; 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 { tr#tf_module_parcours>td {
background-color: rgb(229, 229, 229); 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) """Création d'une partitions nommée "Parcours" (PARTITION_PARCOURS)
avec un groupe par parcours.""" avec un groupe par parcours."""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if scu.PARTITION_PARCOURS in (p.partition_name for p in formsemestre.partitions): formsemestre.setup_parcours_groups()
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.")
return flask.redirect( return flask.redirect(
url_for( url_for(
"scolar.edit_partition_form", "scolar.edit_partition_form",