forked from ScoDoc/ScoDoc
Merge branch 'dev92' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
aff01323c7
@ -296,10 +296,12 @@ def create_app(config_class=DevConfig):
|
|||||||
|
|
||||||
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
|
from app.but.bulletin_but_pdf import BulletinGeneratorStandardBUT
|
||||||
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
||||||
|
|
||||||
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements.
|
# l'ordre est important, le premier sera le "défaut" pour les nouveaux départements.
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||||
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandardBUT)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||||
if app.testing or app.debug:
|
if app.testing or app.debug:
|
||||||
|
@ -11,8 +11,8 @@ import datetime
|
|||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
|
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite, formsemestre
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_bulletins, sco_utils as scu
|
||||||
from app.scodoc import sco_bulletins_json
|
from app.scodoc import sco_bulletins_json
|
||||||
from app.scodoc import sco_bulletins_pdf
|
from app.scodoc import sco_bulletins_pdf
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -217,7 +217,7 @@ class BulletinBUT:
|
|||||||
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
||||||
|
|
||||||
def bulletin_etud(
|
def bulletin_etud(
|
||||||
self, etud: Identite, formsemestre, force_publishing=False
|
self, etud: Identite, formsemestre: FormSemestre, force_publishing=False
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
||||||
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||||
@ -317,11 +317,29 @@ class BulletinBUT:
|
|||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def bulletin_etud_complet(self, etud) -> dict:
|
def bulletin_etud_complet(self, etud: Identite) -> dict:
|
||||||
"""Bulletin dict complet avec toutes les infos pour les bulletins pdf"""
|
"""Bulletin dict complet avec toutes les infos pour les bulletins pdf"""
|
||||||
d = self.bulletin_etud(force_publishing=True)
|
d = self.bulletin_etud(etud, self.res.formsemestre, force_publishing=True)
|
||||||
|
d["etudid"] = etud.id
|
||||||
|
d["etud"] = d["etudiant"]
|
||||||
|
d["etud"]["nomprenom"] = etud.nomprenom
|
||||||
|
d.update(self.res.sem)
|
||||||
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||||
self.res.get_etud_etat(etud.id), self.prefs
|
self.res.get_etud_etat(etud.id),
|
||||||
|
self.prefs,
|
||||||
|
decision_sem=d["semestre"].get("decision_sem"),
|
||||||
)
|
)
|
||||||
# XXX TODO A COMPLETER
|
# --- Absences
|
||||||
raise NotImplementedError()
|
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||||
|
# --- Rangs
|
||||||
|
d[
|
||||||
|
"rang_nt"
|
||||||
|
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||||
|
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||||
|
|
||||||
|
# --- Appréciations
|
||||||
|
d.update(
|
||||||
|
sco_bulletins.get_appreciations_list(self.res.formsemestre.id, etud.id)
|
||||||
|
)
|
||||||
|
# XXX TODO A COMPLETER ?
|
||||||
|
return d
|
||||||
|
116
app/but/bulletin_but_pdf.py
Normal file
116
app/but/bulletin_but_pdf.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Génération bulletin BUT au format PDF standard
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from app.scodoc.sco_pdf import blue, cm, mm
|
||||||
|
|
||||||
|
from flask import url_for, g
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
|
from app.scodoc import gen_tables
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc import sco_bulletins_json
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import fmt_note
|
||||||
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
|
|
||||||
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
|
|
||||||
|
|
||||||
|
class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||||
|
"""Génération du bulletin de BUT au format PDF.
|
||||||
|
|
||||||
|
self.infos est le dict issu de BulletinBUT.bulletin_etud_complet()
|
||||||
|
"""
|
||||||
|
|
||||||
|
list_in_menu = False # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur
|
||||||
|
|
||||||
|
def bul_table(self, format="html"):
|
||||||
|
"""Génère la table centrale du bulletin de notes
|
||||||
|
Renvoie:
|
||||||
|
- en HTML: une chaine
|
||||||
|
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
|
||||||
|
"""
|
||||||
|
formsemestre_id = self.infos["formsemestre_id"]
|
||||||
|
(
|
||||||
|
synth_col_keys,
|
||||||
|
synth_P,
|
||||||
|
synth_pdf_style,
|
||||||
|
synth_col_widths,
|
||||||
|
) = self.but_table_synthese()
|
||||||
|
#
|
||||||
|
table_synthese = gen_tables.GenTable(
|
||||||
|
rows=synth_P,
|
||||||
|
columns_ids=synth_col_keys,
|
||||||
|
pdf_table_style=synth_pdf_style,
|
||||||
|
pdf_col_widths=[synth_col_widths[k] for k in synth_col_keys],
|
||||||
|
preferences=self.preferences,
|
||||||
|
html_class="notes_bulletin",
|
||||||
|
html_class_ignore_default=True,
|
||||||
|
html_with_td_classes=True,
|
||||||
|
)
|
||||||
|
# Ici on ajoutera table des ressources, tables des UE
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# XXX à modifier pour générer plusieurs tables:
|
||||||
|
return table_synthese.gen(format=format)
|
||||||
|
|
||||||
|
def but_table_synthese(self):
|
||||||
|
"""La table de synthèse; pour chaque UE, liste des ressources et SAÉs avec leurs notes
|
||||||
|
et leurs coefs.
|
||||||
|
Renvoie: colkeys, P, pdf_style, colWidths
|
||||||
|
- colkeys: nom des colonnes de la table (clés)
|
||||||
|
- P : table (liste de dicts de chaines de caracteres)
|
||||||
|
- pdf_style : commandes table Platypus
|
||||||
|
- largeurs de colonnes pour PDF
|
||||||
|
"""
|
||||||
|
col_widths = {
|
||||||
|
"titre": None,
|
||||||
|
"moyenne": 2 * cm,
|
||||||
|
"coef": 2 * cm,
|
||||||
|
}
|
||||||
|
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
||||||
|
col_keys = ["titre", "moyenne"] # noms des colonnes à afficher
|
||||||
|
for ue_acronym, ue in self.infos["ues"].items():
|
||||||
|
# 1er ligne titre UE
|
||||||
|
moy_ue = ue.get("moyenne")
|
||||||
|
t = {
|
||||||
|
"titre": f"{ue_acronym} - {ue['titre']}",
|
||||||
|
"moyenne": moy_ue.get("value", "-") if moy_ue is not None else "-",
|
||||||
|
"_css_row_class": "note_bold",
|
||||||
|
"_pdf_row_markup": ["b"],
|
||||||
|
"_pdf_style": [],
|
||||||
|
}
|
||||||
|
P.append(t)
|
||||||
|
# 2eme ligne titre UE (bonus/malus/ects)
|
||||||
|
t = {
|
||||||
|
"titre": "",
|
||||||
|
"moyenne": f"""Bonus: {ue['bonus']} - Malus: {
|
||||||
|
ue["malus"]} - ECTS: {ue["ECTS"]["acquis"]} / {ue["ECTS"]["total"]}""",
|
||||||
|
"_css_row_class": "note_bold",
|
||||||
|
"_pdf_row_markup": ["b"],
|
||||||
|
"_pdf_style": [
|
||||||
|
(
|
||||||
|
"LINEBELOW",
|
||||||
|
(0, 0),
|
||||||
|
(-1, 0),
|
||||||
|
self.PDF_LINEWIDTH,
|
||||||
|
self.PDF_LINECOLOR,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
P.append(t)
|
||||||
|
|
||||||
|
# Global pdf style commands:
|
||||||
|
pdf_style = [
|
||||||
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||||
|
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||||
|
]
|
||||||
|
return col_keys, P, pdf_style, col_widths
|
@ -13,6 +13,7 @@ Les classes de Bonus fournissent deux méthodes:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
import math
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@ -52,7 +53,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:
|
||||||
@ -106,6 +107,8 @@ class BonusSport:
|
|||||||
# sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
|
# sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
|
||||||
# ou (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
# ou (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||||
nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
|
nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
|
||||||
|
if nb_etuds == 0 or nb_mod_sport == 0:
|
||||||
|
return # no bonus at all
|
||||||
# Enlève les NaN du numérateur:
|
# Enlève les NaN du numérateur:
|
||||||
sem_modimpl_moys_no_nan = np.nan_to_num(sem_modimpl_moys_spo, nan=0.0)
|
sem_modimpl_moys_no_nan = np.nan_to_num(sem_modimpl_moys_spo, nan=0.0)
|
||||||
|
|
||||||
@ -157,7 +160,8 @@ class BonusSport:
|
|||||||
"""Calcul des bonus: méthode virtuelle à écraser.
|
"""Calcul des bonus: méthode virtuelle à écraser.
|
||||||
Arguments:
|
Arguments:
|
||||||
- sem_modimpl_moys_inscrits:
|
- sem_modimpl_moys_inscrits:
|
||||||
ndarray (nb_etuds, mod_sport) ou en APC (nb_etuds, mods_sport, nb_ue_non_bonus)
|
ndarray (nb_etuds, mod_sport)
|
||||||
|
ou en APC (nb_etuds, mods_sport, nb_ue_non_bonus)
|
||||||
les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans.
|
les notes aux modules sports auxquel l'étudiant est inscrit, 0 sinon. Pas de nans.
|
||||||
- modimpl_coefs_etuds_no_nan:
|
- modimpl_coefs_etuds_no_nan:
|
||||||
les coefficients: float ndarray
|
les coefficients: float ndarray
|
||||||
@ -201,7 +205,8 @@ class BonusSportAdditif(BonusSport):
|
|||||||
"""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...
|
||||||
@ -224,22 +229,28 @@ class BonusSportAdditif(BonusSport):
|
|||||||
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)
|
||||||
|
|
||||||
# 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(
|
||||||
bonus_moy_arr, index=self.etuds_idx, dtype=float
|
bonus_moy_arr, index=self.etuds_idx, dtype=float
|
||||||
)
|
)
|
||||||
|
|
||||||
# if len(bonus_moy_arr.shape) > 1:
|
|
||||||
# bonus_moy_arr = bonus_moy_arr.sum(axis=1)
|
|
||||||
# Laisse bonus_moy_gen à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
|
||||||
|
|
||||||
|
|
||||||
class BonusSportMultiplicatif(BonusSport):
|
class BonusSportMultiplicatif(BonusSport):
|
||||||
"""Bonus sport qui multiplie les moyennes d'UE par un facteur"""
|
"""Bonus sport qui multiplie les moyennes d'UE par un facteur"""
|
||||||
@ -284,6 +295,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,8 +306,68 @@ class BonusDirect(BonusSportAdditif):
|
|||||||
proportion_point = 1.0
|
proportion_point = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
"""Calcul bonus modules optionels (sport), règle IUT de Béthune.
|
"""Calcul bonus modules optionnels (sport), règle IUT de Béthune.
|
||||||
|
|
||||||
Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre.
|
Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre.
|
||||||
Ce bonus est égal au nombre de points divisé par 200 et multiplié par la
|
Ce bonus est égal au nombre de points divisé par 200 et multiplié par la
|
||||||
@ -309,7 +381,7 @@ class BonusBethune(BonusSportMultiplicatif):
|
|||||||
|
|
||||||
|
|
||||||
class BonusBezier(BonusSportAdditif):
|
class BonusBezier(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT de Bézier.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Bézier.
|
||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
sport , etc) non rattachés à une unité d'enseignement. Les points
|
sport , etc) non rattachés à une unité d'enseignement. Les points
|
||||||
@ -330,28 +402,91 @@ class BonusBezier(BonusSportAdditif):
|
|||||||
|
|
||||||
|
|
||||||
class BonusBordeaux1(BonusSportMultiplicatif):
|
class BonusBordeaux1(BonusSportMultiplicatif):
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
|
||||||
et UE.
|
et UE.
|
||||||
|
<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
|
||||||
|
|
||||||
|
|
||||||
|
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 BonusColmar(BonusSportAdditif):
|
class BonusColmar(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Colmar.
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Colmar.
|
||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non
|
de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non
|
||||||
@ -374,19 +509,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"
|
||||||
@ -411,11 +548,13 @@ class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
|||||||
|
|
||||||
|
|
||||||
class BonusLaRochelle(BonusSportAdditif):
|
class BonusLaRochelle(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (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"
|
||||||
@ -440,16 +579,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"
|
||||||
@ -471,14 +611,15 @@ class BonusLeMans(BonusSportAdditif):
|
|||||||
|
|
||||||
# Bonus simple, mais avec changement de paramètres en 2010 !
|
# Bonus simple, mais avec changement de paramètres en 2010 !
|
||||||
class BonusLille(BonusSportAdditif):
|
class BonusLille(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (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"
|
||||||
@ -530,17 +671,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"
|
||||||
@ -561,11 +704,11 @@ 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
|
||||||
|
|
||||||
|
|
||||||
class BonusStDenis(BonusSportAdditif):
|
class BonusStDenis(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Saint-Denis
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Saint-Denis
|
||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'Université Paris 13 (sports, musique, deuxième langue,
|
de l'Université Paris 13 (sports, musique, deuxième langue,
|
||||||
@ -584,13 +727,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"
|
||||||
@ -611,15 +755,17 @@ class BonusTours(BonusDirect):
|
|||||||
|
|
||||||
|
|
||||||
class BonusVilleAvray(BonusSport):
|
class BonusVilleAvray(BonusSport):
|
||||||
"""Bonus modules optionels (sport, culture), règle IUT Ville d'Avray.
|
"""Bonus modules optionnels (sport, culture), règle IUT Ville d'Avray.
|
||||||
|
|
||||||
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"
|
||||||
@ -645,7 +791,7 @@ class BonusVilleAvray(BonusSport):
|
|||||||
|
|
||||||
|
|
||||||
class BonusIUTV(BonusSportAdditif):
|
class BonusIUTV(BonusSportAdditif):
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT Villetaneuse
|
||||||
|
|
||||||
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
de l'Université Paris 13 (sports, musique, deuxième langue,
|
de l'Université Paris 13 (sports, musique, deuxième langue,
|
||||||
@ -657,7 +803,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):
|
||||||
|
52
app/comp/moy_mat.py
Normal file
52
app/comp/moy_mat.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
##############################################################################
|
||||||
|
# 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_gen, _, _ = moy_ue.compute_ue_moys_classic(
|
||||||
|
formsemestre,
|
||||||
|
sem_matrix=sem_matrix,
|
||||||
|
ues=ues,
|
||||||
|
modimpl_inscr_df=modimpl_inscr_df,
|
||||||
|
modimpl_coefs=modimpl_coefs,
|
||||||
|
modimpl_mask=modimpl_mask,
|
||||||
|
)
|
||||||
|
matiere_moy[matiere_id] = etud_moy_gen
|
||||||
|
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,6 +373,10 @@ 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)
|
||||||
|
if (
|
||||||
|
modimpl.module.module_type == ModuleType.RESSOURCE
|
||||||
|
or modimpl.module.module_type == ModuleType.SAE
|
||||||
|
):
|
||||||
for ue_poids in EvaluationUEPoids.query.join(
|
for ue_poids in EvaluationUEPoids.query.join(
|
||||||
EvaluationUEPoids.evaluation
|
EvaluationUEPoids.evaluation
|
||||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||||
|
@ -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)
|
||||||
@ -266,6 +270,8 @@ def compute_ue_moys_apc(
|
|||||||
)
|
)
|
||||||
# Annule les coefs des modules NaN
|
# Annule les coefs des modules NaN
|
||||||
modimpl_coefs_etuds_no_nan = np.where(np.isnan(sem_cube), 0.0, modimpl_coefs_etuds)
|
modimpl_coefs_etuds_no_nan = np.where(np.isnan(sem_cube), 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)
|
||||||
#
|
#
|
||||||
# Version vectorisée
|
# Version vectorisée
|
||||||
#
|
#
|
||||||
@ -348,7 +354,8 @@ def compute_ue_moys_classic(
|
|||||||
modimpl_coefs_etuds_no_nan = np.where(
|
modimpl_coefs_etuds_no_nan = np.where(
|
||||||
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
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)
|
||||||
# --------------------- Calcul des moyennes d'UE
|
# --------------------- Calcul des moyennes d'UE
|
||||||
ue_modules = np.array(
|
ue_modules = np.array(
|
||||||
[[m.module.ue == ue for m in formsemestre.modimpls_sorted] for ue in ues]
|
[[m.module.ue == ue for m in formsemestre.modimpls_sorted] for ue in ues]
|
||||||
@ -358,6 +365,8 @@ def compute_ue_moys_classic(
|
|||||||
)
|
)
|
||||||
# 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
|
||||||
|
coefs = coefs.astype(np.float)
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
etud_moy_ue = (
|
etud_moy_ue = (
|
||||||
np.sum(coefs * sem_matrix_inscrits, axis=2) / np.sum(coefs, axis=2)
|
np.sum(coefs * sem_matrix_inscrits, axis=2) / np.sum(coefs, axis=2)
|
||||||
|
@ -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,6 +69,7 @@ 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(
|
||||||
@ -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(
|
||||||
|
@ -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,8 +114,17 @@ 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
|
||||||
|
elif bonus_mg is not None:
|
||||||
|
# Applique le bonus moyenne générale renvoyé
|
||||||
|
self.etud_moy_gen += bonus_mg
|
||||||
|
|
||||||
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
# compat nt, utilisé pour l'afficher sur les bulletins:
|
# compat nt, utilisé pour l'afficher sur les bulletins:
|
||||||
self.bonus = bonus_mg
|
self.bonus = bonus_mg
|
||||||
@ -124,6 +134,10 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
# --- 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 +163,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
|
||||||
|
@ -39,6 +39,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 +58,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 +168,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,7 +186,9 @@ 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"]):
|
||||||
@ -195,6 +199,12 @@ 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, ue):
|
||||||
""""""
|
""""""
|
||||||
@ -510,8 +520,9 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
|
|
||||||
def get_etud_mat_moy(self, matiere_id, etudid):
|
def get_etud_mat_moy(self, matiere_id, etudid):
|
||||||
"""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][etudid]
|
||||||
|
|
||||||
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
|
||||||
|
@ -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)
|
||||||
@ -356,7 +361,7 @@ class FormSemestre(db.Model):
|
|||||||
|
|
||||||
def get_abs_count(self, etudid):
|
def get_abs_count(self, etudid):
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs, nb abs justifiées)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_abs
|
from app.scodoc import sco_abs
|
||||||
|
@ -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",
|
||||||
|
@ -34,7 +34,7 @@ class Module(db.Model):
|
|||||||
# id de l'element pedagogique Apogee correspondant:
|
# id de l'element pedagogique Apogee correspondant:
|
||||||
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
||||||
# Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
|
# Type: ModuleType: DEFAULT, MALUS, RESSOURCE, MODULE_SAE (enum)
|
||||||
module_type = db.Column(db.Integer)
|
module_type = db.Column(db.Integer, nullable=False, default=0, server_default="0")
|
||||||
# Relations:
|
# Relations:
|
||||||
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
||||||
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
|
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
|
||||||
|
@ -1037,7 +1037,7 @@ def get_abs_count(etudid, sem):
|
|||||||
|
|
||||||
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs, nb abs justifiées)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
|
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
|
||||||
|
@ -65,7 +65,7 @@ from app.scodoc import sco_preferences
|
|||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pvjury
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
|
|
||||||
# ----- CLASSES DE BULLETINS DE NOTES
|
# ----- CLASSES DE BULLETINS DE NOTES
|
||||||
@ -189,7 +189,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
formsemestre.etuds_inscriptions[etudid].etat
|
formsemestre.etuds_inscriptions[etudid].etat
|
||||||
)
|
)
|
||||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||||
I["filigranne"] = sco_bulletins_pdf.get_filigranne(I["etud_etat"], prefs)
|
I["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||||
|
I["etud_etat"], prefs, decision_dem=I["decision_sem"]
|
||||||
|
)
|
||||||
I["demission"] = ""
|
I["demission"] = ""
|
||||||
if I["etud_etat"] == scu.DEMISSION:
|
if I["etud_etat"] == scu.DEMISSION:
|
||||||
I["demission"] = "(Démission)"
|
I["demission"] = "(Démission)"
|
||||||
@ -197,15 +199,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["demission"] = "(Défaillant)"
|
I["demission"] = "(Défaillant)"
|
||||||
|
|
||||||
# --- Appreciations
|
# --- Appreciations
|
||||||
cnx = ndb.GetDBConnexion()
|
I.update(get_appreciations_list(formsemestre_id, etudid))
|
||||||
apprecs = sco_etud.appreciations_list(
|
|
||||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
|
||||||
)
|
|
||||||
I["appreciations_list"] = apprecs
|
|
||||||
I["appreciations_txt"] = [x["date"] + ": " + x["comment"] for x in apprecs]
|
|
||||||
I["appreciations"] = I[
|
|
||||||
"appreciations_txt"
|
|
||||||
] # deprecated / keep it for backward compat in templates
|
|
||||||
|
|
||||||
# --- Notes
|
# --- Notes
|
||||||
ues = nt.get_ues_stat_dict()
|
ues = nt.get_ues_stat_dict()
|
||||||
@ -297,7 +291,9 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
else:
|
else:
|
||||||
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"] = f"bonus de {fmt_note(x)} points"
|
||||||
|
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"])
|
||||||
@ -395,6 +391,21 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
return C
|
return C
|
||||||
|
|
||||||
|
|
||||||
|
def get_appreciations_list(formsemestre_id: int, etudid: int) -> dict:
|
||||||
|
"""Appréciations pour cet étudiant dans ce semestre"""
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
apprecs = sco_etud.appreciations_list(
|
||||||
|
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||||
|
)
|
||||||
|
d = {
|
||||||
|
"appreciations_list": apprecs,
|
||||||
|
"appreciations_txt": [x["date"] + ": " + x["comment"] for x in apprecs],
|
||||||
|
}
|
||||||
|
# deprecated / keep it for backward compat in templates:
|
||||||
|
d["appreciations"] = d["appreciations_txt"]
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _get_etud_etat_html(etat: str) -> str:
|
def _get_etud_etat_html(etat: str) -> str:
|
||||||
"""chaine html représentant l'état (backward compat sco7)"""
|
"""chaine html représentant l'état (backward compat sco7)"""
|
||||||
if etat == scu.INSCRIT: # "I"
|
if etat == scu.INSCRIT: # "I"
|
||||||
@ -921,7 +932,7 @@ def do_formsemestre_bulletinetud(
|
|||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
etud = Identite.query.get(etudid)
|
etud = Identite.query.get(etudid)
|
||||||
r = bulletin_but.BulletinBUT(formsemestre)
|
r = bulletin_but.BulletinBUT(formsemestre)
|
||||||
I = r.bulletin_etud_complet(etud, formsemestre)
|
I = r.bulletin_etud_complet(etud)
|
||||||
else:
|
else:
|
||||||
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||||
etud = I["etud"]
|
etud = I["etud"]
|
||||||
|
@ -63,41 +63,6 @@ from app.scodoc import sco_pdf
|
|||||||
from app.scodoc.sco_pdf import PDFLOCK
|
from app.scodoc.sco_pdf import PDFLOCK
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
# Liste des types des classes de générateurs de bulletins PDF:
|
|
||||||
BULLETIN_CLASSES = collections.OrderedDict()
|
|
||||||
|
|
||||||
|
|
||||||
def register_bulletin_class(klass):
|
|
||||||
BULLETIN_CLASSES[klass.__name__] = klass
|
|
||||||
|
|
||||||
|
|
||||||
def bulletin_class_descriptions():
|
|
||||||
return [x.description for x in BULLETIN_CLASSES.values()]
|
|
||||||
|
|
||||||
|
|
||||||
def bulletin_class_names():
|
|
||||||
return list(BULLETIN_CLASSES.keys())
|
|
||||||
|
|
||||||
|
|
||||||
def bulletin_default_class_name():
|
|
||||||
return bulletin_class_names()[0]
|
|
||||||
|
|
||||||
|
|
||||||
def bulletin_get_class(class_name):
|
|
||||||
return BULLETIN_CLASSES[class_name]
|
|
||||||
|
|
||||||
|
|
||||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
|
||||||
"""Le nom du générateur utilisé, en clair"""
|
|
||||||
from app.scodoc import sco_preferences
|
|
||||||
|
|
||||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
|
||||||
try:
|
|
||||||
gen_class = bulletin_get_class(bul_class_name)
|
|
||||||
return gen_class.description
|
|
||||||
except:
|
|
||||||
return "invalide ! (voir paramètres)"
|
|
||||||
|
|
||||||
|
|
||||||
class BulletinGenerator:
|
class BulletinGenerator:
|
||||||
"Virtual superclass for PDF bulletin generators" ""
|
"Virtual superclass for PDF bulletin generators" ""
|
||||||
@ -105,6 +70,7 @@ class BulletinGenerator:
|
|||||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||||
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
||||||
description = "superclass for bulletins" # description for user interface
|
description = "superclass for bulletins" # description for user interface
|
||||||
|
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -270,9 +236,14 @@ def make_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
formsemestre_id = infos["formsemestre_id"]
|
formsemestre_id = infos["formsemestre_id"]
|
||||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||||
try:
|
|
||||||
|
gen_class = None
|
||||||
|
if infos.get("type") == "BUT" and format.startswith("pdf"):
|
||||||
|
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||||
|
if gen_class is None:
|
||||||
gen_class = bulletin_get_class(bul_class_name)
|
gen_class = bulletin_get_class(bul_class_name)
|
||||||
except:
|
|
||||||
|
if gen_class is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
||||||
)
|
)
|
||||||
@ -313,3 +284,48 @@ def make_formsemestre_bulletinetud(
|
|||||||
filename = bul_generator.get_filename()
|
filename = bul_generator.get_filename()
|
||||||
|
|
||||||
return data, filename
|
return data, filename
|
||||||
|
|
||||||
|
|
||||||
|
####
|
||||||
|
|
||||||
|
# Liste des types des classes de générateurs de bulletins PDF:
|
||||||
|
BULLETIN_CLASSES = collections.OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
|
def register_bulletin_class(klass):
|
||||||
|
BULLETIN_CLASSES[klass.__name__] = klass
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_class_descriptions():
|
||||||
|
return [x.description for x in BULLETIN_CLASSES.values()]
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_class_names() -> list[str]:
|
||||||
|
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
|
||||||
|
return [
|
||||||
|
class_name
|
||||||
|
for class_name in BULLETIN_CLASSES
|
||||||
|
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_default_class_name():
|
||||||
|
return bulletin_class_names()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_get_class(class_name: str) -> BulletinGenerator:
|
||||||
|
"""La class de génération de bulletin de ce nom,
|
||||||
|
ou None si pas trouvée
|
||||||
|
"""
|
||||||
|
return BULLETIN_CLASSES.get(class_name)
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||||
|
"""Le nom du générateur utilisé, en clair"""
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||||
|
gen_class = bulletin_get_class(bul_class_name)
|
||||||
|
if gen_class is None:
|
||||||
|
return "invalide ! (voir paramètres)"
|
||||||
|
return gen_class.description
|
||||||
|
@ -276,13 +276,13 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
|||||||
return pdfdoc, filename
|
return pdfdoc, filename
|
||||||
|
|
||||||
|
|
||||||
def get_filigranne(etud_etat: str, prefs) -> str:
|
def get_filigranne(etud_etat: str, prefs, decision_sem=None) -> str:
|
||||||
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
||||||
if etud_etat == scu.DEMISSION:
|
if etud_etat == scu.DEMISSION:
|
||||||
return "Démission"
|
return "Démission"
|
||||||
elif etud_etat == sco_codes_parcours.DEF:
|
elif etud_etat == sco_codes_parcours.DEF:
|
||||||
return "Défaillant"
|
return "Défaillant"
|
||||||
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
|
elif (prefs["bul_show_temporary"] and not decision_sem) or prefs[
|
||||||
"bul_show_temporary_forced"
|
"bul_show_temporary_forced"
|
||||||
]:
|
]:
|
||||||
return prefs["bul_temporary_txt"]
|
return prefs["bul_temporary_txt"]
|
||||||
|
@ -66,7 +66,8 @@ from app.scodoc import sco_groups
|
|||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import gen_tables
|
from app.scodoc import gen_tables
|
||||||
|
|
||||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
# Important: Le nom de la classe ne doit pas changer (bien le choisir),
|
||||||
|
# car il sera stocké en base de données (dans les préférences)
|
||||||
class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||||
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
|
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
|
||||||
supported_formats = ["html", "pdf"]
|
supported_formats = ["html", "pdf"]
|
||||||
@ -264,11 +265,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
|
|
||||||
def build_bulletin_table(self):
|
def build_bulletin_table(self):
|
||||||
"""Génère la table centrale du bulletin de notes
|
"""Génère la table centrale du bulletin de notes
|
||||||
Renvoie: colkeys, P, pdf_style, colWidths
|
Renvoie: col_keys, P, pdf_style, col_widths
|
||||||
- colkeys: nom des colonnes de la table (clés)
|
- col_keys: nom des colonnes de la table (clés)
|
||||||
- table (liste de dicts de chaines de caracteres)
|
- table: liste de dicts de chaines de caractères
|
||||||
- style (commandes table Platypus)
|
- pdf_style: commandes table Platypus
|
||||||
- largeurs de colonnes pour PDF
|
- col_widths: largeurs de colonnes pour PDF
|
||||||
"""
|
"""
|
||||||
I = self.infos
|
I = self.infos
|
||||||
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
||||||
@ -287,25 +288,25 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
with_col_coef = prefs["bul_show_coef"]
|
with_col_coef = prefs["bul_show_coef"]
|
||||||
with_col_ects = prefs["bul_show_ects"]
|
with_col_ects = prefs["bul_show_ects"]
|
||||||
|
|
||||||
colkeys = ["titre", "module"] # noms des colonnes à afficher
|
col_keys = ["titre", "module"] # noms des colonnes à afficher
|
||||||
if with_col_rang:
|
if with_col_rang:
|
||||||
colkeys += ["rang"]
|
col_keys += ["rang"]
|
||||||
if with_col_minmax:
|
if with_col_minmax:
|
||||||
colkeys += ["min"]
|
col_keys += ["min"]
|
||||||
if with_col_moypromo:
|
if with_col_moypromo:
|
||||||
colkeys += ["moy"]
|
col_keys += ["moy"]
|
||||||
if with_col_minmax:
|
if with_col_minmax:
|
||||||
colkeys += ["max"]
|
col_keys += ["max"]
|
||||||
colkeys += ["note"]
|
col_keys += ["note"]
|
||||||
if with_col_coef:
|
if with_col_coef:
|
||||||
colkeys += ["coef"]
|
col_keys += ["coef"]
|
||||||
if with_col_ects:
|
if with_col_ects:
|
||||||
colkeys += ["ects"]
|
col_keys += ["ects"]
|
||||||
if with_col_abs:
|
if with_col_abs:
|
||||||
colkeys += ["abs"]
|
col_keys += ["abs"]
|
||||||
colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus)
|
colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus)
|
||||||
i = 0
|
i = 0
|
||||||
for k in colkeys:
|
for k in col_keys:
|
||||||
colidx[k] = i
|
colidx[k] = i
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
@ -313,7 +314,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm
|
bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm
|
||||||
else:
|
else:
|
||||||
bul_pdf_mod_colwidth = None
|
bul_pdf_mod_colwidth = None
|
||||||
colWidths = {
|
col_widths = {
|
||||||
"titre": None,
|
"titre": None,
|
||||||
"module": bul_pdf_mod_colwidth,
|
"module": bul_pdf_mod_colwidth,
|
||||||
"min": 1.5 * cm,
|
"min": 1.5 * cm,
|
||||||
@ -541,7 +542,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||||
]
|
]
|
||||||
#
|
#
|
||||||
return colkeys, P, pdf_style, colWidths
|
return col_keys, P, pdf_style, col_widths
|
||||||
|
|
||||||
def _list_modules(
|
def _list_modules(
|
||||||
self,
|
self,
|
||||||
|
@ -500,13 +500,20 @@ 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
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
|
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
|
||||||
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
|
||||||
|
|
||||||
|
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
||||||
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
|
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
|
||||||
|
|
||||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||||
@ -734,8 +741,11 @@ def module_edit(module_id=None):
|
|||||||
else:
|
else:
|
||||||
# l'UE de rattachement peut changer
|
# l'UE de rattachement peut changer
|
||||||
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
|
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
|
||||||
|
x, y = tf[2]["ue_matiere_id"].split("!")
|
||||||
|
tf[2]["ue_id"] = int(x)
|
||||||
|
tf[2]["matiere_id"] = int(y)
|
||||||
old_ue_id = a_module.ue.id
|
old_ue_id = a_module.ue.id
|
||||||
new_ue_id = int(tf[2]["ue_id"])
|
new_ue_id = tf[2]["ue_id"]
|
||||||
if (old_ue_id != new_ue_id) and in_use:
|
if (old_ue_id != new_ue_id) and in_use:
|
||||||
new_ue = UniteEns.query.get_or_404(new_ue_id)
|
new_ue = UniteEns.query.get_or_404(new_ue_id)
|
||||||
if new_ue.semestre_idx != a_module.ue.semestre_idx:
|
if new_ue.semestre_idx != a_module.ue.semestre_idx:
|
||||||
|
@ -601,7 +601,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 +669,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
|
||||||
@ -930,13 +941,13 @@ 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:
|
|
||||||
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:
|
||||||
lab = "Semestre %s:" % ue["semestre_id"]
|
lab = "Semestre %s:" % ue["semestre_id"]
|
||||||
H.append('<div class="ue_list_tit_sem">%s</div>' % lab)
|
H.append(
|
||||||
|
'<div class="ue_list_div"><div class="ue_list_tit_sem">%s</div>' % lab
|
||||||
|
)
|
||||||
H.append('<ul class="notes_ue_list">')
|
H.append('<ul class="notes_ue_list">')
|
||||||
H.append('<li class="notes_ue_list">')
|
H.append('<li class="notes_ue_list">')
|
||||||
if iue != 0 and editable:
|
if iue != 0 and editable:
|
||||||
@ -953,7 +964,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 +1011,16 @@ 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>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
iue += 1
|
||||||
|
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,9 +633,7 @@ 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,
|
moduleimpl_id,
|
||||||
Mod["code"] or "",
|
Mod["code"] or "",
|
||||||
Mod["titre"] or "?",
|
Mod["titre"] or "?",
|
||||||
@ -645,7 +641,6 @@ def evaluation_describe(evaluation_id="", edit_in_place=True):
|
|||||||
resp,
|
resp,
|
||||||
link,
|
link,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
etit = E["description"] or ""
|
etit = E["description"] or ""
|
||||||
if etit:
|
if etit:
|
||||||
|
@ -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,
|
||||||
|
@ -1286,7 +1286,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>")
|
||||||
|
@ -302,7 +302,12 @@ class DisplayedGroupsInfos(object):
|
|||||||
if group_ids:
|
if group_ids:
|
||||||
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
|
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
group_ids = [int(g) for g in group_ids]
|
group_ids = [int(g) for g in group_ids]
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
"identifiant de groupe invalide (mettre à jour vos bookmarks ?)"
|
||||||
|
) from exc
|
||||||
if not formsemestre_id and moduleimpl_id:
|
if not formsemestre_id and moduleimpl_id:
|
||||||
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if len(mods) != 1:
|
if len(mods) != 1:
|
||||||
|
@ -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"]
|
||||||
|
@ -1699,7 +1699,7 @@ ul.notes_ue_list {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
padding-top: 1em;
|
/* padding-top: 1em; */
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@ -1707,6 +1707,9 @@ ul.notes_ue_list {
|
|||||||
li.notes_ue_list {
|
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 +1752,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 +1761,27 @@ ul.notes_module_list {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.ue_list_div {
|
||||||
|
border: 3px solid rgb(35, 0, 160);
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.ue_list_tit_sem {
|
||||||
|
font-size: 120%;
|
||||||
|
font-weight: bold;
|
||||||
|
color: orangered;
|
||||||
|
display: list-item; /* This has to be "list-item" */
|
||||||
|
list-style-type: disc; /* See https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type */
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.sco_tag_checkbox {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.notes_ue_list a.stdlink {
|
.notes_ue_list a.stdlink {
|
||||||
color: #001084;
|
color: #001084;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -48,6 +48,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>
|
||||||
|
@ -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:
|
||||||
|
@ -397,7 +397,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ue_infos/<ue_id>")
|
@bp.route("/ue_infos/<int:ue_id>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def ue_infos(ue_id):
|
def ue_infos(ue_id):
|
||||||
|
@ -68,7 +68,7 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
|
|
||||||
|
|
||||||
@bp.route("/table_modules_ue_coefs/<int:formation_id>")
|
@bp.route("/table_modules_ue_coefs/<int:formation_id>")
|
||||||
@bp.route("/table_modules_ue_coefs/<int:formation_id>/<semestre_idx>")
|
@bp.route("/table_modules_ue_coefs/<int:formation_id>/<int:semestre_idx>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def table_modules_ue_coefs(formation_id, semestre_idx=None):
|
def table_modules_ue_coefs(formation_id, semestre_idx=None):
|
||||||
|
@ -121,7 +121,7 @@ def create_dept():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/toggle_dept_vis/<dept_id>", methods=["GET", "POST"])
|
@bp.route("/ScoDoc/toggle_dept_vis/<int:dept_id>", methods=["GET", "POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def toggle_dept_vis(dept_id):
|
def toggle_dept_vis(dept_id):
|
||||||
"""Cache ou rend visible un dept"""
|
"""Cache ou rend visible un dept"""
|
||||||
|
41
migrations/versions/b9aadc10227f_module_type_non_null.py
Normal file
41
migrations/versions/b9aadc10227f_module_type_non_null.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""module_type_non_null
|
||||||
|
|
||||||
|
Revision ID: b9aadc10227f
|
||||||
|
Revises: bd2c1c3d866e
|
||||||
|
Create Date: 2022-02-15 21:47:29.212329
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
from sqlalchemy.orm import sessionmaker # added by ev
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "b9aadc10227f"
|
||||||
|
down_revision = "bd2c1c3d866e"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
Session = sessionmaker()
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Added by ev: remove duplicates
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = Session(bind=bind)
|
||||||
|
session.execute(
|
||||||
|
"""UPDATE notes_modules SET module_type=0 WHERE module_type IS NULL;"""
|
||||||
|
)
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"notes_modules", "module_type", existing_type=sa.INTEGER(), nullable=False
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"notes_modules", "module_type", existing_type=sa.INTEGER(), nullable=True
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
@ -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.2a-57"
|
SCOVERSION = "9.2a-62"
|
||||||
|
|
||||||
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