Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
16 changed files with 249 additions and 151 deletions
Showing only changes of commit 531ac1cb0c - Show all commits

View File

@ -12,6 +12,7 @@ from operator import attrgetter
from flask import g, request
from flask_json import as_json
from flask_login import login_required
from sqlalchemy.exc import IntegrityError
import app
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.scodoc import sco_cache
from app.scodoc import sco_groups
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
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}:
return json_error(404, "etud non inscrit au formsemestre du groupe")
sco_groups.change_etud_group_in_partition(
etudid, group_id, group.partition.to_dict()
)
try:
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}

View File

@ -865,11 +865,16 @@ class DecisionsProposeesAnnee(DecisionsProposees):
self.etud.id, self.formsemestre.id
)
for dec_ue in self.decisions_ues.values():
if dec_ue.formsemestre.id == self.formsemestre.id:
if (
dec_ue
and self.formsemestre
and dec_ue.formsemestre.id == self.formsemestre.id
):
dec_ue.erase()
else:
for dec_ue in self.decisions_ues.values():
dec_ue.erase()
if dec_ue:
dec_ue.erase()
if self.formsemestre_impair:
ScolarAutorisationInscription.delete_autorisation_etud(

View File

@ -827,16 +827,32 @@ class BonusStMalo(BonusIUTRennes1):
class BonusLaRocheSurYon(BonusSportAdditif):
"""Bonus IUT de La Roche-sur-Yon
Si une note de bonus est saisie, l'étudiant est gratifié de 0,2 points
sur sa moyenne générale ou, en BUT, sur la moyenne de chaque UE.
<p>
<b>La note saisie s'applique directement</b>: si on saisit 0,2, un bonus de 0,2 points est appliqué
aux moyennes.
La valeur maximale du bonus est 1 point. Il est appliqué sur les moyennes d'UEs en BUT,
ou sur la moyenne générale dans les autres formations.
</p>
<p>Pour les <b>semestres antérieurs à janvier 2023</b>: si une note de bonus est saisie,
l'étudiant est gratifié de 0,2 points sur sa moyenne générale ou, en BUT, sur la
moyenne de chaque UE.
</p>
"""
name = "bonus_larochesuryon"
displayed_name = "IUT de La Roche-sur-Yon"
seuil_moy_gen = 0.0
seuil_comptage = 0.0
proportion_point = 1e10 # le moindre point sature le bonus
bonus_max = 0.2 # à 0.2
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
"""calcul du bonus, avec réglage différent suivant la date"""
if self.formsemestre.date_debut > datetime.date(2022, 12, 31):
self.proportion_point = 1.0
self.bonus_max = 1
else: # ancienne règle
self.proportion_point = 1e10 # le moindre point sature le bonus
self.bonus_max = 0.2 # à 0.2
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
class BonusLaRochelle(BonusSportAdditif):

View File

@ -381,7 +381,11 @@ class ResultatsSemestre(ResultatsCache):
was_capitalized = False
if etudid in self.validations.ue_capitalisees.index:
ue_cap = self._get_etud_ue_cap(etudid, ue)
if ue_cap and not np.isnan(ue_cap["moy_ue"]):
if (
ue_cap
and (ue_cap["moy_ue"] is not None)
and not np.isnan(ue_cap["moy_ue"])
):
was_capitalized = True
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
moy_ue = ue_cap["moy_ue"]

View File

@ -575,6 +575,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.

View File

@ -8,11 +8,13 @@
"""ScoDoc models: Groups & partitions
"""
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 AccessDenied, ScoValueError
class Partition(db.Model):
@ -117,6 +119,81 @@ class Partition(db.Model):
.first()
)
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
ou que l'étudiant n'est pas inscrit au semestre.
Return True si changement, False s'il était déjà dans ce groupe.
"""
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 etud.id not in (e.id for e in self.formsemestre.etuds):
raise ScoValueError(
f"""étudiant {etud.nomprenom} non inscrit au formsemestre du groupe {
group.group_name}"""
)
try:
existing_row = (
db.session.query(group_membership)
.filter_by(etudid=etud.id)
.join(GroupDescr)
.filter_by(partition_id=self.id)
.first()
)
if existing_row:
existing_group_id = existing_row[1]
if group.id == existing_group_id:
return False
# 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=etud.id, group_id=group.id
)
db.session.execute(new_row)
db.session.commit()
except IntegrityError:
db.session.rollback()
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"""

View File

@ -949,6 +949,7 @@ def do_formsemestre_validate_ue(
"ue_id": ue_id,
"semestre_id": semestre_id,
"is_external": is_external,
"moy_ue": moy_ue,
}
if date:
args["event_date"] = date
@ -965,12 +966,11 @@ def do_formsemestre_validate_ue(
cursor.execute("delete from scolar_formsemestre_validation where " + cond, args)
# insert
args["code"] = code
if code == ADM:
if moy_ue is None:
# stocke la moyenne d'UE capitalisée:
ue_status = nt.get_etud_ue_status(etudid, ue_id)
moy_ue = ue_status["moy"] if ue_status else ""
args["moy_ue"] = moy_ue
if (code == ADM) and (moy_ue is None):
# stocke la moyenne d'UE capitalisée:
ue_status = nt.get_etud_ue_status(etudid, ue_id)
moy_ue = ue_status["moy"] if ue_status else ""
log("formsemestre_validate_ue: create %s" % args)
if code is not None:
scolar_formsemestre_validation_create(cnx, args)

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)
flash(f"UE créée (code {ue.ue_code})")
else:
if not tf[2]["numero"]:
tf[2]["numero"] = 0
do_ue_edit(tf[2])
flash("UE modifiée")

View File

@ -793,13 +793,16 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
{tf[1]}
"""
elif tf[0] == -1:
return redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
if formsemestre:
return redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
)
)
)
else:
return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
else:
if tf[2]["gestion_compensation_lst"]:
tf[2]["gestion_compensation"] = True

View File

@ -34,7 +34,6 @@ Optimisation possible:
"""
import collections
import operator
import time
from xml.etree import ElementTree
@ -45,15 +44,14 @@ 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
from app.models import FormSemestre, Identite, Scolog
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.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
@ -94,7 +92,7 @@ groupEditor = ndb.EditableTable(
group_list = groupEditor.list
def get_group(group_id: int) -> dict:
def get_group(group_id: int) -> dict: # OBSOLETE !
"""Returns group object, with partition"""
r = ndb.SimpleDictFetch(
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
@ -124,7 +122,7 @@ def group_delete(group_id: int):
)
def get_partition(partition_id):
def get_partition(partition_id): # OBSOLETE
r = ndb.SimpleDictFetch(
"""SELECT p.id AS partition_id, p.*
FROM partition p
@ -200,7 +198,7 @@ def get_formsemestre_etuds_groups(formsemestre_id: int) -> dict:
return d
def get_partition_groups(partition):
def get_partition_groups(partition): # OBSOLETE !
"""List of groups in this partition (list of dicts).
Some groups may be empty."""
return ndb.SimpleDictFetch(
@ -637,7 +635,7 @@ def _comp_etud_origin(etud: dict, cur_formsemestre: FormSemestre):
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.
Return True if ok, False si deja inscrit.
Warning:
@ -664,55 +662,33 @@ def set_group(etudid: int, group_id: int) -> bool:
return True
def change_etud_group_in_partition(etudid: int, group_id: int, partition: dict = None):
"""Inscrit etud au groupe de cette partition,
et le desinscrit d'autres groupes de cette partition.
def change_etud_group_in_partition(etudid: int, group: GroupDescr) -> bool:
"""Inscrit etud au groupe
(et le désinscrit d'autres groupes de cette partition)
Return True si changement, False s'il était déjà dans ce groupe.
"""
log("change_etud_group_in_partition: etudid=%s group_id=%s" % (etudid, group_id))
# 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)
etud: Identite = Identite.query.get_or_404(etudid)
if not group.partition.set_etud_group(etud, group):
return # pas de changement
# 3- log
formsemestre_id = partition["formsemestre_id"]
cnx = ndb.GetDBConnexion()
logdb(
cnx,
# - log
formsemestre: FormSemestre = group.partition.formsemestre
log(f"change_etud_group_in_partition: etudid={etudid} group={group}")
Scolog.logdb(
method="changeGroup",
etudid=etudid,
msg="formsemestre_id=%s,partition_name=%s, group_name=%s"
% (formsemestre_id, partition["partition_name"], group["group_name"]),
msg=f"""formsemestre_id={formsemestre.id}, partition_name={
group.partition.partition_name or ""}, group_name={group.group_name or ""}""",
commit=True,
)
cnx.commit()
# 5- Update parcours
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
formsemestre.update_inscriptions_parcours_from_groups()
# - Update parcours
if group.partition.partition_name == scu.PARTITION_PARCOURS:
formsemestre.update_inscriptions_parcours_from_groups()
# 6- invalidate cache
# - invalidate cache
sco_cache.invalidate_formsemestre(
formsemestre_id=formsemestre_id
formsemestre_id=formsemestre.id
) # > change etud group
@ -729,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 = (
@ -739,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()
@ -769,26 +745,23 @@ def setGroups(
except ValueError:
log(f"setGroups: ignoring invalid group_id={group_id}")
continue
group = get_group(group_id)
group: GroupDescr = GroupDescr.query.get_or_404(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
change_etud_group_in_partition(etudid, group_id, partition)
change_etud_group_in_partition(etudid, group)
# Retire les anciens membres:
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},
@ -798,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:
@ -819,10 +792,10 @@ def setGroups(
return xml_error(msg, code=404)
# Place dans ce groupe les etudiants indiqués:
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
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>'
@ -835,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):
@ -856,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
@ -1400,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
@ -1427,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(
@ -1452,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
group_ids = []
groups = []
for group_name in group_names:
if group_name.strip():
group_ids.append(create_group(partition_id, group_name).id)
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] = [
@ -1481,16 +1458,19 @@ def groups_auto_repartition(partition_id=None):
# affect aux groupes:
n = len(identdict)
igroup = 0
nbgroups = len(group_ids)
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_id = group_ids[igroup]
group = groups[igroup]
igroup = (igroup + 1) % nbgroups
change_etud_group_in_partition(etudid, group_id, partition)
log("%s in group %s" % (etudid, group_id))
log(f"in {etudid} in group {group.id}")
change_etud_group_in_partition(etudid, group)
log(f"{etudid} in group {group.id}")
return flask.redirect(dest_url)
@ -1498,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)
@ -1520,10 +1498,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
vides ne sont pas supprimés).
"""
# A RE-ECRIRE pour utiliser les modèles.
from app.scodoc import sco_formsemestre_inscriptions
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(
args={"formsemestre_id": formsemestre_id}
)
@ -1542,20 +1521,17 @@ def create_etapes_partition(formsemestre_id, partition_name="apo_etapes"):
pid = partition_create(
formsemestre_id, partition_name=partition_name, redirect=False
)
partition = get_partition(pid)
groups = get_partition_groups(partition)
partition: Partition = Partition.query.get(pid)
groups = partition.groups
groups_by_names = {g["group_name"]: g for g in groups}
for etape in etapes:
if not (etape in groups_by_names):
if etape not in groups_by_names:
new_group = create_group(pid, etape)
g = get_group(new_group.id) # XXX transition: recupere old style dict
groups_by_names[etape] = g
groups_by_names[etape] = new_group
# Place les etudiants dans les groupes
for i in ins:
if i["etape"]:
change_etud_group_in_partition(
i["etudid"], groups_by_names[i["etape"]]["group_id"], partition
)
change_etud_group_in_partition(i["etudid"], groups_by_names[i["etape"]])
def do_evaluation_listeetuds_groups(

View File

@ -639,10 +639,10 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
fields = adm_get_fields(titles, formsemestre_id)
idx_nom = None
idx_prenom = None
for idx in fields:
if fields[idx][0] == "nom":
for idx, field in fields.items():
if field[0] == "nom":
idx_nom = idx
if fields[idx][0] == "prenom":
if field[0] == "prenom":
idx_prenom = idx
if (idx_nom is None) or (idx_prenom is None):
log("fields indices=" + ", ".join([str(x) for x in fields]))
@ -664,21 +664,20 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
# Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
nom = adm_normalize_string(line[idx_nom])
prenom = adm_normalize_string(line[idx_prenom])
if not (nom, prenom) in etuds_by_nomprenom:
log(
"unable to find %s %s among members" % (line[idx_nom], line[idx_prenom])
)
if (nom, prenom) not in etuds_by_nomprenom:
msg = f"""Étudiant <b>{line[idx_nom]} {line[idx_prenom]} inexistant</b>"""
diag.append(msg)
else:
etud = etuds_by_nomprenom[(nom, prenom)]
cur_adm = sco_etud.admission_list(cnx, args={"etudid": etud["etudid"]})[0]
# peuple les champs presents dans le tableau
args = {}
for idx in fields:
field_name, convertor = fields[idx]
for idx, field in fields.items():
field_name, convertor = field
if field_name in modifiable_fields:
try:
val = convertor(line[idx])
except ValueError:
except ValueError as exc:
raise ScoFormatError(
'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"'
% (nline, field_name, line[idx]),
@ -687,7 +686,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
),
)
) from exc
if val is not None: # note: ne peut jamais supprimer une valeur
args[field_name] = val
if args:
@ -723,7 +722,7 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
group = GroupDescr.query.get(group_id)
if group.partition.groups_editable:
sco_groups.change_etud_group_in_partition(
args["etudid"], group_id
args["etudid"], group
)
else:
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.sco_utils as scu
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 import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import codes_cursus
from app.scodoc import sco_etud
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
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)
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.setup_parcours_groups()
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
@ -220,11 +220,8 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
# Inscrit aux groupes
for partition_group in partition_groups:
sco_groups.change_etud_group_in_partition(
etudid,
partition_group["group_id"],
partition_group,
)
group: GroupDescr = GroupDescr.query.get(partition_group["group_id"])
sco_groups.change_etud_group_in_partition(etudid, group)
def do_desinscrit(sem, etudids):

View File

@ -84,6 +84,8 @@ def SU(s: str) -> str:
s = html.unescape(s)
# Remplace les <br> par des <br/>
s = re.sub(r"<br\s*>", "<br/>", s)
# And substitute unicode characters not supported by ReportLab
s = s.replace("", "-")
return s

View File

@ -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é

View File

@ -2339,14 +2339,14 @@ def formsemestre_validation_but(
formsemestre: FormSemestre = FormSemestre.query.filter_by(
id=formsemestre_id, dept_id=g.scodoc_dept_id
).first_or_404()
etud = Identite.get_etud(etudid)
nb_etuds = formsemestre.etuds.count()
# la route ne donne pas le type d'etudid pour pouvoir construire des URLs
# provisoires avec NEXT et PREV
try:
etudid = int(etudid)
except ValueError:
abort(404, "invalid etudid")
except ValueError as exc:
raise ScoValueError("adresse invalide") from exc
etud = Identite.get_etud(etudid)
nb_etuds = formsemestre.etuds.count()
read_only = not formsemestre.can_edit_jury()
# --- Navigation

View File

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