forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -573,6 +573,17 @@ class FormSemestre(db.Model):
|
||||
user
|
||||
)
|
||||
|
||||
def can_change_groups(self, user: User = None) -> bool:
|
||||
"""Vrai si l'utilisateur (par def. current) peut changer les groupes dans
|
||||
ce semestre: vérifie permission et verrouillage.
|
||||
"""
|
||||
if not self.etat:
|
||||
return False # semestre verrouillé
|
||||
user = user or current_user
|
||||
if user.has_permission(Permission.ScoEtudChangeGroups):
|
||||
return True # typiquement admin, chef dept
|
||||
return self.est_responsable(user)
|
||||
|
||||
def can_edit_jury(self, user: User = None):
|
||||
"""Vrai si utilisateur (par def. current) peut saisir decision de jury
|
||||
dans ce semestre: vérifie permission et verrouillage.
|
||||
|
@ -10,11 +10,11 @@
|
||||
from operator import attrgetter
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import GROUPNAME_STR_LEN
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
|
||||
|
||||
class Partition(db.Model):
|
||||
@ -119,7 +119,7 @@ class Partition(db.Model):
|
||||
.first()
|
||||
)
|
||||
|
||||
def set_etud_group(self, etudid: int, group: "GroupDescr") -> bool:
|
||||
def set_etud_group(self, etud: "Identite", group: "GroupDescr") -> bool:
|
||||
"""Affect etudid to group_id in given partition.
|
||||
Raises IntegrityError si conflit,
|
||||
or ValueError si ce group_id n'est pas dans cette partition
|
||||
@ -128,36 +128,37 @@ class Partition(db.Model):
|
||||
"""
|
||||
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"}"""
|
||||
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):
|
||||
if etud.id not in (e.id for e in self.formsemestre.etuds):
|
||||
raise ScoValueError(
|
||||
f"etudiant {etudid} non inscrit au formsemestre du groupe {group.id}"
|
||||
f"""étudiant {etud.nomprenom} non inscrit au formsemestre du groupe {
|
||||
group.group_name}"""
|
||||
)
|
||||
try:
|
||||
existing_row = (
|
||||
db.session.query(group_membership)
|
||||
.filter_by(etudid=etudid)
|
||||
.filter_by(etudid=etud.id)
|
||||
.join(GroupDescr)
|
||||
.filter_by(partition_id=self.id)
|
||||
.first()
|
||||
)
|
||||
existing_group_id = existing_row[1]
|
||||
if existing_row:
|
||||
existing_group_id = existing_row[1]
|
||||
if group.id == existing_group_id:
|
||||
return False
|
||||
update_row = (
|
||||
group_membership.update()
|
||||
.where(
|
||||
group_membership.c.etudid == etudid,
|
||||
group_membership.c.group_id == existing_group_id,
|
||||
)
|
||||
.values(group_id=group.id)
|
||||
)
|
||||
db.session.execute(update_row)
|
||||
# Fait le changement avec l'ORM sinon risque élevé de blocage
|
||||
existing_group = GroupDescr.query.get(existing_group_id)
|
||||
db.session.commit()
|
||||
group.etuds.append(etud)
|
||||
existing_group.etuds.remove(etud)
|
||||
db.session.add(etud)
|
||||
db.session.add(existing_group)
|
||||
db.session.add(group)
|
||||
else:
|
||||
new_row = group_membership.insert().values(
|
||||
etudid=etudid, group_id=group.id
|
||||
etudid=etud.id, group_id=group.id
|
||||
)
|
||||
db.session.execute(new_row)
|
||||
db.session.commit()
|
||||
@ -166,6 +167,33 @@ class Partition(db.Model):
|
||||
raise
|
||||
return True
|
||||
|
||||
def create_group(self, group_name="", default=False) -> "GroupDescr":
|
||||
"Crée un groupe dans cette partition"
|
||||
if not self.formsemestre.can_change_groups():
|
||||
raise AccessDenied(
|
||||
"""Vous n'avez pas le droit d'effectuer cette opération,
|
||||
ou bien le semestre est verrouillé !"""
|
||||
)
|
||||
if group_name:
|
||||
group_name = group_name.strip()
|
||||
if not group_name and not default:
|
||||
raise ValueError("invalid group name: ()")
|
||||
if not GroupDescr.check_name(self, group_name, default=default):
|
||||
raise ScoValueError(
|
||||
f"Le groupe {group_name} existe déjà dans cette partition"
|
||||
)
|
||||
numeros = [g.numero if g.numero is not None else 0 for g in self.groups]
|
||||
if len(numeros) > 0:
|
||||
new_numero = max(numeros) + 1
|
||||
else:
|
||||
new_numero = 0
|
||||
group = GroupDescr(partition=self, group_name=group_name, numero=new_numero)
|
||||
db.session.add(group)
|
||||
db.session.commit()
|
||||
log(f"create_group: created group_id={group.id}")
|
||||
#
|
||||
return group
|
||||
|
||||
|
||||
class GroupDescr(db.Model):
|
||||
"""Description d'un groupe d'une partition"""
|
||||
|
@ -34,7 +34,6 @@ Optimisation possible:
|
||||
|
||||
"""
|
||||
import collections
|
||||
import operator
|
||||
import time
|
||||
|
||||
from xml.etree import ElementTree
|
||||
@ -45,7 +44,7 @@ from flask import g, request
|
||||
from flask import url_for, make_response
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from app import db
|
||||
from app import cache, db, log
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import FormSemestre, Identite, Scolog
|
||||
@ -53,7 +52,6 @@ from app.models import GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||
from app.models.groups import GroupDescr, Partition, group_membership
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log, cache
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
@ -669,7 +667,8 @@ def change_etud_group_in_partition(etudid: int, group: GroupDescr) -> bool:
|
||||
(et le désinscrit d'autres groupes de cette partition)
|
||||
Return True si changement, False s'il était déjà dans ce groupe.
|
||||
"""
|
||||
if not group.partition.set_etud_group(etudid, group):
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
if not group.partition.set_etud_group(etud, group):
|
||||
return # pas de changement
|
||||
|
||||
# - log
|
||||
@ -706,7 +705,6 @@ def setGroups(
|
||||
|
||||
Ne peux pas modifier les groupes des partitions non éditables.
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
def xml_error(msg, code=404):
|
||||
data = (
|
||||
@ -716,26 +714,27 @@ def setGroups(
|
||||
response.headers["Content-Type"] = scu.XML_MIMETYPE
|
||||
return response
|
||||
|
||||
partition = get_partition(partition_id)
|
||||
if not partition["groups_editable"] and (groupsToCreate or groupsToDelete):
|
||||
partition: Partition = Partition.query.get(partition_id)
|
||||
if not partition.groups_editable and (groupsToCreate or groupsToDelete):
|
||||
msg = "setGroups: partition non editable"
|
||||
log(msg)
|
||||
return xml_error(msg, code=403)
|
||||
formsemestre_id = partition["formsemestre_id"]
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if not sco_permissions_check.can_change_groups(formsemestre_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 !")
|
||||
log("***setGroups: partition_id=%s" % partition_id)
|
||||
log("groupsLists=%s" % groupsLists)
|
||||
log("groupsToCreate=%s" % groupsToCreate)
|
||||
log("groupsToDelete=%s" % groupsToDelete)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
if not sem["etat"]:
|
||||
|
||||
if not partition.formsemestre.etat:
|
||||
raise AccessDenied("Modification impossible: semestre verrouillé")
|
||||
|
||||
groupsToDelete = [g for g in groupsToDelete.split(";") if g]
|
||||
|
||||
etud_groups = formsemestre_get_etud_groupnames(formsemestre_id, attr="group_id")
|
||||
etud_groups = formsemestre_get_etud_groupnames(
|
||||
partition.formsemestre.id, attr="group_id"
|
||||
)
|
||||
for line in groupsLists.split("\n"): # for each group_id (one per line)
|
||||
fs = line.split(";")
|
||||
group_id = fs[0].strip()
|
||||
@ -748,15 +747,13 @@ def setGroups(
|
||||
continue
|
||||
group: GroupDescr = GroupDescr.query.get(group_id)
|
||||
# Anciens membres du groupe:
|
||||
old_members = get_group_members(group_id)
|
||||
old_members_set = set([x["etudid"] for x in old_members])
|
||||
old_members_set = {etud.id for etud in group.etuds}
|
||||
# Place dans ce groupe les etudiants indiqués:
|
||||
for etudid_str in fs[1:-1]:
|
||||
etudid = int(etudid_str)
|
||||
if etudid in old_members_set:
|
||||
old_members_set.remove(
|
||||
etudid
|
||||
) # a nouveau dans ce groupe, pas besoin de l'enlever
|
||||
# était dans ce groupe, l'enlever
|
||||
old_members_set.remove(etudid)
|
||||
if (etudid not in etud_groups) or (
|
||||
group_id != etud_groups[etudid].get(partition_id, "")
|
||||
): # pas le meme groupe qu'actuel
|
||||
@ -765,7 +762,6 @@ def setGroups(
|
||||
cnx = ndb.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
for etudid in old_members_set:
|
||||
log("removing %s from group %s" % (etudid, group_id))
|
||||
ndb.SimpleQuery(
|
||||
"DELETE FROM group_membership WHERE etudid=%(etudid)s and group_id=%(group_id)s",
|
||||
{"etudid": etudid, "group_id": group_id},
|
||||
@ -775,8 +771,8 @@ def setGroups(
|
||||
cnx,
|
||||
method="removeFromGroup",
|
||||
etudid=etudid,
|
||||
msg="formsemestre_id=%s,partition_name=%s, group_name=%s"
|
||||
% (formsemestre_id, partition["partition_name"], group["group_name"]),
|
||||
msg=f"""formsemestre_id={partition.formsemestre.id},partition_name={
|
||||
partition.partition_name}, group_name={group.group_name}""",
|
||||
)
|
||||
|
||||
# Supprime les groupes indiqués comme supprimés:
|
||||
@ -799,7 +795,7 @@ def setGroups(
|
||||
change_etud_group_in_partition(etudid, group.id)
|
||||
|
||||
# Update parcours
|
||||
formsemestre.update_inscriptions_parcours_from_groups()
|
||||
partition.formsemestre.update_inscriptions_parcours_from_groups()
|
||||
|
||||
data = (
|
||||
'<?xml version="1.0" encoding="utf-8"?><response>Groupes enregistrés</response>'
|
||||
@ -812,6 +808,7 @@ def setGroups(
|
||||
def create_group(partition_id, group_name="", default=False) -> GroupDescr:
|
||||
"""Create a new group in this partition.
|
||||
If default, create default partition (with no name)
|
||||
Obsolete: utiliser Partition.create_group
|
||||
"""
|
||||
partition = Partition.query.get_or_404(partition_id)
|
||||
if not sco_permissions_check.can_change_groups(partition.formsemestre_id):
|
||||
@ -833,7 +830,7 @@ def create_group(partition_id, group_name="", default=False) -> GroupDescr:
|
||||
group = GroupDescr(partition=partition, group_name=group_name, numero=new_numero)
|
||||
db.session.add(group)
|
||||
db.session.commit()
|
||||
log("create_group: created group_id={group.id}")
|
||||
log(f"create_group: created group_id={group.id}")
|
||||
#
|
||||
return group
|
||||
|
||||
@ -1377,11 +1374,11 @@ def groups_auto_repartition(partition_id=None):
|
||||
"""Reparti les etudiants dans des groupes dans une partition, en respectant le niveau
|
||||
et la mixité.
|
||||
"""
|
||||
partition = get_partition(partition_id)
|
||||
if not partition["groups_editable"]:
|
||||
partition: Partition = Partition.query.get_or_404(partition_id)
|
||||
if not partition.groups_editable:
|
||||
raise AccessDenied("Partition non éditable")
|
||||
formsemestre_id = partition["formsemestre_id"]
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
formsemestre_id = partition.formsemestre_id
|
||||
formsemestre = partition.formsemestre
|
||||
# renvoie sur page édition groupes
|
||||
dest_url = url_for(
|
||||
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
||||
@ -1404,12 +1401,14 @@ def groups_auto_repartition(partition_id=None):
|
||||
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Répartition des groupes"),
|
||||
"<h2>Répartition des groupes de %s</h2>" % partition["partition_name"],
|
||||
f"<p>Semestre {formsemestre.titre_annee()}</p>",
|
||||
"""<p class="help">Les groupes existants seront <b>effacés</b> et remplacés par
|
||||
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>""",
|
||||
chaque étudiant) et de maximiser la mixité de chaque groupe.
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
|
||||
tf = TrivialFormulator(
|
||||
@ -1429,23 +1428,24 @@ def groups_auto_repartition(partition_id=None):
|
||||
# form submission
|
||||
log(
|
||||
"groups_auto_repartition( partition_id=%s partition_name=%s"
|
||||
% (partition_id, partition["partition_name"])
|
||||
% (partition_id, partition.partition_name)
|
||||
)
|
||||
groupNames = tf[2]["groupNames"]
|
||||
group_names = sorted(set([x.strip() for x in groupNames.split(",")]))
|
||||
group_names = sorted({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_id"])
|
||||
for group in partition.groups:
|
||||
db.session.delete(group)
|
||||
db.session.commit()
|
||||
# Crée les nouveaux groupes
|
||||
groups = []
|
||||
for group_name in group_names:
|
||||
if group_name.strip():
|
||||
groups.append(create_group(partition_id, group_name))
|
||||
groups.append(partition.create_group(group_name))
|
||||
#
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
identdict = nt.identdict
|
||||
# build: { civilite : liste etudids trie par niveau croissant }
|
||||
civilites = set([x["civilite"] for x in identdict.values()])
|
||||
civilites = {x["civilite"] for x in identdict.values()}
|
||||
listes = {}
|
||||
for civilite in civilites:
|
||||
listes[civilite] = [
|
||||
@ -1460,14 +1460,17 @@ def groups_auto_repartition(partition_id=None):
|
||||
igroup = 0
|
||||
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("%s in group %s" % (etudid, group.id))
|
||||
log(f"{etudid} in group {group.id}")
|
||||
return flask.redirect(dest_url)
|
||||
|
||||
|
||||
@ -1475,8 +1478,6 @@ def _get_prev_moy(etudid, formsemestre_id):
|
||||
"""Donne la derniere moyenne generale calculee pour cette étudiant,
|
||||
ou 0 si on n'en trouve pas (nouvel inscrit,...).
|
||||
"""
|
||||
from app.scodoc import sco_cursus_dut
|
||||
|
||||
info = sco_etud.get_etud_info(etudid=etudid, filled=True)
|
||||
if not info:
|
||||
raise ScoValueError("etudiant invalide: etudid=%s" % etudid)
|
||||
|
@ -142,7 +142,9 @@ def check_access_diretud(formsemestre_id, required_permission=Permission.ScoImpl
|
||||
|
||||
|
||||
def can_change_groups(formsemestre_id: int) -> bool:
|
||||
"Vrai si l'utilisateur peut changer les groupes dans ce semestre"
|
||||
"""Vrai si l'utilisateur peut changer les groupes dans ce semestre
|
||||
Obsolete: utiliser FormSemestre.can_change_groups
|
||||
"""
|
||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if not formsemestre.etat:
|
||||
return False # semestre verrouillé
|
||||
|
Loading…
Reference in New Issue
Block a user