BUT: dispenses d'UE capitalisées. Voir #537.
This commit is contained in:
parent
ae9aad0619
commit
a87dbd9927
@ -80,6 +80,9 @@ class BulletinBUT:
|
|||||||
"""
|
"""
|
||||||
res = self.res
|
res = self.res
|
||||||
|
|
||||||
|
if (etud.id, ue.id) in self.res.dispense_ues:
|
||||||
|
return {}
|
||||||
|
|
||||||
if ue.type == UE_SPORT:
|
if ue.type == UE_SPORT:
|
||||||
modimpls_spo = [
|
modimpls_spo = [
|
||||||
modimpl
|
modimpl
|
||||||
|
@ -32,9 +32,17 @@ import pandas as pd
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
from app.models import (
|
||||||
|
DispenseUE,
|
||||||
|
FormSemestre,
|
||||||
|
FormSemestreInscription,
|
||||||
|
Identite,
|
||||||
|
Module,
|
||||||
|
ModuleImpl,
|
||||||
|
ModuleUECoef,
|
||||||
|
UniteEns,
|
||||||
|
)
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
from app.models.formsemestre import FormSemestre
|
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
@ -140,7 +148,8 @@ def df_load_modimpl_coefs(
|
|||||||
mod_coef.ue_id
|
mod_coef.ue_id
|
||||||
] = mod_coef.coef
|
] = mod_coef.coef
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# il peut y avoir en base des coefs sur des modules ou UE qui ont depuis été retirés de la formation
|
# il peut y avoir en base des coefs sur des modules ou UE
|
||||||
|
# qui ont depuis été retirés de la formation
|
||||||
pass
|
pass
|
||||||
# Initialisation des poids non fixés:
|
# Initialisation des poids non fixés:
|
||||||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||||
@ -199,7 +208,7 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
modimpls_results[modimpl.id] = mod_results
|
modimpls_results[modimpl.id] = mod_results
|
||||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
modimpls_evals_poids[modimpl.id] = evals_poids
|
||||||
modimpls_notes.append(etuds_moy_module)
|
modimpls_notes.append(etuds_moy_module)
|
||||||
if len(modimpls_notes):
|
if len(modimpls_notes) > 0:
|
||||||
cube = notes_sem_assemble_cube(modimpls_notes)
|
cube = notes_sem_assemble_cube(modimpls_notes)
|
||||||
else:
|
else:
|
||||||
nb_etuds = formsemestre.etuds.count()
|
nb_etuds = formsemestre.etuds.count()
|
||||||
@ -211,14 +220,39 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_dispense_ues(
|
||||||
|
formsemestre: FormSemestre, etudids: pd.Index, ues: list[UniteEns]
|
||||||
|
) -> set[tuple[int, int]]:
|
||||||
|
"""Construit l'ensemble des
|
||||||
|
etudids = modimpl_inscr_df.index, # les etudids
|
||||||
|
ue_ids : modimpl_coefs_df.index, # les UE du formsemestre sans les UE bonus sport
|
||||||
|
|
||||||
|
Résultat: set de (etudid, ue_id).
|
||||||
|
"""
|
||||||
|
dispense_ues = set()
|
||||||
|
ue_sem_by_code = {ue.ue_code: ue for ue in ues}
|
||||||
|
# Prend toutes les dispenses obtenues par des étudiants de ce formsemestre,
|
||||||
|
# puis filtre sur inscrits et code d'UE UE
|
||||||
|
for dispense_ue in DispenseUE.query.join(
|
||||||
|
Identite, FormSemestreInscription
|
||||||
|
).filter_by(formsemestre_id=formsemestre.id):
|
||||||
|
if dispense_ue.etudid in etudids:
|
||||||
|
# UE dans le semestre avec même code ?
|
||||||
|
ue = ue_sem_by_code.get(dispense_ue.ue.ue_code)
|
||||||
|
if ue is not None:
|
||||||
|
dispense_ues.add((dispense_ue.etudid, ue.id))
|
||||||
|
|
||||||
|
return dispense_ues
|
||||||
|
|
||||||
|
|
||||||
def compute_ue_moys_apc(
|
def compute_ue_moys_apc(
|
||||||
sem_cube: np.array,
|
sem_cube: np.array,
|
||||||
etuds: list,
|
etuds: list,
|
||||||
modimpls: list,
|
modimpls: list,
|
||||||
ues: list,
|
|
||||||
modimpl_inscr_df: pd.DataFrame,
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
modimpl_coefs_df: pd.DataFrame,
|
modimpl_coefs_df: pd.DataFrame,
|
||||||
modimpl_mask: np.array,
|
modimpl_mask: np.array,
|
||||||
|
dispense_ues: set[tuple[int, int]],
|
||||||
block: bool = False,
|
block: bool = False,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||||
@ -230,7 +264,7 @@ def compute_ue_moys_apc(
|
|||||||
etuds : liste des étudiants (dim. 0 du cube)
|
etuds : liste des étudiants (dim. 0 du cube)
|
||||||
modimpls : liste des module_impl (dim. 1 du cube)
|
modimpls : liste des module_impl (dim. 1 du cube)
|
||||||
ues : liste des UE (dim. 2 du cube)
|
ues : liste des UE (dim. 2 du cube)
|
||||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl)
|
||||||
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
||||||
modimpl_mask: liste de booléens, indiquants le module doit être pris ou pas.
|
modimpl_mask: liste de booléens, indiquants le module doit être pris ou pas.
|
||||||
(utilisé pour éliminer les bonus, et pourra servir à cacluler
|
(utilisé pour éliminer les bonus, et pourra servir à cacluler
|
||||||
@ -239,7 +273,6 @@ def compute_ue_moys_apc(
|
|||||||
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
||||||
"""
|
"""
|
||||||
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
||||||
nb_ues_tot = len(ues)
|
|
||||||
assert len(modimpls) == nb_modules
|
assert len(modimpls) == nb_modules
|
||||||
if block or nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
|
if block or nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
@ -278,11 +311,16 @@ def compute_ue_moys_apc(
|
|||||||
etud_moy_ue = np.sum(
|
etud_moy_ue = np.sum(
|
||||||
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
return pd.DataFrame(
|
etud_moy_ue_df = pd.DataFrame(
|
||||||
etud_moy_ue,
|
etud_moy_ue,
|
||||||
index=modimpl_inscr_df.index, # les etudids
|
index=modimpl_inscr_df.index, # les etudids
|
||||||
columns=modimpl_coefs_df.index, # les UE sans les UE bonus sport
|
columns=modimpl_coefs_df.index, # les UE sans les UE bonus sport
|
||||||
)
|
)
|
||||||
|
# Les "dispenses" sont très peu nombreuses et traitées en python:
|
||||||
|
for dispense_ue in dispense_ues:
|
||||||
|
etud_moy_ue_df[dispense_ue[1]][dispense_ue[0]] = 0.0
|
||||||
|
|
||||||
|
return etud_moy_ue_df
|
||||||
|
|
||||||
|
|
||||||
def compute_ue_moys_classic(
|
def compute_ue_moys_classic(
|
||||||
@ -435,7 +473,7 @@ def compute_mat_moys_classic(
|
|||||||
Résultat:
|
Résultat:
|
||||||
- moyennes: pd.Series, index etudid
|
- moyennes: pd.Series, index etudid
|
||||||
"""
|
"""
|
||||||
if (not len(modimpl_mask)) or (
|
if (0 == len(modimpl_mask)) or (
|
||||||
sem_matrix.shape[0] == 0
|
sem_matrix.shape[0] == 0
|
||||||
): # aucun module ou aucun étudiant
|
): # aucun module ou aucun étudiant
|
||||||
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
# etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||||
|
@ -39,6 +39,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
"""ndarray (etuds x modimpl x ue)"""
|
"""ndarray (etuds x modimpl x ue)"""
|
||||||
self.etuds_parcour_id = None
|
self.etuds_parcour_id = None
|
||||||
"""Parcours de chaque étudiant { etudid : parcour_id }"""
|
"""Parcours de chaque étudiant { etudid : parcour_id }"""
|
||||||
|
|
||||||
if not self.load_cached():
|
if not self.load_cached():
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
self.compute()
|
self.compute()
|
||||||
@ -71,14 +72,17 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
modimpl.module.ue.type != UE_SPORT
|
modimpl.module.ue.type != UE_SPORT
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
]
|
]
|
||||||
|
self.dispense_ues = moy_ue.load_dispense_ues(
|
||||||
|
self.formsemestre, self.modimpl_inscr_df.index, self.ues
|
||||||
|
)
|
||||||
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
self.sem_cube,
|
self.sem_cube,
|
||||||
self.etuds,
|
self.etuds,
|
||||||
self.formsemestre.modimpls_sorted,
|
self.formsemestre.modimpls_sorted,
|
||||||
self.ues,
|
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs_df,
|
self.modimpl_coefs_df,
|
||||||
modimpls_mask,
|
modimpls_mask,
|
||||||
|
self.dispense_ues,
|
||||||
block=self.formsemestre.block_moyennes,
|
block=self.formsemestre.block_moyennes,
|
||||||
)
|
)
|
||||||
# Les coefficients d'UE ne sont pas utilisés en APC
|
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||||
|
@ -48,12 +48,13 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
_cached_attrs = (
|
_cached_attrs = (
|
||||||
"bonus",
|
"bonus",
|
||||||
"bonus_ues",
|
"bonus_ues",
|
||||||
|
"dispense_ues",
|
||||||
|
"etud_coef_ue_df",
|
||||||
"etud_moy_gen_ranks",
|
"etud_moy_gen_ranks",
|
||||||
"etud_moy_gen",
|
"etud_moy_gen",
|
||||||
"etud_moy_ue",
|
"etud_moy_ue",
|
||||||
"modimpl_inscr_df",
|
"modimpl_inscr_df",
|
||||||
"modimpls_results",
|
"modimpls_results",
|
||||||
"etud_coef_ue_df",
|
|
||||||
"moyennes_matieres",
|
"moyennes_matieres",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"Bonus sur moy. gen. Series de float, index etudid"
|
"Bonus sur moy. gen. Series de float, index etudid"
|
||||||
self.bonus_ues: pd.DataFrame = None # virtuel
|
self.bonus_ues: pd.DataFrame = None # virtuel
|
||||||
"DataFrame de float, index etudid, columns: ue.id"
|
"DataFrame de float, index etudid, columns: ue.id"
|
||||||
|
self.dispense_ues: set[tuple[int, int]] = set()
|
||||||
|
"""set des dispenses d'UE: (etudid, ue_id), en APC seulement."""
|
||||||
# ResultatsSemestreBUT ou ResultatsSemestreClassic
|
# ResultatsSemestreBUT ou ResultatsSemestreClassic
|
||||||
self.etud_moy_ue = {}
|
self.etud_moy_ue = {}
|
||||||
"etud_moy_ue: DataFrame columns UE, rows etudid"
|
"etud_moy_ue: DataFrame columns UE, rows etudid"
|
||||||
|
@ -36,7 +36,7 @@ from app.models.etudiants import (
|
|||||||
from app.models.events import Scolog, ScolarNews
|
from app.models.events import Scolog, ScolarNews
|
||||||
from app.models.formations import Formation, Matiere
|
from app.models.formations import Formation, Matiere
|
||||||
from app.models.modules import Module, ModuleUECoef, NotesTag, notes_modules_tags
|
from app.models.modules import Module, ModuleUECoef, NotesTag, notes_modules_tags
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import DispenseUE, UniteEns
|
||||||
from app.models.formsemestre import (
|
from app.models.formsemestre import (
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
FormSemestreEtape,
|
FormSemestreEtape,
|
||||||
|
@ -58,6 +58,12 @@ class Identite(db.Model):
|
|||||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||||
#
|
#
|
||||||
admission = db.relationship("Admission", backref="identite", lazy="dynamic")
|
admission = db.relationship("Admission", backref="identite", lazy="dynamic")
|
||||||
|
dispense_ues = db.relationship(
|
||||||
|
"DispenseUE",
|
||||||
|
back_populates="etud",
|
||||||
|
cascade="all, delete",
|
||||||
|
passive_deletes=True,
|
||||||
|
)
|
||||||
|
|
||||||
# Relations avec les assiduites et les justificatifs
|
# Relations avec les assiduites et les justificatifs
|
||||||
assiduites = db.relationship("Assiduite", backref="etudiant", lazy="dynamic")
|
assiduites = db.relationship("Assiduite", backref="etudiant", lazy="dynamic")
|
||||||
|
@ -5,6 +5,7 @@ from app import db, 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.but_refcomp import ApcNiveau, ApcParcours
|
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||||
|
from app.models.modules import Module
|
||||||
from app.scodoc.sco_exceptions import ScoFormationConflict
|
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -57,6 +58,12 @@ class UniteEns(db.Model):
|
|||||||
# relations
|
# relations
|
||||||
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
||||||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||||
|
dispense_ues = db.relationship(
|
||||||
|
"DispenseUE",
|
||||||
|
back_populates="ue",
|
||||||
|
cascade="all, delete",
|
||||||
|
passive_deletes=True,
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<{self.__class__.__name__}(id={self.id}, formation_id={
|
return f"""<{self.__class__.__name__}(id={self.id}, formation_id={
|
||||||
@ -237,3 +244,31 @@ class UniteEns(db.Model):
|
|||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
log(f"ue.set_parcour( {self}, {parcour} )")
|
log(f"ue.set_parcour( {self}, {parcour} )")
|
||||||
|
|
||||||
|
|
||||||
|
class DispenseUE(db.Model):
|
||||||
|
"""Dispense d'UE
|
||||||
|
Utilisé en PCC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée
|
||||||
|
qu'ils ne refont pas.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__table_args__ = (db.UniqueConstraint("ue_id", "etudid"),)
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
ue_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey(UniteEns.id, ondelete="CASCADE"),
|
||||||
|
index=True,
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
ue = db.relationship("UniteEns", back_populates="dispense_ues")
|
||||||
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||||
|
index=True,
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
etud = db.relationship("Identite", back_populates="dispense_ues")
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"""<{self.__class__.__name__} {self.id} etud={
|
||||||
|
repr(self.etud)} ue={repr(self.ue)}>"""
|
||||||
|
@ -36,7 +36,7 @@ from flask_login import current_user
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
|
|
||||||
from app.models import ScolarNews
|
from app.models import ModuleImpl, ScolarNews
|
||||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
||||||
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
|
||||||
@ -126,10 +126,13 @@ def do_evaluation_create(
|
|||||||
"""Create an evaluation"""
|
"""Create an evaluation"""
|
||||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
||||||
raise AccessDenied(
|
raise AccessDenied(
|
||||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||||
)
|
)
|
||||||
args = locals()
|
args = locals()
|
||||||
log("do_evaluation_create: args=" + str(args))
|
log("do_evaluation_create: args=" + str(args))
|
||||||
|
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
|
if modimpl is None:
|
||||||
|
raise ValueError("module not found")
|
||||||
check_evaluation_args(args)
|
check_evaluation_args(args)
|
||||||
# Check numeros
|
# Check numeros
|
||||||
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
||||||
@ -172,16 +175,18 @@ def do_evaluation_create(
|
|||||||
r = _evaluationEditor.create(cnx, args)
|
r = _evaluationEditor.create(cnx, args)
|
||||||
|
|
||||||
# news
|
# news
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
url = url_for(
|
||||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
"notes.moduleimpl_status",
|
||||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
scodoc_dept=g.scodoc_dept,
|
||||||
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
)
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_NOTE,
|
typ=ScolarNews.NEWS_NOTE,
|
||||||
obj=moduleimpl_id,
|
obj=moduleimpl_id,
|
||||||
text='Création d\'une évaluation dans <a href="%(url)s">%(titre)s</a>' % mod,
|
text=f"""Création d'une évaluation dans <a href="{url}">{
|
||||||
url=mod["url"],
|
modimpl.module.titre or '(module sans titre)'}</a>""",
|
||||||
|
url=url,
|
||||||
)
|
)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
@ -262,6 +262,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||||||
"""
|
"""
|
||||||
authuser = current_user
|
authuser = current_user
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
is_apc = formsemestre.formation.is_apc()
|
is_apc = formsemestre.formation.is_apc()
|
||||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
@ -390,65 +391,80 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
|
|
||||||
# Etudiants "dispensés" d'une UE (capitalisée)
|
# Etudiants "dispensés" d'une UE (capitalisée)
|
||||||
UECaps = get_etuds_with_capitalized_ue(formsemestre_id)
|
ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id)
|
||||||
if UECaps:
|
if ues_cap_info:
|
||||||
H.append('<h3>Etudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">')
|
H.append('<h3>Étudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">')
|
||||||
ues = [sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in UECaps.keys()]
|
ues = [
|
||||||
|
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
|
||||||
|
]
|
||||||
ues.sort(key=lambda u: u["numero"])
|
ues.sort(key=lambda u: u["numero"])
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
H.append(
|
H.append(
|
||||||
'<li class="tit"><span class="tit">%(acronyme)s: %(titre)s</span>' % ue
|
f"""<li class="tit"><span class="tit">{ue['acronyme']}: {ue['titre']}</span>"""
|
||||||
)
|
)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for info in UECaps[ue["ue_id"]]:
|
for info in ues_cap_info[ue["ue_id"]]:
|
||||||
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
||||||
H.append(
|
H.append(
|
||||||
'<li class="etud"><a class="discretelink" href="%s">%s</a>'
|
f"""<li class="etud"><a class="discretelink" href="{
|
||||||
% (
|
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.ficheEtud",
|
"scolar.ficheEtud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
),
|
)
|
||||||
etud["nomprenom"],
|
}">{etud["nomprenom"]}</a>"""
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if info["ue_status"]["event_date"]:
|
if info["ue_status"]["event_date"]:
|
||||||
H.append(
|
H.append(
|
||||||
"(cap. le %s)"
|
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
|
||||||
% (info["ue_status"]["event_date"]).strftime("%d/%m/%Y")
|
|
||||||
)
|
|
||||||
|
|
||||||
if info["is_ins"]:
|
|
||||||
dm = ", ".join(
|
|
||||||
[
|
|
||||||
m["code"] or m["abbrev"] or "pas_de_code"
|
|
||||||
for m in info["is_ins"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
'actuellement inscrit dans <a title="%s" class="discretelink">%d modules</a>'
|
|
||||||
% (dm, len(info["is_ins"]))
|
|
||||||
)
|
)
|
||||||
|
if is_apc:
|
||||||
|
is_inscrit_ue = (etud["etudid"], ue["id"]) not in res.dispense_ues
|
||||||
|
else:
|
||||||
|
# CLASSIQUE
|
||||||
|
is_inscrit_ue = info["is_ins"]
|
||||||
|
if is_inscrit_ue:
|
||||||
|
dm = ", ".join(
|
||||||
|
[
|
||||||
|
m["code"] or m["abbrev"] or "pas_de_code"
|
||||||
|
for m in info["is_ins"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
H.append(
|
||||||
|
f"""actuellement inscrit dans <a title="{dm}" class="discretelink"
|
||||||
|
>{len(info["is_ins"])} modules</a>"""
|
||||||
|
)
|
||||||
|
if is_inscrit_ue:
|
||||||
if info["ue_status"]["is_capitalized"]:
|
if info["ue_status"]["is_capitalized"]:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div><em style="font-size: 70%">UE actuelle moins bonne que l'UE capitalisée</em></div>"""
|
"""<div><em style="font-size: 70%">UE actuelle moins bonne que
|
||||||
|
l'UE capitalisée</em>
|
||||||
|
</div>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div><em style="font-size: 70%">UE actuelle meilleure que l'UE capitalisée</em></div>"""
|
"""<div><em style="font-size: 70%">UE actuelle meilleure que
|
||||||
|
l'UE capitalisée</em>
|
||||||
|
</div>"""
|
||||||
)
|
)
|
||||||
if can_change:
|
if can_change:
|
||||||
H.append(
|
H.append(
|
||||||
'<div><a class="stdlink" href="etud_desinscrit_ue?etudid=%s&formsemestre_id=%s&ue_id=%s">désinscrire des modules de cette UE</a></div>'
|
f"""<div><a class="stdlink" href="{
|
||||||
% (etud["etudid"], formsemestre_id, ue["ue_id"])
|
url_for("notes.etud_desinscrit_ue",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
||||||
|
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
||||||
|
}">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append("(non réinscrit dans cette UE)")
|
H.append("(non réinscrit dans cette UE)")
|
||||||
if can_change:
|
if can_change:
|
||||||
H.append(
|
H.append(
|
||||||
'<div><a class="stdlink" href="etud_inscrit_ue?etudid=%s&formsemestre_id=%s&ue_id=%s">inscrire à tous les modules de cette UE</a></div>'
|
f"""<div><a class="stdlink" href="{
|
||||||
% (etud["etudid"], formsemestre_id, ue["ue_id"])
|
url_for("notes.etud_inscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
||||||
|
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
||||||
|
}">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append("</li>")
|
H.append("</li>")
|
||||||
H.append("</ul></li>")
|
H.append("</ul></li>")
|
||||||
@ -524,11 +540,11 @@ def _fmt_etud_set(ins, max_list_size=7):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_etuds_with_capitalized_ue(formsemestre_id):
|
def get_etuds_with_capitalized_ue(formsemestre_id: int) -> list[dict]:
|
||||||
"""For each UE, computes list of students capitalizing the UE.
|
"""For each UE, computes list of students capitalizing the UE.
|
||||||
returns { ue_id : [ { infos } ] }
|
returns { ue_id : [ { infos } ] }
|
||||||
"""
|
"""
|
||||||
UECaps = scu.DictDefault(defaultvalue=[])
|
ues_cap_info = scu.DictDefault(defaultvalue=[])
|
||||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
|
||||||
@ -540,21 +556,22 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
|
|||||||
for etud in inscrits:
|
for etud in inscrits:
|
||||||
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
||||||
if ue_status and ue_status["was_capitalized"]:
|
if ue_status and ue_status["was_capitalized"]:
|
||||||
UECaps[ue["ue_id"]].append(
|
ues_cap_info[ue["ue_id"]].append(
|
||||||
{
|
{
|
||||||
"etudid": etud["etudid"],
|
"etudid": etud["etudid"],
|
||||||
"ue_status": ue_status,
|
"ue_status": ue_status,
|
||||||
"is_ins": is_inscrit_ue(
|
"is_ins": etud_modules_ue_inscr(
|
||||||
etud["etudid"], formsemestre_id, ue["ue_id"]
|
etud["etudid"], formsemestre_id, ue["ue_id"]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return UECaps
|
return ues_cap_info
|
||||||
|
|
||||||
|
|
||||||
def is_inscrit_ue(etudid, formsemestre_id, ue_id):
|
def etud_modules_ue_inscr(etudid, formsemestre_id, ue_id) -> list[int]:
|
||||||
"""Modules de cette UE dans ce semestre
|
"""Modules de cette UE dans ce semestre
|
||||||
auxquels l'étudiant est inscrit.
|
auxquels l'étudiant est inscrit.
|
||||||
|
Utile pour formations classiques seulement.
|
||||||
"""
|
"""
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT mod.id AS module_id, mod.*
|
"""SELECT mod.id AS module_id, mod.*
|
||||||
@ -573,8 +590,10 @@ def is_inscrit_ue(etudid, formsemestre_id, ue_id):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
def do_etud_desinscrit_ue_classic(etudid, formsemestre_id, ue_id):
|
||||||
"""Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
"""Désinscrit l'etudiant de tous les modules de cette UE dans ce semestre.
|
||||||
|
N'utiliser que pour les formations classiques, pas APC.
|
||||||
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
@ -597,7 +616,7 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
|||||||
cnx,
|
cnx,
|
||||||
method="etud_desinscrit_ue",
|
method="etud_desinscrit_ue",
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
msg="desinscription UE %s" % ue_id,
|
msg=f"desinscription UE {ue_id}",
|
||||||
commit=False,
|
commit=False,
|
||||||
)
|
)
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
|
@ -58,7 +58,7 @@ from app.models.formsemestre import FormSemestre
|
|||||||
from app.models.formsemestre import FormSemestreUEComputationExpr
|
from app.models.formsemestre import FormSemestreUEComputationExpr
|
||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.modules import Module
|
from app.models.modules import Module
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import DispenseUE, UniteEns
|
||||||
from app.scodoc.sco_exceptions import ScoFormationConflict
|
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
|
|
||||||
@ -1588,12 +1588,30 @@ sco_publish(
|
|||||||
@permission_required(Permission.ScoEtudInscrit)
|
@permission_required(Permission.ScoEtudInscrit)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
||||||
"""Desinscrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
"""
|
||||||
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id)
|
- En classique: désinscrit l'etudiant de tous les modules de cette UE dans ce semestre.
|
||||||
|
- En APC: dispense de l'UE indiquée.
|
||||||
|
"""
|
||||||
|
etud = Identite.query.get_or_404(etudid)
|
||||||
|
ue = UniteEns.query.get_or_404(ue_id)
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
if ue.formation.is_apc():
|
||||||
|
if DispenseUE.query.filter_by(etudid=etudid, ue_id=ue_id).count() == 0:
|
||||||
|
disp = DispenseUE(ue_id=ue_id, etudid=etudid)
|
||||||
|
db.session.add(disp)
|
||||||
|
db.session.commit()
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
|
||||||
|
else:
|
||||||
|
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(
|
||||||
|
etudid, formsemestre_id, ue_id
|
||||||
|
)
|
||||||
|
flash(f"{etud.nomprenom} déinscrit de {ue.acronyme}")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
scu.ScoURL()
|
url_for(
|
||||||
+ "/Notes/moduleimpl_inscriptions_stats?formsemestre_id="
|
"notes.moduleimpl_inscriptions_stats",
|
||||||
+ str(formsemestre_id)
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
43
migrations/versions/f95656fdd3ef_dispenseue.py
Normal file
43
migrations/versions/f95656fdd3ef_dispenseue.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""DispenseUE
|
||||||
|
|
||||||
|
Revision ID: f95656fdd3ef
|
||||||
|
Revises: 5542cac8c34a
|
||||||
|
Create Date: 2022-11-30 22:22:05.045255
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "f95656fdd3ef"
|
||||||
|
down_revision = "5542cac8c34a"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"dispenseUE",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("ue_id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("etudid", sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(["etudid"], ["identite.id"], ondelete="CASCADE"),
|
||||||
|
sa.ForeignKeyConstraint(["ue_id"], ["notes_ue.id"], ondelete="CASCADE"),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("ue_id", "etudid"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_dispenseUE_etudid"), "dispenseUE", ["etudid"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_dispenseUE_ue_id"), "dispenseUE", ["ue_id"], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f("ix_dispenseUE_ue_id"), table_name="dispenseUE")
|
||||||
|
op.drop_index(op.f("ix_dispenseUE_etudid"), table_name="dispenseUE")
|
||||||
|
op.drop_table("dispenseUE")
|
||||||
|
# ### end Alembic commands ###
|
@ -1,13 +1,17 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.4.6"
|
SCOVERSION = "9.4.7"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
SCONEWS = """
|
SCONEWS = """
|
||||||
<h4>Année 2022</h4>
|
<h4>Année 2022</h4>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>ScoDoc 9.4</li>
|
||||||
|
<ul>
|
||||||
|
<li>Jury BUT2 avec parcours BUT</li>
|
||||||
|
</ul>
|
||||||
<li>ScoDoc 9.3</li>
|
<li>ScoDoc 9.3</li>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
|
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
|
||||||
|
@ -74,7 +74,6 @@ def test_ue_moy(test_client):
|
|||||||
sem_cube,
|
sem_cube,
|
||||||
etuds,
|
etuds,
|
||||||
modimpls,
|
modimpls,
|
||||||
ues,
|
|
||||||
modimpl_inscr_df,
|
modimpl_inscr_df,
|
||||||
modimpl_coefs_df,
|
modimpl_coefs_df,
|
||||||
modimpl_mask,
|
modimpl_mask,
|
||||||
@ -123,7 +122,7 @@ def test_ue_moy(test_client):
|
|||||||
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
||||||
]
|
]
|
||||||
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df, modimpl_mask
|
sem_cube, etuds, modimpls, modimpl_inscr_df, modimpl_coefs_df, modimpl_mask
|
||||||
)
|
)
|
||||||
assert etud_moy_ue[ue1.id][etudid] == n1
|
assert etud_moy_ue[ue1.id][etudid] == n1
|
||||||
assert etud_moy_ue[ue2.id][etudid] == n1
|
assert etud_moy_ue[ue2.id][etudid] == n1
|
||||||
|
Loading…
Reference in New Issue
Block a user