Début de travaux pour améliorer le backend groupes/partitions.

This commit is contained in:
Emmanuel Viennet 2023-07-05 19:15:33 +02:00
parent 90c1454b21
commit 0824598aa4
7 changed files with 91 additions and 78 deletions

View File

@ -12,6 +12,7 @@ from operator import attrgetter
from flask import g, request from flask import g, request
from flask_json import as_json from flask_json import as_json
from flask_login import login_required from flask_login import login_required
from sqlalchemy.exc import IntegrityError
import app import app
from app import db, log from app import db, log
@ -23,6 +24,7 @@ from app.models import GroupDescr, Partition, Scolog
from app.models.groups import group_membership from app.models.groups import group_membership
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -182,10 +184,12 @@ def set_etud_group(etudid: int, group_id: int):
if etud.id not in {e.id for e in group.partition.formsemestre.etuds}: if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
return json_error(404, "etud non inscrit au formsemestre du groupe") return json_error(404, "etud non inscrit au formsemestre du groupe")
sco_groups.change_etud_group_in_partition( try:
etudid, group_id, group.partition.to_dict() sco_groups.change_etud_group_in_partition(etudid, group)
) except ScoValueError as exc:
return json_error(404, exc.args[0])
except IntegrityError:
return json_error(404, "échec de l'enregistrement")
return {"group_id": group_id, "etudid": etudid} return {"group_id": group_id, "etudid": etudid}

View File

@ -8,11 +8,13 @@
"""ScoDoc models: Groups & partitions """ScoDoc models: Groups & partitions
""" """
from operator import attrgetter from operator import attrgetter
from sqlalchemy.exc import IntegrityError
from app import db from app import db
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models import GROUPNAME_STR_LEN from app.models import GROUPNAME_STR_LEN
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError
class Partition(db.Model): class Partition(db.Model):
@ -117,6 +119,40 @@ class Partition(db.Model):
.first() .first()
) )
def set_etud_group(self, etudid: int, group: "GroupDescr"):
"""Affect etudid to group_id in given partition.
Raises IntegrityError si conflit,
or ValueError si ce group_id n'est pas dans cette partition
ou que l'étudiant n'est pas inscrit au semestre.
"""
if not group.id in (g.id for g in self.groups):
raise ScoValueError(
f"""Le groupe {group.id} n'est pas dans la partition {self.partition_name or "tous"}"""
)
if etudid not in (e.id for e in self.formsemestre.etuds):
raise ScoValueError(
f"etudiant {etudid} non inscrit au formsemestre du groupe {group.id}"
)
try:
existing_row = (
db.session.query(group_membership)
.filter_by(etudid=etudid)
.join(GroupDescr)
.filter_by(partition_id=self.id)
.first()
)
if existing_row:
existing_row.update({"group_id": group.id})
else:
new_row = group_membership.insert().values(
etudid=etudid, group_id=group.id
)
db.session.execute(new_row)
db.session.commit()
except IntegrityError:
db.session.rollback()
raise
class GroupDescr(db.Model): class GroupDescr(db.Model):
"""Description d'un groupe d'une partition""" """Description d'un groupe d'une partition"""

View File

@ -546,6 +546,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
ue = UniteEns.query.get(ue_id) ue = UniteEns.query.get(ue_id)
flash(f"UE créée (code {ue.ue_code})") flash(f"UE créée (code {ue.ue_code})")
else: else:
if not tf[2]["numero"]:
tf[2]["numero"] = 0
do_ue_edit(tf[2]) do_ue_edit(tf[2])
flash("UE modifiée") flash("UE modifiée")

View File

@ -48,9 +48,9 @@ from sqlalchemy.sql import text
from app import db from app import db
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre, Identite from app.models import FormSemestre, Identite, Scolog
from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
from app.models.groups import GroupDescr, Partition from app.models.groups import GroupDescr, Partition, group_membership
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app import log, cache from app import log, cache
@ -94,7 +94,7 @@ groupEditor = ndb.EditableTable(
group_list = groupEditor.list group_list = groupEditor.list
def get_group(group_id: int) -> dict: def get_group(group_id: int) -> dict: # OBSOLETE !
"""Returns group object, with partition""" """Returns group object, with partition"""
r = ndb.SimpleDictFetch( r = ndb.SimpleDictFetch(
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
@ -124,7 +124,7 @@ def group_delete(group_id: int):
) )
def get_partition(partition_id): def get_partition(partition_id): # OBSOLETE
r = ndb.SimpleDictFetch( r = ndb.SimpleDictFetch(
"""SELECT p.id AS partition_id, p.* """SELECT p.id AS partition_id, p.*
FROM partition p FROM partition p
@ -200,7 +200,7 @@ def get_formsemestre_etuds_groups(formsemestre_id: int) -> dict:
return d return d
def get_partition_groups(partition): def get_partition_groups(partition): # OBSOLETE !
"""List of groups in this partition (list of dicts). """List of groups in this partition (list of dicts).
Some groups may be empty.""" Some groups may be empty."""
return ndb.SimpleDictFetch( return ndb.SimpleDictFetch(
@ -637,7 +637,7 @@ def _comp_etud_origin(etud: dict, cur_formsemestre: FormSemestre):
return "" # parcours normal, ne le signale pas return "" # parcours normal, ne le signale pas
def set_group(etudid: int, group_id: int) -> bool: def set_group(etudid: int, group_id: int) -> bool: # OBSOLETE !
"""Inscrit l'étudiant au groupe. """Inscrit l'étudiant au groupe.
Return True if ok, False si deja inscrit. Return True if ok, False si deja inscrit.
Warning: Warning:
@ -664,55 +664,31 @@ def set_group(etudid: int, group_id: int) -> bool:
return True return True
def change_etud_group_in_partition(etudid: int, group_id: int, partition: dict = None): def change_etud_group_in_partition(etudid: int, group: GroupDescr):
"""Inscrit etud au groupe de cette partition, """Inscrit etud au groupe
et le desinscrit d'autres groupes de cette partition. (et le desinscrit d'autres groupes de cette partition.)
""" """
log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id)) log(f"change_etud_group_in_partition: etudid={etudid} group={group}")
# 0- La partition
group = get_group(group_id)
if partition:
# verifie que le groupe est bien dans cette partition:
if group["partition_id"] != partition["partition_id"]:
raise ValueError(
"inconsistent group/partition (group_id=%s, partition_id=%s)"
% (group_id, partition["partition_id"])
)
else:
partition = get_partition(group["partition_id"])
# 1- Supprime membership dans cette partition
ndb.SimpleQuery(
"""DELETE FROM group_membership gm
WHERE EXISTS
(SELECT 1 FROM group_descr gd
WHERE gm.etudid = %(etudid)s
AND gm.group_id = gd.id
AND gd.partition_id = %(partition_id)s)
""",
{"etudid": etudid, "partition_id": partition["partition_id"]},
)
# 2- associe au nouveau groupe
set_group(etudid, group_id)
# 3- log group.partition.set_etud_group(etudid, group)
formsemestre_id = partition["formsemestre_id"]
cnx = ndb.GetDBConnexion() # - log
logdb( formsemestre: FormSemestre = group.partition.formsemestre
cnx, Scolog.logdb(
method="changeGroup", method="changeGroup",
etudid=etudid, etudid=etudid,
msg="formsemestre_id=%s,partition_name=%s, group_name=%s" msg=f"""formsemestre_id={formsemestre.id}, partition_name={
% (formsemestre_id, partition["partition_name"], group["group_name"]), group.partition.partition_name or ""}, group_name={group.group_name or ""}""",
commit=True,
) )
cnx.commit()
# 5- Update parcours # - Update parcours
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) if group.partition.partition_name == scu.PARTITION_PARCOURS:
formsemestre.update_inscriptions_parcours_from_groups() formsemestre.update_inscriptions_parcours_from_groups()
# 6- invalidate cache # - invalidate cache
sco_cache.invalidate_formsemestre( sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id formsemestre_id=formsemestre.id
) # > change etud group ) # > change etud group
@ -769,7 +745,7 @@ def setGroups(
except ValueError: except ValueError:
log(f"setGroups: ignoring invalid group_id={group_id}") log(f"setGroups: ignoring invalid group_id={group_id}")
continue continue
group = get_group(group_id) group: GroupDescr = GroupDescr.query.get(group_id)
# Anciens membres du groupe: # Anciens membres du groupe:
old_members = get_group_members(group_id) old_members = get_group_members(group_id)
old_members_set = set([x["etudid"] for x in old_members]) old_members_set = set([x["etudid"] for x in old_members])
@ -783,7 +759,7 @@ def setGroups(
if (etudid not in etud_groups) or ( if (etudid not in etud_groups) or (
group_id != etud_groups[etudid].get(partition_id, "") group_id != etud_groups[etudid].get(partition_id, "")
): # pas le meme groupe qu'actuel ): # pas le meme groupe qu'actuel
change_etud_group_in_partition(etudid, group_id, partition) change_etud_group_in_partition(etudid, group)
# Retire les anciens membres: # Retire les anciens membres:
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@ -819,7 +795,7 @@ def setGroups(
return xml_error(msg, code=404) return xml_error(msg, code=404)
# Place dans ce groupe les etudiants indiqués: # Place dans ce groupe les etudiants indiqués:
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)
# Update parcours # Update parcours
formsemestre.update_inscriptions_parcours_from_groups() formsemestre.update_inscriptions_parcours_from_groups()
@ -1460,10 +1436,10 @@ def groups_auto_repartition(partition_id=None):
for old_group in get_partition_groups(partition): for old_group in get_partition_groups(partition):
group_delete(old_group["group_id"]) group_delete(old_group["group_id"])
# Crée les nouveaux groupes # Crée les nouveaux groupes
group_ids = [] groups = []
for group_name in group_names: for group_name in group_names:
if group_name.strip(): if group_name.strip():
group_ids.append(create_group(partition_id, group_name).id) groups.append(create_group(partition_id, group_name))
# #
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre) nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
identdict = nt.identdict identdict = nt.identdict
@ -1481,16 +1457,16 @@ def groups_auto_repartition(partition_id=None):
# affect aux groupes: # affect aux groupes:
n = len(identdict) n = len(identdict)
igroup = 0 igroup = 0
nbgroups = len(group_ids) nbgroups = len(groups)
while n > 0: while n > 0:
for civilite in civilites: for civilite in civilites:
if len(listes[civilite]): if len(listes[civilite]):
n -= 1 n -= 1
etudid = listes[civilite].pop()[1] etudid = listes[civilite].pop()[1]
group_id = group_ids[igroup] group = groups[igroup]
igroup = (igroup + 1) % nbgroups igroup = (igroup + 1) % nbgroups
change_etud_group_in_partition(etudid, group_id, partition) change_etud_group_in_partition(etudid, group)
log("%s in group %s" % (etudid, group_id)) log("%s in group %s" % (etudid, group.id))
return flask.redirect(dest_url) return flask.redirect(dest_url)
@ -1520,10 +1496,11 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
Si la partition existe déjà, ses groupes sont mis à jour (les groupes devenant Si la partition existe déjà, ses groupes sont mis à jour (les groupes devenant
vides ne sont pas supprimés). vides ne sont pas supprimés).
""" """
# A RE-ECRIRE pour utiliser les modèles.
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
partition_name = str(partition_name) partition_name = str(partition_name)
log("create_etapes_partition(%s)" % formsemestre_id) log(f"create_etapes_partition({formsemestre_id})")
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
args={"formsemestre_id": formsemestre_id} args={"formsemestre_id": formsemestre_id}
) )
@ -1542,20 +1519,17 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
pid = partition_create( pid = partition_create(
formsemestre_id, partition_name=partition_name, redirect=False formsemestre_id, partition_name=partition_name, redirect=False
) )
partition = get_partition(pid) partition: Partition = Partition.query.get(pid)
groups = get_partition_groups(partition) groups = partition.groups
groups_by_names = {g["group_name"]: g for g in groups} groups_by_names = {g["group_name"]: g for g in groups}
for etape in etapes: for etape in etapes:
if not (etape in groups_by_names): if etape not in groups_by_names:
new_group = create_group(pid, etape) new_group = create_group(pid, etape)
g = get_group(new_group.id) # XXX transition: recupere old style dict groups_by_names[etape] = new_group
groups_by_names[etape] = g
# Place les etudiants dans les groupes # Place les etudiants dans les groupes
for i in ins: for i in ins:
if i["etape"]: if i["etape"]:
change_etud_group_in_partition( change_etud_group_in_partition(i["etudid"], groups_by_names[i["etape"]])
i["etudid"], groups_by_names[i["etape"]]["group_id"], partition
)
def do_evaluation_listeetuds_groups( def do_evaluation_listeetuds_groups(

View File

@ -723,7 +723,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
group = GroupDescr.query.get(group_id) group = GroupDescr.query.get(group_id)
if group.partition.groups_editable: if group.partition.groups_editable:
sco_groups.change_etud_group_in_partition( sco_groups.change_etud_group_in_partition(
args["etudid"], group_id args["etudid"], group
) )
else: else:
log("scolars_import_admission: partition non editable") log("scolars_import_admission: partition non editable")

View File

@ -36,13 +36,12 @@ from flask import url_for, g, request
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import log from app import log
from app.models import Formation, FormSemestre from app.models import Formation, FormSemestre, GroupDescr
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_groups from app.scodoc import sco_groups
@ -177,6 +176,7 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
(la liste doit avoir été vérifiée au préalable) (la liste doit avoir été vérifiée au préalable)
En option: inscrit aux mêmes groupes que dans le semestre origine En option: inscrit aux mêmes groupes que dans le semestre origine
""" """
# TODO à ré-écrire pour utiliser le smodèle, notamment GroupDescr
formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"]) formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"])
formsemestre.setup_parcours_groups() formsemestre.setup_parcours_groups()
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}") log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
@ -220,11 +220,8 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
# Inscrit aux groupes # Inscrit aux groupes
for partition_group in partition_groups: for partition_group in partition_groups:
sco_groups.change_etud_group_in_partition( group: GroupDescr = GroupDescr.query.get(partition_group["group_id"])
etudid, sco_groups.change_etud_group_in_partition(etudid, group)
partition_group["group_id"],
partition_group,
)
def do_desinscrit(sem, etudids): def do_desinscrit(sem, etudids):

View File

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