BUT: Partition de parcours et inscriptions
This commit is contained in:
parent
5af4b5bed6
commit
45449f0465
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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"])
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user