forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/ScoDoc/ScoDoc into api
This commit is contained in:
commit
f0bdb5e9bd
@ -228,6 +228,10 @@ class BonusSportAdditif(BonusSport):
|
|||||||
else: # necessaire pour éviter bonus négatifs !
|
else: # necessaire pour éviter bonus négatifs !
|
||||||
bonus_moy_arr = np.clip(bonus_moy_arr, 0.0, 20.0, out=bonus_moy_arr)
|
bonus_moy_arr = np.clip(bonus_moy_arr, 0.0, 20.0, out=bonus_moy_arr)
|
||||||
|
|
||||||
|
self.bonus_additif(bonus_moy_arr)
|
||||||
|
|
||||||
|
def bonus_additif(self, bonus_moy_arr: np.array):
|
||||||
|
"Set bonus_ues et bonus_moy_gen"
|
||||||
# en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
|
# en APC, bonus_moy_arr est (nb_etuds, nb_ues_non_bonus)
|
||||||
if self.formsemestre.formation.is_apc():
|
if self.formsemestre.formation.is_apc():
|
||||||
# Bonus sur les UE et None sur moyenne générale
|
# Bonus sur les UE et None sur moyenne générale
|
||||||
@ -306,6 +310,47 @@ class BonusDirect(BonusSportAdditif):
|
|||||||
proportion_point = 1.0
|
proportion_point = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
class BonusAisneStQuentin(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Aisne St Quentin
|
||||||
|
|
||||||
|
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université de St Quentin non rattachés à une unité d'enseignement.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Si la note est >= 10 et < 12.1, bonus de 0.1 point</li>
|
||||||
|
<li>Si la note est >= 12.1 et < 14.1, bonus de 0.2 point</li>
|
||||||
|
<li>Si la note est >= 14.1 et < 16.1, bonus de 0.3 point</li>
|
||||||
|
<li>Si la note est >= 16.1 et < 18.1, bonus de 0.4 point</li>
|
||||||
|
<li>Si la note est >= 18.1, bonus de 0.5 point</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||||
|
l'étudiant (en BUT, s'ajoute à la moyenne de chaque UE).
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutstq"
|
||||||
|
displayed_name = "IUT de Saint-Quentin"
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||||
|
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||||
|
return
|
||||||
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
|
bonus_moy_arr = np.sum(
|
||||||
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
|
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 18.1] = 0.5
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 16.1] = 0.4
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 14.1] = 0.3
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 12.1] = 0.2
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 10] = 0.1
|
||||||
|
|
||||||
|
self.bonus_additif(bonus_moy_arr)
|
||||||
|
|
||||||
|
|
||||||
class BonusAmiens(BonusSportAdditif):
|
class BonusAmiens(BonusSportAdditif):
|
||||||
"""Bonus IUT Amiens pour les modules optionnels (sport, culture, ...).
|
"""Bonus IUT Amiens pour les modules optionnels (sport, culture, ...).
|
||||||
|
|
||||||
@ -705,6 +750,7 @@ class BonusRoanne(BonusSportAdditif):
|
|||||||
seuil_moy_gen = 0.0
|
seuil_moy_gen = 0.0
|
||||||
bonus_max = 0.6 # plafonnement à 0.6 points
|
bonus_max = 0.6 # plafonnement à 0.6 points
|
||||||
classic_use_bonus_ues = True # sur les UE, même en DUT et LP
|
classic_use_bonus_ues = True # sur les UE, même en DUT et LP
|
||||||
|
proportion_point = 1
|
||||||
|
|
||||||
|
|
||||||
class BonusStDenis(BonusSportAdditif):
|
class BonusStDenis(BonusSportAdditif):
|
||||||
@ -773,21 +819,19 @@ class BonusVilleAvray(BonusSport):
|
|||||||
|
|
||||||
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
"""calcul du bonus"""
|
"""calcul du bonus"""
|
||||||
|
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||||
|
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||||
|
return
|
||||||
# Calcule moyenne pondérée des notes de sport:
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
bonus_moy_arr = np.sum(
|
bonus_moy_arr = np.sum(
|
||||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||||
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
|
||||||
bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3
|
bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
||||||
|
|
||||||
# Bonus moyenne générale, et 0 sur les UE
|
self.bonus_additif(bonus_moy_arr)
|
||||||
self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float)
|
|
||||||
if self.bonus_max is not None:
|
|
||||||
# Seuil: bonus (sur moy. gen.) limité à bonus_max points
|
|
||||||
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max)
|
|
||||||
|
|
||||||
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
|
||||||
|
|
||||||
|
|
||||||
class BonusIUTV(BonusSportAdditif):
|
class BonusIUTV(BonusSportAdditif):
|
||||||
|
@ -30,8 +30,10 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from flask import flash
|
||||||
|
|
||||||
def compute_sem_moys_apc(
|
|
||||||
|
def compute_sem_moys_apc_using_coefs(
|
||||||
etud_moy_ue_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
etud_moy_ue_df: pd.DataFrame, modimpl_coefs_df: pd.DataFrame
|
||||||
) -> pd.Series:
|
) -> pd.Series:
|
||||||
"""Calcule les moyennes générales indicatives de tous les étudiants
|
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||||
@ -48,6 +50,28 @@ def compute_sem_moys_apc(
|
|||||||
return moy_gen
|
return moy_gen
|
||||||
|
|
||||||
|
|
||||||
|
def compute_sem_moys_apc_using_ects(
|
||||||
|
etud_moy_ue_df: pd.DataFrame, ects: list, formation_id=None
|
||||||
|
) -> pd.Series:
|
||||||
|
"""Calcule les moyennes générales indicatives de tous les étudiants
|
||||||
|
= moyenne des moyennes d'UE, pondérée par leurs ECTS.
|
||||||
|
|
||||||
|
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||||
|
ects: liste de floats ou None, 1 par UE
|
||||||
|
|
||||||
|
Result: panda Series, index etudid, valeur float (moyenne générale)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
moy_gen = (etud_moy_ue_df * ects).sum(axis=1) / sum(ects)
|
||||||
|
except TypeError:
|
||||||
|
if None in ects:
|
||||||
|
flash("""Calcul moyenne générale impossible: ECTS des UE manquants !""")
|
||||||
|
moy_gen = pd.Series(np.NaN, index=etud_moy_ue_df.index)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return moy_gen
|
||||||
|
|
||||||
|
|
||||||
def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
def comp_ranks_series(notes: pd.Series) -> (pd.Series, pd.Series):
|
||||||
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
"""Calcul rangs à partir d'une séries ("vecteur") de notes (index etudid, valeur
|
||||||
numérique) en tenant compte des ex-aequos.
|
numérique) en tenant compte des ex-aequos.
|
||||||
|
@ -14,7 +14,7 @@ from app import log
|
|||||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.comp.bonus_spo import BonusSport
|
from app.comp.bonus_spo import BonusSport
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import ScoDocSiteConfig, formsemestre
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
)
|
)
|
||||||
# Les coefficients d'UE ne sont pas utilisés en APC
|
# Les coefficients d'UE ne sont pas utilisés en APC
|
||||||
self.etud_coef_ue_df = pd.DataFrame(
|
self.etud_coef_ue_df = pd.DataFrame(
|
||||||
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
0.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Modules de MALUS sur les UEs
|
# --- Modules de MALUS sur les UEs
|
||||||
@ -103,8 +103,13 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
# Moyenne générale indicative:
|
# Moyenne générale indicative:
|
||||||
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
|
# (note: le bonus sport a déjà été appliqué aux moyennes d'UE, et impacte
|
||||||
# donc la moyenne indicative)
|
# donc la moyenne indicative)
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
# self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_coefs(
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
# self.etud_moy_ue, self.modimpl_coefs_df
|
||||||
|
# )
|
||||||
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc_using_ects(
|
||||||
|
self.etud_moy_ue,
|
||||||
|
[ue.ects for ue in self.ues if ue.type != UE_SPORT],
|
||||||
|
formation_id=self.formsemestre.formation_id,
|
||||||
)
|
)
|
||||||
# --- UE capitalisées
|
# --- UE capitalisées
|
||||||
self.apply_capitalisation()
|
self.apply_capitalisation()
|
||||||
|
@ -9,18 +9,22 @@ from functools import cached_property
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from flask import g, flash, url_for
|
||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp.aux_stats import StatsMoyenne
|
from app.comp.aux_stats import StatsMoyenne
|
||||||
from app.comp import moy_sem
|
from app.comp import moy_sem
|
||||||
from app.comp.res_cache import ResultatsCache
|
from app.comp.res_cache import ResultatsCache
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, Identite, ModuleImpl
|
from app.models import FormSemestre, FormSemestreUECoef
|
||||||
from app.models import FormSemestreUECoef
|
from app.models import Identite
|
||||||
|
from app.models import ModuleImpl, ModuleImplInscription
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreCache
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
# Il faut bien distinguer
|
# Il faut bien distinguer
|
||||||
# - ce qui est caché de façon persistente (via redis):
|
# - ce qui est caché de façon persistente (via redis):
|
||||||
@ -191,7 +195,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
if ue_cap["is_capitalized"]:
|
if ue_cap["is_capitalized"]:
|
||||||
recompute_mg = True
|
recompute_mg = True
|
||||||
coef = ue_cap["coef_ue"]
|
coef = ue_cap["coef_ue"]
|
||||||
if not np.isnan(ue_cap["moy"]):
|
if not np.isnan(ue_cap["moy"]) and coef:
|
||||||
sum_notes_ue += ue_cap["moy"] * coef
|
sum_notes_ue += ue_cap["moy"] * coef
|
||||||
sum_coefs_ue += coef
|
sum_coefs_ue += coef
|
||||||
|
|
||||||
@ -206,12 +210,18 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
0.0, min(self.etud_moy_gen[etudid], 20.0)
|
0.0, min(self.etud_moy_gen[etudid], 20.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_etud_ue_cap(self, etudid, ue):
|
def _get_etud_ue_cap(self, etudid: int, ue: UniteEns) -> dict:
|
||||||
""""""
|
"""Donne les informations sur la capitalisation de l'UE ue pour cet étudiant.
|
||||||
|
Résultat:
|
||||||
|
Si pas capitalisée: None
|
||||||
|
Si capitalisée: un dict, avec les colonnes de validation.
|
||||||
|
"""
|
||||||
capitalisations = self.validations.ue_capitalisees.loc[etudid]
|
capitalisations = self.validations.ue_capitalisees.loc[etudid]
|
||||||
if isinstance(capitalisations, pd.DataFrame):
|
if isinstance(capitalisations, pd.DataFrame):
|
||||||
ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code]
|
ue_cap = capitalisations[capitalisations["ue_code"] == ue.ue_code]
|
||||||
if isinstance(ue_cap, pd.DataFrame) and not ue_cap.empty:
|
if ue_cap.empty:
|
||||||
|
return None
|
||||||
|
if isinstance(ue_cap, pd.DataFrame):
|
||||||
# si plusieurs fois capitalisée, prend le max
|
# si plusieurs fois capitalisée, prend le max
|
||||||
cap_idx = ue_cap["moy_ue"].values.argmax()
|
cap_idx = ue_cap["moy_ue"].values.argmax()
|
||||||
ue_cap = ue_cap.iloc[cap_idx]
|
ue_cap = ue_cap.iloc[cap_idx]
|
||||||
@ -219,8 +229,9 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
if capitalisations["ue_code"] == ue.ue_code:
|
if capitalisations["ue_code"] == ue.ue_code:
|
||||||
ue_cap = capitalisations
|
ue_cap = capitalisations
|
||||||
else:
|
else:
|
||||||
ue_cap = None
|
return None
|
||||||
return ue_cap
|
# converti la Series en dict, afin que les np.int64 reviennent en int
|
||||||
|
return ue_cap.to_dict()
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
||||||
"""L'état de l'UE pour cet étudiant.
|
"""L'état de l'UE pour cet étudiant.
|
||||||
@ -248,22 +259,45 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
cur_moy_ue = self.etud_moy_ue[ue_id][etudid]
|
||||||
moy_ue = cur_moy_ue
|
moy_ue = cur_moy_ue
|
||||||
is_capitalized = False # si l'UE prise en compte est une UE capitalisée
|
is_capitalized = False # si l'UE prise en compte est une UE capitalisée
|
||||||
was_capitalized = (
|
# s'il y a precedemment une UE capitalisée (pas forcement meilleure):
|
||||||
False # s'il y a precedemment une UE capitalisée (pas forcement meilleure)
|
was_capitalized = False
|
||||||
)
|
|
||||||
if etudid in self.validations.ue_capitalisees.index:
|
if etudid in self.validations.ue_capitalisees.index:
|
||||||
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
ue_cap = self._get_etud_ue_cap(etudid, ue)
|
||||||
if (
|
if ue_cap and not np.isnan(ue_cap["moy_ue"]):
|
||||||
ue_cap is not None
|
|
||||||
and not ue_cap.empty
|
|
||||||
and not np.isnan(ue_cap["moy_ue"])
|
|
||||||
):
|
|
||||||
was_capitalized = True
|
was_capitalized = True
|
||||||
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
|
if ue_cap["moy_ue"] > cur_moy_ue or np.isnan(cur_moy_ue):
|
||||||
moy_ue = ue_cap["moy_ue"]
|
moy_ue = ue_cap["moy_ue"]
|
||||||
is_capitalized = True
|
is_capitalized = True
|
||||||
|
|
||||||
|
# Coef l'UE dans le semestre courant:
|
||||||
|
if self.is_apc:
|
||||||
|
# utilise les ECTS comme coef.
|
||||||
|
coef_ue = ue.ects
|
||||||
|
else:
|
||||||
|
# formations classiques
|
||||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||||
|
if (not coef_ue) and is_capitalized: # étudiant non inscrit dans l'UE courante
|
||||||
|
if self.is_apc:
|
||||||
|
# Coefs de l'UE capitalisée en formation APC: donné par ses ECTS
|
||||||
|
ue_capitalized = UniteEns.query.get(ue_cap["ue_id"])
|
||||||
|
coef_ue = ue_capitalized.ects
|
||||||
|
if coef_ue is None:
|
||||||
|
orig_sem = FormSemestre.query.get(ue_cap["formsemestre_id"])
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""L'UE capitalisée {ue_capitalized.acronyme}
|
||||||
|
du semestre {orig_sem.titre_annee()}
|
||||||
|
n'a pas d'indication d'ECTS.
|
||||||
|
Corrigez ou faite corriger le programme
|
||||||
|
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=ue_capitalized.formation_id)}">via cette page</a>.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Coefs de l'UE capitalisée en formation classique:
|
||||||
|
# va chercher le coef dans le semestre d'origine
|
||||||
|
coef_ue = ModuleImplInscription.sum_coefs_modimpl_ue(
|
||||||
|
ue_cap["formsemestre_id"], etudid, ue_cap["ue_id"]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"is_capitalized": is_capitalized,
|
"is_capitalized": is_capitalized,
|
||||||
@ -385,21 +419,31 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"""Stats (moy/min/max) sur la moyenne générale"""
|
"""Stats (moy/min/max) sur la moyenne générale"""
|
||||||
return StatsMoyenne(self.etud_moy_gen)
|
return StatsMoyenne(self.etud_moy_gen)
|
||||||
|
|
||||||
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
def get_ues_stat_dict(
|
||||||
|
self, filter_sport=False, check_apc_ects=True
|
||||||
|
) -> list[dict]: # was get_ues()
|
||||||
"""Liste des UEs, ordonnée par numero.
|
"""Liste des UEs, ordonnée par numero.
|
||||||
Si filter_sport, retire les UE de type SPORT.
|
Si filter_sport, retire les UE de type SPORT.
|
||||||
Résultat: liste de dicts { champs UE U stats moyenne UE }
|
Résultat: liste de dicts { champs UE U stats moyenne UE }
|
||||||
"""
|
"""
|
||||||
ues = []
|
ues = self.formsemestre.query_ues(with_sport=not filter_sport)
|
||||||
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
ues_dict = []
|
||||||
|
for ue in ues:
|
||||||
d = ue.to_dict()
|
d = ue.to_dict()
|
||||||
if ue.type != UE_SPORT:
|
if ue.type != UE_SPORT:
|
||||||
moys = self.etud_moy_ue[ue.id]
|
moys = self.etud_moy_ue[ue.id]
|
||||||
else:
|
else:
|
||||||
moys = None
|
moys = None
|
||||||
d.update(StatsMoyenne(moys).to_dict())
|
d.update(StatsMoyenne(moys).to_dict())
|
||||||
ues.append(d)
|
ues_dict.append(d)
|
||||||
return ues
|
if check_apc_ects and self.is_apc and not hasattr(g, "checked_apc_ects"):
|
||||||
|
g.checked_apc_ects = True
|
||||||
|
if None in [ue.ects for ue in ues if ue.type != UE_SPORT]:
|
||||||
|
flash(
|
||||||
|
"""Calcul moyenne générale impossible: ECTS des UE manquants !""",
|
||||||
|
category="danger",
|
||||||
|
)
|
||||||
|
return ues_dict
|
||||||
|
|
||||||
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
|
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
|
||||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||||
|
@ -193,7 +193,7 @@ def scodoc7func(func):
|
|||||||
# necessary for db ids and boolean values
|
# necessary for db ids and boolean values
|
||||||
try:
|
try:
|
||||||
v = int(v)
|
v = int(v)
|
||||||
except ValueError:
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
pos_arg_values.append(v)
|
pos_arg_values.append(v)
|
||||||
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
# current_app.logger.info("pos_arg_values=%s" % pos_arg_values)
|
||||||
|
@ -30,17 +30,15 @@ Formulaires configuration logos
|
|||||||
|
|
||||||
Contrib @jmp, dec 21
|
Contrib @jmp, dec 21
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
|
|
||||||
from flask import flash, url_for, redirect, render_template
|
from flask import flash, url_for, redirect, render_template
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask_wtf.file import FileField, FileAllowed
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
|
from wtforms import SubmitField, FormField, validators, FieldList
|
||||||
|
from wtforms import ValidationError
|
||||||
from wtforms.fields.simple import StringField, HiddenField
|
from wtforms.fields.simple import StringField, HiddenField
|
||||||
|
|
||||||
from app import AccessDenied
|
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.models import ScoDocSiteConfig
|
|
||||||
from app.scodoc import sco_logos, html_sco_header
|
from app.scodoc import sco_logos, html_sco_header
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_config_actions import (
|
from app.scodoc.sco_config_actions import (
|
||||||
@ -49,10 +47,11 @@ from app.scodoc.sco_config_actions import (
|
|||||||
LogoInsert,
|
LogoInsert,
|
||||||
)
|
)
|
||||||
|
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
|
|
||||||
|
|
||||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
||||||
|
|
||||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||||
@ -111,6 +110,15 @@ def dept_key_to_id(dept_key):
|
|||||||
return dept_key
|
return dept_key
|
||||||
|
|
||||||
|
|
||||||
|
def logo_name_validator(message=None):
|
||||||
|
def validate_logo_name(form, field):
|
||||||
|
name = field.data if field.data else ""
|
||||||
|
if not scu.is_valid_filename(name):
|
||||||
|
raise ValidationError(message)
|
||||||
|
|
||||||
|
return validate_logo_name
|
||||||
|
|
||||||
|
|
||||||
class AddLogoForm(FlaskForm):
|
class AddLogoForm(FlaskForm):
|
||||||
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
||||||
|
|
||||||
@ -118,11 +126,7 @@ class AddLogoForm(FlaskForm):
|
|||||||
name = StringField(
|
name = StringField(
|
||||||
label="Nom",
|
label="Nom",
|
||||||
validators=[
|
validators=[
|
||||||
validators.regexp(
|
logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
|
||||||
r"^[a-zA-Z0-9-_]*$",
|
|
||||||
re.IGNORECASE,
|
|
||||||
"Ne doit comporter que lettres, chiffres, _ ou -",
|
|
||||||
),
|
|
||||||
validators.Length(
|
validators.Length(
|
||||||
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
||||||
),
|
),
|
||||||
@ -373,11 +377,11 @@ def config_logos():
|
|||||||
if action:
|
if action:
|
||||||
action.execute()
|
action.execute()
|
||||||
flash(action.message)
|
flash(action.message)
|
||||||
return redirect(
|
return redirect(url_for("scodoc.configure_logos"))
|
||||||
url_for(
|
else:
|
||||||
"scodoc.configure_logos",
|
if not form.validate():
|
||||||
)
|
scu.flash_errors(form)
|
||||||
)
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"config_logos.html",
|
"config_logos.html",
|
||||||
scodoc_dept=None,
|
scodoc_dept=None,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"""ScoDoc models: moduleimpls
|
"""ScoDoc models: moduleimpls
|
||||||
"""
|
"""
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import flask_sqlalchemy
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import df_cache
|
from app.comp import df_cache
|
||||||
@ -129,14 +130,36 @@ class ModuleImplInscription(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def nb_inscriptions_dans_ue(
|
def etud_modimpls_in_ue(
|
||||||
cls, formsemestre_id: int, etudid: int, ue_id: int
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
) -> int:
|
) -> flask_sqlalchemy.BaseQuery:
|
||||||
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
"""moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||||
return ModuleImplInscription.query.filter(
|
return ModuleImplInscription.query.filter(
|
||||||
ModuleImplInscription.etudid == etudid,
|
ModuleImplInscription.etudid == etudid,
|
||||||
ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
|
ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
|
||||||
ModuleImpl.formsemestre_id == formsemestre_id,
|
ModuleImpl.formsemestre_id == formsemestre_id,
|
||||||
ModuleImpl.module_id == Module.id,
|
ModuleImpl.module_id == Module.id,
|
||||||
Module.ue_id == ue_id,
|
Module.ue_id == ue_id,
|
||||||
).count()
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def nb_inscriptions_dans_ue(
|
||||||
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
|
) -> int:
|
||||||
|
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||||
|
return cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id).count()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sum_coefs_modimpl_ue(
|
||||||
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
|
) -> float:
|
||||||
|
"""Somme des coefficients des modules auxquels l'étudiant est inscrit
|
||||||
|
dans l'UE du semestre indiqué.
|
||||||
|
N'utilise que les coefficients, donc inadapté aux formations APC.
|
||||||
|
"""
|
||||||
|
return sum(
|
||||||
|
[
|
||||||
|
inscr.modimpl.module.coefficient
|
||||||
|
for inscr in cls.etud_modimpls_in_ue(formsemestre_id, etudid, ue_id)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ -54,13 +54,15 @@ class UniteEns(db.Model):
|
|||||||
'EXTERNE' if self.is_external else ''})>"""
|
'EXTERNE' if self.is_external else ''})>"""
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""as a dict, with the same conversions as in ScoDoc7"""
|
"""as a dict, with the same conversions as in ScoDoc7
|
||||||
|
(except ECTS: keep None)
|
||||||
|
"""
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
e.pop("_sa_instance_state", None)
|
e.pop("_sa_instance_state", None)
|
||||||
# ScoDoc7 output_formators
|
# ScoDoc7 output_formators
|
||||||
e["ue_id"] = self.id
|
e["ue_id"] = self.id
|
||||||
e["numero"] = e["numero"] if e["numero"] else 0
|
e["numero"] = e["numero"] if e["numero"] else 0
|
||||||
e["ects"] = e["ects"] if e["ects"] else 0.0
|
e["ects"] = e["ects"]
|
||||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||||
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
||||||
return e
|
return e
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
import html
|
import html
|
||||||
|
|
||||||
from flask import g
|
from flask import render_template
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
@ -280,6 +280,9 @@ def sco_header(
|
|||||||
if not no_side_bar:
|
if not no_side_bar:
|
||||||
H.append(html_sidebar.sidebar())
|
H.append(html_sidebar.sidebar())
|
||||||
H.append("""<div id="gtrcontent">""")
|
H.append("""<div id="gtrcontent">""")
|
||||||
|
# En attendant le replacement complet de cette fonction,
|
||||||
|
# inclusion ici des messages flask
|
||||||
|
H.append(render_template("flashed_messages.html"))
|
||||||
#
|
#
|
||||||
# Barre menu semestre:
|
# Barre menu semestre:
|
||||||
H.append(formsemestre_page_title())
|
H.append(formsemestre_page_title())
|
||||||
|
@ -98,7 +98,7 @@ from chardet import detect as chardet_detect
|
|||||||
from app import log
|
from app import log
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre, Identite
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||||
@ -111,7 +111,6 @@ from app.scodoc.sco_codes_parcours import (
|
|||||||
NAR,
|
NAR,
|
||||||
RAT,
|
RAT,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_cache
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
@ -454,6 +453,12 @@ class ApoEtud(dict):
|
|||||||
|
|
||||||
def comp_elt_semestre(self, nt, decision, etudid):
|
def comp_elt_semestre(self, nt, decision, etudid):
|
||||||
"""Calcul résultat apo semestre"""
|
"""Calcul résultat apo semestre"""
|
||||||
|
if decision is None:
|
||||||
|
etud = Identite.query.get(etudid)
|
||||||
|
nomprenom = etud.nomprenom if etud else "(inconnu)"
|
||||||
|
raise ScoValueError(
|
||||||
|
f"decision absente pour l'étudiant {nomprenom} ({etudid})"
|
||||||
|
)
|
||||||
# resultat du semestre
|
# resultat du semestre
|
||||||
decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"])
|
decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"])
|
||||||
note = nt.get_etud_moy_gen(etudid)
|
note = nt.get_etud_moy_gen(etudid)
|
||||||
|
@ -291,15 +291,17 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["matieres_modules"] = {}
|
I["matieres_modules"] = {}
|
||||||
I["matieres_modules_capitalized"] = {}
|
I["matieres_modules_capitalized"] = {}
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
|
u = ue.copy()
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
if (
|
if (
|
||||||
ModuleImplInscription.nb_inscriptions_dans_ue(
|
ModuleImplInscription.nb_inscriptions_dans_ue(
|
||||||
formsemestre_id, etudid, ue["ue_id"]
|
formsemestre_id, etudid, ue["ue_id"]
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
):
|
) and not ue_status["is_capitalized"]:
|
||||||
|
# saute les UE où l'on est pas inscrit et n'avons pas de capitalisation
|
||||||
continue
|
continue
|
||||||
u = ue.copy()
|
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
|
||||||
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
||||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
||||||
|
@ -282,7 +282,7 @@ class TypeParcours(object):
|
|||||||
return [
|
return [
|
||||||
ue_status
|
ue_status
|
||||||
for ue_status in ues_status
|
for ue_status in ues_status
|
||||||
if ue_status["coef_ue"] > 0
|
if ue_status["coef_ue"]
|
||||||
and isinstance(ue_status["moy"], float)
|
and isinstance(ue_status["moy"], float)
|
||||||
and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"])
|
and ue_status["moy"] < self.get_barre_ue(ue_status["ue"]["type"])
|
||||||
]
|
]
|
||||||
|
@ -186,6 +186,7 @@ def _send_db(ano_db_name):
|
|||||||
|
|
||||||
log("uploading anonymized dump...")
|
log("uploading anonymized dump...")
|
||||||
files = {"file": (ano_db_name + ".dump", dump)}
|
files = {"file": (ano_db_name + ".dump", dump)}
|
||||||
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
scu.SCO_DUMP_UP_URL,
|
scu.SCO_DUMP_UP_URL,
|
||||||
files=files,
|
files=files,
|
||||||
@ -198,6 +199,15 @@ def _send_db(ano_db_name):
|
|||||||
"sco_fullversion": scu.get_scodoc_version(),
|
"sco_fullversion": scu.get_scodoc_version(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
except requests.exceptions.ConnectionError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
"""
|
||||||
|
Impossible de joindre le serveur d'assistance (scodoc.org).
|
||||||
|
Veuillez contacter le service informatique de votre établissement pour
|
||||||
|
corriger la configuration de ScoDoc. Dans la plupart des cas, il
|
||||||
|
s'agit d'un proxy mal configuré.
|
||||||
|
"""
|
||||||
|
) from exc
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@ -551,7 +551,11 @@ def module_edit(module_id=None):
|
|||||||
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
|
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
|
||||||
module_types = (
|
module_types = (
|
||||||
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
|
set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
|
||||||
) | {a_module.module_type or scu.ModuleType.STANDARD}
|
) | {
|
||||||
|
scu.ModuleType(a_module.module_type)
|
||||||
|
if a_module.module_type
|
||||||
|
else scu.ModuleType.STANDARD
|
||||||
|
}
|
||||||
|
|
||||||
descr = [
|
descr = [
|
||||||
(
|
(
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, render_template
|
from flask import flash, render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
@ -89,6 +89,7 @@ _ueEditor = ndb.EditableTable(
|
|||||||
input_formators={
|
input_formators={
|
||||||
"type": ndb.int_null_is_zero,
|
"type": ndb.int_null_is_zero,
|
||||||
"is_external": ndb.bool_or_str,
|
"is_external": ndb.bool_or_str,
|
||||||
|
"ects": ndb.float_null_is_null,
|
||||||
},
|
},
|
||||||
output_formators={
|
output_formators={
|
||||||
"numero": ndb.int_null_is_zero,
|
"numero": ndb.int_null_is_zero,
|
||||||
@ -107,8 +108,6 @@ def ue_list(*args, **kw):
|
|||||||
|
|
||||||
def do_ue_create(args):
|
def do_ue_create(args):
|
||||||
"create an ue"
|
"create an ue"
|
||||||
from app.scodoc import sco_formations
|
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
# check duplicates
|
# check duplicates
|
||||||
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
ues = ue_list({"formation_id": args["formation_id"], "acronyme": args["acronyme"]})
|
||||||
@ -117,6 +116,14 @@ def do_ue_create(args):
|
|||||||
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||||
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||||
)
|
)
|
||||||
|
if not "ue_code" in args:
|
||||||
|
# évite les conflits de code
|
||||||
|
while True:
|
||||||
|
cursor = db.session.execute("select notes_newid_ucod();")
|
||||||
|
code = cursor.fetchone()[0]
|
||||||
|
if UniteEns.query.filter_by(ue_code=code).count() == 0:
|
||||||
|
break
|
||||||
|
args["ue_code"] = code
|
||||||
# create
|
# create
|
||||||
ue_id = _ueEditor.create(cnx, args)
|
ue_id = _ueEditor.create(cnx, args)
|
||||||
|
|
||||||
@ -128,6 +135,8 @@ def do_ue_create(args):
|
|||||||
formation = Formation.query.get(args["formation_id"])
|
formation = Formation.query.get(args["formation_id"])
|
||||||
formation.invalidate_module_coefs()
|
formation.invalidate_module_coefs()
|
||||||
# news
|
# news
|
||||||
|
ue = UniteEns.query.get(ue_id)
|
||||||
|
flash(f"UE créée (code {ue.ue_code})")
|
||||||
formation = Formation.query.get(args["formation_id"])
|
formation = Formation.query.get(args["formation_id"])
|
||||||
sco_news.add(
|
sco_news.add(
|
||||||
typ=sco_news.NEWS_FORM,
|
typ=sco_news.NEWS_FORM,
|
||||||
@ -339,6 +348,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"title": "ECTS",
|
"title": "ECTS",
|
||||||
"explanation": "nombre de crédits ECTS",
|
"explanation": "nombre de crédits ECTS",
|
||||||
|
"allow_null": not is_apc, # ects requis en APC
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -462,8 +472,10 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
"semestre_id": tf[2]["semestre_idx"],
|
"semestre_id": tf[2]["semestre_idx"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
flash("UE créée")
|
||||||
else:
|
else:
|
||||||
do_ue_edit(tf[2])
|
do_ue_edit(tf[2])
|
||||||
|
flash("UE modifiée")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.ue_table",
|
"notes.ue_table",
|
||||||
@ -746,6 +758,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
H.append('<div class="formation_classic_infos">')
|
||||||
H.append(
|
H.append(
|
||||||
_ue_table_ues(
|
_ue_table_ues(
|
||||||
parcours,
|
parcours,
|
||||||
@ -775,7 +788,7 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
H.append("</div>")
|
||||||
H.append("</div>") # formation_ue_list
|
H.append("</div>") # formation_ue_list
|
||||||
|
|
||||||
if ues_externes:
|
if ues_externes:
|
||||||
@ -924,10 +937,10 @@ def _ue_table_ues(
|
|||||||
cur_ue_semestre_id = None
|
cur_ue_semestre_id = None
|
||||||
iue = 0
|
iue = 0
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
if ue["ects"]:
|
if ue["ects"] is None:
|
||||||
ue["ects_str"] = ", %g ECTS" % ue["ects"]
|
|
||||||
else:
|
|
||||||
ue["ects_str"] = ""
|
ue["ects_str"] = ""
|
||||||
|
else:
|
||||||
|
ue["ects_str"] = ", %g ECTS" % ue["ects"]
|
||||||
if editable:
|
if editable:
|
||||||
klass = "span_apo_edit"
|
klass = "span_apo_edit"
|
||||||
else:
|
else:
|
||||||
@ -1286,7 +1299,6 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||||||
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
|
||||||
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
(chaque UE doit avoir un acronyme unique dans la formation)"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# On ne peut pas supprimer le code UE:
|
# On ne peut pas supprimer le code UE:
|
||||||
if "ue_code" in args and not args["ue_code"]:
|
if "ue_code" in args and not args["ue_code"]:
|
||||||
del args["ue_code"]
|
del args["ue_code"]
|
||||||
|
@ -39,6 +39,7 @@ from app.scodoc.gen_tables import GenTable
|
|||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
@ -221,7 +222,10 @@ def search_etuds_infos(expnom=None, code_nip=None):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
if expnom and not may_be_nip:
|
if expnom and not may_be_nip:
|
||||||
expnom = expnom.upper() # les noms dans la BD sont en uppercase
|
expnom = expnom.upper() # les noms dans la BD sont en uppercase
|
||||||
|
try:
|
||||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
||||||
|
except ScoException:
|
||||||
|
etuds = []
|
||||||
else:
|
else:
|
||||||
code_nip = code_nip or expnom
|
code_nip = code_nip or expnom
|
||||||
if code_nip:
|
if code_nip:
|
||||||
|
@ -1078,7 +1078,7 @@ def formsemestre_status(formsemestre_id=None):
|
|||||||
"</p>",
|
"</p>",
|
||||||
]
|
]
|
||||||
|
|
||||||
if use_ue_coefs:
|
if use_ue_coefs and not formsemestre.formation.is_apc():
|
||||||
H.append(
|
H.append(
|
||||||
"""
|
"""
|
||||||
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
<p class="infop">utilise les coefficients d'UE pour calculer la moyenne générale.</p>
|
||||||
|
@ -585,15 +585,17 @@ def formsemestre_recap_parcours_table(
|
|||||||
else:
|
else:
|
||||||
H.append('<td colspan="%d"><em>en cours</em></td>')
|
H.append('<td colspan="%d"><em>en cours</em></td>')
|
||||||
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
||||||
# acronymes UEs auxquelles l'étudiant est inscrit:
|
# acronymes UEs auxquelles l'étudiant est inscrit (ou capitalisé)
|
||||||
# XXX il est probable que l'on doive ici ajouter les
|
|
||||||
# XXX UE capitalisées
|
|
||||||
ues = nt.get_ues_stat_dict(filter_sport=True)
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
etud_ue_status = {
|
||||||
|
ue["ue_id"]: nt.get_etud_ue_status(etudid, ue["ue_id"]) for ue in ues
|
||||||
|
}
|
||||||
ues = [
|
ues = [
|
||||||
ue
|
ue
|
||||||
for ue in ues
|
for ue in ues
|
||||||
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
|
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
|
||||||
|
or etud_ue_status[ue["ue_id"]]["is_capitalized"]
|
||||||
]
|
]
|
||||||
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
@ -644,7 +646,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
code = decisions_ue[ue["ue_id"]]["code"]
|
code = decisions_ue[ue["ue_id"]]["code"]
|
||||||
else:
|
else:
|
||||||
code = ""
|
code = ""
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = etud_ue_status[ue["ue_id"]]
|
||||||
moy_ue = ue_status["moy"] if ue_status else ""
|
moy_ue = ue_status["moy"] if ue_status else ""
|
||||||
explanation_ue = [] # list of strings
|
explanation_ue = [] # list of strings
|
||||||
if code == ADM:
|
if code == ADM:
|
||||||
|
@ -151,6 +151,8 @@ class Logo:
|
|||||||
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
|
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
|
||||||
"""
|
"""
|
||||||
self.logoname = secure_filename(logoname)
|
self.logoname = secure_filename(logoname)
|
||||||
|
if not self.logoname:
|
||||||
|
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
|
||||||
self.scodoc_dept_id = dept_id
|
self.scodoc_dept_id = dept_id
|
||||||
self.prefix = prefix or ""
|
self.prefix = prefix or ""
|
||||||
if self.scodoc_dept_id:
|
if self.scodoc_dept_id:
|
||||||
|
@ -139,9 +139,7 @@ class SituationEtudParcoursGeneric(object):
|
|||||||
# pour le DUT, le dernier est toujours S4.
|
# pour le DUT, le dernier est toujours S4.
|
||||||
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
|
# Ici: terminal si semestre == NB_SEM ou bien semestre_id==-1
|
||||||
# (licences et autres formations en 1 seule session))
|
# (licences et autres formations en 1 seule session))
|
||||||
self.semestre_non_terminal = (
|
self.semestre_non_terminal = self.sem["semestre_id"] != self.parcours.NB_SEM
|
||||||
self.sem["semestre_id"] != self.parcours.NB_SEM
|
|
||||||
) # True | False
|
|
||||||
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
if self.sem["semestre_id"] == NO_SEMESTRE_ID:
|
||||||
self.semestre_non_terminal = False
|
self.semestre_non_terminal = False
|
||||||
# Liste des semestres du parcours de cet étudiant:
|
# Liste des semestres du parcours de cet étudiant:
|
||||||
|
@ -220,12 +220,16 @@ class ScolarsPageTemplate(PageTemplate):
|
|||||||
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
|
PageTemplate.__init__(self, "ScolarsPageTemplate", [content])
|
||||||
self.logo = None
|
self.logo = None
|
||||||
logo = find_logo(
|
logo = find_logo(
|
||||||
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=None
|
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id
|
||||||
|
) or find_logo(
|
||||||
|
logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=""
|
||||||
)
|
)
|
||||||
if logo is None:
|
if logo is None:
|
||||||
# Also try to use PV background
|
# Also try to use PV background
|
||||||
logo = find_logo(
|
logo = find_logo(
|
||||||
logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=None
|
logoname="letter_background", dept_id=g.scodoc_dept_id
|
||||||
|
) or find_logo(
|
||||||
|
logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=""
|
||||||
)
|
)
|
||||||
if logo is not None:
|
if logo is not None:
|
||||||
self.background_image_filename = logo.filepath
|
self.background_image_filename = logo.filepath
|
||||||
|
@ -206,12 +206,18 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
background = find_logo(
|
background = find_logo(
|
||||||
logoname="pvjury_background",
|
logoname="pvjury_background",
|
||||||
dept_id=g.scodoc_dept_id,
|
dept_id=g.scodoc_dept_id,
|
||||||
|
) or find_logo(
|
||||||
|
logoname="pvjury_background",
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
prefix="",
|
prefix="",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
background = find_logo(
|
background = find_logo(
|
||||||
logoname="letter_background",
|
logoname="letter_background",
|
||||||
dept_id=g.scodoc_dept_id,
|
dept_id=g.scodoc_dept_id,
|
||||||
|
) or find_logo(
|
||||||
|
logoname="letter_background",
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
prefix="",
|
prefix="",
|
||||||
)
|
)
|
||||||
if not self.background_image_filename and background is not None:
|
if not self.background_image_filename and background is not None:
|
||||||
|
@ -50,7 +50,7 @@ import pydot
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask import url_for, make_response, jsonify
|
from flask import flash, url_for, make_response, jsonify
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from app import log
|
from app import log
|
||||||
@ -616,6 +616,16 @@ def bul_filename(sem, etud, format):
|
|||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def flash_errors(form):
|
||||||
|
"""Flashes form errors (version sommaire)"""
|
||||||
|
for field, errors in form.errors.items():
|
||||||
|
flash(
|
||||||
|
"Erreur: voir le champs %s" % (getattr(form, field).label.text,),
|
||||||
|
"warning",
|
||||||
|
)
|
||||||
|
# see https://getbootstrap.com/docs/4.0/components/alerts/
|
||||||
|
|
||||||
|
|
||||||
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
|
def sendCSVFile(data, filename): # DEPRECATED utiliser send_file
|
||||||
"""publication fichier CSV."""
|
"""publication fichier CSV."""
|
||||||
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
|
return send_file(data, filename=filename, mime=CSV_MIMETYPE, attached=True)
|
||||||
|
@ -138,7 +138,7 @@ div.head_message {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-family : arial, verdana, sans-serif ;
|
font-family : arial, verdana, sans-serif ;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
width: 40%;
|
width: 70%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,15 +287,15 @@ div.logo-insidebar {
|
|||||||
width: 75px; /* la marge fait 130px */
|
width: 75px; /* la marge fait 130px */
|
||||||
}
|
}
|
||||||
div.logo-logo {
|
div.logo-logo {
|
||||||
|
margin-left: -5px;
|
||||||
text-align: center ;
|
text-align: center ;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.logo-logo img {
|
div.logo-logo img {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
margin-top: -10px;
|
margin-top: 10px; /* -10px */
|
||||||
width: 128px;
|
width: 135px; /* 128px */
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
margin-left: -75px;
|
|
||||||
}
|
}
|
||||||
div.sidebar-bottom {
|
div.sidebar-bottom {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -1671,7 +1671,10 @@ div.formation_list_modules ul.notes_module_list {
|
|||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
span.missing_ue_ects {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
li.module_malus span.formation_module_tit {
|
li.module_malus span.formation_module_tit {
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -1703,8 +1706,11 @@ ul.notes_ue_list {
|
|||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.formation_classic_infos ul.notes_ue_list {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
li.notes_ue_list {
|
.formation_classic_infos li.notes_ue_list {
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
border: 1px solid maroon;
|
border: 1px solid maroon;
|
||||||
@ -1761,6 +1767,11 @@ ul.notes_module_list {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.ue_list_tit_sem {
|
||||||
|
font-size: 120%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.notes_ue_list a.stdlink {
|
.notes_ue_list a.stdlink {
|
||||||
color: #001084;
|
color: #001084;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 37 KiB |
@ -57,12 +57,10 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% for category, message in messages %}
|
||||||
{% for message in messages %}
|
<div class="alert alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||||
<div class="alert alert-info" role="alert">{{ message }}</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{# application content needs to be provided in the app_content block #}
|
{# application content needs to be provided in the app_content block #}
|
||||||
|
9
app/templates/flashed_messages.html
Normal file
9
app/templates/flashed_messages.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{# Message flask : utilisé uniquement par les anciennes pages ScoDoc #}
|
||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
<div class="head_message_container">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="head_message alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
@ -38,7 +38,8 @@
|
|||||||
{% set virg = joiner(", ") %}
|
{% set virg = joiner(", ") %}
|
||||||
<span class="ue_code">(
|
<span class="ue_code">(
|
||||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||||
{{ virg() }}{{ue.ects or 0}} ECTS)
|
{{ virg() }}{{ue.ects if ue.ects is not none
|
||||||
|
else '<span class="missing_ue_ects">aucun</span>'|safe}} ECTS)
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -23,12 +23,10 @@
|
|||||||
|
|
||||||
<div id="gtrcontent" class="gtrcontent">
|
<div id="gtrcontent" class="gtrcontent">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% for category, message in messages %}
|
||||||
{% for message in messages %}
|
<div class="alert alert-info alert-{{ category }}" role="alert">{{ message }}</div>
|
||||||
<div class="alert alert-info" role="alert">{{ message }}</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
{% if sco.sem %}
|
{% if sco.sem %}
|
||||||
|
@ -35,7 +35,7 @@ from operator import itemgetter
|
|||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import flash, jsonify, render_template, url_for
|
from flask import abort, flash, jsonify, render_template, url_for
|
||||||
from flask import current_app, g, request
|
from flask import current_app, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
@ -68,10 +68,14 @@ from app.scodoc import sco_utils as scu
|
|||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app import log, send_scodoc_alarm
|
from app import log, send_scodoc_alarm
|
||||||
|
|
||||||
from app.scodoc import scolog
|
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoInvalidIdType
|
from app.scodoc.sco_exceptions import (
|
||||||
|
AccessDenied,
|
||||||
|
ScoException,
|
||||||
|
ScoValueError,
|
||||||
|
ScoInvalidIdType,
|
||||||
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.pe import pe_view
|
from app.pe import pe_view
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
@ -2672,12 +2676,15 @@ def check_integrity_all():
|
|||||||
def moduleimpl_list(
|
def moduleimpl_list(
|
||||||
moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json"
|
moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json"
|
||||||
):
|
):
|
||||||
|
try:
|
||||||
data = sco_moduleimpl.moduleimpl_list(
|
data = sco_moduleimpl.moduleimpl_list(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
module_id=module_id,
|
module_id=module_id,
|
||||||
)
|
)
|
||||||
return scu.sendResult(data, format=format)
|
return scu.sendResult(data, format=format)
|
||||||
|
except ScoException:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/do_moduleimpl_withmodule_list") # ancien nom
|
@bp.route("/do_moduleimpl_withmodule_list") # ancien nom
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.64"
|
SCOVERSION = "9.1.66"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user