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
|
||||
|
||||
if (etud.id, ue.id) in self.res.dispense_ues:
|
||||
return {}
|
||||
|
||||
if ue.type == UE_SPORT:
|
||||
modimpls_spo = [
|
||||
modimpl
|
||||
|
@ -32,9 +32,17 @@ import pandas as pd
|
||||
|
||||
from app import db
|
||||
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.models.formsemestre import FormSemestre
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
@ -140,7 +148,8 @@ def df_load_modimpl_coefs(
|
||||
mod_coef.ue_id
|
||||
] = mod_coef.coef
|
||||
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
|
||||
# Initialisation des poids non fixés:
|
||||
# 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_evals_poids[modimpl.id] = evals_poids
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
if len(modimpls_notes):
|
||||
if len(modimpls_notes) > 0:
|
||||
cube = notes_sem_assemble_cube(modimpls_notes)
|
||||
else:
|
||||
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(
|
||||
sem_cube: np.array,
|
||||
etuds: list,
|
||||
modimpls: list,
|
||||
ues: list,
|
||||
modimpl_inscr_df: pd.DataFrame,
|
||||
modimpl_coefs_df: pd.DataFrame,
|
||||
modimpl_mask: np.array,
|
||||
dispense_ues: set[tuple[int, int]],
|
||||
block: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""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)
|
||||
modimpls : liste des module_impl (dim. 1 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_mask: liste de booléens, indiquants le module doit être pris ou pas.
|
||||
(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
|
||||
"""
|
||||
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
||||
nb_ues_tot = len(ues)
|
||||
assert len(modimpls) == nb_modules
|
||||
if block or nb_modules == 0 or nb_etuds == 0 or nb_ues_no_bonus == 0:
|
||||
return pd.DataFrame(
|
||||
@ -278,11 +311,16 @@ def compute_ue_moys_apc(
|
||||
etud_moy_ue = np.sum(
|
||||
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||
return pd.DataFrame(
|
||||
etud_moy_ue_df = pd.DataFrame(
|
||||
etud_moy_ue,
|
||||
index=modimpl_inscr_df.index, # les etudids
|
||||
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(
|
||||
@ -435,7 +473,7 @@ def compute_mat_moys_classic(
|
||||
Résultat:
|
||||
- moyennes: pd.Series, index etudid
|
||||
"""
|
||||
if (not len(modimpl_mask)) or (
|
||||
if (0 == len(modimpl_mask)) or (
|
||||
sem_matrix.shape[0] == 0
|
||||
): # aucun module ou aucun étudiant
|
||||
# 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)"""
|
||||
self.etuds_parcour_id = None
|
||||
"""Parcours de chaque étudiant { etudid : parcour_id }"""
|
||||
|
||||
if not self.load_cached():
|
||||
t0 = time.time()
|
||||
self.compute()
|
||||
@ -71,14 +72,17 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
||||
modimpl.module.ue.type != UE_SPORT
|
||||
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.sem_cube,
|
||||
self.etuds,
|
||||
self.formsemestre.modimpls_sorted,
|
||||
self.ues,
|
||||
self.modimpl_inscr_df,
|
||||
self.modimpl_coefs_df,
|
||||
modimpls_mask,
|
||||
self.dispense_ues,
|
||||
block=self.formsemestre.block_moyennes,
|
||||
)
|
||||
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||
|
@ -48,12 +48,13 @@ class ResultatsSemestre(ResultatsCache):
|
||||
_cached_attrs = (
|
||||
"bonus",
|
||||
"bonus_ues",
|
||||
"dispense_ues",
|
||||
"etud_coef_ue_df",
|
||||
"etud_moy_gen_ranks",
|
||||
"etud_moy_gen",
|
||||
"etud_moy_ue",
|
||||
"modimpl_inscr_df",
|
||||
"modimpls_results",
|
||||
"etud_coef_ue_df",
|
||||
"moyennes_matieres",
|
||||
)
|
||||
|
||||
@ -66,6 +67,8 @@ class ResultatsSemestre(ResultatsCache):
|
||||
"Bonus sur moy. gen. Series de float, index etudid"
|
||||
self.bonus_ues: pd.DataFrame = None # virtuel
|
||||
"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
|
||||
self.etud_moy_ue = {}
|
||||
"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.formations import Formation, Matiere
|
||||
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 (
|
||||
FormSemestre,
|
||||
FormSemestreEtape,
|
||||
|
@ -58,6 +58,12 @@ class Identite(db.Model):
|
||||
billets = db.relationship("BilletAbsence", backref="etudiant", 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
|
||||
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 SHORT_STR_LEN
|
||||
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||
from app.models.modules import Module
|
||||
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
@ -57,6 +58,12 @@ class UniteEns(db.Model):
|
||||
# relations
|
||||
matieres = db.relationship("Matiere", 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):
|
||||
return f"""<{self.__class__.__name__}(id={self.id}, formation_id={
|
||||
@ -237,3 +244,31 @@ class UniteEns(db.Model):
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
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.models import ScolarNews
|
||||
from app.models import ModuleImpl, ScolarNews
|
||||
from app.models.evaluations import evaluation_enrich_dict, check_evaluation_args
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -126,10 +126,13 @@ def do_evaluation_create(
|
||||
"""Create an evaluation"""
|
||||
if not sco_permissions_check.can_edit_evaluation(moduleimpl_id=moduleimpl_id):
|
||||
raise AccessDenied(
|
||||
"Modification évaluation impossible pour %s" % current_user.get_nomplogin()
|
||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
||||
)
|
||||
args = locals()
|
||||
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 numeros
|
||||
module_evaluation_renumber(moduleimpl_id, only_if_unumbered=True)
|
||||
@ -172,16 +175,18 @@ def do_evaluation_create(
|
||||
r = _evaluationEditor.create(cnx, args)
|
||||
|
||||
# news
|
||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=M["formsemestre_id"])
|
||||
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
mod["moduleimpl_id"] = M["moduleimpl_id"]
|
||||
mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
|
||||
url = url_for(
|
||||
"notes.moduleimpl_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_NOTE,
|
||||
obj=moduleimpl_id,
|
||||
text='Création d\'une évaluation dans <a href="%(url)s">%(titre)s</a>' % mod,
|
||||
url=mod["url"],
|
||||
text=f"""Création d'une évaluation dans <a href="{url}">{
|
||||
modimpl.module.titre or '(module sans titre)'}</a>""",
|
||||
url=url,
|
||||
)
|
||||
|
||||
return r
|
||||
|
@ -262,6 +262,7 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
"""
|
||||
authuser = current_user
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
is_apc = formsemestre.formation.is_apc()
|
||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||
args={"formsemestre_id": formsemestre_id}
|
||||
@ -390,65 +391,80 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
||||
H.append("</table>")
|
||||
|
||||
# Etudiants "dispensés" d'une UE (capitalisée)
|
||||
UECaps = get_etuds_with_capitalized_ue(formsemestre_id)
|
||||
if UECaps:
|
||||
H.append('<h3>Etudiants 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_cap_info = get_etuds_with_capitalized_ue(formsemestre_id)
|
||||
if ues_cap_info:
|
||||
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 ues_cap_info.keys()
|
||||
]
|
||||
ues.sort(key=lambda u: u["numero"])
|
||||
for ue in ues:
|
||||
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>")
|
||||
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]
|
||||
H.append(
|
||||
'<li class="etud"><a class="discretelink" href="%s">%s</a>'
|
||||
% (
|
||||
f"""<li class="etud"><a class="discretelink" href="{
|
||||
url_for(
|
||||
"scolar.ficheEtud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
etudid=etud["etudid"],
|
||||
),
|
||||
etud["nomprenom"],
|
||||
)
|
||||
)
|
||||
}">{etud["nomprenom"]}</a>"""
|
||||
)
|
||||
if info["ue_status"]["event_date"]:
|
||||
H.append(
|
||||
"(cap. le %s)"
|
||||
% (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"]))
|
||||
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
|
||||
)
|
||||
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"]:
|
||||
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:
|
||||
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:
|
||||
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>'
|
||||
% (etud["etudid"], formsemestre_id, ue["ue_id"])
|
||||
f"""<div><a class="stdlink" href="{
|
||||
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:
|
||||
H.append("(non réinscrit dans cette UE)")
|
||||
if can_change:
|
||||
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>'
|
||||
% (etud["etudid"], formsemestre_id, ue["ue_id"])
|
||||
f"""<div><a class="stdlink" href="{
|
||||
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("</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.
|
||||
returns { ue_id : [ { infos } ] }
|
||||
"""
|
||||
UECaps = scu.DictDefault(defaultvalue=[])
|
||||
ues_cap_info = scu.DictDefault(defaultvalue=[])
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
|
||||
@ -540,21 +556,22 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
|
||||
for etud in inscrits:
|
||||
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
||||
if ue_status and ue_status["was_capitalized"]:
|
||||
UECaps[ue["ue_id"]].append(
|
||||
ues_cap_info[ue["ue_id"]].append(
|
||||
{
|
||||
"etudid": etud["etudid"],
|
||||
"ue_status": ue_status,
|
||||
"is_ins": is_inscrit_ue(
|
||||
"is_ins": etud_modules_ue_inscr(
|
||||
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
|
||||
auxquels l'étudiant est inscrit.
|
||||
Utile pour formations classiques seulement.
|
||||
"""
|
||||
r = ndb.SimpleDictFetch(
|
||||
"""SELECT mod.id AS module_id, mod.*
|
||||
@ -573,8 +590,10 @@ def is_inscrit_ue(etudid, formsemestre_id, ue_id):
|
||||
return r
|
||||
|
||||
|
||||
def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
||||
"""Desincrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
||||
def do_etud_desinscrit_ue_classic(etudid, formsemestre_id, ue_id):
|
||||
"""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()
|
||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
cursor.execute(
|
||||
@ -597,7 +616,7 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
||||
cnx,
|
||||
method="etud_desinscrit_ue",
|
||||
etudid=etudid,
|
||||
msg="desinscription UE %s" % ue_id,
|
||||
msg=f"desinscription UE {ue_id}",
|
||||
commit=False,
|
||||
)
|
||||
sco_cache.invalidate_formsemestre(
|
||||
|
@ -58,7 +58,7 @@ from app.models.formsemestre import FormSemestre
|
||||
from app.models.formsemestre import FormSemestreUEComputationExpr
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
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.views import notes_bp as bp
|
||||
|
||||
@ -1588,12 +1588,30 @@ sco_publish(
|
||||
@permission_required(Permission.ScoEtudInscrit)
|
||||
@scodoc7func
|
||||
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(
|
||||
scu.ScoURL()
|
||||
+ "/Notes/moduleimpl_inscriptions_stats?formsemestre_id="
|
||||
+ str(formsemestre_id)
|
||||
url_for(
|
||||
"notes.moduleimpl_inscriptions_stats",
|
||||
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 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.4.6"
|
||||
SCOVERSION = "9.4.7"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
SCONEWS = """
|
||||
<h4>Année 2022</h4>
|
||||
<ul>
|
||||
<li>ScoDoc 9.4</li>
|
||||
<ul>
|
||||
<li>Jury BUT2 avec parcours BUT</li>
|
||||
</ul>
|
||||
<li>ScoDoc 9.3</li>
|
||||
<ul>
|
||||
<li>Nouvelle API REST pour connecter ScoDoc à d'autres applications<li>
|
||||
|
@ -74,7 +74,6 @@ def test_ue_moy(test_client):
|
||||
sem_cube,
|
||||
etuds,
|
||||
modimpls,
|
||||
ues,
|
||||
modimpl_inscr_df,
|
||||
modimpl_coefs_df,
|
||||
modimpl_mask,
|
||||
@ -123,7 +122,7 @@ def test_ue_moy(test_client):
|
||||
modimpl.module.ue.type != UE_SPORT for modimpl in formsemestre.modimpls_sorted
|
||||
]
|
||||
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[ue2.id][etudid] == n1
|
||||
|
Loading…
Reference in New Issue
Block a user