forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into entreprises
This commit is contained in:
commit
aa09a7ef07
@ -21,6 +21,7 @@ from flask import g
|
|||||||
|
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_codes_parcours import ParcoursDUT, ParcoursDUTMono
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ class BonusSport:
|
|||||||
etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
|
etud_moy_gen et etud_moy_ue ne sont PAS modifiés (mais utilisés par certains bonus non additifs).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen reste None)
|
# En classique, active un bonus sur les UEs: (dans ce cas bonus_moy_gen est ajusté pour le prendre en compte)
|
||||||
classic_use_bonus_ues = False
|
classic_use_bonus_ues = False
|
||||||
|
|
||||||
# Attributs virtuels:
|
# Attributs virtuels:
|
||||||
@ -198,23 +199,29 @@ class BonusSportAdditif(BonusSport):
|
|||||||
à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
seuil_moy_gen = 10.0 # seuls les bonus au dessus du seuil sont pris en compte
|
||||||
|
seuil_comptage = (
|
||||||
|
None # les points au dessus du seuil sont comptés (defaut: seuil_moy_gen)
|
||||||
|
)
|
||||||
proportion_point = 0.05 # multiplie les points au dessus du seuil
|
proportion_point = 0.05 # multiplie les points au dessus du seuil
|
||||||
|
|
||||||
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
|
||||||
sem_modimpl_moys_inscrits: les notes de sport
|
sem_modimpl_moys_inscrits: les notes de sport
|
||||||
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||||
modimpl_coefs_etuds_no_nan:
|
En classic: ndarray (nb_etuds, nb_mod_sport)
|
||||||
|
modimpl_coefs_etuds_no_nan: même shape, les coefs.
|
||||||
"""
|
"""
|
||||||
if 0 in sem_modimpl_moys_inscrits.shape:
|
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||||
# pas d'étudiants ou pas d'UE ou pas de module...
|
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||||
return
|
return
|
||||||
|
seuil_comptage = (
|
||||||
|
self.seuil_moy_gen if self.seuil_comptage is None else self.seuil_comptage
|
||||||
|
)
|
||||||
bonus_moy_arr = np.sum(
|
bonus_moy_arr = np.sum(
|
||||||
np.where(
|
np.where(
|
||||||
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
||||||
(sem_modimpl_moys_inscrits - self.seuil_moy_gen)
|
(sem_modimpl_moys_inscrits - seuil_comptage) * self.proportion_point,
|
||||||
* self.proportion_point,
|
|
||||||
0.0,
|
0.0,
|
||||||
),
|
),
|
||||||
axis=1,
|
axis=1,
|
||||||
@ -227,13 +234,27 @@ 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() or self.classic_use_bonus_ues:
|
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
|
||||||
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||||
self.bonus_ues = pd.DataFrame(
|
self.bonus_ues = pd.DataFrame(
|
||||||
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
|
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
|
||||||
)
|
)
|
||||||
|
elif self.classic_use_bonus_ues:
|
||||||
|
# Formations classiques apppliquant le bonus sur les UEs
|
||||||
|
# ici bonus_moy_arr = ndarray 1d nb_etuds
|
||||||
|
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||||
|
self.bonus_ues = pd.DataFrame(
|
||||||
|
np.stack([bonus_moy_arr] * len(ues_idx)).T,
|
||||||
|
index=self.etuds_idx,
|
||||||
|
columns=ues_idx,
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Bonus sur la moyenne générale seulement
|
# Bonus sur la moyenne générale seulement
|
||||||
self.bonus_moy_gen = pd.Series(
|
self.bonus_moy_gen = pd.Series(
|
||||||
@ -284,6 +305,7 @@ class BonusSportMultiplicatif(BonusSport):
|
|||||||
|
|
||||||
class BonusDirect(BonusSportAdditif):
|
class BonusDirect(BonusSportAdditif):
|
||||||
"""Bonus direct: les points sont directement ajoutés à la moyenne générale.
|
"""Bonus direct: les points sont directement ajoutés à la moyenne générale.
|
||||||
|
|
||||||
Les coefficients sont ignorés: tous les points de bonus sont sommés.
|
Les coefficients sont ignorés: tous les points de bonus sont sommés.
|
||||||
(rappel: la note est ramenée sur 20 avant application).
|
(rappel: la note est ramenée sur 20 avant application).
|
||||||
"""
|
"""
|
||||||
@ -294,47 +316,108 @@ class BonusDirect(BonusSportAdditif):
|
|||||||
proportion_point = 1.0
|
proportion_point = 1.0
|
||||||
|
|
||||||
|
|
||||||
class BonusAnnecy(BonusSport):
|
class BonusAisneStQuentin(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Aisne St Quentin
|
||||||
Il peut y avoir plusieurs modules de bonus.
|
|
||||||
Prend pour chaque étudiant la meilleure de ses notes bonus et
|
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
ajoute à chaque UE :
|
de l'Université de St Quentin non rattachés à une unité d'enseignement.
|
||||||
0.05 point si >=10,
|
</p>
|
||||||
0.1 point si >=12,
|
<ul>
|
||||||
0.15 point si >=14,
|
<li>Si la note est >= 10 et < 12.1, bonus de 0.1 point</li>
|
||||||
0.2 point si >=16,
|
<li>Si la note est >= 12.1 et < 14.1, bonus de 0.2 point</li>
|
||||||
0.25 point si >=18.
|
<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_iut_annecy"
|
name = "bonus_iutstq"
|
||||||
displayed_name = "IUT d'Annecy"
|
displayed_name = "IUT de Saint-Quentin"
|
||||||
|
|
||||||
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 math.prod(sem_modimpl_moys_inscrits.shape) == 0:
|
if 0 in sem_modimpl_moys_inscrits.shape:
|
||||||
# return # no etuds or no mod sport
|
# pas d'étudiants ou pas d'UE ou pas de module...
|
||||||
# Prend la note de chaque modimpl, sans considération d'UE
|
return
|
||||||
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
bonus_moy_arr = np.sum(
|
||||||
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
bonus = np.zeros(note_bonus_max.shape)
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
bonus[note_bonus_max >= 18.0] = 0.25
|
np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False)
|
||||||
bonus[note_bonus_max >= 16.0] = 0.20
|
|
||||||
bonus[note_bonus_max >= 14.0] = 0.15
|
|
||||||
bonus[note_bonus_max >= 12.0] = 0.10
|
|
||||||
bonus[note_bonus_max >= 10.0] = 0.05
|
|
||||||
|
|
||||||
# Bonus moyenne générale et sur les UE
|
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||||
self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
|
bonus_moy_arr[bonus_moy_arr >= 18.1] = 0.5
|
||||||
ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
bonus_moy_arr[bonus_moy_arr >= 16.1] = 0.4
|
||||||
nb_ues_no_bonus = len(ues_idx)
|
bonus_moy_arr[bonus_moy_arr >= 14.1] = 0.3
|
||||||
self.bonus_ues = pd.DataFrame(
|
bonus_moy_arr[bonus_moy_arr >= 12.1] = 0.2
|
||||||
np.stack([bonus] * nb_ues_no_bonus, axis=1),
|
bonus_moy_arr[bonus_moy_arr >= 10] = 0.1
|
||||||
columns=ues_idx,
|
|
||||||
index=self.etuds_idx,
|
self.bonus_additif(bonus_moy_arr)
|
||||||
dtype=float,
|
|
||||||
)
|
|
||||||
|
class BonusAmiens(BonusSportAdditif):
|
||||||
|
"""Bonus IUT Amiens pour les modules optionnels (sport, culture, ...).
|
||||||
|
|
||||||
|
Toute note non nulle, peu importe sa valeur, entraine un bonus de 0,1 point
|
||||||
|
sur toutes les moyennes d'UE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_amiens"
|
||||||
|
displayed_name = "IUT d'Amiens"
|
||||||
|
seuil_moy_gen = 0.0 # tous les points sont comptés
|
||||||
|
proportion_point = 1e10
|
||||||
|
bonus_max = 0.1
|
||||||
|
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||||
|
|
||||||
|
|
||||||
|
# Finalement ils n'en veulent pas.
|
||||||
|
# class BonusAnnecy(BonusSport):
|
||||||
|
# """Calcul bonus modules optionnels (sport), règle IUT d'Annecy.
|
||||||
|
|
||||||
|
# Il peut y avoir plusieurs modules de bonus.
|
||||||
|
# Prend pour chaque étudiant la meilleure de ses notes bonus et
|
||||||
|
# ajoute à chaque UE :<br>
|
||||||
|
# 0.05 point si >=10,<br>
|
||||||
|
# 0.1 point si >=12,<br>
|
||||||
|
# 0.15 point si >=14,<br>
|
||||||
|
# 0.2 point si >=16,<br>
|
||||||
|
# 0.25 point si >=18.
|
||||||
|
# """
|
||||||
|
|
||||||
|
# name = "bonus_iut_annecy"
|
||||||
|
# displayed_name = "IUT d'Annecy"
|
||||||
|
|
||||||
|
# def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
# """calcul du bonus"""
|
||||||
|
# # if math.prod(sem_modimpl_moys_inscrits.shape) == 0:
|
||||||
|
# # return # no etuds or no mod sport
|
||||||
|
# # Prend la note de chaque modimpl, sans considération d'UE
|
||||||
|
# if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||||
|
# sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||||
|
# # ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||||
|
# note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||||
|
# bonus = np.zeros(note_bonus_max.shape)
|
||||||
|
# bonus[note_bonus_max >= 10.0] = 0.05
|
||||||
|
# bonus[note_bonus_max >= 12.0] = 0.10
|
||||||
|
# bonus[note_bonus_max >= 14.0] = 0.15
|
||||||
|
# bonus[note_bonus_max >= 16.0] = 0.20
|
||||||
|
# bonus[note_bonus_max >= 18.0] = 0.25
|
||||||
|
|
||||||
|
# # Bonus moyenne générale et sur les UE
|
||||||
|
# self.bonus_moy_gen = pd.Series(bonus, index=self.etuds_idx, dtype=float)
|
||||||
|
# ues_idx = [ue.id for ue in self.formsemestre.query_ues(with_sport=False)]
|
||||||
|
# nb_ues_no_bonus = len(ues_idx)
|
||||||
|
# self.bonus_ues = pd.DataFrame(
|
||||||
|
# np.stack([bonus] * nb_ues_no_bonus, axis=1),
|
||||||
|
# columns=ues_idx,
|
||||||
|
# index=self.etuds_idx,
|
||||||
|
# dtype=float,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
class BonusBethune(BonusSportMultiplicatif):
|
class BonusBethune(BonusSportMultiplicatif):
|
||||||
@ -373,26 +456,150 @@ class BonusBezier(BonusSportAdditif):
|
|||||||
|
|
||||||
|
|
||||||
class BonusBordeaux1(BonusSportMultiplicatif):
|
class BonusBordeaux1(BonusSportMultiplicatif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1,
|
||||||
et UE.
|
sur moyenne générale et UEs.
|
||||||
|
<p>
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement.
|
de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement.
|
||||||
|
</p><p>
|
||||||
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
|
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
|
||||||
qui augmente la moyenne de chaque UE et la moyenne générale.
|
qui augmente la moyenne de chaque UE et la moyenne générale.<br>
|
||||||
Formule : le % = points>moyenne / 2
|
Formule : pourcentage = (points au dessus de 10) / 2
|
||||||
|
</p><p>
|
||||||
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_iutBordeaux1"
|
name = "bonus_iutBordeaux1"
|
||||||
displayed_name = "IUT de Bordeaux 1"
|
displayed_name = "IUT de Bordeaux"
|
||||||
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||||
seuil_moy_gen = 10.0
|
seuil_moy_gen = 10.0
|
||||||
amplitude = 0.005
|
amplitude = 0.005
|
||||||
|
|
||||||
|
|
||||||
|
# Exactement le même que Bordeaux:
|
||||||
|
class BonusBrest(BonusSportMultiplicatif):
|
||||||
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Brest,
|
||||||
|
sur moyenne générale et UEs.
|
||||||
|
<p>
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université (sport, théâtre) non rattachés à une unité d'enseignement.
|
||||||
|
</p><p>
|
||||||
|
Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un %
|
||||||
|
qui augmente la moyenne de chaque UE et la moyenne générale.<br>
|
||||||
|
Formule : pourcentage = (points au dessus de 10) / 2
|
||||||
|
</p><p>
|
||||||
|
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
||||||
|
</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iut_brest"
|
||||||
|
displayed_name = "IUT de Brest"
|
||||||
|
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||||
|
seuil_moy_gen = 10.0
|
||||||
|
amplitude = 0.005
|
||||||
|
|
||||||
|
|
||||||
|
class BonusCachan1(BonusSportAdditif):
|
||||||
|
"""Calcul bonus optionnels (sport, culture), règle IUT de Cachan 1.
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li> DUT/LP : la meilleure note d'option, si elle est supérieure à 10,
|
||||||
|
bonifie les moyennes d'UE (<b>sauf l'UE41 dont le code est UE41_E</b>) à raison
|
||||||
|
de <em>bonus = (option - 10)/10</em>.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li> BUT : la meilleure note d'option, si elle est supérieure à 10, bonifie
|
||||||
|
les moyennes d'UE à raison de <em>bonus = (option - 10)*5%</em>.</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_cachan1"
|
||||||
|
displayed_name = "IUT de Cachan 1"
|
||||||
|
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||||
|
proportion_point = 0.05
|
||||||
|
classic_use_bonus_ues = True
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus, avec réglage différent suivant le type de formation"""
|
||||||
|
# Prend la note de chaque modimpl, sans considération d'UE
|
||||||
|
if len(sem_modimpl_moys_inscrits.shape) > 2: # apc
|
||||||
|
sem_modimpl_moys_inscrits = sem_modimpl_moys_inscrits[:, :, 0]
|
||||||
|
# ici sem_modimpl_moys_inscrits est nb_etuds x nb_mods_bonus, en APC et en classic
|
||||||
|
note_bonus_max = np.max(sem_modimpl_moys_inscrits, axis=1) # 1d, nb_etuds
|
||||||
|
ues = self.formsemestre.query_ues(with_sport=False).all()
|
||||||
|
ues_idx = [ue.id for ue in ues]
|
||||||
|
|
||||||
|
if self.formsemestre.formation.is_apc(): # --- BUT
|
||||||
|
bonus_moy_arr = np.where(
|
||||||
|
note_bonus_max > self.seuil_moy_gen,
|
||||||
|
(note_bonus_max - self.seuil_moy_gen) * self.proportion_point,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.bonus_ues = pd.DataFrame(
|
||||||
|
np.stack([bonus_moy_arr] * len(ues)).T,
|
||||||
|
index=self.etuds_idx,
|
||||||
|
columns=ues_idx,
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
else: # --- DUT
|
||||||
|
# pareil mais proportion différente et exclusion d'une UE
|
||||||
|
proportion_point = 0.1
|
||||||
|
bonus_moy_arr = np.where(
|
||||||
|
note_bonus_max > self.seuil_moy_gen,
|
||||||
|
(note_bonus_max - self.seuil_moy_gen) * proportion_point,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
self.bonus_ues = pd.DataFrame(
|
||||||
|
np.stack([bonus_moy_arr] * len(ues)).T,
|
||||||
|
index=self.etuds_idx,
|
||||||
|
columns=ues_idx,
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
# Pas de bonus sur la ou les ue de code "UE41_E"
|
||||||
|
ue_exclues = [ue for ue in ues if ue.ue_code == "UE41_E"]
|
||||||
|
for ue in ue_exclues:
|
||||||
|
self.bonus_ues[ue.id] = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class BonusCalais(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT LCO.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT LCO peuvent suivre des enseignements optionnels non
|
||||||
|
rattachés à une unité d'enseignement. Les points au-dessus de 10
|
||||||
|
sur 20 obtenus dans chacune des matières optionnelles sont cumulés
|
||||||
|
dans la limite de 10 points. 6% de ces points cumulés s'ajoutent :
|
||||||
|
<ul>
|
||||||
|
<li><b>en DUT</b> à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
<li><b>en BUT et LP</b> à la moyenne des UE dont l'acronyme fini par <b>BS</b> (ex : UE2.1BS, UE32BS)
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_calais"
|
||||||
|
displayed_name = "IUT du Littoral"
|
||||||
|
bonus_max = 0.6
|
||||||
|
seuil_moy_gen = 10.0 # au dessus de 10
|
||||||
|
proportion_point = 0.06 # 6%
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
parcours = self.formsemestre.formation.get_parcours()
|
||||||
|
# Variantes de DUT ?
|
||||||
|
if (
|
||||||
|
isinstance(parcours, ParcoursDUT)
|
||||||
|
or parcours.TYPE_PARCOURS == ParcoursDUTMono.TYPE_PARCOURS
|
||||||
|
): # DUT
|
||||||
|
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||||
|
else:
|
||||||
|
self.classic_use_bonus_ues = True # pour les LP
|
||||||
|
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||||
|
ues = self.formsemestre.query_ues(with_sport=False).all()
|
||||||
|
ues_sans_bs = [
|
||||||
|
ue for ue in ues if ue.acronyme[-2:].upper() != "BS"
|
||||||
|
] # les 2 derniers cars forcés en majus
|
||||||
|
for ue in ues_sans_bs:
|
||||||
|
self.bonus_ues[ue.id] = 0.0
|
||||||
|
|
||||||
|
|
||||||
class BonusColmar(BonusSportAdditif):
|
class BonusColmar(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Colmar.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Colmar.
|
||||||
|
|
||||||
@ -417,19 +624,21 @@ class BonusColmar(BonusSportAdditif):
|
|||||||
class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
||||||
"""Bonus IUT1 de Grenoble
|
"""Bonus IUT1 de Grenoble
|
||||||
|
|
||||||
|
<p>
|
||||||
À compter de sept. 2021:
|
À compter de sept. 2021:
|
||||||
La note de sport est sur 20, et on calcule une bonification (en %)
|
La note de sport est sur 20, et on calcule une bonification (en %)
|
||||||
qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant
|
qui va s'appliquer à la moyenne de chaque UE du semestre en appliquant
|
||||||
la formule : bonification (en %) = (note-10)*0,5.
|
la formule : bonification (en %) = (note-10)*0,5.
|
||||||
|
</p><p>
|
||||||
Bonification qui ne s'applique que si la note est >10.
|
<em>La bonification ne s'applique que si la note est supérieure à 10.</em>
|
||||||
|
</p><p>
|
||||||
(Une note de 10 donne donc 0% de bonif ; note de 20 : 5% de bonif)
|
(Une note de 10 donne donc 0% de bonif, et une note de 20 : 5% de bonif)
|
||||||
|
</p><p>
|
||||||
Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
|
Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
|
||||||
Chaque point correspondait à 0.25% d'augmentation de la moyenne
|
Chaque point correspondait à 0.25% d'augmentation de la moyenne
|
||||||
générale.
|
générale.
|
||||||
Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
|
Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_iut1grenoble_2017"
|
name = "bonus_iut1grenoble_2017"
|
||||||
@ -456,15 +665,18 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
|||||||
class BonusLaRochelle(BonusSportAdditif):
|
class BonusLaRochelle(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de La Rochelle.
|
||||||
|
|
||||||
Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
|
<ul>
|
||||||
Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
|
<li>Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.</li>
|
||||||
note sur la moyenne générale du semestre (ou sur les UE en BUT).
|
<li>Si la note de sport est comprise entre 10 et 20 : ajout de 1% de cette
|
||||||
|
note sur la moyenne générale du semestre (ou sur les UE en BUT).</li>
|
||||||
|
</ul>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_iutlr"
|
name = "bonus_iutlr"
|
||||||
displayed_name = "IUT de La Rochelle"
|
displayed_name = "IUT de La Rochelle"
|
||||||
seuil_moy_gen = 10.0 # tous les points sont comptés
|
seuil_moy_gen = 10.0 # si bonus > 10,
|
||||||
proportion_point = 0.01
|
seuil_comptage = 0.0 # tous les points sont comptés
|
||||||
|
proportion_point = 0.01 # 1%
|
||||||
|
|
||||||
|
|
||||||
class BonusLeHavre(BonusSportMultiplicatif):
|
class BonusLeHavre(BonusSportMultiplicatif):
|
||||||
@ -483,16 +695,17 @@ class BonusLeHavre(BonusSportMultiplicatif):
|
|||||||
class BonusLeMans(BonusSportAdditif):
|
class BonusLeMans(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Le Mans.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Le Mans.
|
||||||
|
|
||||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
<p>Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
optionnelles sont cumulés.
|
optionnelles sont cumulés.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>En BUT: la moyenne de chacune des UE du semestre est augmentée de
|
||||||
|
2% du cumul des points de bonus;</li>
|
||||||
|
|
||||||
|
<li>En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
|
||||||
En BUT: la moyenne de chacune des UE du semestre est augmentée de
|
</li>
|
||||||
2% du cumul des points de bonus,
|
</ul>
|
||||||
|
<p>Dans tous les cas, le bonus est dans la limite de 0,5 point.</p>
|
||||||
En DUT/LP: la moyenne générale est augmentée de 5% du cumul des points bonus.
|
|
||||||
|
|
||||||
Dans tous les cas, le bonus est dans la limite de 0,5 point.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_iutlemans"
|
name = "bonus_iutlemans"
|
||||||
@ -516,12 +729,13 @@ class BonusLeMans(BonusSportAdditif):
|
|||||||
class BonusLille(BonusSportAdditif):
|
class BonusLille(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionnels (sport, culture), règle IUT Villeneuve d'Ascq
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Villeneuve d'Ascq
|
||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
<p>Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.
|
de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.
|
||||||
|
</p><p>
|
||||||
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
optionnelles sont cumulés et 4% (2% avant août 2010) de ces points cumulés
|
optionnelles sont cumulés et 4% (2% avant août 2010) de ces points cumulés
|
||||||
s'ajoutent à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
s'ajoutent à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_lille"
|
name = "bonus_lille"
|
||||||
@ -573,17 +787,19 @@ class BonusMulhouse(BonusSportAdditif):
|
|||||||
class BonusNantes(BonusSportAdditif):
|
class BonusNantes(BonusSportAdditif):
|
||||||
"""IUT de Nantes (Septembre 2018)
|
"""IUT de Nantes (Septembre 2018)
|
||||||
|
|
||||||
Nous avons différents types de bonification
|
<p>Nous avons différents types de bonification
|
||||||
(sport, culture, engagement citoyen).
|
(sport, culture, engagement citoyen).
|
||||||
|
</p><p>
|
||||||
Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
|
Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
|
||||||
la bonification totale ne doit pas excéder les 0,5 point.
|
la bonification totale ne doit pas excéder les 0,5 point.
|
||||||
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
|
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
|
||||||
|
</p><p>
|
||||||
Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura des modules
|
Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura
|
||||||
pour chaque activité (Sport, Associations, ...)
|
des modules pour chaque activité (Sport, Associations, ...)
|
||||||
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
|
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20,
|
||||||
valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
|
mais en fait ce sera la valeur de la bonification: entrer 0,1/20 signifiera
|
||||||
|
un bonus de 0,1 point la moyenne générale).
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_nantes"
|
name = "bonus_nantes"
|
||||||
@ -604,7 +820,8 @@ class BonusRoanne(BonusSportAdditif):
|
|||||||
displayed_name = "IUT de Roanne"
|
displayed_name = "IUT de Roanne"
|
||||||
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
|
||||||
apply_bonus_mg_to_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):
|
||||||
@ -627,13 +844,14 @@ class BonusStDenis(BonusSportAdditif):
|
|||||||
class BonusTours(BonusDirect):
|
class BonusTours(BonusDirect):
|
||||||
"""Calcul bonus sport & culture IUT Tours.
|
"""Calcul bonus sport & culture IUT Tours.
|
||||||
|
|
||||||
Les notes des UE bonus (ramenées sur 20) sont sommées
|
<p>Les notes des UE bonus (ramenées sur 20) sont sommées
|
||||||
et 1/40 (2,5%) est ajouté aux moyennes: soit à la moyenne générale,
|
et 1/40 (2,5%) est ajouté aux moyennes: soit à la moyenne générale,
|
||||||
soit pour le BUT à chaque moyenne d'UE.
|
soit pour le BUT à chaque moyenne d'UE.
|
||||||
|
</p><p>
|
||||||
Attention: en GEII, facteur 1/40, ailleurs facteur 1.
|
<em>Attention: en GEII, facteur 1/40, ailleurs facteur 1.</em>
|
||||||
|
</p><p>
|
||||||
Le bonus total est limité à 1 point.
|
Le bonus total est limité à 1 point.
|
||||||
|
</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_tours"
|
name = "bonus_tours"
|
||||||
@ -658,11 +876,13 @@ class BonusVilleAvray(BonusSport):
|
|||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
|
de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement.
|
||||||
Si la note est >= 10 et < 12, bonus de 0.1 point
|
<ul>
|
||||||
Si la note est >= 12 et < 16, bonus de 0.2 point
|
<li>Si la note est >= 10 et < 12, bonus de 0.1 point</li>
|
||||||
Si la note est >= 16, bonus de 0.3 point
|
<li>Si la note est >= 12 et < 16, bonus de 0.2 point</li>
|
||||||
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
<li>Si la note est >= 16, bonus de 0.3 point</li>
|
||||||
l'étudiant.
|
</ul>
|
||||||
|
<p>Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||||
|
l'étudiant.</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "bonus_iutva"
|
name = "bonus_iutva"
|
||||||
@ -670,21 +890,21 @@ 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(
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
bonus_moy_arr = np.sum(
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
np.nan_to_num(bonus_moy_arr, nan=0.0, copy=False)
|
||||||
|
bonus_moy_arr[bonus_moy_arr < 10.0] = 0.0
|
||||||
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):
|
||||||
@ -700,7 +920,7 @@ class BonusIUTV(BonusSportAdditif):
|
|||||||
|
|
||||||
name = "bonus_iutv"
|
name = "bonus_iutv"
|
||||||
displayed_name = "IUT de Villetaneuse"
|
displayed_name = "IUT de Villetaneuse"
|
||||||
pass # oui, c'ets le bonus par défaut
|
pass # oui, c'est le bonus par défaut
|
||||||
|
|
||||||
|
|
||||||
def get_bonus_class_dict(start=BonusSport, d=None):
|
def get_bonus_class_dict(start=BonusSport, d=None):
|
||||||
|
50
app/comp/moy_mat.py
Normal file
50
app/comp/moy_mat.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Calcul des moyennes de matières
|
||||||
|
"""
|
||||||
|
|
||||||
|
# C'est un recalcul (optionnel) effectué _après_ le calcul standard.
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from app.comp import moy_ue
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
|
def compute_mat_moys_classic(
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
sem_matrix: np.array,
|
||||||
|
ues: list,
|
||||||
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
|
modimpl_coefs: np.array,
|
||||||
|
) -> dict:
|
||||||
|
"""Calcul des moyennes par matières.
|
||||||
|
Result: dict, { matiere_id : Series, index etudid }
|
||||||
|
"""
|
||||||
|
modimpls_std = [
|
||||||
|
m
|
||||||
|
for m in formsemestre.modimpls_sorted
|
||||||
|
if (m.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (m.module.ue.type != UE_SPORT)
|
||||||
|
]
|
||||||
|
matiere_ids = {m.module.matiere.id for m in modimpls_std}
|
||||||
|
matiere_moy = {} # { matiere_id : moy pd.Series, index etudid }
|
||||||
|
for matiere_id in matiere_ids:
|
||||||
|
modimpl_mask = np.array(
|
||||||
|
[m.module.matiere.id == matiere_id for m in formsemestre.modimpls_sorted]
|
||||||
|
)
|
||||||
|
etud_moy_mat = moy_ue.compute_mat_moys_classic(
|
||||||
|
sem_matrix=sem_matrix,
|
||||||
|
modimpl_inscr_df=modimpl_inscr_df,
|
||||||
|
modimpl_coefs=modimpl_coefs,
|
||||||
|
modimpl_mask=modimpl_mask,
|
||||||
|
)
|
||||||
|
matiere_moy[matiere_id] = etud_moy_mat
|
||||||
|
return matiere_moy
|
@ -335,15 +335,17 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
notes_rat / (eval_rat.note_max / 20.0),
|
notes_rat / (eval_rat.note_max / 20.0),
|
||||||
np.nan,
|
np.nan,
|
||||||
)
|
)
|
||||||
|
# "Étend" le rattrapage sur les UE: la note de rattrapage est la même
|
||||||
|
# pour toutes les UE mais ne remplace que là où elle est supérieure
|
||||||
|
notes_rat_ues = np.stack([notes_rat] * nb_ues, axis=1)
|
||||||
# prend le max
|
# prend le max
|
||||||
etuds_use_rattrapage = notes_rat > etuds_moy_module
|
etuds_use_rattrapage = notes_rat_ues > etuds_moy_module
|
||||||
etuds_moy_module = np.where(
|
etuds_moy_module = np.where(
|
||||||
etuds_use_rattrapage[:, np.newaxis],
|
etuds_use_rattrapage, notes_rat_ues, etuds_moy_module
|
||||||
np.tile(notes_rat[:, np.newaxis], nb_ues),
|
|
||||||
etuds_moy_module,
|
|
||||||
)
|
)
|
||||||
|
# Serie indiquant que l'étudiant utilise une note de rattarage sur l'une des UE:
|
||||||
self.etuds_use_rattrapage = pd.Series(
|
self.etuds_use_rattrapage = pd.Series(
|
||||||
etuds_use_rattrapage, index=self.evals_notes.index
|
etuds_use_rattrapage.any(axis=1), index=self.evals_notes.index
|
||||||
)
|
)
|
||||||
self.etuds_moy_module = pd.DataFrame(
|
self.etuds_moy_module = pd.DataFrame(
|
||||||
etuds_moy_module,
|
etuds_moy_module,
|
||||||
@ -359,6 +361,10 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||||
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
|
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
|
||||||
(sauf pour module bonus, defaut à 1)
|
(sauf pour module bonus, defaut à 1)
|
||||||
|
|
||||||
|
Si le module n'est pas une ressource ou une SAE, ne charge pas de poids
|
||||||
|
et renvoie toujours les poids par défaut.
|
||||||
|
|
||||||
Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
|
Résultat: (evals_poids, liste de UEs du semestre sauf le sport)
|
||||||
"""
|
"""
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
@ -367,13 +373,17 @@ def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
|||||||
ue_ids = [ue.id for ue in ues]
|
ue_ids = [ue.id for ue in ues]
|
||||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||||
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||||
for ue_poids in EvaluationUEPoids.query.join(
|
if (
|
||||||
EvaluationUEPoids.evaluation
|
modimpl.module.module_type == ModuleType.RESSOURCE
|
||||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
or modimpl.module.module_type == ModuleType.SAE
|
||||||
try:
|
):
|
||||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
for ue_poids in EvaluationUEPoids.query.join(
|
||||||
except KeyError as exc:
|
EvaluationUEPoids.evaluation
|
||||||
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||||
|
try:
|
||||||
|
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||||
|
except KeyError as exc:
|
||||||
|
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
||||||
|
|
||||||
# Initialise poids non enregistrés:
|
# Initialise poids non enregistrés:
|
||||||
default_poids = (
|
default_poids = (
|
||||||
|
@ -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.
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT)
|
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT)
|
||||||
"""
|
"""
|
||||||
from re import X
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@ -218,21 +217,25 @@ def compute_ue_moys_apc(
|
|||||||
ues: 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,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
ERR erreur dans une formule utilisateurs (pas gérées ici).
|
||||||
|
|
||||||
sem_cube: notes moyennes aux modules
|
sem_cube: notes moyennes aux modules
|
||||||
ndarray (etuds x modimpls x UEs)
|
ndarray (etuds x modimpls x UEs)
|
||||||
(floats avec des NaN)
|
(floats avec des NaN)
|
||||||
etuds : liste des étudiants (dim. 0 du cube)
|
etuds : liste des étudiants (dim. 0 du cube)
|
||||||
modimpls : liste des modules à considérer (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 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.
|
||||||
|
(utilisé pour éliminer les bonus, et pourra servir à cacluler
|
||||||
|
sur des sous-ensembles de modules)
|
||||||
|
|
||||||
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
Résultat: DataFrame columns UE (sans bonus), rows etudid
|
||||||
"""
|
"""
|
||||||
@ -249,7 +252,8 @@ def compute_ue_moys_apc(
|
|||||||
assert modimpl_coefs_df.shape[0] == nb_ues_no_bonus
|
assert modimpl_coefs_df.shape[0] == nb_ues_no_bonus
|
||||||
assert modimpl_coefs_df.shape[1] == nb_modules
|
assert modimpl_coefs_df.shape[1] == nb_modules
|
||||||
modimpl_inscr = modimpl_inscr_df.values
|
modimpl_inscr = modimpl_inscr_df.values
|
||||||
modimpl_coefs = modimpl_coefs_df.values
|
# Met à zéro tous les coefs des modules non sélectionnés dans le masque:
|
||||||
|
modimpl_coefs = np.where(modimpl_mask, modimpl_coefs_df.values, 0.0)
|
||||||
|
|
||||||
# Duplique les inscriptions sur les UEs non bonus:
|
# Duplique les inscriptions sur les UEs non bonus:
|
||||||
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
|
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
|
||||||
@ -290,7 +294,8 @@ def compute_ue_moys_classic(
|
|||||||
modimpl_coefs: np.array,
|
modimpl_coefs: np.array,
|
||||||
modimpl_mask: np.array,
|
modimpl_mask: np.array,
|
||||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||||
"""Calcul de la moyenne d'UE en mode classique.
|
"""Calcul de la moyenne d'UE et de la moy. générale en mode classique (DUT, LMD, ...).
|
||||||
|
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
@ -359,7 +364,7 @@ def compute_ue_moys_classic(
|
|||||||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||||
)
|
)
|
||||||
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions
|
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions:
|
||||||
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||||
if coefs.dtype == np.object: # arrive sur des tableaux vides
|
if coefs.dtype == np.object: # arrive sur des tableaux vides
|
||||||
coefs = coefs.astype(np.float)
|
coefs = coefs.astype(np.float)
|
||||||
@ -404,6 +409,68 @@ def compute_ue_moys_classic(
|
|||||||
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||||
|
|
||||||
|
|
||||||
|
def compute_mat_moys_classic(
|
||||||
|
sem_matrix: np.array,
|
||||||
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
|
modimpl_coefs: np.array,
|
||||||
|
modimpl_mask: np.array,
|
||||||
|
) -> pd.Series:
|
||||||
|
"""Calcul de la moyenne sur un sous-enemble de modules en formation CLASSIQUE
|
||||||
|
|
||||||
|
La moyenne est un nombre (note/20 ou NaN.
|
||||||
|
|
||||||
|
Le masque modimpl_mask est un tableau de booléens (un par modimpl) qui
|
||||||
|
permet de sélectionner un sous-ensemble de modules (ceux de la matière d'intérêt).
|
||||||
|
|
||||||
|
sem_matrix: notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||||
|
ndarray (etuds x modimpls)
|
||||||
|
(floats avec des NaN)
|
||||||
|
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||||
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
|
modimpl_coefs: vecteur des coefficients de modules
|
||||||
|
modimpl_mask: masque des modimpls à prendre en compte
|
||||||
|
|
||||||
|
Résultat:
|
||||||
|
- moyennes: pd.Series, index etudid
|
||||||
|
"""
|
||||||
|
if (not 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
|
||||||
|
return pd.Series(
|
||||||
|
[0.0] * len(modimpl_inscr_df.index), index=modimpl_inscr_df.index
|
||||||
|
)
|
||||||
|
# Restreint aux modules sélectionnés:
|
||||||
|
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||||
|
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||||
|
modimpl_coefs = modimpl_coefs[modimpl_mask]
|
||||||
|
|
||||||
|
nb_etuds, nb_modules = sem_matrix.shape
|
||||||
|
assert len(modimpl_coefs) == nb_modules
|
||||||
|
|
||||||
|
# Enlève les NaN du numérateur:
|
||||||
|
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes:
|
||||||
|
sem_matrix_inscrits = np.where(modimpl_inscr, sem_matrix_no_nan, 0.0)
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules NaN (nb_etuds x nb_mods)
|
||||||
|
modimpl_coefs_etuds_no_nan = np.where(
|
||||||
|
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||||
|
)
|
||||||
|
if modimpl_coefs_etuds_no_nan.dtype == np.object: # arrive sur des tableaux vides
|
||||||
|
modimpl_coefs_etuds_no_nan = modimpl_coefs_etuds_no_nan.astype(np.float)
|
||||||
|
|
||||||
|
etud_moy_mat = (modimpl_coefs_etuds_no_nan * sem_matrix_inscrits).sum(
|
||||||
|
axis=1
|
||||||
|
) / modimpl_coefs_etuds_no_nan.sum(axis=1)
|
||||||
|
|
||||||
|
return pd.Series(etud_moy_mat, index=modimpl_inscr_df.index)
|
||||||
|
|
||||||
|
|
||||||
def compute_malus(
|
def compute_malus(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
sem_modimpl_moys: np.array,
|
sem_modimpl_moys: np.array,
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -56,14 +56,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
|
|
||||||
# Elimine les coefs des modimpl bonus sports:
|
# Masque de tous les modules _sauf_ les bonus (sport)
|
||||||
modimpls_sport = [
|
modimpls_mask = [
|
||||||
modimpl
|
modimpl.module.ue.type != UE_SPORT
|
||||||
for modimpl in self.formsemestre.modimpls_sorted
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
if modimpl.module.ue.type == UE_SPORT
|
|
||||||
]
|
]
|
||||||
for modimpl in modimpls_sport:
|
|
||||||
self.modimpl_coefs_df[modimpl.id] = 0
|
|
||||||
|
|
||||||
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,
|
||||||
@ -72,10 +69,11 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.ues,
|
self.ues,
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs_df,
|
self.modimpl_coefs_df,
|
||||||
|
modimpls_mask,
|
||||||
)
|
)
|
||||||
# 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
|
||||||
@ -85,7 +83,7 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.etud_moy_ue -= self.malus
|
self.etud_moy_ue -= self.malus
|
||||||
|
|
||||||
# --- Bonus Sport & Culture
|
# --- Bonus Sport & Culture
|
||||||
if len(modimpls_sport) > 0:
|
if not all(modimpls_mask): # au moins un module bonus
|
||||||
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||||
if bonus_class is not None:
|
if bonus_class is not None:
|
||||||
bonus: BonusSport = bonus_class(
|
bonus: BonusSport = bonus_class(
|
||||||
@ -100,13 +98,20 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.bonus_ues = bonus.get_bonus_ues()
|
self.bonus_ues = bonus.get_bonus_ues()
|
||||||
if self.bonus_ues is not None:
|
if self.bonus_ues is not None:
|
||||||
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
|
||||||
|
# Clippe toutes les moyennes d'UE dans [0,20]
|
||||||
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
|
||||||
# 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()
|
||||||
|
@ -15,7 +15,7 @@ from flask import g, url_for
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.comp import moy_mod, moy_ue, inscr_mod
|
from app.comp import moy_mat, moy_mod, moy_ue, 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
|
||||||
@ -24,6 +24,7 @@ from app.models.formsemestre import 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
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
)
|
)
|
||||||
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||||
self.modimpl_coefs = np.array(
|
self.modimpl_coefs = np.array(
|
||||||
[m.module.coefficient for m in self.formsemestre.modimpls_sorted]
|
[m.module.coefficient or 0.0 for m in self.formsemestre.modimpls_sorted]
|
||||||
)
|
)
|
||||||
self.modimpl_idx = {
|
self.modimpl_idx = {
|
||||||
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
|
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
|
||||||
@ -113,17 +114,34 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
bonus_mg = bonus.get_bonus_moy_gen()
|
bonus_mg = bonus.get_bonus_moy_gen()
|
||||||
if bonus_mg is not None:
|
if bonus_mg is None and self.bonus_ues is not None:
|
||||||
|
# pas de bonus explicite sur la moyenne générale
|
||||||
|
# on l'ajuste pour refléter les modifs d'UE, à l'aide des coefs d'UE.
|
||||||
|
bonus_mg = (self.etud_coef_ue_df * self.bonus_ues).sum(
|
||||||
|
axis=1
|
||||||
|
) / self.etud_coef_ue_df.sum(axis=1)
|
||||||
self.etud_moy_gen += bonus_mg
|
self.etud_moy_gen += bonus_mg
|
||||||
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
elif bonus_mg is not None:
|
||||||
# compat nt, utilisé pour l'afficher sur les bulletins:
|
# Applique le bonus moyenne générale renvoyé
|
||||||
self.bonus = bonus_mg
|
self.etud_moy_gen += bonus_mg
|
||||||
|
|
||||||
|
# compat nt, utilisé pour l'afficher sur les bulletins:
|
||||||
|
self.bonus = bonus_mg
|
||||||
|
|
||||||
# --- UE capitalisées
|
# --- UE capitalisées
|
||||||
self.apply_capitalisation()
|
self.apply_capitalisation()
|
||||||
|
|
||||||
|
# Clippe toutes les moyennes dans [0,20]
|
||||||
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
|
||||||
# --- Classements:
|
# --- Classements:
|
||||||
self.compute_rangs()
|
self.compute_rangs()
|
||||||
|
|
||||||
|
# --- En option, moyennes par matières
|
||||||
|
if sco_preferences.get_preference("bul_show_matieres", self.formsemestre.id):
|
||||||
|
self.compute_moyennes_matieres()
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
"""La moyenne de l'étudiant dans le moduleimpl
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
||||||
@ -149,6 +167,16 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def compute_moyennes_matieres(self):
|
||||||
|
"""Calcul les moyennes par matière. Doit être appelée au besoin, en fin de compute."""
|
||||||
|
self.moyennes_matieres = moy_mat.compute_mat_moys_classic(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_matrix,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs,
|
||||||
|
)
|
||||||
|
|
||||||
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
|
||||||
"""Détermine le coefficient de l'UE pour cet étudiant.
|
"""Détermine le coefficient de l'UE pour cet étudiant.
|
||||||
N'est utilisé que pour l'injection des UE capitalisées dans la
|
N'est utilisé que pour l'injection des UE capitalisées dans la
|
||||||
|
@ -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):
|
||||||
@ -39,6 +43,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"modimpl_inscr_df",
|
"modimpl_inscr_df",
|
||||||
"modimpls_results",
|
"modimpls_results",
|
||||||
"etud_coef_ue_df",
|
"etud_coef_ue_df",
|
||||||
|
"moyennes_matieres",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
@ -57,6 +62,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
self.etud_coef_ue_df = None
|
self.etud_coef_ue_df = None
|
||||||
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
"""coefs d'UE effectifs pour chaque étudiant (pour form. classiques)"""
|
||||||
self.validations = None
|
self.validations = None
|
||||||
|
self.moyennes_matieres = {}
|
||||||
|
"""Moyennes de matières, si calculées. { matiere_id : Series, index etudid }"""
|
||||||
|
|
||||||
def compute(self):
|
def compute(self):
|
||||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||||
@ -165,7 +172,6 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"""
|
"""
|
||||||
# Supposant qu'il y a peu d'UE capitalisées,
|
# Supposant qu'il y a peu d'UE capitalisées,
|
||||||
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
|
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
|
||||||
# return # XXX XXX XXX
|
|
||||||
if not self.validations:
|
if not self.validations:
|
||||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||||
ue_capitalisees = self.validations.ue_capitalisees
|
ue_capitalisees = self.validations.ue_capitalisees
|
||||||
@ -184,10 +190,12 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
sum_coefs_ue = 0.0
|
sum_coefs_ue = 0.0
|
||||||
for ue in self.formsemestre.query_ues():
|
for ue in self.formsemestre.query_ues():
|
||||||
ue_cap = self.get_etud_ue_status(etudid, ue.id)
|
ue_cap = self.get_etud_ue_status(etudid, ue.id)
|
||||||
if ue_cap and ue_cap["is_capitalized"]:
|
if ue_cap is None:
|
||||||
|
continue
|
||||||
|
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
|
||||||
|
|
||||||
@ -195,13 +203,25 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
# On doit prendre en compte une ou plusieurs UE capitalisées
|
# On doit prendre en compte une ou plusieurs UE capitalisées
|
||||||
# et donc recalculer la moyenne générale
|
# et donc recalculer la moyenne générale
|
||||||
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
|
self.etud_moy_gen[etudid] = sum_notes_ue / sum_coefs_ue
|
||||||
|
# Ajoute le bonus sport
|
||||||
|
if self.bonus is not None and self.bonus[etudid]:
|
||||||
|
self.etud_moy_gen[etudid] += self.bonus[etudid]
|
||||||
|
self.etud_moy_gen[etudid] = max(
|
||||||
|
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]
|
||||||
@ -209,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.
|
||||||
@ -238,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_ue = self.etud_coef_ue_df[ue_id][etudid]
|
# 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]
|
||||||
|
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,
|
||||||
@ -375,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),
|
||||||
@ -508,10 +562,15 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
return ""
|
return ""
|
||||||
return ins.etat
|
return ins.etat
|
||||||
|
|
||||||
def get_etud_mat_moy(self, matiere_id, etudid):
|
def get_etud_mat_moy(self, matiere_id: int, etudid: int) -> str:
|
||||||
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
|
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
|
||||||
# non supporté en 9.2
|
if not self.moyennes_matieres:
|
||||||
return "na"
|
return "nd"
|
||||||
|
return (
|
||||||
|
self.moyennes_matieres[matiere_id].get(etudid, "-")
|
||||||
|
if matiere_id in self.moyennes_matieres
|
||||||
|
else "-"
|
||||||
|
)
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
"""La moyenne de l'étudiant dans le moduleimpl
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
|
@ -8,11 +8,13 @@
|
|||||||
"""
|
"""
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
|
from app import db
|
||||||
from app.comp.jury import ValidationsSemestre
|
from app.comp.jury import ValidationsSemestre
|
||||||
from app.comp.res_common import ResultatsSemestre
|
from app.comp.res_common import ResultatsSemestre
|
||||||
from app.comp.res_classic import ResultatsSemestreClassic
|
from app.comp.res_classic import ResultatsSemestreClassic
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc import sco_cache
|
||||||
|
|
||||||
|
|
||||||
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
||||||
@ -23,6 +25,13 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
|||||||
Search in local cache (g.formsemestre_result_cache)
|
Search in local cache (g.formsemestre_result_cache)
|
||||||
If not in cache, build it and cache it.
|
If not in cache, build it and cache it.
|
||||||
"""
|
"""
|
||||||
|
is_apc = formsemestre.formation.is_apc()
|
||||||
|
if is_apc and formsemestre.semestre_id == -1:
|
||||||
|
formsemestre.semestre_id = 1
|
||||||
|
db.session.add(formsemestre)
|
||||||
|
db.session.commit()
|
||||||
|
sco_cache.invalidate_formsemestre(formsemestre.id)
|
||||||
|
|
||||||
# --- Try local cache (within the same request context)
|
# --- Try local cache (within the same request context)
|
||||||
if not hasattr(g, "formsemestre_results_cache"):
|
if not hasattr(g, "formsemestre_results_cache"):
|
||||||
g.formsemestre_results_cache = {}
|
g.formsemestre_results_cache = {}
|
||||||
@ -30,11 +39,7 @@ def load_formsemestre_results(formsemestre: FormSemestre) -> ResultatsSemestre:
|
|||||||
if formsemestre.id in g.formsemestre_results_cache:
|
if formsemestre.id in g.formsemestre_results_cache:
|
||||||
return g.formsemestre_results_cache[formsemestre.id]
|
return g.formsemestre_results_cache[formsemestre.id]
|
||||||
|
|
||||||
klass = (
|
klass = ResultatsSemestreBUT if is_apc else ResultatsSemestreClassic
|
||||||
ResultatsSemestreBUT
|
|
||||||
if formsemestre.formation.is_apc()
|
|
||||||
else ResultatsSemestreClassic
|
|
||||||
)
|
|
||||||
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
g.formsemestre_results_cache[formsemestre.id] = klass(formsemestre)
|
||||||
return g.formsemestre_results_cache[formsemestre.id]
|
return g.formsemestre_results_cache[formsemestre.id]
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -55,6 +55,9 @@ def create_dept(acronym: str, visible=True) -> Departement:
|
|||||||
"Create new departement"
|
"Create new departement"
|
||||||
from app.models import ScoPreference
|
from app.models import ScoPreference
|
||||||
|
|
||||||
|
existing = Departement.query.filter_by(acronym=acronym).count()
|
||||||
|
if existing:
|
||||||
|
raise ValueError(f"acronyme {acronym} déjà existant")
|
||||||
departement = Departement(acronym=acronym, visible=visible)
|
departement = Departement(acronym=acronym, visible=visible)
|
||||||
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
|
p1 = ScoPreference(name="DeptName", value=acronym, departement=departement)
|
||||||
db.session.add(p1)
|
db.session.add(p1)
|
||||||
|
@ -104,6 +104,11 @@ class FormSemestre(db.Model):
|
|||||||
lazy=True,
|
lazy=True,
|
||||||
backref=db.backref("formsemestres", lazy=True),
|
backref=db.backref("formsemestres", lazy=True),
|
||||||
)
|
)
|
||||||
|
partitions = db.relationship(
|
||||||
|
"Partition",
|
||||||
|
backref=db.backref("formsemestre", lazy=True),
|
||||||
|
lazy="dynamic",
|
||||||
|
)
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
# ne pas utiliser après migrate_scodoc7_dept_archives
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
@ -201,7 +206,11 @@ class FormSemestre(db.Model):
|
|||||||
modimpls = self.modimpls.all()
|
modimpls = self.modimpls.all()
|
||||||
if self.formation.is_apc():
|
if self.formation.is_apc():
|
||||||
modimpls.sort(
|
modimpls.sort(
|
||||||
key=lambda m: (m.module.module_type, m.module.numero, m.module.code)
|
key=lambda m: (
|
||||||
|
m.module.module_type or 0,
|
||||||
|
m.module.numero or 0,
|
||||||
|
m.module.code or 0,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
modimpls.sort(
|
modimpls.sort(
|
||||||
|
@ -31,6 +31,11 @@ class Partition(db.Model):
|
|||||||
show_in_lists = db.Column(
|
show_in_lists = db.Column(
|
||||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||||
)
|
)
|
||||||
|
groups = db.relationship(
|
||||||
|
"GroupDescr",
|
||||||
|
backref=db.backref("partition", lazy=True),
|
||||||
|
lazy="dynamic",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Partition, self).__init__(**kwargs)
|
super(Partition, self).__init__(**kwargs)
|
||||||
@ -42,6 +47,9 @@ class Partition(db.Model):
|
|||||||
else:
|
else:
|
||||||
self.numero = 1
|
self.numero = 1
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"""<{self.__class__.__name__} {self.id} "{self.partition_name or '(default)'}">"""
|
||||||
|
|
||||||
|
|
||||||
class GroupDescr(db.Model):
|
class GroupDescr(db.Model):
|
||||||
"""Description d'un groupe d'une partition"""
|
"""Description d'un groupe d'une partition"""
|
||||||
@ -55,6 +63,11 @@ class GroupDescr(db.Model):
|
|||||||
# "A", "C2", ... (NULL for 'all'):
|
# "A", "C2", ... (NULL for 'all'):
|
||||||
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
group_name = db.Column(db.String(GROUPNAME_STR_LEN))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (
|
||||||
|
f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
group_membership = db.Table(
|
group_membership = db.Table(
|
||||||
"group_membership",
|
"group_membership",
|
||||||
|
@ -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
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import send_file, request
|
from flask import send_file, request
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
@ -97,8 +98,12 @@ def pe_view_sem_recap(
|
|||||||
template_latex = ""
|
template_latex = ""
|
||||||
# template fourni via le formulaire Web
|
# template fourni via le formulaire Web
|
||||||
if avis_tmpl_file:
|
if avis_tmpl_file:
|
||||||
template_latex = avis_tmpl_file.read().decode('utf-8')
|
try:
|
||||||
template_latex = template_latex
|
template_latex = avis_tmpl_file.read().decode("utf-8")
|
||||||
|
except UnicodeDecodeError as e:
|
||||||
|
raise ScoValueError(
|
||||||
|
"Données (template) invalides (caractères non UTF8 ?)"
|
||||||
|
) from e
|
||||||
else:
|
else:
|
||||||
# template indiqué dans préférences ScoDoc ?
|
# template indiqué dans préférences ScoDoc ?
|
||||||
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
template_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||||
@ -114,7 +119,7 @@ def pe_view_sem_recap(
|
|||||||
footer_latex = ""
|
footer_latex = ""
|
||||||
# template fourni via le formulaire Web
|
# template fourni via le formulaire Web
|
||||||
if footer_tmpl_file:
|
if footer_tmpl_file:
|
||||||
footer_latex = footer_tmpl_file.read().decode('utf-8')
|
footer_latex = footer_tmpl_file.read().decode("utf-8")
|
||||||
footer_latex = footer_latex
|
footer_latex = footer_latex
|
||||||
else:
|
else:
|
||||||
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||||
|
@ -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"])
|
||||||
@ -315,13 +317,13 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
|
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
|
||||||
else:
|
else:
|
||||||
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
||||||
|
if nt.bonus_ues is not None:
|
||||||
|
u["cur_moy_ue_txt"] += " (+ues)"
|
||||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||||
if ue_status["coef_ue"] != None:
|
if ue_status["coef_ue"] != None:
|
||||||
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
||||||
else:
|
else:
|
||||||
# C'est un bug:
|
u["coef_ue_txt"] = "-"
|
||||||
log("u=" + pprint.pformat(u))
|
|
||||||
raise Exception("invalid None coef for ue")
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
dpv
|
dpv
|
||||||
@ -1011,11 +1013,16 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
|||||||
intro_mail = sco_preferences.get_preference("bul_intro_mail", formsemestre_id)
|
intro_mail = sco_preferences.get_preference("bul_intro_mail", formsemestre_id)
|
||||||
|
|
||||||
if intro_mail:
|
if intro_mail:
|
||||||
hea = intro_mail % {
|
try:
|
||||||
"nomprenom": etud["nomprenom"],
|
hea = intro_mail % {
|
||||||
"dept": dept,
|
"nomprenom": etud["nomprenom"],
|
||||||
"webmaster": webmaster,
|
"dept": dept,
|
||||||
}
|
"webmaster": webmaster,
|
||||||
|
}
|
||||||
|
except KeyError as e:
|
||||||
|
raise ScoValueError(
|
||||||
|
"format 'Message d'accompagnement' (bul_intro_mail) invalide, revoir les réglages dans les préférences"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
hea = ""
|
hea = ""
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
)
|
)
|
||||||
with_col_moypromo = prefs["bul_show_moypromo"]
|
with_col_moypromo = prefs["bul_show_moypromo"]
|
||||||
with_col_rang = prefs["bul_show_rangs"]
|
with_col_rang = prefs["bul_show_rangs"]
|
||||||
with_col_coef = prefs["bul_show_coef"]
|
with_col_coef = prefs["bul_show_coef"] or prefs["bul_show_ue_coef"]
|
||||||
with_col_ects = prefs["bul_show_ects"]
|
with_col_ects = prefs["bul_show_ects"]
|
||||||
|
|
||||||
colkeys = ["titre", "module"] # noms des colonnes à afficher
|
colkeys = ["titre", "module"] # noms des colonnes à afficher
|
||||||
@ -409,7 +409,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
# Chaque UE:
|
# Chaque UE:
|
||||||
for ue in I["ues"]:
|
for ue in I["ues"]:
|
||||||
ue_type = None
|
ue_type = None
|
||||||
coef_ue = ue["coef_ue_txt"]
|
coef_ue = ue["coef_ue_txt"] if prefs["bul_show_ue_coef"] else ""
|
||||||
ue_descr = ue["ue_descr_txt"]
|
ue_descr = ue["ue_descr_txt"]
|
||||||
rowstyle = ""
|
rowstyle = ""
|
||||||
plusminus = minuslink #
|
plusminus = minuslink #
|
||||||
@ -592,7 +592,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
"_titre_colspan": 2,
|
"_titre_colspan": 2,
|
||||||
"rang": mod["mod_rang_txt"], # vide si pas option rang
|
"rang": mod["mod_rang_txt"], # vide si pas option rang
|
||||||
"note": mod["mod_moy_txt"],
|
"note": mod["mod_moy_txt"],
|
||||||
"coef": mod["mod_coef_txt"],
|
"coef": mod["mod_coef_txt"] if prefs["bul_show_coef"] else "",
|
||||||
"abs": mod.get(
|
"abs": mod.get(
|
||||||
"mod_abs_txt", ""
|
"mod_abs_txt", ""
|
||||||
), # absent si pas option show abs module
|
), # absent si pas option show abs module
|
||||||
@ -656,7 +656,9 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
eval_style = ""
|
eval_style = ""
|
||||||
t = {
|
t = {
|
||||||
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
||||||
"coef": "<i>" + e["coef_txt"] + "</i>",
|
"coef": ("<i>" + e["coef_txt"] + "</i>")
|
||||||
|
if prefs["bul_show_coef"]
|
||||||
|
else "",
|
||||||
"_hidden": hidden,
|
"_hidden": hidden,
|
||||||
"_module_target": e["target_html"],
|
"_module_target": e["target_html"],
|
||||||
# '_module_help' : ,
|
# '_module_help' : ,
|
||||||
|
@ -33,17 +33,12 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# API ScoDoc8 pour les caches:
|
# API pour les caches:
|
||||||
# sco_cache.NotesTableCache.get( formsemestre_id)
|
# sco_cache.MyCache.get( formsemestre_id)
|
||||||
# => sco_cache.NotesTableCache.get(formsemestre_id)
|
# => sco_cache.MyCache.get(formsemestre_id)
|
||||||
#
|
#
|
||||||
# sco_core.inval_cache(formsemestre_id=None, pdfonly=False, formsemestre_id_list=None)
|
# sco_cache.MyCache.delete(formsemestre_id)
|
||||||
# => deprecated, NotesTableCache.invalidate_formsemestre(formsemestre_id=None, pdfonly=False)
|
# sco_cache.MyCache.delete_many(formsemestre_id_list)
|
||||||
#
|
|
||||||
#
|
|
||||||
# Nouvelles fonctions:
|
|
||||||
# sco_cache.NotesTableCache.delete(formsemestre_id)
|
|
||||||
# sco_cache.NotesTableCache.delete_many(formsemestre_id_list)
|
|
||||||
#
|
#
|
||||||
# Bulletins PDF:
|
# Bulletins PDF:
|
||||||
# sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version)
|
# sco_cache.SemBulletinsPDFCache.get(formsemestre_id, version)
|
||||||
@ -203,49 +198,6 @@ class SemInscriptionsCache(ScoDocCache):
|
|||||||
duration = 12 * 60 * 60 # ttl 12h
|
duration = 12 * 60 * 60 # ttl 12h
|
||||||
|
|
||||||
|
|
||||||
class NotesTableCache(ScoDocCache):
|
|
||||||
"""Cache pour les NotesTable
|
|
||||||
Clé: formsemestre_id
|
|
||||||
Valeur: NotesTable instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
prefix = "NT"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, formsemestre_id, compute=True):
|
|
||||||
"""Returns NotesTable for this formsemestre
|
|
||||||
Search in local cache (g.nt_cache) or global app cache (eg REDIS)
|
|
||||||
If not in cache:
|
|
||||||
If compute is True, build it and cache it
|
|
||||||
Else return None
|
|
||||||
"""
|
|
||||||
# try local cache (same request)
|
|
||||||
if not hasattr(g, "nt_cache"):
|
|
||||||
g.nt_cache = {}
|
|
||||||
else:
|
|
||||||
if formsemestre_id in g.nt_cache:
|
|
||||||
return g.nt_cache[formsemestre_id]
|
|
||||||
# try REDIS
|
|
||||||
key = cls._get_key(formsemestre_id)
|
|
||||||
nt = CACHE.get(key)
|
|
||||||
if nt:
|
|
||||||
g.nt_cache[formsemestre_id] = nt # cache locally (same request)
|
|
||||||
return nt
|
|
||||||
if not compute:
|
|
||||||
return None
|
|
||||||
# Recompute requested table:
|
|
||||||
from app.scodoc import notes_table
|
|
||||||
|
|
||||||
t0 = time.time()
|
|
||||||
nt = notes_table.NotesTable(formsemestre_id)
|
|
||||||
t1 = time.time()
|
|
||||||
_ = cls.set(formsemestre_id, nt) # cache in REDIS
|
|
||||||
t2 = time.time()
|
|
||||||
log(f"cached formsemestre_id={formsemestre_id} ({(t1-t0):g}s +{(t2-t1):g}s)")
|
|
||||||
g.nt_cache[formsemestre_id] = nt
|
|
||||||
return nt
|
|
||||||
|
|
||||||
|
|
||||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||||
formsemestre_id=None, pdfonly=False
|
formsemestre_id=None, pdfonly=False
|
||||||
):
|
):
|
||||||
@ -278,22 +230,24 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
|
|
||||||
if not pdfonly:
|
if not pdfonly:
|
||||||
# Delete cached notes and evaluations
|
# Delete cached notes and evaluations
|
||||||
NotesTableCache.delete_many(formsemestre_ids)
|
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
for fid in formsemestre_ids:
|
for fid in formsemestre_ids:
|
||||||
EvaluationCache.invalidate_sem(fid)
|
EvaluationCache.invalidate_sem(fid)
|
||||||
if hasattr(g, "nt_cache") and fid in g.nt_cache:
|
if (
|
||||||
del g.nt_cache[fid]
|
hasattr(g, "formsemestre_results_cache")
|
||||||
|
and fid in g.formsemestre_results_cache
|
||||||
|
):
|
||||||
|
del g.formsemestre_results_cache[fid]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# optimization when we invalidate all evaluations:
|
# optimization when we invalidate all evaluations:
|
||||||
EvaluationCache.invalidate_all_sems()
|
EvaluationCache.invalidate_all_sems()
|
||||||
if hasattr(g, "nt_cache"):
|
if hasattr(g, "formsemestre_results_cache"):
|
||||||
del g.nt_cache
|
del g.formsemestre_results_cache
|
||||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||||
|
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||||
|
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
||||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||||
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
|
||||||
ValidationsSemestreCache.delete_many(formsemestre_ids)
|
|
||||||
|
|
||||||
|
|
||||||
class DefferedSemCacheManager:
|
class DefferedSemCacheManager:
|
||||||
|
@ -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"])
|
||||||
]
|
]
|
||||||
@ -587,7 +587,7 @@ class ParcoursILEPS(TypeParcours):
|
|||||||
# SESSION_ABBRV = 'A' # A1, A2, ...
|
# SESSION_ABBRV = 'A' # A1, A2, ...
|
||||||
COMPENSATION_UE = False
|
COMPENSATION_UE = False
|
||||||
UNUSED_CODES = set((ADC, ATT, ATB, ATJ))
|
UNUSED_CODES = set((ADC, ATT, ATB, ATJ))
|
||||||
ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE]
|
ALLOWED_UE_TYPES = [UE_STANDARD, UE_OPTIONNELLE, UE_SPORT]
|
||||||
# Barre moy gen. pour validation semestre:
|
# Barre moy gen. pour validation semestre:
|
||||||
BARRE_MOY = 10.0
|
BARRE_MOY = 10.0
|
||||||
# Barre pour UE ILEPS: 8/20 pour UE standards ("fondamentales")
|
# Barre pour UE ILEPS: 8/20 pour UE standards ("fondamentales")
|
||||||
|
@ -51,6 +51,7 @@ import fcntl
|
|||||||
import subprocess
|
import subprocess
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from flask import flash
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
@ -124,6 +125,7 @@ def sco_dump_and_send_db():
|
|||||||
fcntl.flock(x, fcntl.LOCK_UN)
|
fcntl.flock(x, fcntl.LOCK_UN)
|
||||||
|
|
||||||
log("sco_dump_and_send_db: done.")
|
log("sco_dump_and_send_db: done.")
|
||||||
|
flash("Données envoyées au serveur d'assistance")
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
|
|
||||||
|
|
||||||
@ -186,18 +188,28 @@ 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)}
|
||||||
r = requests.post(
|
try:
|
||||||
scu.SCO_DUMP_UP_URL,
|
r = requests.post(
|
||||||
files=files,
|
scu.SCO_DUMP_UP_URL,
|
||||||
data={
|
files=files,
|
||||||
"dept_name": sco_preferences.get_preference("DeptName"),
|
data={
|
||||||
"serial": _get_scodoc_serial(),
|
"dept_name": sco_preferences.get_preference("DeptName"),
|
||||||
"sco_user": str(current_user),
|
"serial": _get_scodoc_serial(),
|
||||||
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
|
"sco_user": str(current_user),
|
||||||
"sco_version": sco_version.SCOVERSION,
|
"sent_by": sco_users.user_info(str(current_user))["nomcomplet"],
|
||||||
"sco_fullversion": scu.get_scodoc_version(),
|
"sco_version": sco_version.SCOVERSION,
|
||||||
},
|
"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
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ def html_edit_formation_apc(
|
|||||||
"""
|
"""
|
||||||
parcours = formation.get_parcours()
|
parcours = formation.get_parcours()
|
||||||
assert parcours.APC_SAE
|
assert parcours.APC_SAE
|
||||||
|
|
||||||
ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by(
|
ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE).order_by(
|
||||||
Module.semestre_id, Module.numero, Module.code
|
Module.semestre_id, Module.numero, Module.code
|
||||||
)
|
)
|
||||||
@ -68,6 +69,19 @@ def html_edit_formation_apc(
|
|||||||
).order_by(
|
).order_by(
|
||||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ues_by_sem = {}
|
||||||
|
ects_by_sem = {}
|
||||||
|
for semestre_idx in semestre_ids:
|
||||||
|
ues_by_sem[semestre_idx] = formation.ues.filter_by(
|
||||||
|
semestre_idx=semestre_idx
|
||||||
|
).order_by(UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme)
|
||||||
|
ects = [ue.ects for ue in ues_by_sem[semestre_idx]]
|
||||||
|
if None in ects:
|
||||||
|
ects_by_sem[semestre_idx] = '<span class="missing_ue_ects">manquant</span>'
|
||||||
|
else:
|
||||||
|
ects_by_sem[semestre_idx] = sum(ects)
|
||||||
|
|
||||||
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||||
|
|
||||||
icons = {
|
icons = {
|
||||||
@ -93,7 +107,8 @@ def html_edit_formation_apc(
|
|||||||
editable=editable,
|
editable=editable,
|
||||||
tag_editable=tag_editable,
|
tag_editable=tag_editable,
|
||||||
icons=icons,
|
icons=icons,
|
||||||
UniteEns=UniteEns,
|
ues_by_sem=ues_by_sem,
|
||||||
|
ects_by_sem=ects_by_sem,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
for semestre_idx in semestre_ids:
|
for semestre_idx in semestre_ids:
|
||||||
|
@ -500,6 +500,13 @@ def module_edit(module_id=None):
|
|||||||
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
|
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
|
||||||
|
|
||||||
if is_apc:
|
if is_apc:
|
||||||
|
# ne conserve que la 1ere matière de chaque UE,
|
||||||
|
# et celle à laquelle ce module est rattaché
|
||||||
|
matieres = [
|
||||||
|
mat
|
||||||
|
for mat in matieres
|
||||||
|
if a_module.matiere.id == mat.id or mat.id == mat.ue.matieres.first().id
|
||||||
|
]
|
||||||
mat_names = [
|
mat_names = [
|
||||||
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
|
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
|
||||||
]
|
]
|
||||||
@ -544,14 +551,18 @@ 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 = [
|
||||||
(
|
(
|
||||||
"code",
|
"code",
|
||||||
{
|
{
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"explanation": "code du module (doit être unique dans la formation)",
|
"explanation": "code du module (issu du programme, exemple M1203 ou R2.01. Doit être unique dans la formation)",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
|
"validator": lambda val, field, formation_id=formation_id: check_module_code_unicity(
|
||||||
val, field, formation_id, module_id=module_id
|
val, field, formation_id, module_id=module_id
|
||||||
@ -690,7 +701,10 @@ def module_edit(module_id=None):
|
|||||||
{
|
{
|
||||||
"title": "Code Apogée",
|
"title": "Code Apogée",
|
||||||
"size": 25,
|
"size": 25,
|
||||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
|
||||||
|
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
|
||||||
|
du référent Apogée).
|
||||||
|
""",
|
||||||
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
@ -296,7 +305,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
(
|
(
|
||||||
"numero",
|
"numero",
|
||||||
{
|
{
|
||||||
"size": 2,
|
"size": 4,
|
||||||
"explanation": "numéro (1,2,3,4) de l'UE pour l'ordre d'affichage",
|
"explanation": "numéro (1,2,3,4) de l'UE pour l'ordre d'affichage",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
},
|
},
|
||||||
@ -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",
|
||||||
@ -601,7 +613,12 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
_add_ue_semestre_id(ues_externes, is_apc)
|
_add_ue_semestre_id(ues_externes, is_apc)
|
||||||
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||||
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||||
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
|
# Codes dupliqués (pour aider l'utilisateur)
|
||||||
|
seen = set()
|
||||||
|
duplicated_codes = {
|
||||||
|
ue["ue_code"] for ue in ues if ue["ue_code"] in seen or seen.add(ue["ue_code"])
|
||||||
|
}
|
||||||
|
ues_with_duplicated_code = [ue for ue in ues if ue["ue_code"] in duplicated_codes]
|
||||||
|
|
||||||
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
||||||
# editable = (not locked) and has_perm_change
|
# editable = (not locked) and has_perm_change
|
||||||
@ -664,11 +681,17 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
if msg:
|
if msg:
|
||||||
H.append('<p class="msg">' + msg + "</p>")
|
H.append('<p class="msg">' + msg + "</p>")
|
||||||
|
|
||||||
if has_duplicate_ue_codes:
|
if ues_with_duplicated_code:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="ue_warning"><span>Attention: plusieurs UE de cette
|
f"""<div class="ue_warning"><span>Attention: plusieurs UE de cette
|
||||||
formation ont le même code. Il faut corriger cela ci-dessous,
|
formation ont le même code : <tt>{
|
||||||
sinon les calculs d'ECTS seront erronés !</span></div>"""
|
', '.join([
|
||||||
|
'<a class="stdlink" href="' + url_for( "notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue["ue_id"] )
|
||||||
|
+ '">' + ue["acronyme"] + " (code " + ue["ue_code"] + ")</a>"
|
||||||
|
for ue in ues_with_duplicated_code ])
|
||||||
|
}</tt>.
|
||||||
|
Il faut corriger cela, sinon les capitalisations et ECTS seront
|
||||||
|
erronés !</span></div>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
# Description de la formation
|
# Description de la formation
|
||||||
@ -699,16 +722,16 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
<a href="{url_for('notes.refcomp_show',
|
<a href="{url_for('notes.refcomp_show',
|
||||||
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id)}">
|
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id)}">
|
||||||
{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}
|
{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}
|
||||||
</a> """
|
</a> """
|
||||||
msg_refcomp = "changer"
|
msg_refcomp = "changer"
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<ul>
|
<ul>
|
||||||
<li>{descr_refcomp} <a class="stdlink" href="{url_for('notes.refcomp_assoc_formation',
|
<li>{descr_refcomp} <a class="stdlink" href="{url_for('notes.refcomp_assoc_formation',
|
||||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||||
}">{msg_refcomp}</a>
|
}">{msg_refcomp}</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="stdlink" href="{
|
<li> <a class="stdlink" href="{
|
||||||
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=semestre_idx)
|
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=semestre_idx)
|
||||||
}">éditer les coefficients des ressources et SAÉs</a>
|
}">éditer les coefficients des ressources et SAÉs</a>
|
||||||
</li>
|
</li>
|
||||||
@ -735,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,
|
||||||
@ -764,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:
|
||||||
@ -913,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:
|
||||||
@ -930,8 +954,8 @@ def _ue_table_ues(
|
|||||||
|
|
||||||
if cur_ue_semestre_id != ue["semestre_id"]:
|
if cur_ue_semestre_id != ue["semestre_id"]:
|
||||||
cur_ue_semestre_id = ue["semestre_id"]
|
cur_ue_semestre_id = ue["semestre_id"]
|
||||||
if iue > 0:
|
# if iue > 0:
|
||||||
H.append("</ul>")
|
# H.append("</ul>")
|
||||||
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
||||||
lab = "Pas d'indication de semestre:"
|
lab = "Pas d'indication de semestre:"
|
||||||
else:
|
else:
|
||||||
@ -953,7 +977,6 @@ def _ue_table_ues(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(arrow_none)
|
H.append(arrow_none)
|
||||||
iue += 1
|
|
||||||
ue["acro_titre"] = str(ue["acronyme"])
|
ue["acro_titre"] = str(ue["acronyme"])
|
||||||
if ue["titre"] != ue["acronyme"]:
|
if ue["titre"] != ue["acronyme"]:
|
||||||
ue["acro_titre"] += " " + str(ue["titre"])
|
ue["acro_titre"] += " " + str(ue["titre"])
|
||||||
@ -1001,6 +1024,14 @@ def _ue_table_ues(
|
|||||||
delete_disabled_icon,
|
delete_disabled_icon,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if (iue >= len(ues) - 1) or ue["semestre_id"] != ues[iue + 1]["semestre_id"]:
|
||||||
|
H.append(
|
||||||
|
f"""</ul><ul><li><a href="{url_for('notes.ue_create', scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=ue['formation_id'], semestre_idx=ue['semestre_id'])
|
||||||
|
}">Ajouter une UE dans le semestre {ue['semestre_id'] or ''}</a></li></ul>"""
|
||||||
|
)
|
||||||
|
iue += 1
|
||||||
|
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
@ -1268,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"]
|
||||||
|
@ -53,7 +53,7 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
|
|
||||||
|
|
||||||
def apo_semset_maq_status(
|
def apo_semset_maq_status(
|
||||||
semset_id="",
|
semset_id: int,
|
||||||
allow_missing_apo=False,
|
allow_missing_apo=False,
|
||||||
allow_missing_decisions=False,
|
allow_missing_decisions=False,
|
||||||
allow_missing_csv=False,
|
allow_missing_csv=False,
|
||||||
@ -65,7 +65,7 @@ def apo_semset_maq_status(
|
|||||||
):
|
):
|
||||||
"""Page statut / tableau de bord"""
|
"""Page statut / tableau de bord"""
|
||||||
if not semset_id:
|
if not semset_id:
|
||||||
raise ValueError("invalid null semset_id")
|
raise ScoValueError("invalid null semset_id")
|
||||||
semset = sco_semset.SemSet(semset_id=semset_id)
|
semset = sco_semset.SemSet(semset_id=semset_id)
|
||||||
semset.fill_formsemestres()
|
semset.fill_formsemestres()
|
||||||
# autorise export meme si etudiants Apo manquants:
|
# autorise export meme si etudiants Apo manquants:
|
||||||
|
@ -405,7 +405,6 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||||||
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
"""Page avec calendrier de toutes les evaluations de ce semestre"""
|
||||||
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)
|
||||||
sem = formsemestre.to_dict()
|
|
||||||
|
|
||||||
evals = nt.get_evaluations_etats()
|
evals = nt.get_evaluations_etats()
|
||||||
nb_evals = len(evals)
|
nb_evals = len(evals)
|
||||||
@ -416,8 +415,8 @@ def formsemestre_evaluations_cal(formsemestre_id):
|
|||||||
|
|
||||||
today = time.strftime("%Y-%m-%d")
|
today = time.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
year = int(sem["annee_debut"])
|
year = formsemestre.date_debut.year
|
||||||
if sem["mois_debut_ord"] < 8:
|
if formsemestre.date_debut.month < 8:
|
||||||
year -= 1 # calendrier septembre a septembre
|
year -= 1 # calendrier septembre a septembre
|
||||||
events = {} # (day, halfday) : event
|
events = {} # (day, halfday) : event
|
||||||
for e in evals:
|
for e in evals:
|
||||||
@ -537,11 +536,10 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||||
le nombre de jours avant la publication des notes.
|
le nombre de jours avant la publication des notes.
|
||||||
|
|
||||||
N'indique pas les évaluations de ratrapage ni celles des modules de bonus/malus.
|
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
sem = formsemestre.to_dict()
|
|
||||||
|
|
||||||
evals = nt.get_evaluations_etats()
|
evals = nt.get_evaluations_etats()
|
||||||
T = []
|
T = []
|
||||||
@ -607,7 +605,7 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, format="html"):
|
|||||||
origin="Généré par %s le " % sco_version.SCONAME
|
origin="Généré par %s le " % sco_version.SCONAME
|
||||||
+ scu.timedate_human_repr()
|
+ scu.timedate_human_repr()
|
||||||
+ "",
|
+ "",
|
||||||
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]),
|
filename=scu.make_filename("evaluations_delais_" + formsemestre.titre_annee()),
|
||||||
)
|
)
|
||||||
return tab.make_page(format=format)
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
@ -635,16 +633,13 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
|||||||
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
|
'<span class="evallink"><a class="stdlink" href="evaluation_listenotes?moduleimpl_id=%s">voir toutes les notes du module</a></span>'
|
||||||
% moduleimpl_id
|
% moduleimpl_id
|
||||||
)
|
)
|
||||||
mod_descr = (
|
mod_descr = '<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s' % (
|
||||||
'<a href="moduleimpl_status?moduleimpl_id=%s">%s %s</a> <span class="resp">(resp. <a title="%s">%s</a>)</span> %s'
|
moduleimpl_id,
|
||||||
% (
|
Mod["code"] or "",
|
||||||
moduleimpl_id,
|
Mod["titre"] or "?",
|
||||||
Mod["code"] or "",
|
nomcomplet,
|
||||||
Mod["titre"] or "?",
|
resp,
|
||||||
nomcomplet,
|
link,
|
||||||
resp,
|
|
||||||
link,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
etit = E["description"] or ""
|
etit = E["description"] or ""
|
||||||
|
@ -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
|
||||||
etuds = sco_etud.etudident_list(cnx, args={"nom": expnom}, test="~")
|
try:
|
||||||
|
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:
|
||||||
|
@ -151,8 +151,14 @@ def formation_export(
|
|||||||
if mod["ects"] is None:
|
if mod["ects"] is None:
|
||||||
del mod["ects"]
|
del mod["ects"]
|
||||||
|
|
||||||
|
filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}"
|
||||||
return scu.sendResult(
|
return scu.sendResult(
|
||||||
F, name="formation", format=format, force_outer_xml_tag=False, attached=True
|
F,
|
||||||
|
name="formation",
|
||||||
|
format=format,
|
||||||
|
force_outer_xml_tag=False,
|
||||||
|
attached=True,
|
||||||
|
filename=filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ def formsemestre_createwithmodules():
|
|||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Création d'un semestre",
|
page_title="Création d'un semestre",
|
||||||
javascripts=["libjs/AutoSuggest.js"],
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||||
bodyOnLoad="init_tf_form('')",
|
bodyOnLoad="init_tf_form('')",
|
||||||
),
|
),
|
||||||
@ -99,7 +99,7 @@ def formsemestre_editwithmodules(formsemestre_id):
|
|||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
"Modification du semestre",
|
"Modification du semestre",
|
||||||
javascripts=["libjs/AutoSuggest.js"],
|
javascripts=["libjs/AutoSuggest.js", "js/formsemestre_edit.js"],
|
||||||
cssstyles=["css/autosuggest_inquisitor.css"],
|
cssstyles=["css/autosuggest_inquisitor.css"],
|
||||||
bodyOnLoad="init_tf_form('')",
|
bodyOnLoad="init_tf_form('')",
|
||||||
)
|
)
|
||||||
@ -213,7 +213,10 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
# en APC, ne permet pas de changer de semestre
|
# en APC, ne permet pas de changer de semestre
|
||||||
semestre_id_list = [formsemestre.semestre_id]
|
semestre_id_list = [formsemestre.semestre_id]
|
||||||
else:
|
else:
|
||||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
semestre_id_list = list(range(1, NB_SEM + 1))
|
||||||
|
if not formation.is_apc():
|
||||||
|
# propose "pas de semestre" seulement en classique
|
||||||
|
semestre_id_list.insert(0, -1)
|
||||||
|
|
||||||
semestre_id_labels = []
|
semestre_id_labels = []
|
||||||
for sid in semestre_id_list:
|
for sid in semestre_id_list:
|
||||||
@ -341,6 +344,9 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
||||||
if formation.is_apc()
|
if formation.is_apc()
|
||||||
else "",
|
else "",
|
||||||
|
"attributes": ['onchange="change_semestre_id();"']
|
||||||
|
if formation.is_apc()
|
||||||
|
else "",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -493,7 +499,8 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
{
|
{
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"title": "",
|
"title": "",
|
||||||
"explanation": "Autoriser tous les enseignants associés à un module à y créer des évaluations",
|
"explanation": """Autoriser tous les enseignants associés
|
||||||
|
à un module à y créer des évaluations""",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -534,11 +541,19 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
]
|
]
|
||||||
|
|
||||||
nbmod = 0
|
nbmod = 0
|
||||||
if edit:
|
|
||||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"
|
|
||||||
else:
|
|
||||||
templ_sep = "<tr><td>%(label)s</td><td><b>Responsable</b></td></tr>"
|
|
||||||
for semestre_id in semestre_ids:
|
for semestre_id in semestre_ids:
|
||||||
|
if formation.is_apc():
|
||||||
|
# pour restreindre l'édition aux module du semestre sélectionné
|
||||||
|
tr_class = f'class="sem{semestre_id}"'
|
||||||
|
else:
|
||||||
|
tr_class = ""
|
||||||
|
if edit:
|
||||||
|
templ_sep = f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td><td><b>Inscrire</b></td></tr>"""
|
||||||
|
else:
|
||||||
|
templ_sep = (
|
||||||
|
f"""<tr {tr_class}><td>%(label)s</td><td><b>Responsable</b></td></tr>"""
|
||||||
|
)
|
||||||
modform.append(
|
modform.append(
|
||||||
(
|
(
|
||||||
"sep",
|
"sep",
|
||||||
@ -588,12 +603,12 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
)
|
)
|
||||||
fcg += "</select>"
|
fcg += "</select>"
|
||||||
itemtemplate = (
|
itemtemplate = (
|
||||||
"""<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>"""
|
||||||
+ fcg
|
+ fcg
|
||||||
+ "</td></tr>"
|
+ "</td></tr>"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
itemtemplate = """<tr><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
itemtemplate = f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>"""
|
||||||
modform.append(
|
modform.append(
|
||||||
(
|
(
|
||||||
"MI" + str(mod["module_id"]),
|
"MI" + str(mod["module_id"]),
|
||||||
|
@ -595,11 +595,12 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
|||||||
"""Description du semestre sous forme de table exportable
|
"""Description du semestre sous forme de table exportable
|
||||||
Liste des modules et de leurs coefficients
|
Liste des modules et de leurs coefficients
|
||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
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)
|
||||||
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
|
||||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
F = sco_formations.formation_list(args={"formation_id": formsemestre.formation_id})[
|
||||||
|
0
|
||||||
|
]
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||||
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
formsemestre_id=formsemestre_id, sort_by_ue=True
|
formsemestre_id=formsemestre_id, sort_by_ue=True
|
||||||
@ -709,7 +710,7 @@ def formsemestre_description_table(formsemestre_id, with_evals=False):
|
|||||||
titles["coefficient"] = "Coef. éval."
|
titles["coefficient"] = "Coef. éval."
|
||||||
titles["evalcomplete_str"] = "Complète"
|
titles["evalcomplete_str"] = "Complète"
|
||||||
titles["publish_incomplete_str"] = "Toujours Utilisée"
|
titles["publish_incomplete_str"] = "Toujours Utilisée"
|
||||||
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), sem["titremois"])
|
title = "%s %s" % (parcours.SESSION_NAME.capitalize(), formsemestre.titre_mois())
|
||||||
|
|
||||||
return GenTable(
|
return GenTable(
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
@ -986,7 +987,6 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind
|
|||||||
def formsemestre_status(formsemestre_id=None):
|
def formsemestre_status(formsemestre_id=None):
|
||||||
"""Tableau de bord semestre HTML"""
|
"""Tableau de bord semestre HTML"""
|
||||||
# porté du DTML
|
# porté du DTML
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
modimpls = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
@ -1077,7 +1077,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:
|
||||||
@ -1250,7 +1252,7 @@ def check_formation_ues(formation_id):
|
|||||||
for ue in ues:
|
for ue in ues:
|
||||||
# formsemestres utilisant cette ue ?
|
# formsemestres utilisant cette ue ?
|
||||||
sems = ndb.SimpleDictFetch(
|
sems = ndb.SimpleDictFetch(
|
||||||
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.*
|
"""SELECT DISTINCT sem.id AS formsemestre_id, sem.*
|
||||||
FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi
|
FROM notes_formsemestre sem, notes_modules mod, notes_moduleimpl mi
|
||||||
WHERE sem.formation_id = %(formation_id)s
|
WHERE sem.formation_id = %(formation_id)s
|
||||||
AND mod.id = mi.module_id
|
AND mod.id = mi.module_id
|
||||||
@ -1269,11 +1271,11 @@ def check_formation_ues(formation_id):
|
|||||||
return "", {}
|
return "", {}
|
||||||
# Genere message HTML:
|
# Genere message HTML:
|
||||||
H = [
|
H = [
|
||||||
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
|
"""<div class="ue_warning"><span>Attention:</span> les UE suivantes de cette formation
|
||||||
sont utilisées dans des
|
sont utilisées dans des
|
||||||
semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour
|
semestres de rangs différents (eg S1 et S3). <br/>Cela peut engendrer des problèmes pour
|
||||||
la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
|
la capitalisation des UE. Il serait préférable d'essayer de rectifier cette situation:
|
||||||
soit modifier le programme de la formation (définir des UE dans chaque semestre),
|
soit modifier le programme de la formation (définir des UE dans chaque semestre),
|
||||||
soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une
|
soit veiller à saisir le bon indice de semestre dans le menu lors de la validation d'une
|
||||||
UE extérieure.
|
UE extérieure.
|
||||||
<ul>
|
<ul>
|
||||||
@ -1286,7 +1288,11 @@ def check_formation_ues(formation_id):
|
|||||||
for x in ue_multiples[ue["ue_id"]]
|
for x in ue_multiples[ue["ue_id"]]
|
||||||
]
|
]
|
||||||
slist = ", ".join(
|
slist = ", ".join(
|
||||||
["%(titreannee)s (<em>semestre %(semestre_id)s</em>)" % s for s in sems]
|
[
|
||||||
|
"""%(titreannee)s (<em>semestre <b class="fontred">%(semestre_id)s</b></em>)"""
|
||||||
|
% s
|
||||||
|
for s in sems
|
||||||
|
]
|
||||||
)
|
)
|
||||||
H.append("<li><b>%s</b> : %s</li>" % (ue["acronyme"], slist))
|
H.append("<li><b>%s</b> : %s</li>" % (ue["acronyme"], slist))
|
||||||
H.append("</ul></div>")
|
H.append("</ul></div>")
|
||||||
|
96
app/scodoc/sco_groups_exports.py
Normal file
96
app/scodoc/sco_groups_exports.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Exports groupes
|
||||||
|
"""
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
from app.scodoc import notesdb as ndb
|
||||||
|
from app.scodoc import sco_excel
|
||||||
|
from app.scodoc import sco_groups_view
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc.gen_tables import GenTable
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
import sco_version
|
||||||
|
|
||||||
|
|
||||||
|
def groups_list_annotation(group_ids: list[int]) -> list[dict]:
|
||||||
|
"""Renvoie la liste des annotations pour les groupes d"étudiants indiqués
|
||||||
|
Arg: liste des id de groupes
|
||||||
|
Clés: etudid, ine, nip, nom, prenom, date, comment
|
||||||
|
"""
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
|
annotations = []
|
||||||
|
for group_id in group_ids:
|
||||||
|
cursor.execute(
|
||||||
|
"""SELECT i.id AS etudid, i.code_nip, i.code_ine, i.nom, i.prenom, ea.date, ea.comment
|
||||||
|
FROM group_membership gm, identite i, etud_annotations ea
|
||||||
|
WHERE gm.group_id=%(group_ids)s
|
||||||
|
AND gm.etudid=i.id
|
||||||
|
AND i.id=ea.etudid
|
||||||
|
""",
|
||||||
|
{"group_ids": group_id},
|
||||||
|
)
|
||||||
|
annotations += cursor.dictfetchall()
|
||||||
|
return annotations
|
||||||
|
|
||||||
|
|
||||||
|
def groups_export_annotations(group_ids, formsemestre_id=None, format="html"):
|
||||||
|
"""Les annotations"""
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids, formsemestre_id=formsemestre_id
|
||||||
|
)
|
||||||
|
annotations = groups_list_annotation(groups_infos.group_ids)
|
||||||
|
for annotation in annotations:
|
||||||
|
annotation["date_str"] = annotation["date"].strftime("%d/%m/%Y à %Hh%M")
|
||||||
|
if format == "xls":
|
||||||
|
columns_ids = ("etudid", "nom", "prenom", "date", "comment")
|
||||||
|
else:
|
||||||
|
columns_ids = ("etudid", "nom", "prenom", "date_str", "comment")
|
||||||
|
table = GenTable(
|
||||||
|
rows=annotations,
|
||||||
|
columns_ids=columns_ids,
|
||||||
|
titles={
|
||||||
|
"etudid": "etudid",
|
||||||
|
"nom": "Nom",
|
||||||
|
"prenom": "Prénom",
|
||||||
|
"date": "Date",
|
||||||
|
"date_str": "Date",
|
||||||
|
"comment": "Annotation",
|
||||||
|
},
|
||||||
|
origin="Généré par %s le " % sco_version.SCONAME
|
||||||
|
+ scu.timedate_human_repr()
|
||||||
|
+ "",
|
||||||
|
page_title=f"Annotations sur les étudiants de {groups_infos.groups_titles}",
|
||||||
|
caption="Annotations",
|
||||||
|
base_url=groups_infos.base_url,
|
||||||
|
html_sortable=True,
|
||||||
|
html_class="table_leftalign",
|
||||||
|
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||||
|
)
|
||||||
|
return table.make_page(format=format)
|
@ -826,6 +826,8 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="trombino?%s&format=pdflist">Liste d'appel avec photos</a></li>"""
|
"""<li><a class="stdlink" href="trombino?%s&format=pdflist">Liste d'appel avec photos</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
|
"""<li><a class="stdlink" href="groups_export_annotations?%s">Liste des annotations</a></li>"""
|
||||||
|
% groups_infos.groups_query_args,
|
||||||
"</ul>",
|
"</ul>",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -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:
|
||||||
@ -276,7 +278,7 @@ class Logo:
|
|||||||
if self.mm is None:
|
if self.mm is None:
|
||||||
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
|
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
|
||||||
else:
|
else:
|
||||||
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm"">'
|
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm">'
|
||||||
|
|
||||||
def last_modified(self):
|
def last_modified(self):
|
||||||
path = Path(self.filepath)
|
path = Path(self.filepath)
|
||||||
|
@ -305,7 +305,10 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||||||
if can_change:
|
if can_change:
|
||||||
c_link = (
|
c_link = (
|
||||||
'<a class="discretelink" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">%s</a>'
|
'<a class="discretelink" href="moduleimpl_inscriptions_edit?moduleimpl_id=%s">%s</a>'
|
||||||
% (mod["moduleimpl_id"], mod["descri"])
|
% (
|
||||||
|
mod["moduleimpl_id"],
|
||||||
|
mod["descri"] or "<i>(inscrire des étudiants)</i>",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
c_link = mod["descri"]
|
c_link = mod["descri"]
|
||||||
|
@ -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
|
||||||
|
@ -1296,11 +1296,21 @@ class BasePreferences(object):
|
|||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"bul_show_ue_coef",
|
||||||
|
{
|
||||||
|
"initvalue": 1,
|
||||||
|
"title": "Afficher coefficient des UE sur les bulletins",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"category": "bul",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"bul_show_coef",
|
"bul_show_coef",
|
||||||
{
|
{
|
||||||
"initvalue": 1,
|
"initvalue": 1,
|
||||||
"title": "Afficher coefficient des ue/modules sur les bulletins",
|
"title": "Afficher coefficient des modules sur les bulletins",
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"category": "bul",
|
"category": "bul",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
|
@ -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:
|
||||||
|
@ -854,23 +854,27 @@ def formsemestre_import_etud_admission(
|
|||||||
apo_emailperso = etud.get("mailperso", "")
|
apo_emailperso = etud.get("mailperso", "")
|
||||||
if info["emailperso"] and not apo_emailperso:
|
if info["emailperso"] and not apo_emailperso:
|
||||||
apo_emailperso = info["emailperso"]
|
apo_emailperso = info["emailperso"]
|
||||||
if (
|
if import_email:
|
||||||
import_email
|
if not "mail" in etud:
|
||||||
and info["email"] != etud["mail"]
|
raise ScoValueError(
|
||||||
or info["emailperso"] != apo_emailperso
|
"la réponse portail n'a pas le champs requis 'mail'"
|
||||||
):
|
)
|
||||||
sco_etud.adresse_edit(
|
if (
|
||||||
cnx,
|
info["email"] != etud["mail"]
|
||||||
args={
|
or info["emailperso"] != apo_emailperso
|
||||||
"etudid": etudid,
|
):
|
||||||
"adresse_id": info["adresse_id"],
|
sco_etud.adresse_edit(
|
||||||
"email": etud["mail"],
|
cnx,
|
||||||
"emailperso": apo_emailperso,
|
args={
|
||||||
},
|
"etudid": etudid,
|
||||||
)
|
"adresse_id": info["adresse_id"],
|
||||||
# notifie seulement les changements d'adresse mail institutionnelle
|
"email": etud["mail"],
|
||||||
if info["email"] != etud["mail"]:
|
"emailperso": apo_emailperso,
|
||||||
changed_mails.append((info, etud["mail"]))
|
},
|
||||||
|
)
|
||||||
|
# notifie seulement les changements d'adresse mail institutionnelle
|
||||||
|
if info["email"] != etud["mail"]:
|
||||||
|
changed_mails.append((info, etud["mail"]))
|
||||||
else:
|
else:
|
||||||
unknowns.append(code_nip)
|
unknowns.append(code_nip)
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
|
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
|
||||||
|
@ -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)
|
||||||
@ -635,21 +645,30 @@ class ScoDocJSONEncoder(json.JSONEncoder):
|
|||||||
return json.JSONEncoder.default(self, o)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
def sendJSON(data, attached=False):
|
def sendJSON(data, attached=False, filename=None):
|
||||||
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
js = json.dumps(data, indent=1, cls=ScoDocJSONEncoder)
|
||||||
return send_file(
|
return send_file(
|
||||||
js, filename="sco_data.json", mime=JSON_MIMETYPE, attached=attached
|
js, filename=filename or "sco_data.json", mime=JSON_MIMETYPE, attached=attached
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sendXML(data, tagname=None, force_outer_xml_tag=True, attached=False, quote=True):
|
def sendXML(
|
||||||
|
data,
|
||||||
|
tagname=None,
|
||||||
|
force_outer_xml_tag=True,
|
||||||
|
attached=False,
|
||||||
|
quote=True,
|
||||||
|
filename=None,
|
||||||
|
):
|
||||||
if type(data) != list:
|
if type(data) != list:
|
||||||
data = [data] # always list-of-dicts
|
data = [data] # always list-of-dicts
|
||||||
if force_outer_xml_tag:
|
if force_outer_xml_tag:
|
||||||
data = [{tagname: data}]
|
data = [{tagname: data}]
|
||||||
tagname += "_list"
|
tagname += "_list"
|
||||||
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname, quote=quote)
|
doc = sco_xml.simple_dictlist2xml(data, tagname=tagname, quote=quote)
|
||||||
return send_file(doc, filename="sco_data.xml", mime=XML_MIMETYPE, attached=attached)
|
return send_file(
|
||||||
|
doc, filename=filename or "sco_data.xml", mime=XML_MIMETYPE, attached=attached
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sendResult(
|
def sendResult(
|
||||||
@ -659,6 +678,7 @@ def sendResult(
|
|||||||
force_outer_xml_tag=True,
|
force_outer_xml_tag=True,
|
||||||
attached=False,
|
attached=False,
|
||||||
quote_xml=True,
|
quote_xml=True,
|
||||||
|
filename=None,
|
||||||
):
|
):
|
||||||
if (format is None) or (format == "html"):
|
if (format is None) or (format == "html"):
|
||||||
return data
|
return data
|
||||||
@ -669,9 +689,10 @@ def sendResult(
|
|||||||
force_outer_xml_tag=force_outer_xml_tag,
|
force_outer_xml_tag=force_outer_xml_tag,
|
||||||
attached=attached,
|
attached=attached,
|
||||||
quote=quote_xml,
|
quote=quote_xml,
|
||||||
|
filename=filename,
|
||||||
)
|
)
|
||||||
elif format == "json":
|
elif format == "json":
|
||||||
return sendJSON(data, attached=attached)
|
return sendJSON(data, attached=attached, filename=filename)
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid format: %s" % format)
|
raise ValueError("invalid format: %s" % format)
|
||||||
|
|
||||||
@ -789,7 +810,7 @@ def abbrev_prenom(prenom):
|
|||||||
|
|
||||||
#
|
#
|
||||||
def timedate_human_repr():
|
def timedate_human_repr():
|
||||||
"representation du temps courant pour utilisateur: a localiser"
|
"representation du temps courant pour utilisateur"
|
||||||
return time.strftime("%d/%m/%Y à %Hh%M")
|
return time.strftime("%d/%m/%Y à %Hh%M")
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -1297,7 +1297,7 @@ th.formsemestre_status_inscrits {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
td.formsemestre_status_code {
|
td.formsemestre_status_code {
|
||||||
width: 2em;
|
/* width: 2em; */
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,10 +1706,16 @@ 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-radius: 10px;
|
||||||
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
span.ue_type_1 {
|
span.ue_type_1 {
|
||||||
color: green;
|
color: green;
|
||||||
@ -1749,6 +1758,7 @@ ul.notes_matiere_list {
|
|||||||
background-color: rgb(220,220,220);
|
background-color: rgb(220,220,220);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
border-top: 1px solid maroon;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.notes_module_list {
|
ul.notes_module_list {
|
||||||
@ -1757,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 |
14
app/static/js/formsemestre_edit.js
Normal file
14
app/static/js/formsemestre_edit.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Formulaire formsemestre_createwithmodules
|
||||||
|
|
||||||
|
function change_semestre_id() {
|
||||||
|
var semestre_id = $("#tf_semestre_id")[0].value;
|
||||||
|
for (var i = -1; i < 12; i++) {
|
||||||
|
$(".sem" + i).hide();
|
||||||
|
}
|
||||||
|
$(".sem" + semestre_id).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$(window).on('load', function () {
|
||||||
|
change_semestre_id();
|
||||||
|
});
|
@ -91,7 +91,7 @@ class releveBUT extends HTMLElement {
|
|||||||
<div>
|
<div>
|
||||||
<div class=decision></div>
|
<div class=decision></div>
|
||||||
<div class=dateInscription>Inscrit le </div>
|
<div class=dateInscription>Inscrit le </div>
|
||||||
<em>Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
<em>Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -61,12 +61,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 #}
|
||||||
|
@ -18,10 +18,12 @@
|
|||||||
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
|
<a href="{{ url_for('notes.refcomp_table', scodoc_dept=g.scodoc_dept, ) }}">
|
||||||
Liste des référentiels de compétences chargés</a>
|
Liste des référentiels de compétences chargés</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if formation is not none %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
|
<a href="{{ url_for('notes.refcomp_assoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id) }}">
|
||||||
Association à la formation {{ formation.acronyme }}</a>
|
Association à la formation {{ formation.acronyme }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
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>
|
@ -65,6 +65,13 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
{% if mod.ue.type != 0 and mod.module_type != 0 %}
|
||||||
|
<span class="warning" title="Une UE de type spécial ne
|
||||||
|
devrait contenir que des modules standards">
|
||||||
|
type incompatible avec son UE de rattachement !
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<span class="sco_tag_edit"><form><textarea data-module_id="{{mod.id}}"
|
<span class="sco_tag_edit"><form><textarea data-module_id="{{mod.id}}"
|
||||||
class="{% if tag_editable %}module_tag_editor{% else %}module_tag_editor_ro{% endif %}">{{mod.tags|join(', ', attribute='title')}}</textarea></form></span>
|
class="{% if tag_editable %}module_tag_editor{% else %}module_tag_editor_ro{% endif %}">{{mod.tags|join(', ', attribute='title')}}</textarea></form></span>
|
||||||
|
|
||||||
|
@ -3,11 +3,9 @@
|
|||||||
<div class="formation_list_ues">
|
<div class="formation_list_ues">
|
||||||
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
||||||
{% for semestre_idx in semestre_ids %}
|
{% for semestre_idx in semestre_ids %}
|
||||||
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}}</div>
|
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}} (ECTS: {{ects_by_sem[semestre_idx] | safe}})</div>
|
||||||
<ul class="apc_ue_list">
|
<ul class="apc_ue_list">
|
||||||
{% for ue in formation.ues.filter_by(semestre_idx=semestre_idx).order_by(
|
{% for ue in ues_by_sem[semestre_idx] %}
|
||||||
UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme
|
|
||||||
) %}
|
|
||||||
<li class="notes_ue_list">
|
<li class="notes_ue_list">
|
||||||
{% if editable and not loop.first %}
|
{% if editable and not loop.first %}
|
||||||
<a href="{{ url_for('notes.ue_move',
|
<a href="{{ url_for('notes.ue_move',
|
||||||
@ -38,7 +36,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>
|
||||||
|
|
||||||
@ -48,6 +47,9 @@
|
|||||||
}}">modifier</a>
|
}}">modifier</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
||||||
|
<span class="warning" title="pas de module, donc pas de bonus calculé">aucun module rattaché !</span>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -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 %}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
{# sidebar_common #}
|
{# sidebar_common #}
|
||||||
<a class="scodoc_title" href="{{
|
<a class="scodoc_title" href="{{
|
||||||
url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc 9.2a</a>
|
url_for('scodoc.index', scodoc_dept=g.scodoc_dept) }}">ScoDoc {{ sco.SCOVERSION }}</a>
|
||||||
<div id="authuser"><a id="authuserlink" href="{{
|
<div id="authuser"><a id="authuserlink" href="{{
|
||||||
url_for('users.user_info_page', scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
url_for('users.user_info_page', scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||||
}}">{{current_user.user_name}}</a>
|
}}">{{current_user.user_name}}</a>
|
||||||
|
@ -16,6 +16,7 @@ from app.scodoc import sco_formsemestre_status
|
|||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
import sco_version
|
||||||
|
|
||||||
scodoc_bp = Blueprint("scodoc", __name__)
|
scodoc_bp = Blueprint("scodoc", __name__)
|
||||||
scolar_bp = Blueprint("scolar", __name__)
|
scolar_bp = Blueprint("scolar", __name__)
|
||||||
@ -53,6 +54,7 @@ class ScoData:
|
|||||||
# Champs utilisés par toutes les pages ScoDoc (sidebar, en-tête)
|
# Champs utilisés par toutes les pages ScoDoc (sidebar, en-tête)
|
||||||
self.Permission = Permission
|
self.Permission = Permission
|
||||||
self.scu = scu
|
self.scu = scu
|
||||||
|
self.SCOVERSION = sco_version.SCOVERSION
|
||||||
# -- Informations étudiant courant, si sélectionné:
|
# -- Informations étudiant courant, si sélectionné:
|
||||||
etudid = g.get("etudid", None)
|
etudid = g.get("etudid", None)
|
||||||
if not etudid:
|
if not etudid:
|
||||||
|
@ -611,8 +611,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
"""<option value="%(modimpl_id)s" %(sel)s>%(modname)s</option>\n"""
|
"""<option value="%(modimpl_id)s" %(sel)s>%(modname)s</option>\n"""
|
||||||
% {
|
% {
|
||||||
"modimpl_id": modimpl["moduleimpl_id"],
|
"modimpl_id": modimpl["moduleimpl_id"],
|
||||||
"modname": modimpl["module"]["code"]
|
"modname": (modimpl["module"]["code"] or "")
|
||||||
or ""
|
|
||||||
+ " "
|
+ " "
|
||||||
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
|
+ (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]),
|
||||||
"sel": sel,
|
"sel": sel,
|
||||||
@ -624,7 +623,7 @@ def SignaleAbsenceGrSemestre(
|
|||||||
sel = "selected" # aucun module specifie
|
sel = "selected" # aucun module specifie
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>
|
"""<p>
|
||||||
Module concerné par ces absences (%(optionel_txt)s):
|
Module concerné par ces absences (%(optionel_txt)s):
|
||||||
<select id="moduleimpl_id" name="moduleimpl_id"
|
<select id="moduleimpl_id" name="moduleimpl_id"
|
||||||
onchange="document.location='%(url)s&moduleimpl_id='+document.getElementById('moduleimpl_id').value">
|
onchange="document.location='%(url)s&moduleimpl_id='+document.getElementById('moduleimpl_id').value">
|
||||||
<option value="" %(sel)s>non spécifié</option>
|
<option value="" %(sel)s>non spécifié</option>
|
||||||
|
@ -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"
|
||||||
):
|
):
|
||||||
data = sco_moduleimpl.moduleimpl_list(
|
try:
|
||||||
moduleimpl_id=moduleimpl_id,
|
data = sco_moduleimpl.moduleimpl_list(
|
||||||
formsemestre_id=formsemestre_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
module_id=module_id,
|
formsemestre_id=formsemestre_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
|
||||||
@ -2686,7 +2693,7 @@ def moduleimpl_list(
|
|||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def moduleimpl_withmodule_list(
|
def moduleimpl_withmodule_list(
|
||||||
moduleimpl_id=None, formsemestre_id=None, module_id=None
|
moduleimpl_id=None, formsemestre_id=None, module_id=None, format="json"
|
||||||
):
|
):
|
||||||
"""API ScoDoc 7"""
|
"""API ScoDoc 7"""
|
||||||
data = sco_moduleimpl.moduleimpl_withmodule_list(
|
data = sco_moduleimpl.moduleimpl_withmodule_list(
|
||||||
|
@ -304,8 +304,9 @@ def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
|
|||||||
# stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
|
# stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
|
||||||
# from app.scodoc.sco_photos import _http_jpeg_file
|
# from app.scodoc.sco_photos import _http_jpeg_file
|
||||||
|
|
||||||
logo = sco_logos.find_logo(name, dept_id, strict).select()
|
logo = sco_logos.find_logo(name, dept_id, strict)
|
||||||
if logo is not None:
|
if logo is not None:
|
||||||
|
logo.select()
|
||||||
suffix = logo.suffix
|
suffix = logo.suffix
|
||||||
if small:
|
if small:
|
||||||
with PILImage.open(logo.filepath) as im:
|
with PILImage.open(logo.filepath) as im:
|
||||||
|
@ -68,8 +68,6 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
|||||||
import app
|
import app
|
||||||
from app.scodoc.gen_tables import GenTable
|
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 html_sidebar
|
|
||||||
from app.scodoc import imageresize
|
|
||||||
from app.scodoc import sco_import_etuds
|
from app.scodoc import sco_import_etuds
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
from app.scodoc import sco_archives_etud
|
from app.scodoc import sco_archives_etud
|
||||||
@ -87,12 +85,9 @@ from app.scodoc import sco_formsemestre_edit
|
|||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_groups_edit
|
from app.scodoc import sco_groups_edit
|
||||||
|
from app.scodoc import sco_groups_exports
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
from app.scodoc import sco_logos
|
|
||||||
from app.scodoc import sco_news
|
|
||||||
from app.scodoc import sco_page_etud
|
from app.scodoc import sco_page_etud
|
||||||
from app.scodoc import sco_parcours_dut
|
|
||||||
from app.scodoc import sco_permissions
|
|
||||||
from app.scodoc import sco_permissions_check
|
from app.scodoc import sco_permissions_check
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
from app.scodoc import sco_portal_apogee
|
from app.scodoc import sco_portal_apogee
|
||||||
@ -364,6 +359,12 @@ sco_publish(
|
|||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sco_publish(
|
||||||
|
"/groups_export_annotations",
|
||||||
|
sco_groups_exports.groups_export_annotations,
|
||||||
|
Permission.ScoView,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/groups_view")
|
@bp.route("/groups_view")
|
||||||
@scodoc
|
@scodoc
|
||||||
|
@ -19,31 +19,6 @@ depends_on = None
|
|||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
op.add_column("apc_competence", sa.Column("id_orebut", sa.Text(), nullable=True))
|
|
||||||
op.drop_constraint(
|
|
||||||
"apc_competence_referentiel_id_titre_key", "apc_competence", type_="unique"
|
|
||||||
)
|
|
||||||
op.create_index(
|
|
||||||
op.f("ix_apc_competence_id_orebut"),
|
|
||||||
"apc_competence",
|
|
||||||
["id_orebut"],
|
|
||||||
)
|
|
||||||
op.add_column(
|
|
||||||
"apc_referentiel_competences", sa.Column("annexe", sa.Text(), nullable=True)
|
|
||||||
)
|
|
||||||
op.add_column(
|
|
||||||
"apc_referentiel_competences",
|
|
||||||
sa.Column("type_structure", sa.Text(), nullable=True),
|
|
||||||
)
|
|
||||||
op.add_column(
|
|
||||||
"apc_referentiel_competences",
|
|
||||||
sa.Column("type_departement", sa.Text(), nullable=True),
|
|
||||||
)
|
|
||||||
op.add_column(
|
|
||||||
"apc_referentiel_competences",
|
|
||||||
sa.Column("version_orebut", sa.Text(), nullable=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
op.create_index(
|
op.create_index(
|
||||||
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
op.f("ix_notes_formsemestre_uecoef_formsemestre_id"),
|
||||||
"notes_formsemestre_uecoef",
|
"notes_formsemestre_uecoef",
|
||||||
@ -80,15 +55,10 @@ def downgrade():
|
|||||||
table_name="notes_formsemestre_uecoef",
|
table_name="notes_formsemestre_uecoef",
|
||||||
)
|
)
|
||||||
|
|
||||||
op.drop_column("apc_referentiel_competences", "version_orebut")
|
|
||||||
op.drop_column("apc_referentiel_competences", "type_departement")
|
|
||||||
op.drop_column("apc_referentiel_competences", "type_structure")
|
|
||||||
op.drop_column("apc_referentiel_competences", "annexe")
|
|
||||||
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
|
op.drop_index(op.f("ix_apc_competence_id_orebut"), table_name="apc_competence")
|
||||||
op.create_unique_constraint(
|
op.create_unique_constraint(
|
||||||
"apc_competence_referentiel_id_titre_key",
|
"apc_competence_referentiel_id_titre_key",
|
||||||
"apc_competence",
|
"apc_competence",
|
||||||
["referentiel_id", "titre"],
|
["referentiel_id", "titre"],
|
||||||
)
|
)
|
||||||
op.drop_column("apc_competence", "id_orebut")
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.57"
|
SCOVERSION = "9.1.71"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -93,6 +93,8 @@ fi
|
|||||||
# nginx:
|
# nginx:
|
||||||
mkdir -p "$slash"/etc/nginx/sites-available || die "can't mkdir nginx config"
|
mkdir -p "$slash"/etc/nginx/sites-available || die "can't mkdir nginx config"
|
||||||
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.nginx "$slash"/etc/nginx/sites-available/scodoc9.nginx.distrib || die "can't copy nginx config"
|
cp -p "$SCODOC_DIR"/tools/etc/scodoc9.nginx "$slash"/etc/nginx/sites-available/scodoc9.nginx.distrib || die "can't copy nginx config"
|
||||||
|
mkdir -p "$slash"/etc/nginx/conf.d || die "can't mkdir nginx conf.d"
|
||||||
|
cp -p "$SCODOC_DIR"/tools/etc/scodoc9-nginx-timeout.conf "$slash"/etc/nginx/conf.d/ || die "can't copy nginx timeout config"
|
||||||
|
|
||||||
# systemd
|
# systemd
|
||||||
mkdir -p "$slash"/etc/systemd/system/ || die "can't mkdir systemd config"
|
mkdir -p "$slash"/etc/systemd/system/ || die "can't mkdir systemd config"
|
||||||
|
5
tools/etc/scodoc9-nginx-timeout.conf
Normal file
5
tools/etc/scodoc9-nginx-timeout.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Reglage des timeout du frontal nginx pour ScoDoc 9 (>= 9.1.59)
|
||||||
|
|
||||||
|
proxy_read_timeout 400;
|
||||||
|
proxy_connect_timeout 400;
|
||||||
|
proxy_send_timeout 400;
|
Loading…
Reference in New Issue
Block a user