forked from ScoDoc/ScoDoc
Merge branch 'refactor_nt' of https://scodoc.org/git/ScoDoc/ScoDoc into entreprises
This commit is contained in:
commit
2207d25e35
@ -18,12 +18,12 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### État actuel (4 dec 21)
|
### État actuel (26 jan 22)
|
||||||
|
|
||||||
- 9.0 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
- 9.1 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||||
- ancien module "Entreprises" (obsolète)
|
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
|
||||||
|
|
||||||
- 9.1 (branche "PNBUT") est la version de développement.
|
- 9.2 (branche refactor_nt) est la version de développement.
|
||||||
|
|
||||||
|
|
||||||
### Lignes de commandes
|
### Lignes de commandes
|
||||||
|
@ -253,7 +253,7 @@ def create_app(config_class=DevConfig):
|
|||||||
host_name = socket.gethostname()
|
host_name = socket.gethostname()
|
||||||
mail_handler = ScoSMTPHandler(
|
mail_handler = ScoSMTPHandler(
|
||||||
mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]),
|
mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]),
|
||||||
fromaddr="no-reply@" + app.config["MAIL_SERVER"],
|
fromaddr=app.config["SCODOC_MAIL_FROM"],
|
||||||
toaddrs=["exception@scodoc.org"],
|
toaddrs=["exception@scodoc.org"],
|
||||||
subject="ScoDoc Exception", # unused see ScoSMTPHandler
|
subject="ScoDoc Exception", # unused see ScoSMTPHandler
|
||||||
credentials=auth,
|
credentials=auth,
|
||||||
|
@ -8,7 +8,7 @@ def send_password_reset_email(user):
|
|||||||
token = user.get_reset_password_token()
|
token = user.get_reset_password_token()
|
||||||
send_email(
|
send_email(
|
||||||
"[ScoDoc] Réinitialisation de votre mot de passe",
|
"[ScoDoc] Réinitialisation de votre mot de passe",
|
||||||
sender=current_app.config["ADMINS"][0],
|
sender=current_app.config["SCODOC_MAIL_FROM"],
|
||||||
recipients=[user.email],
|
recipients=[user.email],
|
||||||
text_body=render_template("email/reset_password.txt", user=user, token=token),
|
text_body=render_template("email/reset_password.txt", user=user, token=token),
|
||||||
html_body=render_template("email/reset_password.html", user=user, token=token),
|
html_body=render_template("email/reset_password.html", user=user, token=token),
|
||||||
|
@ -112,6 +112,7 @@ class User(UserMixin, db.Model):
|
|||||||
self.password_hash = generate_password_hash(password)
|
self.password_hash = generate_password_hash(password)
|
||||||
else:
|
else:
|
||||||
self.password_hash = None
|
self.password_hash = None
|
||||||
|
self.passwd_temp = False
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
"""Check given password vs current one.
|
"""Check given password vs current one.
|
||||||
|
@ -13,6 +13,7 @@ from flask import url_for, g
|
|||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc import sco_bulletins_json
|
from app.scodoc import sco_bulletins_json
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc.sco_utils import fmt_note
|
from app.scodoc.sco_utils import fmt_note
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
|
|
||||||
@ -27,10 +28,12 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
||||||
d = {}
|
d = {}
|
||||||
etud_idx = self.etud_index[etud.id]
|
etud_idx = self.etud_index[etud.id]
|
||||||
|
if ue.type != UE_SPORT:
|
||||||
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
ue_idx = self.modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
etud_moy_module = self.sem_cube[etud_idx] # module x UE
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit
|
if self.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
|
||||||
|
if ue.type != UE_SPORT:
|
||||||
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||||
if coef > 0:
|
if coef > 0:
|
||||||
d[modimpl.module.code] = {
|
d[modimpl.module.code] = {
|
||||||
@ -42,30 +45,54 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
][ue_idx]
|
][ue_idx]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
# else: # modules dans UE bonus sport
|
||||||
|
# d[modimpl.module.code] = {
|
||||||
|
# "id": modimpl.id,
|
||||||
|
# "coef": "",
|
||||||
|
# "moyenne": "?x?",
|
||||||
|
# }
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def etud_ue_results(self, etud, ue):
|
def etud_ue_results(self, etud, ue):
|
||||||
"dict synthèse résultats UE"
|
"dict synthèse résultats UE"
|
||||||
d = {
|
d = {
|
||||||
"id": ue.id,
|
"id": ue.id,
|
||||||
|
"titre": ue.titre,
|
||||||
"numero": ue.numero,
|
"numero": ue.numero,
|
||||||
|
"type": ue.type,
|
||||||
"ECTS": {
|
"ECTS": {
|
||||||
"acquis": 0, # XXX TODO voir jury
|
"acquis": 0, # XXX TODO voir jury
|
||||||
"total": ue.ects,
|
"total": ue.ects,
|
||||||
},
|
},
|
||||||
|
"color": ue.color,
|
||||||
"competence": None, # XXX TODO lien avec référentiel
|
"competence": None, # XXX TODO lien avec référentiel
|
||||||
"moyenne": {
|
"moyenne": None,
|
||||||
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
# Le bonus sport appliqué sur cette UE
|
||||||
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
"bonus": fmt_note(self.bonus_ues[ue.id][etud.id])
|
||||||
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
if self.bonus_ues is not None and ue.id in self.bonus_ues
|
||||||
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
else fmt_note(0.0),
|
||||||
},
|
|
||||||
"bonus": None, # XXX TODO
|
|
||||||
"malus": None, # XXX TODO voir ce qui est ici
|
"malus": None, # XXX TODO voir ce qui est ici
|
||||||
"capitalise": None, # "AAAA-MM-JJ" TODO
|
"capitalise": None, # "AAAA-MM-JJ" TODO
|
||||||
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
|
"ressources": self.etud_ue_mod_results(etud, ue, self.ressources),
|
||||||
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
"saes": self.etud_ue_mod_results(etud, ue, self.saes),
|
||||||
}
|
}
|
||||||
|
if ue.type != UE_SPORT:
|
||||||
|
d["moyenne"] = {
|
||||||
|
"value": fmt_note(self.etud_moy_ue[ue.id][etud.id]),
|
||||||
|
"min": fmt_note(self.etud_moy_ue[ue.id].min()),
|
||||||
|
"max": fmt_note(self.etud_moy_ue[ue.id].max()),
|
||||||
|
"moy": fmt_note(self.etud_moy_ue[ue.id].mean()),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# ceci suppose que l'on a une seule UE bonus,
|
||||||
|
# en tous cas elles auront la même description
|
||||||
|
d["bonus_description"] = self.etud_bonus_description(etud.id)
|
||||||
|
modimpls_spo = [
|
||||||
|
modimpl
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
|
if modimpl.module.ue.type == UE_SPORT
|
||||||
|
]
|
||||||
|
d["modules"] = self.etud_mods_results(etud, modimpls_spo)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def etud_mods_results(self, etud, modimpls) -> dict:
|
def etud_mods_results(self, etud, modimpls) -> dict:
|
||||||
@ -88,7 +115,7 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
# except RuntimeWarning: # all nans in np.nanmean
|
# except RuntimeWarning: # all nans in np.nanmean
|
||||||
# pass
|
# pass
|
||||||
modimpl_results = self.modimpls_results[modimpl.id]
|
modimpl_results = self.modimpls_results[modimpl.id]
|
||||||
if self.modimpl_inscr_df[str(modimpl.id)][etud.id]: # si inscrit
|
if self.modimpl_inscr_df[modimpl.id][etud.id]: # si inscrit
|
||||||
d[modimpl.module.code] = {
|
d[modimpl.module.code] = {
|
||||||
"id": modimpl.id,
|
"id": modimpl.id,
|
||||||
"titre": modimpl.module.titre,
|
"titre": modimpl.module.titre,
|
||||||
@ -144,14 +171,42 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def bulletin_etud(self, etud, formsemestre) -> dict:
|
def etud_bonus_description(self, etudid):
|
||||||
"""Le bulletin de l'étudiant dans ce semestre"""
|
"""description du bonus affichée dans la section "UE bonus"."""
|
||||||
|
if self.bonus_ues is None or self.bonus_ues.shape[1] == 0:
|
||||||
|
return ""
|
||||||
|
import random
|
||||||
|
|
||||||
|
bonus_vect = self.bonus_ues.loc[etudid]
|
||||||
|
if bonus_vect.nunique() > 1:
|
||||||
|
# détail UE par UE
|
||||||
|
details = [
|
||||||
|
f"{fmt_note(bonus_vect[ue.id])} sur {ue.acronyme}"
|
||||||
|
for ue in self.ues
|
||||||
|
if self.modimpls_in_ue(ue.id, etudid)
|
||||||
|
and ue.id in self.bonus_ues
|
||||||
|
and bonus_vect[ue.id] > 0.0
|
||||||
|
]
|
||||||
|
if details:
|
||||||
|
return "Bonus de " + ", ".join(details)
|
||||||
|
else:
|
||||||
|
return "" # aucun bonus
|
||||||
|
else:
|
||||||
|
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
||||||
|
|
||||||
|
def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict:
|
||||||
|
"""Le bulletin de l'étudiant dans ce semestre.
|
||||||
|
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||||
|
(bulletins non publiés).
|
||||||
|
"""
|
||||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||||
nb_inscrits = self.get_inscriptions_counts()[scu.INSCRIT]
|
nb_inscrits = self.get_inscriptions_counts()[scu.INSCRIT]
|
||||||
|
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||||
d = {
|
d = {
|
||||||
"version": "0",
|
"version": "0",
|
||||||
"type": "BUT",
|
"type": "BUT",
|
||||||
"date": datetime.datetime.utcnow().isoformat() + "Z",
|
"date": datetime.datetime.utcnow().isoformat() + "Z",
|
||||||
|
"publie": not formsemestre.bul_hide_xml,
|
||||||
"etudiant": etud.to_dict_bul(),
|
"etudiant": etud.to_dict_bul(),
|
||||||
"formation": {
|
"formation": {
|
||||||
"id": formsemestre.formation.id,
|
"id": formsemestre.formation.id,
|
||||||
@ -163,6 +218,10 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"etat_inscription": etat_inscription,
|
"etat_inscription": etat_inscription,
|
||||||
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
||||||
}
|
}
|
||||||
|
if not published:
|
||||||
|
return d
|
||||||
|
|
||||||
|
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
||||||
semestre_infos = {
|
semestre_infos = {
|
||||||
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
||||||
"date_debut": formsemestre.date_debut.isoformat(),
|
"date_debut": formsemestre.date_debut.isoformat(),
|
||||||
@ -171,9 +230,9 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"inscription": "TODO-MM-JJ", # XXX TODO
|
"inscription": "TODO-MM-JJ", # XXX TODO
|
||||||
"numero": formsemestre.semestre_id,
|
"numero": formsemestre.semestre_id,
|
||||||
"groupes": [], # XXX TODO
|
"groupes": [], # XXX TODO
|
||||||
"absences": { # XXX TODO
|
"absences": {
|
||||||
"injustifie": -1,
|
"injustifie": nbabsjust,
|
||||||
"total": -1,
|
"total": nbabs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
semestre_infos.update(
|
semestre_infos.update(
|
||||||
@ -199,7 +258,11 @@ class BulletinBUT(ResultatsSemestreBUT):
|
|||||||
"ressources": self.etud_mods_results(etud, self.ressources),
|
"ressources": self.etud_mods_results(etud, self.ressources),
|
||||||
"saes": self.etud_mods_results(etud, self.saes),
|
"saes": self.etud_mods_results(etud, self.saes),
|
||||||
"ues": {
|
"ues": {
|
||||||
ue.acronyme: self.etud_ue_results(etud, ue) for ue in self.ues
|
ue.acronyme: self.etud_ue_results(etud, ue)
|
||||||
|
for ue in self.ues
|
||||||
|
if self.modimpls_in_ue(
|
||||||
|
ue.id, etud.id
|
||||||
|
) # si l'UE comporte des modules auxquels on est inscrit
|
||||||
},
|
},
|
||||||
"semestre": semestre_infos,
|
"semestre": semestre_infos,
|
||||||
},
|
},
|
||||||
|
@ -134,8 +134,12 @@ def bulletin_but_xml_compat(
|
|||||||
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
moy=scu.fmt_note(results.etud_moy_gen.mean()), # moyenne des moy. gen.
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rang = 0 # XXX TODO rang de l'étduiant selon la moy gen indicative
|
rang = 0 # XXX TODO rang de l'étudiant selon la moy gen indicative
|
||||||
bonus = 0 # XXX TODO valeur du bonus sport
|
# valeur du bonus sport
|
||||||
|
if results.bonus is not None:
|
||||||
|
bonus = results.bonus[etud.id]
|
||||||
|
else:
|
||||||
|
bonus = 0
|
||||||
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
doc.append(Element("rang", value=str(rang), ninscrits=str(nb_inscrits)))
|
||||||
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
# XXX TODO: ajouter "rang_group" : rangs dans les partitions
|
||||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||||
|
@ -19,7 +19,11 @@ class StatsMoyenne:
|
|||||||
def __init__(self, vals):
|
def __init__(self, vals):
|
||||||
"""Calcul les statistiques.
|
"""Calcul les statistiques.
|
||||||
Les valeurs NAN ou non numériques sont toujours enlevées.
|
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||||
|
Si vals is None, renvoie des zéros (utilisé pour UE bonus)
|
||||||
"""
|
"""
|
||||||
|
if vals is None:
|
||||||
|
self.moy = self.min = self.max = self.size = self.nb_vals = 0
|
||||||
|
else:
|
||||||
self.moy = np.nanmean(vals)
|
self.moy = np.nanmean(vals)
|
||||||
self.min = np.nanmin(vals)
|
self.min = np.nanmin(vals)
|
||||||
self.max = np.nanmax(vals)
|
self.max = np.nanmax(vals)
|
664
app/comp/bonus_spo.py
Normal file
664
app/comp/bonus_spo.py
Normal file
@ -0,0 +1,664 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Classes spécifiques de calcul du bonus sport, culture ou autres activités
|
||||||
|
|
||||||
|
Les classes de Bonus fournissent deux méthodes:
|
||||||
|
- get_bonus_ues()
|
||||||
|
- get_bonus_moy_gen()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
|
def get_bonus_sport_class_from_name(dept_id):
|
||||||
|
"""La classe de bonus sport pour le département indiqué.
|
||||||
|
Note: en ScoDoc 9, le bonus sport est défini gloabelement et
|
||||||
|
ne dépend donc pas du département.
|
||||||
|
Résultat: une sous-classe de BonusSport
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class BonusSport:
|
||||||
|
"""Calcul du bonus sport.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- sem_modimpl_moys :
|
||||||
|
notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||||
|
floats avec des NaN.
|
||||||
|
En classique: sem_matrix, ndarray (etuds x modimpls)
|
||||||
|
En APC: sem_cube, ndarray (etuds x modimpls x UEs non bonus)
|
||||||
|
- ues: les ues du semestre (incluant le bonus sport)
|
||||||
|
- modimpl_inscr_df: matrice d'inscription aux modules du semestre (etud x modimpl)
|
||||||
|
- modimpl_coefs: les coefs des modules
|
||||||
|
En classique: 1d ndarray de float (modimpl)
|
||||||
|
En APC: 2d ndarray de float, (modimpl x UE) <= attention à transposer
|
||||||
|
- etud_moy_gen: Series, index etudid, valeur float (moyenne générale avant bonus)
|
||||||
|
- etud_moy_ue: DataFrame columns UE (sans sport), rows etudid (moyennes avant bonus)
|
||||||
|
|
||||||
|
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)
|
||||||
|
classic_use_bonus_ues = False
|
||||||
|
|
||||||
|
# Attributs virtuels:
|
||||||
|
seuil_moy_gen = None
|
||||||
|
proportion_point = None
|
||||||
|
bonus_max = None
|
||||||
|
|
||||||
|
name = "virtual"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
sem_modimpl_moys: np.array,
|
||||||
|
ues: list,
|
||||||
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
|
modimpl_coefs: np.array,
|
||||||
|
etud_moy_gen,
|
||||||
|
etud_moy_ue,
|
||||||
|
):
|
||||||
|
self.formsemestre = formsemestre
|
||||||
|
self.ues = ues
|
||||||
|
self.etud_moy_gen = etud_moy_gen
|
||||||
|
self.etud_moy_ue = etud_moy_ue
|
||||||
|
self.etuds_idx = modimpl_inscr_df.index # les étudiants inscrits au semestre
|
||||||
|
self.bonus_ues: pd.DataFrame = None # virtual
|
||||||
|
self.bonus_moy_gen: pd.Series = None # virtual (pour formations non apc slt)
|
||||||
|
# Restreint aux modules standards des UE de type "sport":
|
||||||
|
modimpl_mask = np.array(
|
||||||
|
[
|
||||||
|
(m.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (m.module.ue.type == UE_SPORT)
|
||||||
|
for m in formsemestre.modimpls_sorted
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.modimpls_spo = [
|
||||||
|
modimpl
|
||||||
|
for i, modimpl in enumerate(formsemestre.modimpls_sorted)
|
||||||
|
if modimpl_mask[i]
|
||||||
|
]
|
||||||
|
"liste des modimpls sport"
|
||||||
|
|
||||||
|
# Les moyennes des modules "sport": (une par UE en APC)
|
||||||
|
# donc (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||||
|
sem_modimpl_moys_spo = sem_modimpl_moys[:, modimpl_mask]
|
||||||
|
# Les inscriptions aux modules sport:
|
||||||
|
modimpl_inscr_spo = modimpl_inscr_df.values[:, modimpl_mask]
|
||||||
|
# Les coefficients des modules sport (en apc: nb_mod_sport x nb_ue) (toutes ues)
|
||||||
|
modimpl_coefs_spo = modimpl_coefs[modimpl_mask]
|
||||||
|
# sem_modimpl_moys_spo est (nb_etuds, nb_mod_sport)
|
||||||
|
# ou (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||||
|
nb_etuds, nb_mod_sport = sem_modimpl_moys_spo.shape[:2]
|
||||||
|
nb_ues = len(ues)
|
||||||
|
# Enlève les NaN du numérateur:
|
||||||
|
sem_modimpl_moys_no_nan = np.nan_to_num(sem_modimpl_moys_spo, nan=0.0)
|
||||||
|
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
if formsemestre.formation.is_apc():
|
||||||
|
# BUT
|
||||||
|
nb_ues_no_bonus = sem_modimpl_moys.shape[2]
|
||||||
|
# Duplique les inscriptions sur les UEs non bonus:
|
||||||
|
modimpl_inscr_spo_stacked = np.stack(
|
||||||
|
[modimpl_inscr_spo] * nb_ues_no_bonus, axis=2
|
||||||
|
)
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes: (nb_etuds, nb_mod_bonus, nb_ues_non_bonus)
|
||||||
|
sem_modimpl_moys_inscrits = np.where(
|
||||||
|
modimpl_inscr_spo_stacked, sem_modimpl_moys_no_nan, 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr_spo_stacked,
|
||||||
|
np.stack([modimpl_coefs_spo.T] * nb_etuds),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Formations classiques
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes:
|
||||||
|
sem_modimpl_moys_inscrits = np.where(
|
||||||
|
modimpl_inscr_spo, sem_modimpl_moys_no_nan, 0.0
|
||||||
|
)
|
||||||
|
modimpl_coefs_spo = modimpl_coefs_spo.T
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr_spo, np.stack([modimpl_coefs_spo] * nb_etuds), 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules NaN (nb_etuds x nb_mod_sport)
|
||||||
|
modimpl_coefs_etuds_no_nan = np.where(
|
||||||
|
np.isnan(sem_modimpl_moys_spo), 0.0, modimpl_coefs_etuds
|
||||||
|
)
|
||||||
|
#
|
||||||
|
self.compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||||
|
|
||||||
|
def compute_bonus(
|
||||||
|
self,
|
||||||
|
sem_modimpl_moys_inscrits: np.ndarray,
|
||||||
|
modimpl_coefs_etuds_no_nan: np.ndarray,
|
||||||
|
):
|
||||||
|
"""Calcul des bonus: méthode virtuelle à écraser.
|
||||||
|
Arguments:
|
||||||
|
- sem_modimpl_moys_inscrits:
|
||||||
|
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.
|
||||||
|
- modimpl_coefs_etuds_no_nan:
|
||||||
|
les coefficients: float ndarray
|
||||||
|
|
||||||
|
Résultat: None
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("méthode virtuelle")
|
||||||
|
|
||||||
|
def get_bonus_ues(self) -> pd.Series:
|
||||||
|
"""Les bonus à appliquer aux UE
|
||||||
|
Résultat: DataFrame de float, index etudid, columns: ue.id
|
||||||
|
"""
|
||||||
|
if self.classic_use_bonus_ues or self.formsemestre.formation.is_apc():
|
||||||
|
return self.bonus_ues
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_bonus_moy_gen(self):
|
||||||
|
"""Le bonus à appliquer à la moyenne générale.
|
||||||
|
Résultat: Series de float, index etudid
|
||||||
|
"""
|
||||||
|
if self.formsemestre.formation.is_apc():
|
||||||
|
return None # garde-fou
|
||||||
|
return self.bonus_moy_gen
|
||||||
|
|
||||||
|
|
||||||
|
class BonusSportAdditif(BonusSport):
|
||||||
|
"""Bonus sport simples calcule un bonus à partir des notes moyennes de modules
|
||||||
|
de l'UE sport, et ce bonus est soit ajouté à la moyenne générale (formations classiques),
|
||||||
|
soit ajouté à chaque UE (formations APC).
|
||||||
|
|
||||||
|
Le bonus est par défaut calculé comme:
|
||||||
|
Les points au-dessus du seuil (par défaut) 10 sur 20 obtenus dans chacun des
|
||||||
|
modules optionnels sont cumulés et une fraction de ces points cumulés s'ajoute
|
||||||
|
à la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
proportion_point = 0.05 # multiplie les points au dessus du seuil
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus
|
||||||
|
sem_modimpl_moys_inscrits: les notes de sport
|
||||||
|
En APC: ndarray (nb_etuds, nb_mod_sport, nb_ues_non_bonus)
|
||||||
|
modimpl_coefs_etuds_no_nan:
|
||||||
|
"""
|
||||||
|
bonus_moy_arr = np.sum(
|
||||||
|
np.where(
|
||||||
|
sem_modimpl_moys_inscrits > self.seuil_moy_gen,
|
||||||
|
(sem_modimpl_moys_inscrits - self.seuil_moy_gen)
|
||||||
|
* self.proportion_point,
|
||||||
|
0.0,
|
||||||
|
),
|
||||||
|
axis=1,
|
||||||
|
)
|
||||||
|
if self.bonus_max is not None:
|
||||||
|
# Seuil: bonus limité à bonus_max points (et >= 0)
|
||||||
|
bonus_moy_arr = np.clip(
|
||||||
|
bonus_moy_arr, 0.0, self.bonus_max, out=bonus_moy_arr
|
||||||
|
)
|
||||||
|
else: # necessaire pour éviter bonus négatifs !
|
||||||
|
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)
|
||||||
|
if self.formsemestre.formation.is_apc() or self.classic_use_bonus_ues:
|
||||||
|
# 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)]
|
||||||
|
self.bonus_ues = pd.DataFrame(
|
||||||
|
bonus_moy_arr, index=self.etuds_idx, columns=ues_idx, dtype=float
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Bonus sur la moyenne générale seulement
|
||||||
|
self.bonus_moy_gen = pd.Series(
|
||||||
|
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):
|
||||||
|
"""Bonus sport qui multiplie les moyennes d'UE par un facteur"""
|
||||||
|
|
||||||
|
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
amplitude = 0.005 # multiplie les points au dessus du seuil
|
||||||
|
# En classique, les bonus multiplicatifs agissent par défaut sur les UE:
|
||||||
|
classic_use_bonus_ues = True
|
||||||
|
|
||||||
|
# C'est un bonus "multiplicatif": on l'exprime en additif,
|
||||||
|
# sur chaque moyenne d'UE m_0
|
||||||
|
# Augmenter de 5% correspond à multiplier par a=1.05
|
||||||
|
# m_1 = a . m_0
|
||||||
|
# m_1 = m_0 + bonus
|
||||||
|
# bonus = m_0 (a - 1)
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
|
notes = np.sum(
|
||||||
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
|
notes = np.nan_to_num(notes, copy=False)
|
||||||
|
factor = (notes - self.seuil_moy_gen) * self.amplitude # 5% si note=20
|
||||||
|
factor[factor <= 0] = 0.0 # note < seuil_moy_gen, pas de bonus
|
||||||
|
|
||||||
|
# Ne s'applique qu'aux moyennes d'UE
|
||||||
|
if len(factor.shape) == 1: # classic
|
||||||
|
factor = factor[:, np.newaxis]
|
||||||
|
bonus = self.etud_moy_ue * factor
|
||||||
|
if self.bonus_max is not None:
|
||||||
|
# Seuil: bonus limité à bonus_max points
|
||||||
|
bonus.clip(upper=self.bonus_max, inplace=True)
|
||||||
|
|
||||||
|
self.bonus_ues = bonus # DataFrame
|
||||||
|
|
||||||
|
# Les bonus multiplicatifs ne s'appliquent pas à la moyenne générale
|
||||||
|
self.bonus_moy_gen = None
|
||||||
|
|
||||||
|
|
||||||
|
class BonusDirect(BonusSportAdditif):
|
||||||
|
"""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.
|
||||||
|
(rappel: la note est ramenée sur 20 avant application).
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_direct"
|
||||||
|
displayed_name = 'Bonus "direct"'
|
||||||
|
seuil_moy_gen = 0.0 # tous les points sont comptés
|
||||||
|
proportion_point = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
class BonusBethune(BonusSportMultiplicatif):
|
||||||
|
"""Calcul bonus modules optionels (sport), règle IUT de Béthune.
|
||||||
|
|
||||||
|
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
|
||||||
|
moyenne générale du semestre de l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutbethune"
|
||||||
|
displayed_name = "IUT de Béthune"
|
||||||
|
seuil_moy_gen = 10.0
|
||||||
|
amplitude = 0.005
|
||||||
|
|
||||||
|
|
||||||
|
class BonusBezier(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT de Bézier.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
sport , etc) non rattachés à une unité d'enseignement. Les points
|
||||||
|
au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés et 3% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant, dans
|
||||||
|
la limite de 0,3 points.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# note: cela revient à dire que l'on ajoute 5% des points au dessus de 10,
|
||||||
|
# et qu'on limite à 5% de 10, soit 0.5 points
|
||||||
|
# ce bonus est donc strictement identique à celui de St Denis (BonusIUTStDenis)
|
||||||
|
name = "bonus_iutbeziers"
|
||||||
|
displayed_name = "IUT de Bézier"
|
||||||
|
bonus_max = 0.3
|
||||||
|
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||||
|
proportion_point = 0.03
|
||||||
|
|
||||||
|
|
||||||
|
class BonusBordeaux1(BonusSportMultiplicatif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale
|
||||||
|
et UE.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
Formule : le % = points>moyenne / 2
|
||||||
|
Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutBordeaux1"
|
||||||
|
displayed_name = "IUT de Bordeaux 1"
|
||||||
|
classic_use_bonus_ues = True # s'applique aux UEs en DUT et LP
|
||||||
|
seuil_moy_gen = 10.0
|
||||||
|
amplitude = 0.005
|
||||||
|
|
||||||
|
|
||||||
|
class BonusColmar(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Colmar.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non
|
||||||
|
rattachés à une unité d'enseignement. Les points au-dessus de 10
|
||||||
|
sur 20 obtenus dans chacune des matières optionnelles sont cumulés
|
||||||
|
dans la limite de 10 points. 5% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# note: cela revient à dire que l'on ajoute 5% des points au dessus de 10,
|
||||||
|
# et qu'on limite à 5% de 10, soit 0.5 points
|
||||||
|
# ce bonus est donc strictement identique à celui de St Denis (BonusIUTStDenis)
|
||||||
|
name = "bonus_colmar"
|
||||||
|
displayed_name = "IUT de Colmar"
|
||||||
|
bonus_max = 0.5
|
||||||
|
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||||
|
proportion_point = 0.05
|
||||||
|
|
||||||
|
|
||||||
|
class BonusGrenobleIUT1(BonusSportMultiplicatif):
|
||||||
|
"""Bonus IUT1 de Grenoble
|
||||||
|
|
||||||
|
À compter de sept. 2021:
|
||||||
|
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
|
||||||
|
la formule : bonification (en %) = (note-10)*0,5.
|
||||||
|
|
||||||
|
Bonification qui ne s'applique que si la note est >10.
|
||||||
|
|
||||||
|
(Une note de 10 donne donc 0% de bonif ; note de 20 : 5% de bonif)
|
||||||
|
|
||||||
|
Avant sept. 2021, la note de sport allait de 0 à 5 points (sur 20).
|
||||||
|
Chaque point correspondait à 0.25% d'augmentation de la moyenne
|
||||||
|
générale.
|
||||||
|
Par exemple : note de sport 2/5 : la moyenne générale était augmentée de 0.5%.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iut1grenoble_2017"
|
||||||
|
displayed_name = "IUT de Grenoble 1"
|
||||||
|
# C'est un bonus "multiplicatif": on l'exprime en additif,
|
||||||
|
# sur chaque moyenne d'UE m_0
|
||||||
|
# Augmenter de 5% correspond à multiplier par a=1.05
|
||||||
|
# m_1 = a . m_0
|
||||||
|
# m_1 = m_0 + bonus
|
||||||
|
# bonus = m_0 (a - 1)
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus, avec réglage différent suivant la date"""
|
||||||
|
|
||||||
|
if self.formsemestre.date_debut > datetime.date(2021, 7, 15):
|
||||||
|
self.seuil_moy_gen = 10.0
|
||||||
|
self.amplitude = 0.005
|
||||||
|
else: # anciens semestres
|
||||||
|
self.seuil_moy_gen = 0.0
|
||||||
|
self.amplitude = 1 / 400.0
|
||||||
|
|
||||||
|
super().compute_bonus(sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan)
|
||||||
|
|
||||||
|
|
||||||
|
class BonusLaRochelle(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT de La Rochelle.
|
||||||
|
|
||||||
|
Si la note de sport est comprise entre 0 et 10 : pas d'ajout de point.
|
||||||
|
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).
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutlr"
|
||||||
|
displayed_name = "IUT de La Rochelle"
|
||||||
|
seuil_moy_gen = 10.0 # tous les points sont comptés
|
||||||
|
proportion_point = 0.01
|
||||||
|
|
||||||
|
|
||||||
|
class BonusLeHavre(BonusSportMultiplicatif):
|
||||||
|
"""Bonus sport IUT du Havre sur moyenne générale et UE
|
||||||
|
|
||||||
|
Les points des modules bonus au dessus de 10/20 sont ajoutés,
|
||||||
|
et les moyennes d'UE augmentées de 5% de ces points.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutlh"
|
||||||
|
displayed_name = "IUT du Havre"
|
||||||
|
seuil_moy_gen = 10.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
amplitude = 0.005 # multiplie les points au dessus du seuil
|
||||||
|
|
||||||
|
|
||||||
|
class BonusLeMans(BonusSportAdditif):
|
||||||
|
"""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
|
||||||
|
optionnelles sont cumulés.
|
||||||
|
|
||||||
|
|
||||||
|
En BUT: la moyenne de chacune des UE du semestre est augmentée de
|
||||||
|
2% du cumul des points de bonus,
|
||||||
|
|
||||||
|
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"
|
||||||
|
displayed_name = "IUT du Mans"
|
||||||
|
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
||||||
|
bonus_max = 0.5 #
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
# La date du semestre ?
|
||||||
|
if self.formsemestre.formation.is_apc():
|
||||||
|
self.proportion_point = 0.02
|
||||||
|
else:
|
||||||
|
self.proportion_point = 0.05
|
||||||
|
return super().compute_bonus(
|
||||||
|
sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Bonus simple, mais avec changement de paramètres en 2010 !
|
||||||
|
class BonusLille(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Villeneuve d'Ascq
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université Lille (sports, etc) non rattachés à une unité d'enseignement.
|
||||||
|
|
||||||
|
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_lille"
|
||||||
|
displayed_name = "IUT de Lille"
|
||||||
|
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
# La date du semestre ?
|
||||||
|
if self.formsemestre.date_debut > datetime.date(2010, 8, 1):
|
||||||
|
self.proportion_point = 0.04
|
||||||
|
else:
|
||||||
|
self.proportion_point = 0.02
|
||||||
|
return super().compute_bonus(
|
||||||
|
sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BonusLyonProvisoire(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Lyon (provisoire)
|
||||||
|
|
||||||
|
Les points au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés et 1,8% de ces points cumulés
|
||||||
|
s'ajoutent aux moyennes, dans la limite d'1/2 point.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_lyon_provisoire"
|
||||||
|
displayed_name = "IUT de Lyon (provisoire)"
|
||||||
|
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
||||||
|
proportion_point = 0.018
|
||||||
|
bonus_max = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class BonusMulhouse(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionnels (sport, culture), règle IUT de Mulhouse
|
||||||
|
|
||||||
|
La moyenne de chacune des UE du semestre sera majorée à hauteur de
|
||||||
|
5% du cumul des points supérieurs à 10 obtenus en matières optionnelles,
|
||||||
|
dans la limite de 0,5 point.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutmulhouse"
|
||||||
|
displayed_name = "IUT de Mulhouse"
|
||||||
|
seuil_moy_gen = 10.0 # points comptés au dessus de 10.
|
||||||
|
proportion_point = 0.05
|
||||||
|
bonus_max = 0.5 #
|
||||||
|
|
||||||
|
|
||||||
|
class BonusNantes(BonusSportAdditif):
|
||||||
|
"""IUT de Nantes (Septembre 2018)
|
||||||
|
|
||||||
|
Nous avons différents types de bonification
|
||||||
|
(sport, culture, engagement citoyen).
|
||||||
|
|
||||||
|
Nous ajoutons aux moyennes une bonification de 0,2 pour chaque item
|
||||||
|
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.
|
||||||
|
|
||||||
|
Dans ScoDoc: on a déclarera une UE "sport&culture" dans laquelle on aura 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
|
||||||
|
valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_nantes"
|
||||||
|
displayed_name = "IUT de Nantes"
|
||||||
|
seuil_moy_gen = 0.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
proportion_point = 1 # multiplie les points au dessus du seuil
|
||||||
|
bonus_max = 0.5 # plafonnement à 0.5 points
|
||||||
|
|
||||||
|
|
||||||
|
class BonusRoanne(BonusSportAdditif):
|
||||||
|
"""IUT de Roanne.
|
||||||
|
|
||||||
|
Le bonus est compris entre 0 et 0.6 points
|
||||||
|
et est toujours appliqué aux UEs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutr"
|
||||||
|
displayed_name = "IUT de Roanne"
|
||||||
|
seuil_moy_gen = 0.0
|
||||||
|
bonus_max = 0.6 # plafonnement à 0.6 points
|
||||||
|
apply_bonus_mg_to_ues = True # sur les UE, même en DUT et LP
|
||||||
|
|
||||||
|
|
||||||
|
class BonusStDenis(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Saint-Denis
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université Paris 13 (sports, musique, deuxième langue,
|
||||||
|
culture, etc) non rattachés à une unité d'enseignement. Les points
|
||||||
|
au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant, dans la limite
|
||||||
|
d'1/2 point.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iut_stdenis"
|
||||||
|
displayed_name = "IUT de Saint-Denis"
|
||||||
|
bonus_max = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
class BonusTours(BonusDirect):
|
||||||
|
"""Calcul bonus sport & culture IUT Tours.
|
||||||
|
|
||||||
|
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,
|
||||||
|
soit pour le BUT à chaque moyenne d'UE.
|
||||||
|
|
||||||
|
Attention: en GEII, facteur 1/40, ailleurs facteur 1.
|
||||||
|
|
||||||
|
Le bonus total est limité à 1 point.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_tours"
|
||||||
|
displayed_name = "IUT de Tours"
|
||||||
|
bonus_max = 1.0 #
|
||||||
|
seuil_moy_gen = 0.0 # seuls les points au dessus du seuil sont comptés
|
||||||
|
proportion_point = 1.0 / 40.0
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul différencié selon le département !"""
|
||||||
|
if g.scodoc_dept == "GEII":
|
||||||
|
self.proportion_point = 1.0 / 40.0
|
||||||
|
else:
|
||||||
|
self.proportion_point = 1.0
|
||||||
|
return super().compute_bonus(
|
||||||
|
sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BonusVilleAvray(BonusSport):
|
||||||
|
"""Bonus modules optionels (sport, culture), règle IUT Ville d'Avray.
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
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
|
||||||
|
Si la note est >= 12 et < 16, bonus de 0.2 point
|
||||||
|
Si la note est >= 16, bonus de 0.3 point
|
||||||
|
Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par
|
||||||
|
l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutva"
|
||||||
|
displayed_name = "IUT de Ville d'Avray"
|
||||||
|
|
||||||
|
def compute_bonus(self, sem_modimpl_moys_inscrits, modimpl_coefs_etuds_no_nan):
|
||||||
|
"""calcul du bonus"""
|
||||||
|
# Calcule moyenne pondérée des notes de sport:
|
||||||
|
bonus_moy_arr = np.sum(
|
||||||
|
sem_modimpl_moys_inscrits * modimpl_coefs_etuds_no_nan, axis=1
|
||||||
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 10.0] = 0.1
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 12.0] = 0.2
|
||||||
|
bonus_moy_arr[bonus_moy_arr >= 16.0] = 0.3
|
||||||
|
|
||||||
|
# Bonus moyenne générale, et 0 sur les UE
|
||||||
|
self.bonus_moy_gen = pd.Series(bonus_moy_arr, index=self.etuds_idx, dtype=float)
|
||||||
|
if self.bonus_max is not None:
|
||||||
|
# Seuil: bonus (sur moy. gen.) limité à bonus_max points
|
||||||
|
self.bonus_moy_gen = self.bonus_moy_gen.clip(upper=self.bonus_max)
|
||||||
|
|
||||||
|
# Laisse bonus_ues à None, en APC le bonus moy. gen. sera réparti sur les UEs.
|
||||||
|
|
||||||
|
|
||||||
|
class BonusIUTV(BonusSportAdditif):
|
||||||
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse
|
||||||
|
|
||||||
|
Les étudiants de l'IUT peuvent suivre des enseignements optionnels
|
||||||
|
de l'Université Paris 13 (sports, musique, deuxième langue,
|
||||||
|
culture, etc) non rattachés à une unité d'enseignement. Les points
|
||||||
|
au-dessus de 10 sur 20 obtenus dans chacune des matières
|
||||||
|
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||||
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "bonus_iutv"
|
||||||
|
displayed_name = "IUT de Villetaneuse"
|
||||||
|
pass # oui, c'ets le bonus par défaut
|
||||||
|
|
||||||
|
|
||||||
|
def get_bonus_class_dict(start=BonusSport, d=None):
|
||||||
|
"""Dictionnaire des classes de bonus
|
||||||
|
(liste les sous-classes de BonusSport ayant un nom)
|
||||||
|
Resultat: { name : class }
|
||||||
|
"""
|
||||||
|
if d is None:
|
||||||
|
d = {}
|
||||||
|
if start.name != "virtual":
|
||||||
|
d[start.name] = start
|
||||||
|
for subclass in start.__subclasses__():
|
||||||
|
get_bonus_class_dict(subclass, d=d)
|
||||||
|
return d
|
@ -21,7 +21,7 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
|||||||
value: bool (0/1 inscrit ou pas)
|
value: bool (0/1 inscrit ou pas)
|
||||||
"""
|
"""
|
||||||
# méthode la moins lente: une requete par module, merge les dataframes
|
# méthode la moins lente: une requete par module, merge les dataframes
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
etudids = [inscr.etudid for inscr in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(index=etudids, dtype=int)
|
df = pd.DataFrame(index=etudids, dtype=int)
|
||||||
for moduleimpl_id in moduleimpl_ids:
|
for moduleimpl_id in moduleimpl_ids:
|
||||||
@ -35,6 +35,8 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
|||||||
dtype=int,
|
dtype=int,
|
||||||
)
|
)
|
||||||
df = df.merge(ins_df, how="left", left_index=True, right_index=True)
|
df = df.merge(ins_df, how="left", left_index=True, right_index=True)
|
||||||
|
# Force columns names to integers (moduleimpl ids)
|
||||||
|
df.columns = pd.Int64Index([int(x) for x in df.columns], dtype="int")
|
||||||
# les colonnes de df sont en float (Nan) quand il n'y a
|
# les colonnes de df sont en float (Nan) quand il n'y a
|
||||||
# aucun inscrit au module.
|
# aucun inscrit au module.
|
||||||
df.fillna(0, inplace=True) # les non-inscrits
|
df.fillna(0, inplace=True) # les non-inscrits
|
||||||
@ -47,10 +49,10 @@ def df_load_modimpl_inscr(formsemestre) -> pd.DataFrame:
|
|||||||
|
|
||||||
def df_load_modimpl_inscr_v0(formsemestre):
|
def df_load_modimpl_inscr_v0(formsemestre):
|
||||||
# methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente
|
# methode 0, pur SQL Alchemy, 1.5 à 2 fois plus lente
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
ins_mod = df[modimpl.id]
|
ins_mod = df[modimpl.id]
|
||||||
for inscr in modimpl.inscriptions:
|
for inscr in modimpl.inscriptions:
|
||||||
ins_mod[inscr.etudid] = True
|
ins_mod[inscr.etudid] = True
|
||||||
@ -58,7 +60,7 @@ def df_load_modimpl_inscr_v0(formsemestre):
|
|||||||
|
|
||||||
|
|
||||||
def df_load_modimpl_inscr_v2(formsemestre):
|
def df_load_modimpl_inscr_v2(formsemestre):
|
||||||
moduleimpl_ids = [m.id for m in formsemestre.modimpls]
|
moduleimpl_ids = [m.id for m in formsemestre.modimpls_sorted]
|
||||||
etudids = [i.etudid for i in formsemestre.inscriptions]
|
etudids = [i.etudid for i in formsemestre.inscriptions]
|
||||||
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
df = pd.DataFrame(False, columns=moduleimpl_ids, index=etudids, dtype=bool)
|
||||||
cursor = db.engine.execute(
|
cursor = db.engine.execute(
|
||||||
|
@ -40,6 +40,8 @@ import pandas as pd
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -64,9 +66,10 @@ class ModuleImplResults:
|
|||||||
self.moduleimpl_id = moduleimpl.id
|
self.moduleimpl_id = moduleimpl.id
|
||||||
self.module_id = moduleimpl.module.id
|
self.module_id = moduleimpl.module.id
|
||||||
self.etudids = None
|
self.etudids = None
|
||||||
"liste des étudiants inscrits au SEMESTRE"
|
"liste des étudiants inscrits au SEMESTRE (incluant dem et def)"
|
||||||
|
|
||||||
self.nb_inscrits_module = None
|
self.nb_inscrits_module = None
|
||||||
"nombre d'inscrits (non DEM) au module"
|
"nombre d'inscrits (non DEM) à ce module"
|
||||||
self.evaluations_completes = []
|
self.evaluations_completes = []
|
||||||
"séquence de booléens, indiquant les évals à prendre en compte."
|
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
@ -117,7 +120,7 @@ class ModuleImplResults:
|
|||||||
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
||||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||||
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
||||||
self.etudids
|
moduleimpl.formsemestre.etudids_actifs
|
||||||
)
|
)
|
||||||
self.nb_inscrits_module = len(inscrits_module)
|
self.nb_inscrits_module = len(inscrits_module)
|
||||||
|
|
||||||
@ -125,14 +128,14 @@ class ModuleImplResults:
|
|||||||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||||
self.evaluations_completes = []
|
self.evaluations_completes = []
|
||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
|
|
||||||
for evaluation in moduleimpl.evaluations:
|
for evaluation in moduleimpl.evaluations:
|
||||||
eval_df = self._load_evaluation_notes(evaluation)
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||||
# ou évaluaton déclarée "à prise en compte immédiate"
|
# ou évaluation déclarée "à prise en compte immédiate"
|
||||||
is_complete = (
|
is_complete = evaluation.publish_incomplete or (
|
||||||
len(set(eval_df.index).intersection(self.etudids))
|
not (inscrits_module - set(eval_df.index))
|
||||||
== self.nb_inscrits_module
|
)
|
||||||
) or evaluation.publish_incomplete # immédiate
|
|
||||||
self.evaluations_completes.append(is_complete)
|
self.evaluations_completes.append(is_complete)
|
||||||
self.evaluations_completes_dict[evaluation.id] = is_complete
|
self.evaluations_completes_dict[evaluation.id] = is_complete
|
||||||
|
|
||||||
@ -263,14 +266,13 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
|
||||||
def load_evaluations_poids(
|
def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
||||||
moduleimpl_id: int, default_poids=1.0
|
|
||||||
) -> tuple[pd.DataFrame, list]:
|
|
||||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||||
rows = evaluations, columns = UE, value = poids (float).
|
rows = evaluations, columns = UE, value = poids (float).
|
||||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||||
remplies par default_poids.
|
remplies: 1 si le coef de ce module dans l'UE est non nul, zéro sinon
|
||||||
Résultat: (evals_poids, liste de UE du semestre)
|
(sauf pour module bonus, defaut à 1)
|
||||||
|
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)
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||||
@ -281,9 +283,21 @@ def load_evaluations_poids(
|
|||||||
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):
|
||||||
|
try:
|
||||||
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||||
if default_poids is not None:
|
except KeyError as exc:
|
||||||
evals_poids.fillna(value=default_poids, inplace=True)
|
pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre...
|
||||||
|
|
||||||
|
# Initialise poids non enregistrés:
|
||||||
|
default_poids = 1.0 if modimpl.module.ue.type == UE_SPORT else 0.0
|
||||||
|
|
||||||
|
if np.isnan(evals_poids.values.flat).any():
|
||||||
|
ue_coefs = modimpl.module.get_ue_coef_dict()
|
||||||
|
for ue in ues:
|
||||||
|
evals_poids[ue.id][evals_poids[ue.id].isna()] = (
|
||||||
|
1 if ue_coefs.get(ue.id, default_poids) > 0 else 0
|
||||||
|
)
|
||||||
|
|
||||||
return evals_poids, ues
|
return evals_poids, ues
|
||||||
|
|
||||||
|
|
||||||
@ -296,6 +310,7 @@ def moduleimpl_is_conforme(
|
|||||||
évaluations vers une UE de coefficient non nul est non nulle.
|
évaluations vers une UE de coefficient non nul est non nulle.
|
||||||
|
|
||||||
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
|
NB: les UEs dans evals_poids sont sans le bonus sport
|
||||||
"""
|
"""
|
||||||
nb_evals, nb_ues = evals_poids.shape
|
nb_evals, nb_ues = evals_poids.shape
|
||||||
if nb_evals == 0:
|
if nb_evals == 0:
|
||||||
|
@ -38,7 +38,7 @@ def compute_sem_moys_apc(
|
|||||||
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
= moyenne des moyennes d'UE, pondérée par la somme de leurs coefs
|
||||||
|
|
||||||
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
etud_moy_ue_df: DataFrame, colonnes ue_id, lignes etudid
|
||||||
modimpl_coefs_df: DataFrame, colonnes moduleimpl_id, lignes UE
|
modimpl_coefs_df: DataFrame, colonnes moduleimpl_id, lignes UE (sans ue bonus)
|
||||||
|
|
||||||
Result: panda Series, index etudid, valeur float (moyenne générale)
|
Result: panda Series, index etudid, valeur float (moyenne générale)
|
||||||
"""
|
"""
|
||||||
|
@ -36,6 +36,8 @@ from app.models import UniteEns, Module, ModuleImpl, ModuleUECoef
|
|||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -44,10 +46,10 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
|
|
||||||
En APC, ces coefs lient les modules à chaque UE.
|
En APC, ces coefs lient les modules à chaque UE.
|
||||||
|
|
||||||
Résultat: (module_coefs_df, ues, modules)
|
Résultat: (module_coefs_df, ues_no_bonus, modules)
|
||||||
DataFrame rows = UEs, columns = modules, value = coef.
|
DataFrame rows = UEs, columns = modules, value = coef.
|
||||||
|
|
||||||
Considère toutes les UE (sauf sport) et modules du semestre.
|
Considère toutes les UE sauf bonus et tous les modules du semestre.
|
||||||
Les coefs non définis (pas en base) sont mis à zéro.
|
Les coefs non définis (pas en base) sont mis à zéro.
|
||||||
|
|
||||||
Si semestre_idx None, prend toutes les UE de la formation.
|
Si semestre_idx None, prend toutes les UE de la formation.
|
||||||
@ -62,6 +64,10 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
.filter(
|
.filter(
|
||||||
(Module.module_type == ModuleType.RESSOURCE)
|
(Module.module_type == ModuleType.RESSOURCE)
|
||||||
| (Module.module_type == ModuleType.SAE)
|
| (Module.module_type == ModuleType.SAE)
|
||||||
|
| (
|
||||||
|
(Module.ue_id == UniteEns.id)
|
||||||
|
& (UniteEns.type == sco_codes_parcours.UE_SPORT)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.order_by(
|
.order_by(
|
||||||
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
Module.semestre_id, Module.module_type.desc(), Module.numero, Module.code
|
||||||
@ -88,7 +94,17 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
module_coefs_df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
||||||
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
# silently ignore coefs associated to other modules (ie when module_type is changed)
|
||||||
|
|
||||||
module_coefs_df.fillna(value=0, inplace=True)
|
# Initialisation des poids non fixés:
|
||||||
|
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||||
|
# sur toutes les UE)
|
||||||
|
default_poids = {
|
||||||
|
mod.id: 1.0
|
||||||
|
if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
|
||||||
|
else 0.0
|
||||||
|
for mod in modules
|
||||||
|
}
|
||||||
|
|
||||||
|
module_coefs_df.fillna(value=default_poids, inplace=True)
|
||||||
|
|
||||||
return module_coefs_df, ues, modules
|
return module_coefs_df, ues, modules
|
||||||
|
|
||||||
@ -100,15 +116,15 @@ def df_load_modimpl_coefs(
|
|||||||
|
|
||||||
Comme df_load_module_coefs mais prend seulement les UE
|
Comme df_load_module_coefs mais prend seulement les UE
|
||||||
et modules du formsemestre.
|
et modules du formsemestre.
|
||||||
Si ues et modimpls sont None, prend tous ceux du formsemestre.
|
Si ues et modimpls sont None, prend tous ceux du formsemestre (sauf ue bonus).
|
||||||
Résultat: (module_coefs_df, ues, modules)
|
Résultat: (module_coefs_df, ues, modules)
|
||||||
DataFrame rows = UEs, columns = modimpl, value = coef.
|
DataFrame rows = UEs (sans bonus), columns = modimpl, value = coef.
|
||||||
"""
|
"""
|
||||||
if ues is None:
|
if ues is None:
|
||||||
ues = formsemestre.query_ues().all()
|
ues = formsemestre.query_ues().all()
|
||||||
ue_ids = [x.id for x in ues]
|
ue_ids = [x.id for x in ues]
|
||||||
if modimpls is None:
|
if modimpls is None:
|
||||||
modimpls = formsemestre.modimpls.all()
|
modimpls = formsemestre.modimpls_sorted
|
||||||
modimpl_ids = [x.id for x in modimpls]
|
modimpl_ids = [x.id for x in modimpls]
|
||||||
mod2impl = {m.module.id: m.id for m in modimpls}
|
mod2impl = {m.module.id: m.id for m in modimpls}
|
||||||
modimpl_coefs_df = pd.DataFrame(columns=modimpl_ids, index=ue_ids, dtype=float)
|
modimpl_coefs_df = pd.DataFrame(columns=modimpl_ids, index=ue_ids, dtype=float)
|
||||||
@ -120,7 +136,19 @@ def df_load_modimpl_coefs(
|
|||||||
|
|
||||||
for mod_coef in mod_coefs:
|
for mod_coef in mod_coefs:
|
||||||
modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef
|
modimpl_coefs_df[mod2impl[mod_coef.module_id]][mod_coef.ue_id] = mod_coef.coef
|
||||||
modimpl_coefs_df.fillna(value=0, inplace=True)
|
|
||||||
|
# Initialisation des poids non fixés:
|
||||||
|
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||||
|
# sur toutes les UE)
|
||||||
|
default_poids = {
|
||||||
|
modimpl.id: 1.0
|
||||||
|
if (modimpl.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (modimpl.module.ue.type == UE_SPORT)
|
||||||
|
else 0.0
|
||||||
|
for modimpl in formsemestre.modimpls_sorted
|
||||||
|
}
|
||||||
|
|
||||||
|
modimpl_coefs_df.fillna(value=default_poids, inplace=True)
|
||||||
return modimpl_coefs_df, ues, modimpls
|
return modimpl_coefs_df, ues, modimpls
|
||||||
|
|
||||||
|
|
||||||
@ -134,7 +162,7 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
assert len(modimpls_notes)
|
assert len(modimpls_notes)
|
||||||
modimpls_notes_arr = [df.values for df in modimpls_notes]
|
modimpls_notes_arr = [df.values for df in modimpls_notes]
|
||||||
modimpls_notes = np.stack(modimpls_notes_arr)
|
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||||
# passe de (mod x etud x ue) à (etud x mod x UE)
|
# passe de (mod x etud x ue) à (etud x mod x ue)
|
||||||
return modimpls_notes.swapaxes(0, 1)
|
return modimpls_notes.swapaxes(0, 1)
|
||||||
|
|
||||||
|
|
||||||
@ -144,10 +172,14 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
et assemble le cube.
|
et assemble le cube.
|
||||||
|
|
||||||
etuds: tous les inscrits au semestre (avec dem. et def.)
|
etuds: tous les inscrits au semestre (avec dem. et def.)
|
||||||
modimpls: _tous_ les modimpls de ce semestre
|
modimpls: _tous_ les modimpls de ce semestre (y compris bonus sport)
|
||||||
UEs: X?X voir quelles sont les UE considérées ici
|
UEs: toutes les UE du semestre (même si pas d'inscrits) SAUF le sport.
|
||||||
|
|
||||||
Resultat:
|
Attention: la liste des modimpls inclut les modules des UE sport, mais
|
||||||
|
elles ne sont pas dans la troisième dimension car elles n'ont pas de
|
||||||
|
"moyenne d'UE".
|
||||||
|
|
||||||
|
Résultat:
|
||||||
sem_cube : ndarray (etuds x modimpls x UEs)
|
sem_cube : ndarray (etuds x modimpls x UEs)
|
||||||
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
modimpls_evals_poids dict { modimpl.id : evals_poids }
|
||||||
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
||||||
@ -155,7 +187,7 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||||||
modimpls_results = {}
|
modimpls_results = {}
|
||||||
modimpls_evals_poids = {}
|
modimpls_evals_poids = {}
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
@ -194,26 +226,27 @@ def compute_ue_moys_apc(
|
|||||||
modimpls : liste des modules à considérer (dim. 1 du cube)
|
modimpls : liste des modules à considérer (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)
|
modimpl_coefs_df: matrice coefficients (UE x modimpl), sans UEs bonus sport
|
||||||
|
|
||||||
Resultat: DataFrame columns UE, rows etudid
|
Résultat: DataFrame columns UE (sans sport), rows etudid
|
||||||
"""
|
"""
|
||||||
nb_etuds, nb_modules, nb_ues = sem_cube.shape
|
nb_etuds, nb_modules, nb_ues_no_bonus = sem_cube.shape
|
||||||
|
nb_ues_tot = len(ues)
|
||||||
assert len(modimpls) == nb_modules
|
assert len(modimpls) == nb_modules
|
||||||
if nb_modules == 0 or nb_etuds == 0:
|
if nb_modules == 0 or nb_etuds == 0:
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
||||||
)
|
)
|
||||||
assert len(etuds) == nb_etuds
|
assert len(etuds) == nb_etuds
|
||||||
assert len(ues) == nb_ues
|
|
||||||
assert modimpl_inscr_df.shape[0] == nb_etuds
|
assert modimpl_inscr_df.shape[0] == nb_etuds
|
||||||
assert modimpl_inscr_df.shape[1] == nb_modules
|
assert modimpl_inscr_df.shape[1] == nb_modules
|
||||||
assert modimpl_coefs_df.shape[0] == nb_ues
|
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
|
modimpl_coefs = modimpl_coefs_df.values
|
||||||
# Duplique les inscriptions sur les UEs:
|
|
||||||
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues, axis=2)
|
# Duplique les inscriptions sur les UEs non bonus:
|
||||||
|
modimpl_inscr_stacked = np.stack([modimpl_inscr] * nb_ues_no_bonus, axis=2)
|
||||||
# Enlève les NaN du numérateur:
|
# Enlève les NaN du numérateur:
|
||||||
# si on veut prendre en compte les modules avec notes neutralisées ?
|
# si on veut prendre en compte les modules avec notes neutralisées ?
|
||||||
sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0)
|
sem_cube_no_nan = np.nan_to_num(sem_cube, nan=0.0)
|
||||||
@ -234,7 +267,9 @@ def compute_ue_moys_apc(
|
|||||||
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
modimpl_coefs_etuds_no_nan * sem_cube_inscrits, axis=1
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(
|
||||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=modimpl_coefs_df.index
|
etud_moy_ue,
|
||||||
|
index=modimpl_inscr_df.index, # les etudids
|
||||||
|
columns=modimpl_coefs_df.index, # les UE sans les UE bonus sport
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -244,6 +279,7 @@ def compute_ue_moys_classic(
|
|||||||
ues: list,
|
ues: list,
|
||||||
modimpl_inscr_df: pd.DataFrame,
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
modimpl_coefs: np.array,
|
modimpl_coefs: np.array,
|
||||||
|
modimpl_mask: np.array,
|
||||||
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
) -> tuple[pd.Series, pd.DataFrame, pd.DataFrame]:
|
||||||
"""Calcul de la moyenne d'UE en mode classique.
|
"""Calcul de la moyenne d'UE en mode classique.
|
||||||
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
|
||||||
@ -251,13 +287,19 @@ def compute_ue_moys_classic(
|
|||||||
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 utilisateur. [XXX pas encore gérées ici]
|
||||||
|
|
||||||
sem_matrix: notes moyennes aux modules
|
L'éventuel bonus sport n'est PAS appliqué ici.
|
||||||
|
|
||||||
|
Le masque modimpl_mask est un tableau de booléens (un par modimpl) qui
|
||||||
|
permet de sélectionner un sous-ensemble de modules (SAEs, tout sauf sport, ...).
|
||||||
|
|
||||||
|
sem_matrix: notes moyennes aux modules (tous les étuds x tous les modimpls)
|
||||||
ndarray (etuds x modimpls)
|
ndarray (etuds x modimpls)
|
||||||
(floats avec des NaN)
|
(floats avec des NaN)
|
||||||
etuds : listes des étudiants (dim. 0 de la matrice)
|
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||||
ues : liste des UE
|
ues : liste des UE du semestre
|
||||||
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
modimpl_coefs: vecteur des coefficients de modules
|
modimpl_coefs: vecteur des coefficients de modules
|
||||||
|
modimpl_mask: masque des modimpls à prendre en compte
|
||||||
|
|
||||||
Résultat:
|
Résultat:
|
||||||
- moyennes générales: pd.Series, index etudid
|
- moyennes générales: pd.Series, index etudid
|
||||||
@ -266,10 +308,15 @@ def compute_ue_moys_classic(
|
|||||||
les coefficients effectifs de chaque UE pour chaque étudiant
|
les coefficients effectifs de chaque UE pour chaque étudiant
|
||||||
(sommes de coefs de modules pris en compte)
|
(sommes de coefs de modules pris en compte)
|
||||||
"""
|
"""
|
||||||
|
# Restreint aux modules sélectionnés:
|
||||||
|
sem_matrix = sem_matrix[:, modimpl_mask]
|
||||||
|
modimpl_inscr = modimpl_inscr_df.values[:, modimpl_mask]
|
||||||
|
modimpl_coefs = modimpl_coefs[modimpl_mask]
|
||||||
|
|
||||||
nb_etuds, nb_modules = sem_matrix.shape
|
nb_etuds, nb_modules = sem_matrix.shape
|
||||||
assert len(modimpl_coefs) == nb_modules
|
assert len(modimpl_coefs) == nb_modules
|
||||||
nb_ues = len(ues)
|
nb_ues = len(ues) # en comptant bonus
|
||||||
modimpl_inscr = modimpl_inscr_df.values
|
|
||||||
# Enlève les NaN du numérateur:
|
# Enlève les NaN du numérateur:
|
||||||
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||||
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
@ -283,16 +330,11 @@ 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
|
||||||
)
|
)
|
||||||
# Calcul des moyennes générales:
|
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
# --------------------- Calcul des moyennes d'UE
|
||||||
etud_moy_gen = np.sum(
|
|
||||||
modimpl_coefs_etuds_no_nan * sem_matrix_inscrits, axis=1
|
|
||||||
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
|
||||||
etud_moy_gen_s = pd.Series(etud_moy_gen, index=modimpl_inscr_df.index)
|
|
||||||
# Calcul des moyennes d'UE
|
|
||||||
ue_modules = np.array(
|
ue_modules = np.array(
|
||||||
[[m.module.ue == ue for m in formsemestre.modimpls] for ue in ues]
|
[[m.module.ue == ue for m in formsemestre.modimpls_sorted] for ue in ues]
|
||||||
)[..., np.newaxis]
|
)[..., np.newaxis][:, modimpl_mask, :]
|
||||||
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||||
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||||
)
|
)
|
||||||
@ -305,9 +347,35 @@ def compute_ue_moys_classic(
|
|||||||
etud_moy_ue_df = pd.DataFrame(
|
etud_moy_ue_df = pd.DataFrame(
|
||||||
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --------------------- Calcul des moyennes générales
|
||||||
|
if sco_preferences.get_preference("use_ue_coefs", formsemestre.id):
|
||||||
|
# Cas avec coefficients d'UE forcés: (on met à zéro l'UE bonus)
|
||||||
|
etud_coef_ue_df = pd.DataFrame(
|
||||||
|
{ue.id: ue.coefficient if ue.type != UE_SPORT else 0.0 for ue in ues},
|
||||||
|
index=modimpl_inscr_df.index,
|
||||||
|
columns=[ue.id for ue in ues],
|
||||||
|
)
|
||||||
|
# remplace NaN par zéros dans les moyennes d'UE
|
||||||
|
etud_moy_ue_df_no_nan = etud_moy_ue_df.fillna(0.0, inplace=False)
|
||||||
|
# Si on voulait annuler les coef d'UE dont la moyenne d'UE est NaN
|
||||||
|
# etud_coef_ue_df_no_nan = etud_coef_ue_df.where(etud_moy_ue_df.notna(), 0.0)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etud_moy_gen_s = (etud_coef_ue_df * etud_moy_ue_df_no_nan).sum(
|
||||||
|
axis=1
|
||||||
|
) / etud_coef_ue_df.sum(axis=1)
|
||||||
|
else:
|
||||||
|
# Cas normal: pondère directement les modules
|
||||||
etud_coef_ue_df = pd.DataFrame(
|
etud_coef_ue_df = pd.DataFrame(
|
||||||
coefs.sum(axis=2).T,
|
coefs.sum(axis=2).T,
|
||||||
index=modimpl_inscr_df.index, # etudids
|
index=modimpl_inscr_df.index, # etudids
|
||||||
columns=[ue.id for ue in ues],
|
columns=[ue.id for ue in ues],
|
||||||
)
|
)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etud_moy_gen = np.sum(
|
||||||
|
modimpl_coefs_etuds_no_nan * sem_matrix_inscrits, axis=1
|
||||||
|
) / np.sum(modimpl_coefs_etuds_no_nan, axis=1)
|
||||||
|
|
||||||
|
etud_moy_gen_s = pd.Series(etud_moy_gen, index=modimpl_inscr_df.index)
|
||||||
|
|
||||||
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
return etud_moy_gen_s, etud_moy_ue_df, etud_coef_ue_df
|
||||||
|
@ -10,6 +10,9 @@ import pandas as pd
|
|||||||
|
|
||||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.comp.bonus_spo import BonusSport
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreBUT(NotesTableCompat):
|
class ResultatsSemestreBUT(NotesTableCompat):
|
||||||
@ -37,15 +40,25 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
) = moy_ue.notes_sem_load_cube(self.formsemestre)
|
||||||
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_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
self.modimpl_coefs_df, _, _ = moy_ue.df_load_modimpl_coefs(
|
||||||
self.formsemestre, ues=self.ues, modimpls=self.modimpls
|
self.formsemestre, modimpls=self.formsemestre.modimpls_sorted
|
||||||
)
|
)
|
||||||
# l'idx de la colonne du mod modimpl.id est
|
# l'idx de la colonne du mod modimpl.id est
|
||||||
# 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:
|
||||||
|
modimpls_sport = [
|
||||||
|
modimpl
|
||||||
|
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,
|
||||||
self.etuds,
|
self.etuds,
|
||||||
self.modimpls,
|
self.formsemestre.modimpls_sorted,
|
||||||
self.ues,
|
self.ues,
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs_df,
|
self.modimpl_coefs_df,
|
||||||
@ -54,6 +67,28 @@ class ResultatsSemestreBUT(NotesTableCompat):
|
|||||||
self.etud_coef_ue_df = pd.DataFrame(
|
self.etud_coef_ue_df = pd.DataFrame(
|
||||||
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
1.0, index=self.etud_moy_ue.index, columns=self.etud_moy_ue.columns
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- Bonus Sport & Culture
|
||||||
|
if len(modimpls_sport) > 0:
|
||||||
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||||
|
if bonus_class is not None:
|
||||||
|
bonus: BonusSport = bonus_class(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_cube,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs_df.transpose(),
|
||||||
|
self.etud_moy_gen,
|
||||||
|
self.etud_moy_ue,
|
||||||
|
)
|
||||||
|
self.bonus_ues = bonus.get_bonus_ues()
|
||||||
|
if self.bonus_ues is not None:
|
||||||
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
|
||||||
|
# Moyenne générale indicative:
|
||||||
|
# (note: le bonus sport a déjà été appliqué aux moyenens d'UE, et impacte
|
||||||
|
# donc la moyenne indicative)
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
self.etud_moy_ue, self.modimpl_coefs_df
|
||||||
)
|
)
|
||||||
|
@ -11,7 +11,11 @@ import pandas as pd
|
|||||||
|
|
||||||
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||||
from app.comp.res_common import NotesTableCompat
|
from app.comp.res_common import NotesTableCompat
|
||||||
|
from app.comp.bonus_spo import BonusSport
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreClassic(NotesTableCompat):
|
class ResultatsSemestreClassic(NotesTableCompat):
|
||||||
@ -41,11 +45,20 @@ 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]
|
[m.module.coefficient for m in self.formsemestre.modimpls_sorted]
|
||||||
)
|
)
|
||||||
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
self.modimpl_idx = {
|
||||||
|
m.id: i for i, m in enumerate(self.formsemestre.modimpls_sorted)
|
||||||
|
}
|
||||||
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||||
|
|
||||||
|
modimpl_standards_mask = np.array(
|
||||||
|
[
|
||||||
|
(m.module.module_type == ModuleType.STANDARD)
|
||||||
|
and (m.module.ue.type != UE_SPORT)
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
]
|
||||||
|
)
|
||||||
(
|
(
|
||||||
self.etud_moy_gen,
|
self.etud_moy_gen,
|
||||||
self.etud_moy_ue,
|
self.etud_moy_ue,
|
||||||
@ -56,7 +69,32 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
self.ues,
|
self.ues,
|
||||||
self.modimpl_inscr_df,
|
self.modimpl_inscr_df,
|
||||||
self.modimpl_coefs,
|
self.modimpl_coefs,
|
||||||
|
modimpl_standards_mask,
|
||||||
)
|
)
|
||||||
|
# --- Bonus Sport & Culture
|
||||||
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class()
|
||||||
|
if bonus_class is not None:
|
||||||
|
bonus: BonusSport = bonus_class(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_matrix,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs,
|
||||||
|
self.etud_moy_gen,
|
||||||
|
self.etud_moy_ue,
|
||||||
|
)
|
||||||
|
self.bonus_ues = bonus.get_bonus_ues()
|
||||||
|
if self.bonus_ues is not None:
|
||||||
|
self.etud_moy_ue += self.bonus_ues # somme les dataframes
|
||||||
|
self.etud_moy_ue.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
bonus_mg = bonus.get_bonus_moy_gen()
|
||||||
|
if bonus_mg is not None:
|
||||||
|
self.etud_moy_gen += bonus_mg
|
||||||
|
self.etud_moy_gen.clip(lower=0.0, upper=20.0, inplace=True)
|
||||||
|
self.bonus = (
|
||||||
|
bonus_mg # compat nt, utilisé pour l'afficher sur les bulletins
|
||||||
|
)
|
||||||
|
# --- Classements:
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
@ -85,9 +123,9 @@ class ResultatsSemestreClassic(NotesTableCompat):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]:
|
||||||
"""Calcule la matrice des notes du semestre
|
"""Calcule la matrice des notes du semestre
|
||||||
(charge toutes les notes, calcule les moyenne des modules
|
(charge toutes les notes, calcule les moyennes des modules
|
||||||
et assemble la matrice)
|
et assemble la matrice)
|
||||||
Resultat:
|
Resultat:
|
||||||
sem_matrix : 2d-array (etuds x modimpls)
|
sem_matrix : 2d-array (etuds x modimpls)
|
||||||
@ -95,7 +133,7 @@ def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
|||||||
"""
|
"""
|
||||||
modimpls_results = {}
|
modimpls_results = {}
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls_sorted:
|
||||||
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
||||||
etuds_moy_module = mod_results.compute_module_moy()
|
etuds_moy_module = mod_results.compute_module_moy()
|
||||||
modimpls_results[modimpl.id] = mod_results
|
modimpls_results[modimpl.id] = mod_results
|
||||||
|
@ -8,7 +8,7 @@ from collections import defaultdict, Counter
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from app.comp.aux import StatsMoyenne
|
from app.comp.aux_stats import StatsMoyenne
|
||||||
from app.comp.moy_mod import ModuleImplResults
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
from app.models import FormSemestre, Identite, ModuleImpl
|
from app.models import FormSemestre, Identite, ModuleImpl
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
@ -100,42 +100,28 @@ class ResultatsSemestre:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ues(self) -> list[UniteEns]:
|
def ues(self) -> list[UniteEns]:
|
||||||
"""Liste des UEs du semestre
|
"""Liste des UEs du semestre (avec les UE bonus sport)
|
||||||
(indices des DataFrames)
|
(indices des DataFrames)
|
||||||
"""
|
"""
|
||||||
return self.formsemestre.query_ues(with_sport=True).all()
|
return self.formsemestre.query_ues(with_sport=True).all()
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def modimpls(self):
|
|
||||||
"""Liste des modimpls du semestre
|
|
||||||
- triée par numéro de module en APC
|
|
||||||
- triée par numéros d'UE/matières/modules pour les formations standard.
|
|
||||||
"""
|
|
||||||
modimpls = self.formsemestre.modimpls.all()
|
|
||||||
if self.is_apc:
|
|
||||||
modimpls.sort(key=lambda m: (m.module.numero, m.module.code))
|
|
||||||
else:
|
|
||||||
modimpls.sort(
|
|
||||||
key=lambda m: (
|
|
||||||
m.module.ue.numero,
|
|
||||||
m.module.matiere.numero,
|
|
||||||
m.module.numero,
|
|
||||||
m.module.code,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return modimpls
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ressources(self):
|
def ressources(self):
|
||||||
"Liste des ressources du semestre, triées par numéro de module"
|
"Liste des ressources du semestre, triées par numéro de module"
|
||||||
return [
|
return [
|
||||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
m
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||||
]
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def saes(self):
|
def saes(self):
|
||||||
"Liste des SAÉs du semestre, triées par numéro de module"
|
"Liste des SAÉs du semestre, triées par numéro de module"
|
||||||
return [m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE]
|
return [
|
||||||
|
m
|
||||||
|
for m in self.formsemestre.modimpls_sorted
|
||||||
|
if m.module.module_type == scu.ModuleType.SAE
|
||||||
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ue_validables(self) -> list:
|
def ue_validables(self) -> list:
|
||||||
@ -144,6 +130,16 @@ class ResultatsSemestre:
|
|||||||
"""
|
"""
|
||||||
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
return self.formsemestre.query_ues().filter(UniteEns.type != UE_SPORT).all()
|
||||||
|
|
||||||
|
def modimpls_in_ue(self, ue_id, etudid):
|
||||||
|
"""Liste des modimpl de cet ue auxquels l'étudiant est inscrit"""
|
||||||
|
# sert pour l'affichage ou non de l'UE sur le bulletin
|
||||||
|
return [
|
||||||
|
modimpl
|
||||||
|
for modimpl in self.formsemestre.modimpls_sorted
|
||||||
|
if modimpl.module.ue.id == ue_id
|
||||||
|
and self.modimpl_inscr_df[modimpl.id][etudid]
|
||||||
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame:
|
def ue_au_dessus(self, seuil=10.0) -> pd.DataFrame:
|
||||||
"""DataFrame columns UE, rows etudid, valeurs: bool
|
"""DataFrame columns UE, rows etudid, valeurs: bool
|
||||||
@ -163,16 +159,20 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
développements (API malcommode et peu efficace).
|
développements (API malcommode et peu efficace).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
_cached_attrs = ResultatsSemestre._cached_attrs + (
|
||||||
|
"bonus",
|
||||||
|
"bonus_ues",
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, formsemestre: FormSemestre):
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
super().__init__(formsemestre)
|
super().__init__(formsemestre)
|
||||||
|
|
||||||
nb_etuds = len(self.etuds)
|
nb_etuds = len(self.etuds)
|
||||||
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
self.bonus = None # virtuel
|
||||||
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
self.bonus_ues = None # virtuel
|
||||||
|
self.ue_rangs = {u.id: (None, nb_etuds) for u in self.ues}
|
||||||
self.mod_rangs = {
|
self.mod_rangs = {
|
||||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
m.id: (None, nb_etuds) for m in self.formsemestre.modimpls_sorted
|
||||||
}
|
}
|
||||||
self.moy_min = "NA"
|
self.moy_min = "NA"
|
||||||
self.moy_max = "NA"
|
self.moy_max = "NA"
|
||||||
@ -221,18 +221,26 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
ues = []
|
ues = []
|
||||||
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
for ue in self.formsemestre.query_ues(with_sport=not filter_sport):
|
||||||
d = ue.to_dict()
|
d = ue.to_dict()
|
||||||
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
if ue.type != UE_SPORT:
|
||||||
|
moys = self.etud_moy_ue[ue.id]
|
||||||
|
else:
|
||||||
|
moys = None
|
||||||
|
d.update(StatsMoyenne(moys).to_dict())
|
||||||
ues.append(d)
|
ues.append(d)
|
||||||
return ues
|
return ues
|
||||||
|
|
||||||
def get_modimpls_dict(self, ue_id=None):
|
def get_modimpls_dict(self, ue_id=None) -> list[dict]:
|
||||||
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
"""Liste des modules pour une UE (ou toutes si ue_id==None),
|
||||||
triés par numéros (selon le type de formation)
|
triés par numéros (selon le type de formation)
|
||||||
"""
|
"""
|
||||||
if ue_id is None:
|
modimpls_dict = []
|
||||||
return [m.to_dict() for m in self.modimpls]
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
else:
|
if ue_id == None or modimpl.module.ue.id == ue_id:
|
||||||
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
d = modimpl.to_dict()
|
||||||
|
# compat ScoDoc < 9.2: ajoute matières
|
||||||
|
d["mat"] = modimpl.module.matiere.to_dict()
|
||||||
|
modimpls_dict.append(d)
|
||||||
|
return modimpls_dict
|
||||||
|
|
||||||
def get_etud_decision_sem(self, etudid: int) -> dict:
|
def get_etud_decision_sem(self, etudid: int) -> dict:
|
||||||
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
"""Decision du jury prise pour cet etudiant, ou None s'il n'y en pas eu.
|
||||||
@ -261,13 +269,10 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
return ""
|
return ""
|
||||||
return ins.etat
|
return ins.etat
|
||||||
|
|
||||||
def get_etud_moy_gen(self, etudid): # -> float | str
|
def get_etud_mat_moy(self, matiere_id, etudid):
|
||||||
"""Moyenne générale de cet etudiant dans ce semestre.
|
"""moyenne d'un étudiant dans une matière (ou NA si pas de notes)"""
|
||||||
Prend(ra) en compte les UE capitalisées. (TODO) XXX
|
# non supporté en 9.2
|
||||||
Si apc, moyenne indicative.
|
return "na"
|
||||||
Si pas de notes: 'NA'
|
|
||||||
"""
|
|
||||||
return self.etud_moy_gen[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
|
||||||
@ -276,6 +281,14 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError() # virtual method
|
raise NotImplementedError() # virtual method
|
||||||
|
|
||||||
|
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||||
|
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||||
|
Prend(ra) en compte les UE capitalisées. (TODO) XXX
|
||||||
|
Si apc, moyenne indicative.
|
||||||
|
Si pas de notes: 'NA'
|
||||||
|
"""
|
||||||
|
return self.etud_moy_gen[etudid]
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||||
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
coef_ue = self.etud_coef_ue_df[ue_id][etudid]
|
||||||
return {
|
return {
|
||||||
@ -359,12 +372,16 @@ class NotesTableCompat(ResultatsSemestre):
|
|||||||
moy_gen = self.etud_moy_gen.get(etudid, False)
|
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||||
if moy_gen is False:
|
if moy_gen is False:
|
||||||
# pas de moyenne: démissionnaire ou def
|
# pas de moyenne: démissionnaire ou def
|
||||||
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
t = (
|
||||||
|
["-"]
|
||||||
|
+ ["0.00"] * len(self.ues)
|
||||||
|
+ ["NI"] * len(self.formsemestre.modimpls_sorted)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
moy_ues = self.etud_moy_ue.loc[etudid]
|
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||||
t = [moy_gen] + list(moy_ues)
|
t = [moy_gen] + list(moy_ues)
|
||||||
# TODO UE capitalisées: ne pas afficher moyennes modules
|
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||||
for modimpl in self.modimpls:
|
for modimpl in self.formsemestre.modimpls_sorted:
|
||||||
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||||
t.append(val)
|
t.append(val)
|
||||||
t.append(etudid)
|
t.append(etudid)
|
||||||
|
77
app/forms/main/config_apo.py
Normal file
77
app/forms/main/config_apo.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulaires configuration Exports Apogée (codes)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import flash, url_for, redirect, render_template
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SubmitField, validators
|
||||||
|
from wtforms.fields.simple import StringField
|
||||||
|
|
||||||
|
from app import models
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from app.models import SHORT_STR_LEN
|
||||||
|
|
||||||
|
from app.scodoc import sco_codes_parcours
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
|
def _build_code_field(code):
|
||||||
|
return StringField(
|
||||||
|
label=code,
|
||||||
|
description=sco_codes_parcours.CODES_EXPL[code],
|
||||||
|
validators=[
|
||||||
|
validators.regexp(
|
||||||
|
r"^[A-Z0-9_]*$",
|
||||||
|
message="Ne doit comporter que majuscules et des chiffres",
|
||||||
|
),
|
||||||
|
validators.Length(
|
||||||
|
max=SHORT_STR_LEN,
|
||||||
|
message=f"L'acronyme ne doit pas dépasser {SHORT_STR_LEN} caractères",
|
||||||
|
),
|
||||||
|
validators.DataRequired("code requis"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CodesDecisionsForm(FlaskForm):
|
||||||
|
ADC = _build_code_field("ADC")
|
||||||
|
ADJ = _build_code_field("ADJ")
|
||||||
|
ADM = _build_code_field("ADM")
|
||||||
|
AJ = _build_code_field("AJ")
|
||||||
|
ATB = _build_code_field("ATB")
|
||||||
|
ATJ = _build_code_field("ATJ")
|
||||||
|
ATT = _build_code_field("ATT")
|
||||||
|
CMP = _build_code_field("CMP")
|
||||||
|
DEF = _build_code_field("DEF")
|
||||||
|
DEM = _build_code_field("DEF")
|
||||||
|
NAR = _build_code_field("NAR")
|
||||||
|
RAT = _build_code_field("RAT")
|
||||||
|
submit = SubmitField("Valider")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -47,7 +47,6 @@ from app.scodoc.sco_config_actions import (
|
|||||||
LogoDelete,
|
LogoDelete,
|
||||||
LogoUpdate,
|
LogoUpdate,
|
||||||
LogoInsert,
|
LogoInsert,
|
||||||
BonusSportUpdate,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -296,23 +295,15 @@ def _make_depts_data(modele):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def _make_data(bonus_sport, modele):
|
def _make_data(modele):
|
||||||
data = {
|
data = {
|
||||||
"bonus_sport_func_name": bonus_sport,
|
|
||||||
"depts": _make_depts_data(modele=modele),
|
"depts": _make_depts_data(modele=modele),
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ScoDocConfigurationForm(FlaskForm):
|
class LogosConfigurationForm(FlaskForm):
|
||||||
"Panneau de configuration général"
|
"Panneau de configuration des logos"
|
||||||
bonus_sport_func_name = SelectField(
|
|
||||||
label="Fonction de calcul des bonus sport&culture",
|
|
||||||
choices=[
|
|
||||||
(x, x if x else "Aucune")
|
|
||||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
|
||||||
],
|
|
||||||
)
|
|
||||||
depts = FieldList(FormField(DeptForm))
|
depts = FieldList(FormField(DeptForm))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -361,11 +352,6 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
return dept_form.get_form(logoname)
|
return dept_form.get_form(logoname)
|
||||||
|
|
||||||
def select_action(self):
|
def select_action(self):
|
||||||
if (
|
|
||||||
self.data["bonus_sport_func_name"]
|
|
||||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
|
||||||
):
|
|
||||||
return BonusSportUpdate(self.data)
|
|
||||||
for dept_entry in self.depts:
|
for dept_entry in self.depts:
|
||||||
dept_form = dept_entry.form
|
dept_form = dept_entry.form
|
||||||
action = dept_form.select_action()
|
action = dept_form.select_action()
|
||||||
@ -374,14 +360,11 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def configuration():
|
def config_logos():
|
||||||
"""Panneau de configuration général"""
|
"Page de configuration des logos"
|
||||||
auth_name = str(current_user)
|
# nb: le contrôle d'accès (SuperAdmin) doit être fait dans la vue
|
||||||
if not current_user.is_administrator():
|
form = LogosConfigurationForm(
|
||||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
|
||||||
form = ScoDocConfigurationForm(
|
|
||||||
data=_make_data(
|
data=_make_data(
|
||||||
bonus_sport=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
|
||||||
modele=sco_logos.list_logos(),
|
modele=sco_logos.list_logos(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -392,11 +375,11 @@ def configuration():
|
|||||||
flash(action.message)
|
flash(action.message)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scodoc.configuration",
|
"scodoc.configure_logos",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
"configuration.html",
|
"config_logos.html",
|
||||||
scodoc_dept=None,
|
scodoc_dept=None,
|
||||||
title="Configuration ScoDoc",
|
title="Configuration ScoDoc",
|
||||||
form=form,
|
form=form,
|
76
app/forms/main/config_main.py
Normal file
76
app/forms/main/config_main.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulaires configuration Exports Apogée (codes)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import flash, url_for, redirect, request, render_template
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SelectField, SubmitField
|
||||||
|
|
||||||
|
import app
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ScoDocConfigurationForm(FlaskForm):
|
||||||
|
"Panneau de configuration des logos"
|
||||||
|
bonus_sport_func_name = SelectField(
|
||||||
|
label="Fonction de calcul des bonus sport&culture",
|
||||||
|
choices=[
|
||||||
|
(name, displayed_name if name else "Aucune")
|
||||||
|
for (name, displayed_name) in ScoDocSiteConfig.get_bonus_sport_class_list()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
submit = SubmitField("Valider")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
|
||||||
|
|
||||||
|
def configuration():
|
||||||
|
"Page de configuration principale"
|
||||||
|
# nb: le contrôle d'accès (SuperAdmin) doit être fait dans la vue
|
||||||
|
form = ScoDocConfigurationForm(
|
||||||
|
data={
|
||||||
|
"bonus_sport_func_name": ScoDocSiteConfig.get_bonus_sport_class_name(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if request.method == "POST" and form.cancel.data: # cancel button
|
||||||
|
return redirect(url_for("scodoc.index"))
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if (
|
||||||
|
form.data["bonus_sport_func_name"]
|
||||||
|
!= ScoDocSiteConfig.get_bonus_sport_class_name()
|
||||||
|
):
|
||||||
|
ScoDocSiteConfig.set_bonus_sport_class(form.data["bonus_sport_func_name"])
|
||||||
|
app.clear_scodoc_cache()
|
||||||
|
flash(f"Fonction bonus sport&culture configurée.")
|
||||||
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"configuration.html",
|
||||||
|
form=form,
|
||||||
|
)
|
@ -6,13 +6,12 @@ XXX version préliminaire ScoDoc8 #sco8 sans département
|
|||||||
|
|
||||||
CODE_STR_LEN = 16 # chaine pour les codes
|
CODE_STR_LEN = 16 # chaine pour les codes
|
||||||
SHORT_STR_LEN = 32 # courtes chaine, eg acronymes
|
SHORT_STR_LEN = 32 # courtes chaine, eg acronymes
|
||||||
APO_CODE_STR_LEN = 24 # nb de car max d'un code Apogée
|
APO_CODE_STR_LEN = 512 # nb de car max d'un code Apogée (il peut y en avoir plusieurs)
|
||||||
GROUPNAME_STR_LEN = 64
|
GROUPNAME_STR_LEN = 64
|
||||||
|
|
||||||
from app.models.raw_sql_init import create_database_functions
|
from app.models.raw_sql_init import create_database_functions
|
||||||
|
|
||||||
from app.models.absences import Absence, AbsenceNotification, BilletAbsence
|
from app.models.absences import Absence, AbsenceNotification, BilletAbsence
|
||||||
|
|
||||||
from app.models.departements import Departement
|
from app.models.departements import Departement
|
||||||
from app.models.etudiants import (
|
from app.models.etudiants import (
|
||||||
Identite,
|
Identite,
|
||||||
@ -57,7 +56,7 @@ from app.models.notes import (
|
|||||||
NotesNotes,
|
NotesNotes,
|
||||||
NotesNotesLog,
|
NotesNotesLog,
|
||||||
)
|
)
|
||||||
from app.models.preferences import ScoPreference, ScoDocSiteConfig
|
from app.models.preferences import ScoPreference
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
ApcReferentielCompetences,
|
ApcReferentielCompetences,
|
||||||
@ -65,3 +64,4 @@ from app.models.but_refcomp import (
|
|||||||
ApcSituationPro,
|
ApcSituationPro,
|
||||||
ApcAppCritique,
|
ApcAppCritique,
|
||||||
)
|
)
|
||||||
|
from app.models.config import ScoDocSiteConfig
|
||||||
|
209
app/models/config.py
Normal file
209
app/models/config.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
# -*- coding: UTF-8 -*
|
||||||
|
|
||||||
|
"""Model : site config WORK IN PROGRESS #WIP
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import flash
|
||||||
|
from app import db, log
|
||||||
|
from app.comp import bonus_spo
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
from app.scodoc.sco_codes_parcours import (
|
||||||
|
ADC,
|
||||||
|
ADJ,
|
||||||
|
ADM,
|
||||||
|
AJ,
|
||||||
|
ATB,
|
||||||
|
ATJ,
|
||||||
|
ATT,
|
||||||
|
CMP,
|
||||||
|
DEF,
|
||||||
|
DEM,
|
||||||
|
NAR,
|
||||||
|
RAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODES_SCODOC_TO_APO = {
|
||||||
|
ADC: "ADMC",
|
||||||
|
ADJ: "ADM",
|
||||||
|
ADM: "ADM",
|
||||||
|
AJ: "AJ",
|
||||||
|
ATB: "AJAC",
|
||||||
|
ATJ: "AJAC",
|
||||||
|
ATT: "AJAC",
|
||||||
|
CMP: "COMP",
|
||||||
|
DEF: "NAR",
|
||||||
|
DEM: "NAR",
|
||||||
|
NAR: "NAR",
|
||||||
|
RAT: "ATT",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def code_scodoc_to_apo_default(code):
|
||||||
|
"""Conversion code jury ScoDoc en code Apogée
|
||||||
|
(codes par défaut, c'est configurable via ScoDocSiteConfig.get_code_apo)
|
||||||
|
"""
|
||||||
|
return CODES_SCODOC_TO_APO.get(code, "DEF")
|
||||||
|
|
||||||
|
|
||||||
|
class ScoDocSiteConfig(db.Model):
|
||||||
|
"""Config. d'un site
|
||||||
|
Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions
|
||||||
|
antérieures étaient dans scodoc_config.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "scodoc_site_config"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(128), nullable=False, index=True)
|
||||||
|
value = db.Column(db.Text())
|
||||||
|
|
||||||
|
BONUS_SPORT = "bonus_sport_func_name"
|
||||||
|
NAMES = {
|
||||||
|
BONUS_SPORT: str,
|
||||||
|
"always_require_ine": bool,
|
||||||
|
"SCOLAR_FONT": str,
|
||||||
|
"SCOLAR_FONT_SIZE": str,
|
||||||
|
"SCOLAR_FONT_SIZE_FOOT": str,
|
||||||
|
"INSTITUTION_NAME": str,
|
||||||
|
"INSTITUTION_ADDRESS": str,
|
||||||
|
"INSTITUTION_CITY": str,
|
||||||
|
"DEFAULT_PDF_FOOTER_TEMPLATE": str,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, name, value):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_dict(cls) -> dict:
|
||||||
|
"Returns all data as a dict name = value"
|
||||||
|
return {
|
||||||
|
c.name: cls.NAMES.get(c.name, lambda x: x)(c.value)
|
||||||
|
for c in ScoDocSiteConfig.query.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_bonus_sport_class(cls, class_name):
|
||||||
|
"""Record bonus_sport config.
|
||||||
|
If class_name not defined, raise NameError
|
||||||
|
"""
|
||||||
|
if class_name not in cls.get_bonus_sport_class_names():
|
||||||
|
raise NameError("invalid class name for bonus_sport")
|
||||||
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
|
if c:
|
||||||
|
log("setting to " + class_name)
|
||||||
|
c.value = class_name
|
||||||
|
else:
|
||||||
|
c = ScoDocSiteConfig(cls.BONUS_SPORT, class_name)
|
||||||
|
db.session.add(c)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_name(cls):
|
||||||
|
"""Get configured bonus function name, or None if None."""
|
||||||
|
klass = cls.get_bonus_sport_class_from_name()
|
||||||
|
if klass is None:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return klass.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class(cls):
|
||||||
|
"""Get configured bonus function, or None if None."""
|
||||||
|
return cls.get_bonus_sport_class_from_name()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_from_name(cls, class_name=None):
|
||||||
|
"""returns bonus class with specified name.
|
||||||
|
If name not specified, return the configured function.
|
||||||
|
None if no bonus function configured.
|
||||||
|
If class_name not found in module bonus_sport, returns None
|
||||||
|
and flash a warning.
|
||||||
|
"""
|
||||||
|
if not class_name: # None or ""
|
||||||
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
|
if c is None:
|
||||||
|
return None
|
||||||
|
class_name = c.value
|
||||||
|
if class_name == "": # pas de bonus défini
|
||||||
|
return None
|
||||||
|
klass = bonus_spo.get_bonus_class_dict().get(class_name)
|
||||||
|
if klass is None:
|
||||||
|
flash(
|
||||||
|
f"""Fonction de calcul bonus sport inexistante: {class_name}.
|
||||||
|
Changez là ou contactez votre administrateur local."""
|
||||||
|
)
|
||||||
|
return klass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_names(cls) -> list:
|
||||||
|
"""List available bonus class names
|
||||||
|
(starting with empty string to represent "no bonus function").
|
||||||
|
"""
|
||||||
|
return [""] + sorted(bonus_spo.get_bonus_class_dict().keys())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_class_list(cls) -> list[tuple]:
|
||||||
|
"""List available bonus class names
|
||||||
|
(starting with empty string to represent "no bonus function").
|
||||||
|
"""
|
||||||
|
d = bonus_spo.get_bonus_class_dict()
|
||||||
|
class_list = [(name, d[name].displayed_name) for name in d.keys()]
|
||||||
|
class_list.sort(key=lambda x: x[1].replace(" du ", " de "))
|
||||||
|
return [("", "")] + class_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_bonus_sport_func(cls):
|
||||||
|
"""Fonction bonus_sport ScoDoc 7 XXX
|
||||||
|
Transitoire pour les tests durant la transition #sco92
|
||||||
|
"""
|
||||||
|
"""returns bonus func with specified name.
|
||||||
|
If name not specified, return the configured function.
|
||||||
|
None if no bonus function configured.
|
||||||
|
Raises ScoValueError if func_name not found in module bonus_sport.
|
||||||
|
"""
|
||||||
|
from app.scodoc import bonus_sport
|
||||||
|
|
||||||
|
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
||||||
|
if c is None:
|
||||||
|
return None
|
||||||
|
func_name = c.value
|
||||||
|
if func_name == "": # pas de bonus défini
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return getattr(bonus_sport, func_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""Fonction de calcul maison inexistante: {func_name}.
|
||||||
|
(contacter votre administrateur local)."""
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_code_apo(cls, code: str) -> str:
|
||||||
|
"""La représentation d'un code pour les exports Apogée.
|
||||||
|
Par exemple, à l'iUT du H., le code ADM est réprésenté par VAL
|
||||||
|
Les codes par défaut sont donnés dans sco_apogee_csv.
|
||||||
|
|
||||||
|
"""
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name=code).first()
|
||||||
|
if not cfg:
|
||||||
|
code_apo = code_scodoc_to_apo_default(code)
|
||||||
|
else:
|
||||||
|
code_apo = cfg.value
|
||||||
|
return code_apo
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_code_apo(cls, code: str, code_apo: str):
|
||||||
|
"""Enregistre nouvelle représentation du code"""
|
||||||
|
if code_apo != cls.get_code_apo(code):
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name=code).first()
|
||||||
|
if cfg is None:
|
||||||
|
cfg = ScoDocSiteConfig(code, code_apo)
|
||||||
|
else:
|
||||||
|
cfg.value = code_apo
|
||||||
|
db.session.add(cfg)
|
||||||
|
db.session.commit()
|
@ -103,7 +103,16 @@ class Evaluation(db.Model):
|
|||||||
Note: si les poids ne sont pas initialisés (poids par défaut),
|
Note: si les poids ne sont pas initialisés (poids par défaut),
|
||||||
ils ne sont pas affichés.
|
ils ne sont pas affichés.
|
||||||
"""
|
"""
|
||||||
return ", ".join([f"{p.ue.acronyme}: {p.poids}" for p in self.ue_poids])
|
# restreint aux UE du semestre dans lequel est cette évaluation
|
||||||
|
# au cas où le module ait changé de semestre et qu'il reste des poids
|
||||||
|
evaluation_semestre_idx = self.moduleimpl.module.semestre_id
|
||||||
|
return ", ".join(
|
||||||
|
[
|
||||||
|
f"{p.ue.acronyme}: {p.poids}"
|
||||||
|
for p in self.ue_poids
|
||||||
|
if evaluation_semestre_idx == p.ue.semestre_idx
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EvaluationUEPoids(db.Model):
|
class EvaluationUEPoids(db.Model):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""ScoDoc 9 models : Formations
|
"""ScoDoc 9 models : Formations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import df_cache
|
from app.comp import df_cache
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
@ -141,8 +142,7 @@ class Formation(db.Model):
|
|||||||
db.session.add(ue)
|
db.session.add(ue)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if change:
|
app.clear_scodoc_cache()
|
||||||
self.invalidate_module_coefs()
|
|
||||||
|
|
||||||
|
|
||||||
class Matiere(db.Model):
|
class Matiere(db.Model):
|
||||||
@ -161,3 +161,16 @@ class Matiere(db.Model):
|
|||||||
numero = db.Column(db.Integer) # ordre de présentation
|
numero = db.Column(db.Integer) # ordre de présentation
|
||||||
|
|
||||||
modules = db.relationship("Module", lazy="dynamic", backref="matiere")
|
modules = db.relationship("Module", lazy="dynamic", backref="matiere")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"""<{self.__class__.__name__}(id={self.id}, ue_id={
|
||||||
|
self.ue_id}, titre='{self.titre}')>"""
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""as a dict, with the same conversions as in ScoDoc7"""
|
||||||
|
e = dict(self.__dict__)
|
||||||
|
e.pop("_sa_instance_state", None)
|
||||||
|
# ScoDoc7 output_formators
|
||||||
|
e["ue_id"] = self.id
|
||||||
|
e["numero"] = e["numero"] if e["numero"] else 0
|
||||||
|
return e
|
||||||
|
@ -49,7 +49,7 @@ class FormSemestre(db.Model):
|
|||||||
gestion_compensation = db.Column(
|
gestion_compensation = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# ne publie pas le bulletin XML:
|
# ne publie pas le bulletin XML ou JSON:
|
||||||
bul_hide_xml = db.Column(
|
bul_hide_xml = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
@ -112,6 +112,9 @@ class FormSemestre(db.Model):
|
|||||||
if self.modalite is None:
|
if self.modalite is None:
|
||||||
self.modalite = FormationModalite.DEFAULT_MODALITE
|
self.modalite = FormationModalite.DEFAULT_MODALITE
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
d = dict(self.__dict__)
|
d = dict(self.__dict__)
|
||||||
d.pop("_sa_instance_state", None)
|
d.pop("_sa_instance_state", None)
|
||||||
@ -152,6 +155,28 @@ class FormSemestre(db.Model):
|
|||||||
sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
sem_ues = sem_ues.filter(UniteEns.type != sco_codes_parcours.UE_SPORT)
|
||||||
return sem_ues.order_by(UniteEns.numero)
|
return sem_ues.order_by(UniteEns.numero)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def modimpls_sorted(self) -> list[ModuleImpl]:
|
||||||
|
"""Liste des modimpls du semestre
|
||||||
|
- triée par type/numéro/code en APC
|
||||||
|
- triée par numéros d'UE/matières/modules pour les formations standard.
|
||||||
|
"""
|
||||||
|
modimpls = self.modimpls.all()
|
||||||
|
if self.formation.is_apc():
|
||||||
|
modimpls.sort(
|
||||||
|
key=lambda m: (m.module.module_type, m.module.numero, m.module.code)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
modimpls.sort(
|
||||||
|
key=lambda m: (
|
||||||
|
m.module.ue.numero,
|
||||||
|
m.module.matiere.numero,
|
||||||
|
m.module.numero,
|
||||||
|
m.module.code,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return modimpls
|
||||||
|
|
||||||
def est_courant(self) -> bool:
|
def est_courant(self) -> bool:
|
||||||
"""Vrai si la date actuelle (now) est dans le semestre
|
"""Vrai si la date actuelle (now) est dans le semestre
|
||||||
(les dates de début et fin sont incluses)
|
(les dates de début et fin sont incluses)
|
||||||
@ -262,7 +287,7 @@ class FormSemestre(db.Model):
|
|||||||
self.date_fin.year})"""
|
self.date_fin.year})"""
|
||||||
|
|
||||||
def titre_num(self) -> str:
|
def titre_num(self) -> str:
|
||||||
"""Le titre est le semestre, ex ""DUT Informatique semestre 2"" """
|
"""Le titre et le semestre, ex ""DUT Informatique semestre 2"" """
|
||||||
if self.semestre_id == sco_codes_parcours.NO_SEMESTRE_ID:
|
if self.semestre_id == sco_codes_parcours.NO_SEMESTRE_ID:
|
||||||
return self.titre
|
return self.titre
|
||||||
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
|
return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}"
|
||||||
@ -288,6 +313,11 @@ class FormSemestre(db.Model):
|
|||||||
else:
|
else:
|
||||||
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
return [ins.etud for ins in self.inscriptions if ins.etat == scu.INSCRIT]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def etudids_actifs(self) -> set:
|
||||||
|
"Set des etudids inscrits non démissionnaires"
|
||||||
|
return {ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def etuds_inscriptions(self) -> dict:
|
def etuds_inscriptions(self) -> dict:
|
||||||
"""Map { etudid : inscription } (incluant DEM et DEF)"""
|
"""Map { etudid : inscription } (incluant DEM et DEF)"""
|
||||||
|
@ -5,7 +5,7 @@ import pandas as pd
|
|||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.comp import df_cache
|
from app.comp import df_cache
|
||||||
from app.models import UniteEns, Identite
|
from app.models import Identite, Module
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
@ -127,3 +127,16 @@ class ModuleImplInscription(db.Model):
|
|||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
backref=db.backref("inscriptions", cascade="all, delete-orphan"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def nb_inscriptions_dans_ue(
|
||||||
|
cls, formsemestre_id: int, etudid: int, ue_id: int
|
||||||
|
) -> int:
|
||||||
|
"""Nombre de moduleimpls de l'UE auxquels l'étudiant est inscrit"""
|
||||||
|
return ModuleImplInscription.query.filter(
|
||||||
|
ModuleImplInscription.etudid == etudid,
|
||||||
|
ModuleImplInscription.moduleimpl_id == ModuleImpl.id,
|
||||||
|
ModuleImpl.formsemestre_id == formsemestre_id,
|
||||||
|
ModuleImpl.module_id == Module.id,
|
||||||
|
Module.ue_id == ue_id,
|
||||||
|
).count()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
|
|
||||||
|
|
||||||
@ -131,7 +132,8 @@ class Module(db.Model):
|
|||||||
|
|
||||||
def ue_coefs_list(self, include_zeros=True):
|
def ue_coefs_list(self, include_zeros=True):
|
||||||
"""Liste des coefs vers les UE (pour les modules APC).
|
"""Liste des coefs vers les UE (pour les modules APC).
|
||||||
Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre.
|
Si include_zeros, liste aussi les UE sans coef (donc nul) de ce semestre,
|
||||||
|
sauf UE bonus sport.
|
||||||
Result: List of tuples [ (ue, coef) ]
|
Result: List of tuples [ (ue, coef) ]
|
||||||
"""
|
"""
|
||||||
if not self.is_apc():
|
if not self.is_apc():
|
||||||
@ -140,6 +142,7 @@ class Module(db.Model):
|
|||||||
# Toutes les UE du même semestre:
|
# Toutes les UE du même semestre:
|
||||||
ues_semestre = (
|
ues_semestre = (
|
||||||
self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
|
self.formation.ues.filter_by(semestre_idx=self.ue.semestre_idx)
|
||||||
|
.filter(UniteEns.type != UE_SPORT)
|
||||||
.order_by(UniteEns.numero)
|
.order_by(UniteEns.numero)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
"""Model : preferences
|
"""Model : preferences
|
||||||
"""
|
"""
|
||||||
from app import db, log
|
|
||||||
from app.scodoc import bonus_sport
|
from app import db
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
|
||||||
|
|
||||||
|
|
||||||
class ScoPreference(db.Model):
|
class ScoPreference(db.Model):
|
||||||
@ -19,108 +18,3 @@ class ScoPreference(db.Model):
|
|||||||
name = db.Column(db.String(128), nullable=False, index=True)
|
name = db.Column(db.String(128), nullable=False, index=True)
|
||||||
value = db.Column(db.Text())
|
value = db.Column(db.Text())
|
||||||
formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id"))
|
formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id"))
|
||||||
|
|
||||||
|
|
||||||
class ScoDocSiteConfig(db.Model):
|
|
||||||
"""Config. d'un site
|
|
||||||
Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions
|
|
||||||
antérieures étaient dans scodoc_config.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "scodoc_site_config"
|
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
name = db.Column(db.String(128), nullable=False, index=True)
|
|
||||||
value = db.Column(db.Text())
|
|
||||||
|
|
||||||
BONUS_SPORT = "bonus_sport_func_name"
|
|
||||||
NAMES = {
|
|
||||||
BONUS_SPORT: str,
|
|
||||||
"always_require_ine": bool,
|
|
||||||
"SCOLAR_FONT": str,
|
|
||||||
"SCOLAR_FONT_SIZE": str,
|
|
||||||
"SCOLAR_FONT_SIZE_FOOT": str,
|
|
||||||
"INSTITUTION_NAME": str,
|
|
||||||
"INSTITUTION_ADDRESS": str,
|
|
||||||
"INSTITUTION_CITY": str,
|
|
||||||
"DEFAULT_PDF_FOOTER_TEMPLATE": str,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, name, value):
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>"
|
|
||||||
|
|
||||||
def get_dict(self) -> dict:
|
|
||||||
"Returns all data as a dict name = value"
|
|
||||||
return {
|
|
||||||
c.name: self.NAMES.get(c.name, lambda x: x)(c.value)
|
|
||||||
for c in ScoDocSiteConfig.query.all()
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_bonus_sport_func(cls, func_name):
|
|
||||||
"""Record bonus_sport config.
|
|
||||||
If func_name not defined, raise NameError
|
|
||||||
"""
|
|
||||||
if func_name not in cls.get_bonus_sport_func_names():
|
|
||||||
raise NameError("invalid function name for bonus_sport")
|
|
||||||
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
|
||||||
if c:
|
|
||||||
log("setting to " + func_name)
|
|
||||||
c.value = func_name
|
|
||||||
else:
|
|
||||||
c = ScoDocSiteConfig(cls.BONUS_SPORT, func_name)
|
|
||||||
db.session.add(c)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func_name(cls):
|
|
||||||
"""Get configured bonus function name, or None if None."""
|
|
||||||
f = cls.get_bonus_sport_func_from_name()
|
|
||||||
if f is None:
|
|
||||||
return ""
|
|
||||||
else:
|
|
||||||
return f.__name__
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func(cls):
|
|
||||||
"""Get configured bonus function, or None if None."""
|
|
||||||
return cls.get_bonus_sport_func_from_name()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func_from_name(cls, func_name=None):
|
|
||||||
"""returns bonus func with specified name.
|
|
||||||
If name not specified, return the configured function.
|
|
||||||
None if no bonus function configured.
|
|
||||||
Raises ScoValueError if func_name not found in module bonus_sport.
|
|
||||||
"""
|
|
||||||
if func_name is None:
|
|
||||||
c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first()
|
|
||||||
if c is None:
|
|
||||||
return None
|
|
||||||
func_name = c.value
|
|
||||||
if func_name == "": # pas de bonus défini
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return getattr(bonus_sport, func_name)
|
|
||||||
except AttributeError:
|
|
||||||
raise ScoValueError(
|
|
||||||
f"""Fonction de calcul maison inexistante: {func_name}.
|
|
||||||
(contacter votre administrateur local)."""
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_bonus_sport_func_names(cls):
|
|
||||||
"""List available functions names
|
|
||||||
(starting with empty string to represent "no bonus function").
|
|
||||||
"""
|
|
||||||
return [""] + sorted(
|
|
||||||
[
|
|
||||||
getattr(bonus_sport, name).__name__
|
|
||||||
for name in dir(bonus_sport)
|
|
||||||
if name.startswith("bonus_")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
@ -41,6 +41,8 @@ class UniteEns(db.Model):
|
|||||||
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
# coef UE, utilise seulement si l'option use_ue_coefs est activée:
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float)
|
||||||
|
|
||||||
|
color = db.Column(db.Text())
|
||||||
|
|
||||||
# relations
|
# relations
|
||||||
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
||||||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||||
|
@ -35,13 +35,15 @@ Created on Thu Sep 8 09:36:33 2016
|
|||||||
|
|
||||||
@author: barasc
|
@author: barasc
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
@ -54,7 +56,6 @@ if not PE_DEBUG:
|
|||||||
# kw is ignored. log always add a newline
|
# kw is ignored. log always add a newline
|
||||||
log(" ".join(a))
|
log(" ".join(a))
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pe_print = print # print function
|
pe_print = print # print function
|
||||||
|
|
||||||
@ -206,7 +207,9 @@ def add_pe_stuff_to_zip(zipfile, ziproot):
|
|||||||
for name in logos_names:
|
for name in logos_names:
|
||||||
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||||
if logo is not None:
|
if logo is not None:
|
||||||
add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + logo.filename)
|
add_local_file_to_zip(
|
||||||
|
zipfile, ziproot, logo.filepath, "avis/logos/" + logo.filename
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------
|
||||||
|
@ -97,7 +97,7 @@ def pe_view_sem_recap(
|
|||||||
template_latex = ""
|
template_latex = ""
|
||||||
# template fourni via le formulaire Web
|
# template fourni via le formulaire Web
|
||||||
if avis_tmpl_file:
|
if avis_tmpl_file:
|
||||||
template_latex = avis_tmpl_file.read()
|
template_latex = avis_tmpl_file.read().decode('utf-8')
|
||||||
template_latex = template_latex
|
template_latex = template_latex
|
||||||
else:
|
else:
|
||||||
# template indiqué dans préférences ScoDoc ?
|
# template indiqué dans préférences ScoDoc ?
|
||||||
@ -114,7 +114,7 @@ def pe_view_sem_recap(
|
|||||||
footer_latex = ""
|
footer_latex = ""
|
||||||
# template fourni via le formulaire Web
|
# template fourni via le formulaire Web
|
||||||
if footer_tmpl_file:
|
if footer_tmpl_file:
|
||||||
footer_latex = footer_tmpl_file.read()
|
footer_latex = footer_tmpl_file.read().decode('utf-8')
|
||||||
footer_latex = footer_latex
|
footer_latex = footer_latex
|
||||||
else:
|
else:
|
||||||
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
footer_latex = pe_avislatex.get_code_latex_from_scodoc_preference(
|
||||||
|
@ -73,7 +73,8 @@ def TrivialFormulator(
|
|||||||
input_type : 'text', 'textarea', 'password',
|
input_type : 'text', 'textarea', 'password',
|
||||||
'radio', 'menu', 'checkbox',
|
'radio', 'menu', 'checkbox',
|
||||||
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
|
||||||
'boolcheckbox', 'text_suggest'
|
'boolcheckbox', 'text_suggest',
|
||||||
|
'color'
|
||||||
(default text)
|
(default text)
|
||||||
size : text field width
|
size : text field width
|
||||||
rows, cols: textarea geometry
|
rows, cols: textarea geometry
|
||||||
@ -594,6 +595,11 @@ class TF(object):
|
|||||||
var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
elif input_type == "color":
|
||||||
|
lem.append(
|
||||||
|
'<input type="color" name="%s" id="%s" %s' % (field, field, attribs)
|
||||||
|
)
|
||||||
|
lem.append(('value="%(' + field + ')s" >') % values)
|
||||||
else:
|
else:
|
||||||
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
raise ValueError("unkown input_type for form (%s)!" % input_type)
|
||||||
explanation = descr.get("explanation", "")
|
explanation = descr.get("explanation", "")
|
||||||
@ -712,7 +718,11 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||||||
R.append("%s</td>" % title)
|
R.append("%s</td>" % title)
|
||||||
R.append('<td class="tf-ro-field%s">' % klass)
|
R.append('<td class="tf-ro-field%s">' % klass)
|
||||||
|
|
||||||
if input_type == "text" or input_type == "text_suggest":
|
if (
|
||||||
|
input_type == "text"
|
||||||
|
or input_type == "text_suggest"
|
||||||
|
or input_type == "color"
|
||||||
|
):
|
||||||
R.append(("%(" + field + ")s") % self.values)
|
R.append(("%(" + field + ")s") % self.values)
|
||||||
elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"):
|
elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"):
|
||||||
if input_type == "boolcheckbox":
|
if input_type == "boolcheckbox":
|
||||||
|
@ -28,10 +28,12 @@
|
|||||||
from operator import mul
|
from operator import mul
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
"""
|
""" ANCIENS BONUS SPORT pour ScoDoc < 9.2 NON UTILISES A PARTIR DE 9.2 (voir comp/bonus_spo.py)
|
||||||
|
|
||||||
La fonction bonus_sport reçoit:
|
La fonction bonus_sport reçoit:
|
||||||
|
|
||||||
- notes_sport: la liste des notes des modules de sport et culture (une note par module de l'UE de type sport/culture);
|
- notes_sport: la liste des notes des modules de sport et culture (une note par module
|
||||||
|
de l'UE de type sport/culture, toujours dans remise sur 20);
|
||||||
- coefs: un coef (float) pondérant chaque note (la plupart des bonus les ignorent);
|
- coefs: un coef (float) pondérant chaque note (la plupart des bonus les ignorent);
|
||||||
- infos: dictionnaire avec des données pouvant être utilisées pour les calculs.
|
- infos: dictionnaire avec des données pouvant être utilisées pour les calculs.
|
||||||
Ces données dépendent du type de formation.
|
Ces données dépendent du type de formation.
|
||||||
@ -77,7 +79,6 @@ def bonus_iutv(notes_sport, coefs, infos=None):
|
|||||||
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à
|
||||||
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
la moyenne générale du semestre déjà obtenue par l'étudiant.
|
||||||
"""
|
"""
|
||||||
# breakpoint()
|
|
||||||
bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10])
|
bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10])
|
||||||
return bonus
|
return bonus
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ def bonus_direct(notes_sport, coefs, infos=None):
|
|||||||
|
|
||||||
|
|
||||||
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
|
def bonus_iut_stdenis(notes_sport, coefs, infos=None):
|
||||||
"""Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points."""
|
"""Semblable à bonus_iutv mais total limité à 0.5 points."""
|
||||||
points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10
|
points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10
|
||||||
bonus = points * 0.05 # ou / 20
|
bonus = points * 0.05 # ou / 20
|
||||||
return min(bonus, 0.5) # bonus limité à 1/2 point
|
return min(bonus, 0.5) # bonus limité à 1/2 point
|
||||||
@ -374,7 +375,7 @@ def bonus_iutBordeaux1(notes_sport, coefs, infos=None):
|
|||||||
return bonus
|
return bonus
|
||||||
|
|
||||||
|
|
||||||
def bonus_iuto(notes_sport, coefs, infos=None):
|
def bonus_iuto(notes_sport, coefs, infos=None): # OBSOLETE => EN ATTENTE (27/01/2022)
|
||||||
"""Calcul bonus modules optionels (sport, culture), règle IUT Orleans
|
"""Calcul bonus modules optionels (sport, culture), règle IUT Orleans
|
||||||
* Avant aout 2013
|
* Avant aout 2013
|
||||||
Un bonus de 2,5% de la note de sport est accordé à chaque UE sauf
|
Un bonus de 2,5% de la note de sport est accordé à chaque UE sauf
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"""
|
"""
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from html.entities import name2codepoint
|
from html.entities import name2codepoint
|
||||||
|
from multiprocessing.sharedctypes import Value
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
@ -36,17 +37,23 @@ from flask import g, url_for
|
|||||||
from . import listhistogram
|
from . import listhistogram
|
||||||
|
|
||||||
|
|
||||||
def horizontal_bargraph(value, mark):
|
def horizontal_bargraph(value, mark) -> str:
|
||||||
"""html drawing an horizontal bar and a mark
|
"""html drawing an horizontal bar and a mark
|
||||||
used to vizualize the relative level of a student
|
used to vizualize the relative level of a student
|
||||||
"""
|
"""
|
||||||
tmpl = """
|
try:
|
||||||
|
vals = {"value": int(value), "mark": int(mark)}
|
||||||
|
except ValueError:
|
||||||
|
return ""
|
||||||
|
return (
|
||||||
|
"""
|
||||||
<span class="graph">
|
<span class="graph">
|
||||||
<span class="bar" style="width: %(value)d%%;"></span>
|
<span class="bar" style="width: %(value)d%%;"></span>
|
||||||
<span class="mark" style="left: %(mark)d%%; "></span>
|
<span class="mark" style="left: %(mark)d%%; "></span>
|
||||||
</span>
|
</span>
|
||||||
"""
|
"""
|
||||||
return tmpl % {"value": int(value), "mark": int(mark)}
|
% vals
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def histogram_notes(notes):
|
def histogram_notes(notes):
|
||||||
|
@ -170,7 +170,7 @@ class NotesTable:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, formsemestre_id):
|
def __init__(self, formsemestre_id):
|
||||||
log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
# log(f"NotesTable( formsemestre_id={formsemestre_id} )")
|
||||||
# raise NotImplementedError() # XXX
|
# raise NotImplementedError() # XXX
|
||||||
if not formsemestre_id:
|
if not formsemestre_id:
|
||||||
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
raise ValueError("invalid formsemestre_id (%s)" % formsemestre_id)
|
||||||
@ -788,7 +788,12 @@ class NotesTable:
|
|||||||
moy_ue_cap = ue_cap["moy"]
|
moy_ue_cap = ue_cap["moy"]
|
||||||
mu["was_capitalized"] = True
|
mu["was_capitalized"] = True
|
||||||
event_date = event_date or ue_cap["event_date"]
|
event_date = event_date or ue_cap["event_date"]
|
||||||
if (moy_ue_cap != "NA") and (moy_ue_cap > max_moy_ue):
|
if (
|
||||||
|
(moy_ue_cap != "NA")
|
||||||
|
and isinstance(moy_ue_cap, float)
|
||||||
|
and isinstance(max_moy_ue, float)
|
||||||
|
and (moy_ue_cap > max_moy_ue)
|
||||||
|
):
|
||||||
# meilleure UE capitalisée
|
# meilleure UE capitalisée
|
||||||
event_date = ue_cap["event_date"]
|
event_date = ue_cap["event_date"]
|
||||||
max_moy_ue = moy_ue_cap
|
max_moy_ue = moy_ue_cap
|
||||||
@ -909,6 +914,7 @@ class NotesTable:
|
|||||||
if len(coefs_bonus_gen) == 1:
|
if len(coefs_bonus_gen) == 1:
|
||||||
coefs_bonus_gen = [1.0] # irrelevant, may be zero
|
coefs_bonus_gen = [1.0] # irrelevant, may be zero
|
||||||
|
|
||||||
|
# XXX attention: utilise anciens bonus_sport, évidemment
|
||||||
bonus_func = ScoDocSiteConfig.get_bonus_sport_func()
|
bonus_func = ScoDocSiteConfig.get_bonus_sport_func()
|
||||||
if bonus_func:
|
if bonus_func:
|
||||||
bonus = bonus_func(
|
bonus = bonus_func(
|
||||||
@ -1328,7 +1334,11 @@ class NotesTable:
|
|||||||
t[0] = results.etud_moy_gen[etudid]
|
t[0] = results.etud_moy_gen[etudid]
|
||||||
for i, ue in enumerate(ues, start=1):
|
for i, ue in enumerate(ues, start=1):
|
||||||
if ue["type"] != UE_SPORT:
|
if ue["type"] != UE_SPORT:
|
||||||
|
# temporaire pour 9.1.29 !
|
||||||
|
if ue["id"] in results.etud_moy_ue:
|
||||||
t[i] = results.etud_moy_ue[ue["id"]][etudid]
|
t[i] = results.etud_moy_ue[ue["id"]][etudid]
|
||||||
|
else:
|
||||||
|
t[i] = ""
|
||||||
# re-trie selon la nouvelle moyenne générale:
|
# re-trie selon la nouvelle moyenne générale:
|
||||||
self.T.sort(key=self._row_key)
|
self.T.sort(key=self._row_key)
|
||||||
# Remplace aussi le rang:
|
# Remplace aussi le rang:
|
||||||
|
@ -95,30 +95,21 @@ from flask import send_file
|
|||||||
# Pour la détection auto de l'encodage des fichiers Apogée:
|
# Pour la détection auto de l'encodage des fichiers Apogée:
|
||||||
from chardet import detect as chardet_detect
|
from chardet import detect as chardet_detect
|
||||||
|
|
||||||
|
from app.models.config import ScoDocSiteConfig
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||||
from app.scodoc.sco_codes_parcours import code_semestre_validant
|
from app.scodoc.sco_codes_parcours import code_semestre_validant
|
||||||
from app.scodoc.sco_codes_parcours import (
|
from app.scodoc.sco_codes_parcours import (
|
||||||
ADC,
|
|
||||||
ADJ,
|
|
||||||
ADM,
|
|
||||||
AJ,
|
|
||||||
ATB,
|
|
||||||
ATJ,
|
|
||||||
ATT,
|
|
||||||
CMP,
|
|
||||||
DEF,
|
DEF,
|
||||||
|
DEM,
|
||||||
NAR,
|
NAR,
|
||||||
RAT,
|
RAT,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_codes_parcours
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_formsemestre_status
|
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
|
||||||
@ -132,24 +123,6 @@ APO_SEP = "\t"
|
|||||||
APO_NEWLINE = "\r\n"
|
APO_NEWLINE = "\r\n"
|
||||||
|
|
||||||
|
|
||||||
def code_scodoc_to_apo(code):
|
|
||||||
"""Conversion code jury ScoDoc en code Apogée"""
|
|
||||||
return {
|
|
||||||
ATT: "AJAC",
|
|
||||||
ATB: "AJAC",
|
|
||||||
ATJ: "AJAC",
|
|
||||||
ADM: "ADM",
|
|
||||||
ADJ: "ADM",
|
|
||||||
ADC: "ADMC",
|
|
||||||
AJ: "AJ",
|
|
||||||
CMP: "COMP",
|
|
||||||
"DEM": "NAR",
|
|
||||||
DEF: "NAR",
|
|
||||||
NAR: "NAR",
|
|
||||||
RAT: "ATT",
|
|
||||||
}.get(code, "DEF")
|
|
||||||
|
|
||||||
|
|
||||||
def _apo_fmt_note(note):
|
def _apo_fmt_note(note):
|
||||||
"Formatte une note pour Apogée (séparateur décimal: ',')"
|
"Formatte une note pour Apogée (séparateur décimal: ',')"
|
||||||
if not note and isinstance(note, float):
|
if not note and isinstance(note, float):
|
||||||
@ -449,7 +422,7 @@ class ApoEtud(dict):
|
|||||||
N=_apo_fmt_note(ue_status["moy"]),
|
N=_apo_fmt_note(ue_status["moy"]),
|
||||||
B=20,
|
B=20,
|
||||||
J="",
|
J="",
|
||||||
R=code_scodoc_to_apo(code_decision_ue),
|
R=ScoDocSiteConfig.get_code_apo(code_decision_ue),
|
||||||
M="",
|
M="",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -475,13 +448,9 @@ class ApoEtud(dict):
|
|||||||
def comp_elt_semestre(self, nt, decision, etudid):
|
def comp_elt_semestre(self, nt, decision, etudid):
|
||||||
"""Calcul résultat apo semestre"""
|
"""Calcul résultat apo semestre"""
|
||||||
# resultat du semestre
|
# resultat du semestre
|
||||||
decision_apo = code_scodoc_to_apo(decision["code"])
|
decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"])
|
||||||
note = nt.get_etud_moy_gen(etudid)
|
note = nt.get_etud_moy_gen(etudid)
|
||||||
if (
|
if decision_apo == "DEF" or decision["code"] == DEM or decision["code"] == DEF:
|
||||||
decision_apo == "DEF"
|
|
||||||
or decision["code"] == "DEM"
|
|
||||||
or decision["code"] == DEF
|
|
||||||
):
|
|
||||||
note_str = "0,01" # note non nulle pour les démissionnaires
|
note_str = "0,01" # note non nulle pour les démissionnaires
|
||||||
else:
|
else:
|
||||||
note_str = _apo_fmt_note(note)
|
note_str = _apo_fmt_note(note)
|
||||||
@ -520,21 +489,21 @@ class ApoEtud(dict):
|
|||||||
# ou jury intermediaire et etudiant non redoublant...
|
# ou jury intermediaire et etudiant non redoublant...
|
||||||
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
|
return self.comp_elt_semestre(cur_nt, cur_decision, etudid)
|
||||||
|
|
||||||
decision_apo = code_scodoc_to_apo(cur_decision["code"])
|
decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"])
|
||||||
|
|
||||||
autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"])
|
autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"])
|
||||||
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
autre_decision = autre_nt.get_etud_decision_sem(etudid)
|
||||||
if not autre_decision:
|
if not autre_decision:
|
||||||
# pas de decision dans l'autre => pas de résultat annuel
|
# pas de decision dans l'autre => pas de résultat annuel
|
||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
autre_decision_apo = code_scodoc_to_apo(autre_decision["code"])
|
autre_decision_apo = ScoDocSiteConfig.get_code_apo(autre_decision["code"])
|
||||||
if (
|
if (
|
||||||
autre_decision_apo == "DEF"
|
autre_decision_apo == "DEF"
|
||||||
or autre_decision["code"] == "DEM"
|
or autre_decision["code"] == DEM
|
||||||
or autre_decision["code"] == DEF
|
or autre_decision["code"] == DEF
|
||||||
) or (
|
) or (
|
||||||
decision_apo == "DEF"
|
decision_apo == "DEF"
|
||||||
or cur_decision["code"] == "DEM"
|
or cur_decision["code"] == DEM
|
||||||
or cur_decision["code"] == DEF
|
or cur_decision["code"] == DEF
|
||||||
):
|
):
|
||||||
note_str = "0,01" # note non nulle pour les démissionnaires
|
note_str = "0,01" # note non nulle pour les démissionnaires
|
||||||
|
@ -43,6 +43,7 @@ from flask import g, request
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
from app.models.moduleimpls import ModuleImplInscription
|
||||||
|
|
||||||
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
|
||||||
@ -285,21 +286,34 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
else:
|
else:
|
||||||
I["rang_nt"], I["rang_txt"] = "", ""
|
I["rang_nt"], I["rang_txt"] = "", ""
|
||||||
I["note_max"] = 20.0 # notes toujours sur 20
|
I["note_max"] = 20.0 # notes toujours sur 20
|
||||||
I["bonus_sport_culture"] = nt.bonus[etudid]
|
I["bonus_sport_culture"] = nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
I["ues"] = []
|
I["ues"] = []
|
||||||
I["matieres_modules"] = {}
|
I["matieres_modules"] = {}
|
||||||
I["matieres_modules_capitalized"] = {}
|
I["matieres_modules_capitalized"] = {}
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
|
if (
|
||||||
|
ModuleImplInscription.nb_inscriptions_dans_ue(
|
||||||
|
formsemestre_id, etudid, ue["ue_id"]
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
):
|
||||||
|
continue
|
||||||
u = ue.copy()
|
u = ue.copy()
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
u["ue_status"] = ue_status # { 'moy', 'coef_ue', ...}
|
||||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
u["cur_moy_ue_txt"] = scu.fmt_note(ue_status["cur_moy_ue"])
|
||||||
else:
|
else:
|
||||||
|
if nt.bonus is not None:
|
||||||
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
|
x = scu.fmt_note(nt.bonus[etudid], keep_numeric=True)
|
||||||
|
else:
|
||||||
|
x = ""
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
|
if nt.bonus_ues is None:
|
||||||
u["cur_moy_ue_txt"] = "pas de bonus"
|
u["cur_moy_ue_txt"] = "pas de bonus"
|
||||||
|
else:
|
||||||
|
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
|
||||||
else:
|
else:
|
||||||
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
||||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||||
@ -369,7 +383,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if prefs["bul_show_ue_rangs"] and ue["type"] != sco_codes_parcours.UE_SPORT:
|
if prefs["bul_show_ue_rangs"] and ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
if ue_attente: # nt.get_moduleimpls_attente():
|
if ue_attente or nt.ue_rangs[ue["ue_id"]][0] is None:
|
||||||
u["ue_descr_txt"] = "%s/%s" % (
|
u["ue_descr_txt"] = "%s/%s" % (
|
||||||
scu.RANG_ATTENTE_STR,
|
scu.RANG_ATTENTE_STR,
|
||||||
nt.ue_rangs[ue["ue_id"]][1],
|
nt.ue_rangs[ue["ue_id"]][1],
|
||||||
@ -387,8 +401,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["ues"].append(u) # ne montre pas les UE si non inscrit
|
I["ues"].append(u) # ne montre pas les UE si non inscrit
|
||||||
|
|
||||||
# Accès par matieres
|
# Accès par matieres
|
||||||
# voir si on supporte encore cela en #sco92 XXX
|
# En #sco92, pas d'information
|
||||||
# I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||||
|
|
||||||
#
|
#
|
||||||
C = make_context_dict(I["sem"], I["etud"])
|
C = make_context_dict(I["sem"], I["etud"])
|
||||||
@ -605,6 +619,9 @@ def _ue_mod_bulletin(etudid, formsemestre_id, ue_id, modimpls, nt, version):
|
|||||||
# Classement
|
# Classement
|
||||||
if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus:
|
if bul_show_mod_rangs and mod["mod_moy_txt"] != "-" and not is_malus:
|
||||||
rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
|
rg = nt.mod_rangs[modimpl["moduleimpl_id"]]
|
||||||
|
if rg[0] is None:
|
||||||
|
mod["mod_rang_txt"] = ""
|
||||||
|
else:
|
||||||
if mod_attente: # nt.get_moduleimpls_attente():
|
if mod_attente: # nt.get_moduleimpls_attente():
|
||||||
mod["mod_rang"] = scu.RANG_ATTENTE_STR
|
mod["mod_rang"] = scu.RANG_ATTENTE_STR
|
||||||
else:
|
else:
|
||||||
|
@ -98,9 +98,9 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
d = {}
|
d = {}
|
||||||
|
|
||||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||||
published = 1
|
published = True
|
||||||
else:
|
else:
|
||||||
published = 0
|
published = False
|
||||||
if xml_nodate:
|
if xml_nodate:
|
||||||
docdate = ""
|
docdate = ""
|
||||||
else:
|
else:
|
||||||
@ -192,7 +192,9 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
d["note_max"] = dict(value=20) # notes toujours sur 20
|
d["note_max"] = dict(value=20) # notes toujours sur 20
|
||||||
d["bonus_sport_culture"] = dict(value=nt.bonus[etudid])
|
d["bonus_sport_culture"] = dict(
|
||||||
|
value=nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
d["ue"] = []
|
d["ue"] = []
|
||||||
|
@ -195,7 +195,12 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
doc.append(Element("note_max", value="20")) # notes toujours sur 20
|
||||||
doc.append(Element("bonus_sport_culture", value=str(nt.bonus[etudid])))
|
doc.append(
|
||||||
|
Element(
|
||||||
|
"bonus_sport_culture",
|
||||||
|
value=str(nt.bonus[etudid] if nt.bonus is not None else 0.0),
|
||||||
|
)
|
||||||
|
)
|
||||||
# Liste les UE / modules /evals
|
# Liste les UE / modules /evals
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
@ -211,7 +216,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
if ue["type"] != sco_codes_parcours.UE_SPORT:
|
||||||
v = ue_status["cur_moy_ue"]
|
v = ue_status["cur_moy_ue"]
|
||||||
else:
|
else:
|
||||||
v = nt.bonus[etudid]
|
v = nt.bonus[etudid] if nt.bonus is not None else 0.0
|
||||||
x_ue.append(
|
x_ue.append(
|
||||||
Element(
|
Element(
|
||||||
"note",
|
"note",
|
||||||
|
@ -98,8 +98,9 @@ class ScoDocCache:
|
|||||||
status = CACHE.set(key, value, timeout=cls.timeout)
|
status = CACHE.set(key, value, timeout=cls.timeout)
|
||||||
if not status:
|
if not status:
|
||||||
log("Error: cache set failed !")
|
log("Error: cache set failed !")
|
||||||
except:
|
except Exception as exc:
|
||||||
log("XXX CACHE Warning: error in set !!!")
|
log("XXX CACHE Warning: error in set !!!")
|
||||||
|
log(exc)
|
||||||
status = None
|
status = None
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@ CMP = "CMP" # utile pour UE seulement (indique UE acquise car semestre acquis)
|
|||||||
NAR = "NAR"
|
NAR = "NAR"
|
||||||
RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée
|
RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée
|
||||||
DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission)
|
DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission)
|
||||||
|
DEM = "DEM"
|
||||||
|
|
||||||
# codes actions
|
# codes actions
|
||||||
REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1)
|
REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1)
|
||||||
@ -140,22 +141,26 @@ BUG = "BUG"
|
|||||||
|
|
||||||
ALL = "ALL"
|
ALL = "ALL"
|
||||||
|
|
||||||
|
# Explication des codes (de demestre ou d'UE)
|
||||||
CODES_EXPL = {
|
CODES_EXPL = {
|
||||||
ADM: "Validé",
|
|
||||||
ADC: "Validé par compensation",
|
ADC: "Validé par compensation",
|
||||||
ADJ: "Validé par le Jury",
|
ADJ: "Validé par le Jury",
|
||||||
ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)",
|
ADM: "Validé",
|
||||||
|
AJ: "Ajourné",
|
||||||
ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)",
|
ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)",
|
||||||
ATJ: "Décision en attente d'un autre semestre (assiduité insuffisante)",
|
ATJ: "Décision en attente d'un autre semestre (assiduité insuffisante)",
|
||||||
AJ: "Ajourné",
|
ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)",
|
||||||
NAR: "Echec, non autorisé à redoubler",
|
CMP: "Code UE acquise car semestre acquis",
|
||||||
RAT: "En attente d'un rattrapage",
|
|
||||||
DEF: "Défaillant",
|
DEF: "Défaillant",
|
||||||
|
NAR: "Échec, non autorisé à redoubler",
|
||||||
|
RAT: "En attente d'un rattrapage",
|
||||||
}
|
}
|
||||||
# Nota: ces explications sont personnalisables via le fichier
|
# Nota: ces explications sont personnalisables via le fichier
|
||||||
# de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py
|
# de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py
|
||||||
# variable: CONFIG.CODES_EXP
|
# variable: CONFIG.CODES_EXP
|
||||||
|
|
||||||
|
# Les codes de semestres:
|
||||||
|
CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT}
|
||||||
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé
|
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé
|
||||||
CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
|
||||||
|
|
||||||
|
@ -152,28 +152,3 @@ class LogoInsert(Action):
|
|||||||
name=self.parameters["name"],
|
name=self.parameters["name"],
|
||||||
dept_id=dept_id,
|
dept_id=dept_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BonusSportUpdate(Action):
|
|
||||||
"""Action: Change bonus_sport_function_name.
|
|
||||||
bonus_sport_function_name: the new value"""
|
|
||||||
|
|
||||||
def __init__(self, parameters):
|
|
||||||
super().__init__(
|
|
||||||
f"Changement du calcul de bonus sport pour ({parameters['bonus_sport_func_name']}).",
|
|
||||||
parameters,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def build_action(parameters):
|
|
||||||
if (
|
|
||||||
parameters["bonus_sport_func_name"]
|
|
||||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
|
||||||
):
|
|
||||||
return [BonusSportUpdate(parameters)]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
current_app.logger.info(self.message)
|
|
||||||
ScoDocSiteConfig.set_bonus_sport_func(self.parameters["bonus_sport_func_name"])
|
|
||||||
app.clear_scodoc_cache()
|
|
||||||
|
@ -33,6 +33,7 @@ from flask_login import current_user
|
|||||||
from app import db
|
from app import db
|
||||||
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
from app.models import Formation, UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||||
from app.models.notes import ScolarFormSemestreValidation
|
from app.models.notes import ScolarFormSemestreValidation
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -99,12 +100,19 @@ def html_edit_formation_apc(
|
|||||||
ressources_in_sem = ressources.filter_by(semestre_id=semestre_idx)
|
ressources_in_sem = ressources.filter_by(semestre_id=semestre_idx)
|
||||||
saes_in_sem = saes.filter_by(semestre_id=semestre_idx)
|
saes_in_sem = saes.filter_by(semestre_id=semestre_idx)
|
||||||
other_modules_in_sem = other_modules.filter_by(semestre_id=semestre_idx)
|
other_modules_in_sem = other_modules.filter_by(semestre_id=semestre_idx)
|
||||||
|
matiere_parent = Matiere.query.filter(
|
||||||
|
Matiere.ue_id == UniteEns.id,
|
||||||
|
UniteEns.formation_id == formation.id,
|
||||||
|
UniteEns.semestre_idx == semestre_idx,
|
||||||
|
UniteEns.type != UE_SPORT,
|
||||||
|
).first()
|
||||||
H += [
|
H += [
|
||||||
render_template(
|
render_template(
|
||||||
"pn/form_mods.html",
|
"pn/form_mods.html",
|
||||||
formation=formation,
|
formation=formation,
|
||||||
titre=f"Ressources du S{semestre_idx}",
|
titre=f"Ressources du S{semestre_idx}",
|
||||||
create_element_msg="créer une nouvelle ressource",
|
create_element_msg="créer une nouvelle ressource",
|
||||||
|
matiere_parent=matiere_parent,
|
||||||
modules=ressources_in_sem,
|
modules=ressources_in_sem,
|
||||||
module_type=ModuleType.RESSOURCE,
|
module_type=ModuleType.RESSOURCE,
|
||||||
editable=editable,
|
editable=editable,
|
||||||
@ -117,6 +125,7 @@ def html_edit_formation_apc(
|
|||||||
formation=formation,
|
formation=formation,
|
||||||
titre=f"Situations d'Apprentissage et d'Évaluation (SAÉs) S{semestre_idx}",
|
titre=f"Situations d'Apprentissage et d'Évaluation (SAÉs) S{semestre_idx}",
|
||||||
create_element_msg="créer une nouvelle SAÉ",
|
create_element_msg="créer une nouvelle SAÉ",
|
||||||
|
matiere_parent=matiere_parent,
|
||||||
modules=saes_in_sem,
|
modules=saes_in_sem,
|
||||||
module_type=ModuleType.SAE,
|
module_type=ModuleType.SAE,
|
||||||
editable=editable,
|
editable=editable,
|
||||||
|
@ -32,15 +32,16 @@ import flask
|
|||||||
from flask import url_for, render_template
|
from flask import url_for, render_template
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
from app import models
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import Matiere, Module, UniteEns
|
from app.models import Formation, Matiere, Module, UniteEns
|
||||||
|
from app.models import FormSemestre, ModuleImpl
|
||||||
|
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
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
|
||||||
from app import log
|
|
||||||
from app import models
|
|
||||||
from app.models import Formation
|
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
@ -144,7 +145,7 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
|||||||
]
|
]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
H += [
|
H += [
|
||||||
f"""<h2>Création {object_name} dans la formation {ue.formation.acronyme}</h2>"""
|
f"""<h2>Création {object_name} dans la formation {ue.formation.acronyme}, Semestre {ue.semestre_idx}, {ue.acronyme}</h2>"""
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
H += [
|
H += [
|
||||||
@ -190,35 +191,7 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||||
if is_apc: # BUT: choix de l'UE de rattachement (qui donnera le semestre)
|
|
||||||
descr += [
|
|
||||||
(
|
|
||||||
"ue_id",
|
|
||||||
{
|
|
||||||
"input_type": "menu",
|
|
||||||
"type": "int",
|
|
||||||
"title": "UE de rattachement",
|
|
||||||
"explanation": "utilisée pour la présentation dans certains documents",
|
|
||||||
"labels": [f"{u.acronyme} {u.titre}" for u in ues],
|
|
||||||
"allowed_values": [u.id for u in ues],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Formations classiques: choix du semestre
|
|
||||||
descr += [
|
|
||||||
(
|
|
||||||
"semestre_id",
|
|
||||||
{
|
|
||||||
"input_type": "menu",
|
|
||||||
"type": "int",
|
|
||||||
"title": parcours.SESSION_NAME.capitalize(),
|
|
||||||
"explanation": "%s du module" % parcours.SESSION_NAME,
|
|
||||||
"labels": [str(x) for x in semestres_indices],
|
|
||||||
"allowed_values": semestres_indices,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
descr += [
|
descr += [
|
||||||
(
|
(
|
||||||
"module_type",
|
"module_type",
|
||||||
@ -294,6 +267,7 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
|||||||
"title": "Code Apogée",
|
"title": "Code Apogée",
|
||||||
"size": 25,
|
"size": 25,
|
||||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||||
|
"validator": lambda val, _: len(val) < APO_CODE_STR_LEN,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -316,12 +290,7 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
|||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
||||||
else:
|
else:
|
||||||
if is_apc:
|
tf[2]["semestre_id"] = ue.semestre_idx
|
||||||
# BUT: l'UE indique le semestre
|
|
||||||
selected_ue = UniteEns.query.get(tf[2]["ue_id"])
|
|
||||||
if selected_ue is None:
|
|
||||||
raise ValueError("UE invalide")
|
|
||||||
tf[2]["semestre_id"] = selected_ue.semestre_idx
|
|
||||||
|
|
||||||
_ = do_module_create(tf[2])
|
_ = do_module_create(tf[2])
|
||||||
|
|
||||||
@ -472,18 +441,23 @@ def module_edit(module_id=None):
|
|||||||
formation_id = module["formation_id"]
|
formation_id = module["formation_id"]
|
||||||
formation = sco_formations.formation_list(args={"formation_id": formation_id})[0]
|
formation = sco_formations.formation_list(args={"formation_id": formation_id})[0]
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
|
||||||
is_apc = parcours.APC_SAE
|
is_apc = parcours.APC_SAE # BUT
|
||||||
ues_matieres = ndb.SimpleDictFetch(
|
in_use = len(a_module.modimpls.all()) > 0 # il y a des modimpls
|
||||||
"""SELECT ue.acronyme, mat.*, mat.id AS matiere_id
|
matieres = Matiere.query.filter(
|
||||||
FROM notes_matieres mat, notes_ue ue
|
Matiere.ue_id == UniteEns.id, UniteEns.formation_id == formation_id
|
||||||
WHERE mat.ue_id = ue.id
|
).order_by(UniteEns.semestre_idx, UniteEns.numero, Matiere.numero)
|
||||||
AND ue.formation_id = %(formation_id)s
|
if in_use:
|
||||||
ORDER BY ue.numero, mat.numero
|
# restreint aux matières du même semestre
|
||||||
""",
|
matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
|
||||||
{"formation_id": formation_id},
|
|
||||||
)
|
if is_apc:
|
||||||
mat_names = ["%s / %s" % (x["acronyme"], x["titre"]) for x in ues_matieres]
|
mat_names = [
|
||||||
ue_mat_ids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in ues_matieres]
|
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
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]
|
||||||
|
|
||||||
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))
|
||||||
@ -500,12 +474,25 @@ def module_edit(module_id=None):
|
|||||||
),
|
),
|
||||||
"""<h2>Modification du module %(titre)s""" % module,
|
"""<h2>Modification du module %(titre)s""" % module,
|
||||||
""" (formation %(acronyme)s, version %(version)s)</h2>""" % formation,
|
""" (formation %(acronyme)s, version %(version)s)</h2>""" % formation,
|
||||||
render_template("scodoc/help/modules.html", is_apc=is_apc),
|
render_template(
|
||||||
|
"scodoc/help/modules.html",
|
||||||
|
is_apc=is_apc,
|
||||||
|
formsemestres=FormSemestre.query.filter(
|
||||||
|
ModuleImpl.formsemestre_id == FormSemestre.id,
|
||||||
|
ModuleImpl.module_id == module_id,
|
||||||
|
).all(),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if not unlocked:
|
if not unlocked:
|
||||||
H.append(
|
H.append(
|
||||||
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
|
"""<div class="ue_warning"><span>Formation verrouillée, seuls certains éléments peuvent être modifiés</span></div>"""
|
||||||
)
|
)
|
||||||
|
if in_use:
|
||||||
|
H.append(
|
||||||
|
"""<div class="ue_warning"><span>Module déjà utilisé dans des semestres,
|
||||||
|
soyez prudents !
|
||||||
|
</span></div>"""
|
||||||
|
)
|
||||||
|
|
||||||
descr = [
|
descr = [
|
||||||
(
|
(
|
||||||
@ -534,11 +521,17 @@ def module_edit(module_id=None):
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"heures_cours",
|
"heures_cours",
|
||||||
{"size": 4, "type": "float", "explanation": "nombre d'heures de cours"},
|
{
|
||||||
|
"title": "Heures CM :",
|
||||||
|
"size": 4,
|
||||||
|
"type": "float",
|
||||||
|
"explanation": "nombre d'heures de cours",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"heures_td",
|
"heures_td",
|
||||||
{
|
{
|
||||||
|
"title": "Heures TD :",
|
||||||
"size": 4,
|
"size": 4,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"explanation": "nombre d'heures de Travaux Dirigés",
|
"explanation": "nombre d'heures de Travaux Dirigés",
|
||||||
@ -547,6 +540,7 @@ def module_edit(module_id=None):
|
|||||||
(
|
(
|
||||||
"heures_tp",
|
"heures_tp",
|
||||||
{
|
{
|
||||||
|
"title": "Heures TP :",
|
||||||
"size": 4,
|
"size": 4,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"explanation": "nombre d'heures de Travaux Pratiques",
|
"explanation": "nombre d'heures de Travaux Pratiques",
|
||||||
@ -566,9 +560,9 @@ def module_edit(module_id=None):
|
|||||||
"ue_coefs",
|
"ue_coefs",
|
||||||
{
|
{
|
||||||
"readonly": True,
|
"readonly": True,
|
||||||
"title": "Coefficients vers les UE",
|
"title": "Coefficients vers les UE ",
|
||||||
"default": coefs_descr_txt,
|
"default": coefs_descr_txt,
|
||||||
"explanation": "passer par la page d'édition de la formation pour modifier les coefficients",
|
"explanation": " <br>(passer par la page d'édition de la formation pour modifier les coefficients)",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -594,7 +588,14 @@ def module_edit(module_id=None):
|
|||||||
{
|
{
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
"title": "Rattachement :" if is_apc else "Matière :",
|
"title": "Rattachement :" if is_apc else "Matière :",
|
||||||
"explanation": "UE de rattachement, utilisée pour la présentation"
|
"explanation": (
|
||||||
|
"UE de rattachement"
|
||||||
|
+ (
|
||||||
|
" module utilisé, ne peut pas être changé de semestre"
|
||||||
|
if in_use
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
)
|
||||||
if is_apc
|
if is_apc
|
||||||
else "un module appartient à une seule matière.",
|
else "un module appartient à une seule matière.",
|
||||||
"labels": mat_names,
|
"labels": mat_names,
|
||||||
@ -666,8 +667,30 @@ def module_edit(module_id=None):
|
|||||||
initvalues=module,
|
initvalues=module,
|
||||||
submitlabel="Modifier ce module",
|
submitlabel="Modifier ce module",
|
||||||
)
|
)
|
||||||
|
# Affiche liste des formseemstre utilisant ce module
|
||||||
|
if in_use:
|
||||||
|
formsemestre_ids = {modimpl.formsemestre_id for modimpl in a_module.modimpls}
|
||||||
|
formsemestres = [FormSemestre.query.get(fid) for fid in formsemestre_ids]
|
||||||
|
formsemestres.sort(key=lambda f: f.date_debut)
|
||||||
|
items = [
|
||||||
|
f"""<a class="stdlink" href="{
|
||||||
|
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=f.id )
|
||||||
|
}">{f.titre}</a>"""
|
||||||
|
for f in formsemestres
|
||||||
|
]
|
||||||
|
sem_descr = f"""
|
||||||
|
<div class="ue_warning">
|
||||||
|
<div>Ce module est utilisé dans les formsemestres suivants:</div>
|
||||||
|
<ul><li>
|
||||||
|
{"</li><li>".join( items )}
|
||||||
|
</li></ul>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
sem_descr = ""
|
||||||
|
#
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
return "\n".join(H) + tf[1] + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + sem_descr + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -678,8 +701,17 @@ def module_edit(module_id=None):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# l'UE 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("!")
|
||||||
|
old_ue_id = a_module.ue.id
|
||||||
|
new_ue_id = int(tf[2]["ue_id"])
|
||||||
|
if (old_ue_id != new_ue_id) and in_use:
|
||||||
|
new_ue = UniteEns.query.get_or_404(new_ue_id)
|
||||||
|
if new_ue.semestre_idx != a_module.ue.semestre_idx:
|
||||||
|
# pas changer de semestre un module utilisé !
|
||||||
|
raise ScoValueError(
|
||||||
|
"Module utilisé: il ne peut pas être changé de semestre !"
|
||||||
|
)
|
||||||
# En APC, force le semestre égal à celui de l'UE
|
# En APC, force le semestre égal à celui de l'UE
|
||||||
if is_apc:
|
if is_apc:
|
||||||
selected_ue = UniteEns.query.get(tf[2]["ue_id"])
|
selected_ue = UniteEns.query.get(tf[2]["ue_id"])
|
||||||
|
@ -33,13 +33,15 @@ from flask import url_for, render_template
|
|||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from app import log
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import Formation, UniteEns, ModuleImpl, Module
|
from app.models import Formation, UniteEns, ModuleImpl, Module
|
||||||
|
from app.models.formations import Matiere
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
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
|
||||||
from app import log
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
@ -81,6 +83,7 @@ _ueEditor = ndb.EditableTable(
|
|||||||
"is_external",
|
"is_external",
|
||||||
"code_apogee",
|
"code_apogee",
|
||||||
"coefficient",
|
"coefficient",
|
||||||
|
"color",
|
||||||
),
|
),
|
||||||
sortkey="numero",
|
sortkey="numero",
|
||||||
input_formators={
|
input_formators={
|
||||||
@ -330,7 +333,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
|||||||
la moyenne générale</em> est activée. Par défaut, le coefficient
|
la moyenne générale</em> est activée. Par défaut, le coefficient
|
||||||
d'une UE est simplement la somme des coefficients des modules dans
|
d'une UE est simplement la somme des coefficients des modules dans
|
||||||
lesquels l'étudiant a des notes.
|
lesquels l'étudiant a des notes.
|
||||||
|
Jamais utilisé en BUT.
|
||||||
""",
|
""",
|
||||||
|
"enabled": not is_apc,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -358,6 +363,14 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
|||||||
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
|
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"color",
|
||||||
|
{
|
||||||
|
"input_type": "color",
|
||||||
|
"title": "Couleur",
|
||||||
|
"explanation": "pour affichages",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if create and not parcours.UE_IS_MODULE and not is_apc:
|
if create and not parcours.UE_IS_MODULE and not is_apc:
|
||||||
fw.append(
|
fw.append(
|
||||||
@ -379,9 +392,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
|||||||
submitlabel=submitlabel,
|
submitlabel=submitlabel,
|
||||||
)
|
)
|
||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
X = """<div id="ue_list_code"></div>
|
ue_div = """<div id="ue_list_code"></div>"""
|
||||||
"""
|
bonus_div = """<div id="bonus_description"></div>"""
|
||||||
return "\n".join(H) + tf[1] + X + html_sco_header.sco_footer()
|
return "\n".join(H) + tf[1] + bonus_div + ue_div + html_sco_header.sco_footer()
|
||||||
else:
|
else:
|
||||||
if create:
|
if create:
|
||||||
if not tf[2]["ue_code"]:
|
if not tf[2]["ue_code"]:
|
||||||
@ -533,6 +546,12 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||||||
# pour faciliter la transition des anciens programmes non APC
|
# pour faciliter la transition des anciens programmes non APC
|
||||||
for ue in ues_obj:
|
for ue in ues_obj:
|
||||||
ue.guess_semestre_idx()
|
ue.guess_semestre_idx()
|
||||||
|
# vérifie qu'on a bien au moins une matière dans chaque UE
|
||||||
|
for ue in ues_obj:
|
||||||
|
if ue.matieres.count() < 1:
|
||||||
|
mat = Matiere(ue_id=ue.id)
|
||||||
|
db.session.add(mat)
|
||||||
|
db.session.commit()
|
||||||
ues = [ue.to_dict() for ue in ues_obj]
|
ues = [ue.to_dict() for ue in ues_obj]
|
||||||
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
|
ues_externes = [ue.to_dict() for ue in ues_externes_obj]
|
||||||
|
|
||||||
@ -1220,7 +1239,8 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
|
|||||||
def edit_ue_set_code_apogee(id=None, value=None):
|
def edit_ue_set_code_apogee(id=None, value=None):
|
||||||
"set UE code apogee"
|
"set UE code apogee"
|
||||||
ue_id = id
|
ue_id = id
|
||||||
value = value.strip("-_ \t")
|
value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque
|
||||||
|
|
||||||
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value))
|
||||||
|
|
||||||
ues = ue_list(args={"ue_id": ue_id})
|
ues = ue_list(args={"ue_id": ue_id})
|
||||||
|
@ -185,7 +185,8 @@ def _check_evaluation_args(args):
|
|||||||
if (jour > date_fin) or (jour < date_debut):
|
if (jour > date_fin) or (jour < date_debut):
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
"La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !"
|
||||||
% (d, m, y)
|
% (d, m, y),
|
||||||
|
dest_url="javascript:history.back();",
|
||||||
)
|
)
|
||||||
heure_debut = args.get("heure_debut", None)
|
heure_debut = args.get("heure_debut", None)
|
||||||
args["heure_debut"] = heure_debut
|
args["heure_debut"] = heure_debut
|
||||||
|
@ -143,6 +143,7 @@ def evaluation_create_form(
|
|||||||
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
|
if vals.get("tf_submitted", False) and "visibulletinlist" not in vals:
|
||||||
vals["visibulletinlist"] = []
|
vals["visibulletinlist"] = []
|
||||||
#
|
#
|
||||||
|
ue_coef_dict = {}
|
||||||
if is_apc: # BUT: poids vers les UE
|
if is_apc: # BUT: poids vers les UE
|
||||||
ue_coef_dict = ModuleImpl.query.get(moduleimpl_id).module.get_ue_coef_dict()
|
ue_coef_dict = ModuleImpl.query.get(moduleimpl_id).module.get_ue_coef_dict()
|
||||||
for ue in sem_ues:
|
for ue in sem_ues:
|
||||||
@ -290,7 +291,10 @@ def evaluation_create_form(
|
|||||||
"title": f"Poids {ue.acronyme}",
|
"title": f"Poids {ue.acronyme}",
|
||||||
"size": 2,
|
"size": 2,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"explanation": f"{ue.titre}",
|
"explanation": f"""
|
||||||
|
<span class="eval_coef_ue" title="coef. du module dans cette UE">{ue_coef_dict.get(ue.id, 0.)}</span>
|
||||||
|
<span class="eval_coef_ue_titre">{ue.titre}</span>
|
||||||
|
""",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -36,8 +36,19 @@ class ScoException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NoteProcessError(ScoException):
|
class InvalidNoteValue(ScoException):
|
||||||
"misc errors in process"
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Exception qui stoque dest_url
|
||||||
|
class ScoValueError(ScoException):
|
||||||
|
def __init__(self, msg, dest_url=None):
|
||||||
|
super().__init__(msg)
|
||||||
|
self.dest_url = dest_url
|
||||||
|
|
||||||
|
|
||||||
|
class NoteProcessError(ScoValueError):
|
||||||
|
"Valeurs notes invalides"
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -45,17 +56,6 @@ class InvalidEtudId(NoteProcessError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidNoteValue(ScoException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Exception qui stoque dest_url, utilisee dans Zope standard_error_message
|
|
||||||
class ScoValueError(ScoException):
|
|
||||||
def __init__(self, msg, dest_url=None):
|
|
||||||
super().__init__(msg)
|
|
||||||
self.dest_url = dest_url
|
|
||||||
|
|
||||||
|
|
||||||
class ScoFormatError(ScoValueError):
|
class ScoFormatError(ScoValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -28,13 +28,16 @@
|
|||||||
"""Form choix modules / responsables et creation formsemestre
|
"""Form choix modules / responsables et creation formsemestre
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, g, request
|
from flask import url_for, flash
|
||||||
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models import ModuleImpl, Evaluation, EvaluationUEPoids
|
from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteEns
|
||||||
|
from app.models.formations import Formation
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -65,9 +68,9 @@ from app.scodoc import sco_preferences
|
|||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
|
|
||||||
|
|
||||||
def _default_sem_title(F):
|
def _default_sem_title(formation):
|
||||||
"""Default title for a semestre in formation F"""
|
"""Default title for a semestre in formation"""
|
||||||
return F["titre"]
|
return formation.titre
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_createwithmodules():
|
def formsemestre_createwithmodules():
|
||||||
@ -140,6 +143,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
if edit:
|
if edit:
|
||||||
formsemestre_id = int(vals["formsemestre_id"])
|
formsemestre_id = int(vals["formsemestre_id"])
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
if not current_user.has_permission(Permission.ScoImplement):
|
if not current_user.has_permission(Permission.ScoImplement):
|
||||||
if not edit:
|
if not edit:
|
||||||
# il faut ScoImplement pour creer un semestre
|
# il faut ScoImplement pour creer un semestre
|
||||||
@ -161,26 +165,25 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
allowed_user_names = list(uid2display.values()) + [""]
|
allowed_user_names = list(uid2display.values()) + [""]
|
||||||
#
|
#
|
||||||
formation_id = int(vals["formation_id"])
|
formation_id = int(vals["formation_id"])
|
||||||
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
formation = Formation.query.get(formation_id)
|
||||||
if not F:
|
if formation is None:
|
||||||
raise ScoValueError("Formation inexistante !")
|
raise ScoValueError("Formation inexistante !")
|
||||||
F = F[0]
|
|
||||||
if not edit:
|
if not edit:
|
||||||
initvalues = {"titre": _default_sem_title(F)}
|
initvalues = {"titre": _default_sem_title(formation)}
|
||||||
semestre_id = int(vals["semestre_id"])
|
semestre_id = int(vals["semestre_id"])
|
||||||
sem_module_ids = set()
|
module_ids_set = set()
|
||||||
else:
|
else:
|
||||||
# setup form init values
|
# setup form init values
|
||||||
initvalues = sem
|
initvalues = sem
|
||||||
semestre_id = initvalues["semestre_id"]
|
semestre_id = initvalues["semestre_id"]
|
||||||
# add associated modules to tf-checked:
|
# add associated modules to tf-checked:
|
||||||
ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
module_ids_existing = [modimpl.module.id for modimpl in formsemestre.modimpls]
|
||||||
sem_module_ids = set([x["module_id"] for x in ams])
|
module_ids_set = set(module_ids_existing)
|
||||||
initvalues["tf-checked"] = ["MI" + str(x["module_id"]) for x in ams]
|
initvalues["tf-checked"] = ["MI" + str(x) for x in module_ids_existing]
|
||||||
for x in ams:
|
for modimpl in formsemestre.modimpls:
|
||||||
initvalues["MI" + str(x["module_id"])] = uid2display.get(
|
initvalues[f"MI{modimpl.module.id}"] = uid2display.get(
|
||||||
x["responsable_id"],
|
modimpl.responsable_id,
|
||||||
f"inconnu numéro {x['responsable_id']} resp. de {x['moduleimpl_id']} !",
|
f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !",
|
||||||
)
|
)
|
||||||
|
|
||||||
initvalues["responsable_id"] = uid2display.get(
|
initvalues["responsable_id"] = uid2display.get(
|
||||||
@ -192,49 +195,38 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Liste des ID de semestres
|
# Liste des ID de semestres
|
||||||
if F["type_parcours"] is not None:
|
if formation.type_parcours is not None:
|
||||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
parcours = sco_codes_parcours.get_parcours_from_code(formation.type_parcours)
|
||||||
NB_SEM = parcours.NB_SEM
|
NB_SEM = parcours.NB_SEM
|
||||||
else:
|
else:
|
||||||
NB_SEM = 10 # fallback, max 10 semestres
|
NB_SEM = 10 # fallback, max 10 semestres
|
||||||
if NB_SEM == 1:
|
if NB_SEM == 1:
|
||||||
semestre_id_list = [-1]
|
semestre_id_list = [-1]
|
||||||
|
else:
|
||||||
|
if edit and formation.is_apc():
|
||||||
|
# en APC, ne permet pas de changer de semestre
|
||||||
|
semestre_id_list = [formsemestre.semestre_id]
|
||||||
else:
|
else:
|
||||||
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
semestre_id_list = [-1] + list(range(1, NB_SEM + 1))
|
||||||
|
|
||||||
semestre_id_labels = []
|
semestre_id_labels = []
|
||||||
for sid in semestre_id_list:
|
for sid in semestre_id_list:
|
||||||
if sid == -1:
|
if sid == -1:
|
||||||
semestre_id_labels.append("pas de semestres")
|
semestre_id_labels.append("pas de semestres")
|
||||||
else:
|
else:
|
||||||
semestre_id_labels.append(f"S{sid}")
|
semestre_id_labels.append(f"S{sid}")
|
||||||
# Liste des modules dans ce semestre de cette formation
|
# Liste des modules dans cette formation
|
||||||
# on pourrait faire un simple module_list( )
|
if formation.is_apc():
|
||||||
# mais si on veut l'ordre du PPN (groupe par UE et matieres) il faut:
|
modules = formation.modules.order_by(Module.module_type, Module.numero)
|
||||||
mods = [] # liste de dicts
|
else:
|
||||||
uelist = sco_edit_ue.ue_list({"formation_id": formation_id})
|
modules = (
|
||||||
for ue in uelist:
|
Module.query.filter(
|
||||||
matlist = sco_edit_matiere.matiere_list({"ue_id": ue["ue_id"]})
|
Module.formation_id == formation_id, UniteEns.id == Module.ue_id
|
||||||
for mat in matlist:
|
|
||||||
modsmat = sco_edit_module.module_list({"matiere_id": mat["matiere_id"]})
|
|
||||||
# XXX debug checks
|
|
||||||
for m in modsmat:
|
|
||||||
if m["ue_id"] != ue["ue_id"]:
|
|
||||||
log(
|
|
||||||
"XXX createwithmodules: m.ue_id=%s != u.ue_id=%s !"
|
|
||||||
% (m["ue_id"], ue["ue_id"])
|
|
||||||
)
|
)
|
||||||
if m["formation_id"] != formation_id:
|
.order_by(Module.module_type, UniteEns.numero, Module.numero)
|
||||||
log(
|
.all()
|
||||||
"XXX createwithmodules: formation_id=%s\n\tm=%s"
|
|
||||||
% (formation_id, str(m))
|
|
||||||
)
|
)
|
||||||
if m["formation_id"] != ue["formation_id"]:
|
mods = [mod.to_dict() for mod in modules]
|
||||||
log(
|
|
||||||
"XXX createwithmodules: formation_id=%s\n\tue=%s\tm=%s"
|
|
||||||
% (formation_id, str(ue), str(m))
|
|
||||||
)
|
|
||||||
# /debug
|
|
||||||
mods = mods + modsmat
|
|
||||||
# Pour regroupement des modules par semestres:
|
# Pour regroupement des modules par semestres:
|
||||||
semestre_ids = {}
|
semestre_ids = {}
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
@ -319,7 +311,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans
|
||||||
le titre: ils seront automatiquement ajoutés <input type="button"
|
le titre: ils seront automatiquement ajoutés <input type="button"
|
||||||
value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
|
value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>"""
|
||||||
% _default_sem_title(F),
|
% _default_sem_title(formation),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -340,6 +332,9 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
"title": "Semestre dans la formation",
|
"title": "Semestre dans la formation",
|
||||||
"allowed_values": semestre_id_list,
|
"allowed_values": semestre_id_list,
|
||||||
"labels": semestre_id_labels,
|
"labels": semestre_id_labels,
|
||||||
|
"explanation": "en BUT, on ne peut pas modifier le semestre après création"
|
||||||
|
if formation.is_apc()
|
||||||
|
else "",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -549,7 +544,12 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
if mod["semestre_id"] == semestre_id:
|
if mod["semestre_id"] == semestre_id and (
|
||||||
|
(not edit) # creation => tous modules
|
||||||
|
or (not formation.is_apc()) # pas BUT, on peux mixer les semestres
|
||||||
|
or (semestre_id == formsemestre.semestre_id) # module du semestre
|
||||||
|
or (mod["module_id"] in module_ids_set) # module déjà présent
|
||||||
|
):
|
||||||
nbmod += 1
|
nbmod += 1
|
||||||
if edit:
|
if edit:
|
||||||
select_name = "%s!group_id" % mod["module_id"]
|
select_name = "%s!group_id" % mod["module_id"]
|
||||||
@ -560,7 +560,7 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if mod["module_id"] in sem_module_ids:
|
if mod["module_id"] in module_ids_set:
|
||||||
disabled = "disabled"
|
disabled = "disabled"
|
||||||
else:
|
else:
|
||||||
disabled = ""
|
disabled = ""
|
||||||
@ -684,12 +684,13 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
msg = '<ul class="tf-msg"><li class="tf-msg">Code étape Apogée manquant</li></ul>'
|
||||||
|
|
||||||
if tf[0] == 0 or msg:
|
if tf[0] == 0 or msg:
|
||||||
return (
|
return f"""<p>Formation <a class="discretelink" href="{
|
||||||
'<p>Formation <a class="discretelink" href="ue_table?formation_id=%(formation_id)s"><em>%(titre)s</em> (%(acronyme)s), version %(version)s, code %(formation_code)s</a></p>'
|
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||||
% F
|
}"><em>{formation.titre}</em> ({formation.acronyme}), version {formation.version}, code {formation.formation_code}</a>
|
||||||
+ msg
|
</p>
|
||||||
+ str(tf[1])
|
{msg}
|
||||||
)
|
{tf[1]}
|
||||||
|
"""
|
||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return "<h4>annulation</h4>"
|
return "<h4>annulation</h4>"
|
||||||
else:
|
else:
|
||||||
@ -735,42 +736,58 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)]
|
etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# Modules sélectionnés:
|
||||||
|
# (retire le "MI" du début du nom de champs)
|
||||||
|
module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
|
||||||
if not edit:
|
if not edit:
|
||||||
# creation du semestre
|
if formation.is_apc():
|
||||||
|
_formsemestre_check_module_list(
|
||||||
|
module_ids_checked, tf[2]["semestre_id"]
|
||||||
|
)
|
||||||
|
# création du semestre
|
||||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2])
|
formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2])
|
||||||
# creation des modules
|
# création des modules
|
||||||
for module_id in tf[2]["tf-checked"]:
|
for module_id in module_ids_checked:
|
||||||
assert module_id[:2] == "MI"
|
|
||||||
modargs = {
|
modargs = {
|
||||||
"module_id": int(module_id[2:]),
|
"module_id": module_id,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"responsable_id": tf[2][module_id],
|
"responsable_id": tf[2][f"MI{module_id}"],
|
||||||
}
|
}
|
||||||
_ = sco_moduleimpl.do_moduleimpl_create(modargs)
|
_ = sco_moduleimpl.do_moduleimpl_create(modargs)
|
||||||
|
flash("Nouveau semestre créé")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s&head_message=Nouveau%%20semestre%%20créé"
|
url_for(
|
||||||
% formsemestre_id
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# modification du semestre:
|
# Modification du semestre:
|
||||||
# on doit creer les modules nouvellement selectionnés
|
# on doit creer les modules nouvellement selectionnés
|
||||||
# modifier ceux a modifier, et DETRUIRE ceux qui ne sont plus selectionnés.
|
# modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés.
|
||||||
# Note: la destruction echouera s'il y a des objets dependants
|
# Note: la destruction échouera s'il y a des objets dépendants
|
||||||
# (eg des evaluations définies)
|
# (eg des évaluations définies)
|
||||||
# nouveaux modules
|
module_ids_tocreate = [
|
||||||
# (retire le "MI" du début du nom de champs)
|
x for x in module_ids_checked if not x in module_ids_existing
|
||||||
checkedmods = [int(x[2:]) for x in tf[2]["tf-checked"]]
|
]
|
||||||
|
if formation.is_apc():
|
||||||
|
_formsemestre_check_module_list(
|
||||||
|
module_ids_tocreate, tf[2]["semestre_id"]
|
||||||
|
)
|
||||||
|
# modules existants à modifier
|
||||||
|
module_ids_toedit = [
|
||||||
|
x for x in module_ids_checked if x in module_ids_existing
|
||||||
|
]
|
||||||
|
# modules à détruire
|
||||||
|
module_ids_todelete = [
|
||||||
|
x for x in module_ids_existing if not x in module_ids_checked
|
||||||
|
]
|
||||||
|
#
|
||||||
sco_formsemestre.do_formsemestre_edit(tf[2])
|
sco_formsemestre.do_formsemestre_edit(tf[2])
|
||||||
ams = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
|
||||||
existingmods = [x["module_id"] for x in ams]
|
|
||||||
mods_tocreate = [x for x in checkedmods if not x in existingmods]
|
|
||||||
# modules a existants a modifier
|
|
||||||
mods_toedit = [x for x in checkedmods if x in existingmods]
|
|
||||||
# modules a detruire
|
|
||||||
mods_todelete = [x for x in existingmods if not x in checkedmods]
|
|
||||||
#
|
#
|
||||||
msg = []
|
msg = []
|
||||||
for module_id in mods_tocreate:
|
for module_id in module_ids_tocreate:
|
||||||
modargs = {
|
modargs = {
|
||||||
"module_id": module_id,
|
"module_id": module_id,
|
||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
@ -808,9 +825,11 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
% (module_id, moduleimpl_id)
|
% (module_id, moduleimpl_id)
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
ok, diag = formsemestre_delete_moduleimpls(formsemestre_id, mods_todelete)
|
ok, diag = formsemestre_delete_moduleimpls(
|
||||||
|
formsemestre_id, module_ids_todelete
|
||||||
|
)
|
||||||
msg += diag
|
msg += diag
|
||||||
for module_id in mods_toedit:
|
for module_id in module_ids_toedit:
|
||||||
moduleimpl_id = sco_moduleimpl.moduleimpl_list(
|
moduleimpl_id = sco_moduleimpl.moduleimpl_list(
|
||||||
formsemestre_id=formsemestre_id, module_id=module_id
|
formsemestre_id=formsemestre_id, module_id=module_id
|
||||||
)[0]["moduleimpl_id"]
|
)[0]["moduleimpl_id"]
|
||||||
@ -847,6 +866,22 @@ def do_formsemestre_createwithmodules(edit=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _formsemestre_check_module_list(module_ids, semestre_idx):
|
||||||
|
"""En APC: Vérifie que tous les modules de la liste
|
||||||
|
sont dans le semestre indiqué.
|
||||||
|
Sinon, raise ScoValueError.
|
||||||
|
"""
|
||||||
|
# vérification de la cohérence / modules / semestre
|
||||||
|
mod_sems_idx = {
|
||||||
|
Module.query.get_or_404(module_id).ue.semestre_idx for module_id in module_ids
|
||||||
|
}
|
||||||
|
if mod_sems_idx and mod_sems_idx != {semestre_idx}:
|
||||||
|
raise ScoValueError(
|
||||||
|
"Les modules sélectionnés ne sont pas tous dans le semestre choisi !",
|
||||||
|
dest_url="javascript:history.back();",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
||||||
"""Delete moduleimpls
|
"""Delete moduleimpls
|
||||||
module_ids_to_del: list of module_id (warning: not moduleimpl)
|
module_ids_to_del: list of module_id (warning: not moduleimpl)
|
||||||
|
@ -108,10 +108,10 @@ def _build_menu_stats(formsemestre_id):
|
|||||||
"enabled": True,
|
"enabled": True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Documents Avis Poursuite Etudes",
|
"title": "Documents Avis Poursuite Etudes (xp)",
|
||||||
"endpoint": "notes.pe_view_sem_recap",
|
"endpoint": "notes.pe_view_sem_recap",
|
||||||
"args": {"formsemestre_id": formsemestre_id},
|
"args": {"formsemestre_id": formsemestre_id},
|
||||||
"enabled": current_app.config["TESTING"] or current_app.config["DEBUG"],
|
"enabled": True, # current_app.config["TESTING"] or current_app.config["DEBUG"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": 'Table "débouchés"',
|
"title": 'Table "débouchés"',
|
||||||
@ -1107,6 +1107,7 @@ _TABLEAU_MODULES_HEAD = """
|
|||||||
<th class="formsemestre_status">Module</th>
|
<th class="formsemestre_status">Module</th>
|
||||||
<th class="formsemestre_status">Inscrits</th>
|
<th class="formsemestre_status">Inscrits</th>
|
||||||
<th class="resp">Responsable</th>
|
<th class="resp">Responsable</th>
|
||||||
|
<th class="coef">Coefs.</th>
|
||||||
<th class="evals">Évaluations</th>
|
<th class="evals">Évaluations</th>
|
||||||
</tr>
|
</tr>
|
||||||
"""
|
"""
|
||||||
@ -1213,7 +1214,21 @@ def formsemestre_tableau_modules(
|
|||||||
sco_users.user_info(modimpl["responsable_id"])["prenomnom"],
|
sco_users.user_info(modimpl["responsable_id"])["prenomnom"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
H.append("<td>")
|
||||||
|
if mod.module_type in (ModuleType.RESSOURCE, ModuleType.SAE):
|
||||||
|
coefs = mod.ue_coefs_list()
|
||||||
|
for coef in coefs:
|
||||||
|
if coef[1] > 0:
|
||||||
|
H.append(
|
||||||
|
f"""<span class="mod_coef_indicator"
|
||||||
|
title="{coef[0].acronyme}"
|
||||||
|
style="background: {
|
||||||
|
coef[0].color if coef[0].color is not None else 'blue'
|
||||||
|
}"></span>"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
H.append(f"""<span class="mod_coef_indicator_zero"></span>""")
|
||||||
|
H.append("</td>")
|
||||||
if mod.module_type in (
|
if mod.module_type in (
|
||||||
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
None, # ne devrait pas être nécessaire car la migration a remplacé les NULLs
|
||||||
ModuleType.STANDARD,
|
ModuleType.STANDARD,
|
||||||
|
@ -748,7 +748,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Choix code semestre:
|
# Choix code semestre:
|
||||||
codes = list(sco_codes_parcours.CODES_EXPL.keys())
|
codes = list(sco_codes_parcours.CODES_JURY_SEM)
|
||||||
codes.sort() # fortuitement, cet ordre convient bien !
|
codes.sort() # fortuitement, cet ordre convient bien !
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -87,7 +87,7 @@ groupEditor = ndb.EditableTable(
|
|||||||
group_list = groupEditor.list
|
group_list = groupEditor.list
|
||||||
|
|
||||||
|
|
||||||
def get_group(group_id):
|
def get_group(group_id: int):
|
||||||
"""Returns group object, with partition"""
|
"""Returns group object, with partition"""
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
"""SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.*
|
||||||
@ -687,6 +687,11 @@ def setGroups(
|
|||||||
group_id = fs[0].strip()
|
group_id = fs[0].strip()
|
||||||
if not group_id:
|
if not group_id:
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
|
group_id = int(group_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
log("setGroups: ignoring invalid group_id={group_id}")
|
||||||
|
continue
|
||||||
group = get_group(group_id)
|
group = get_group(group_id)
|
||||||
# Anciens membres du groupe:
|
# Anciens membres du groupe:
|
||||||
old_members = get_group_members(group_id)
|
old_members = get_group_members(group_id)
|
||||||
|
@ -49,9 +49,11 @@ from app.scodoc import sco_etud
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
def list_authorized_etuds_by_sem(sem, delai=274):
|
def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
|
||||||
"""Liste des etudiants autorisés à s'inscrire dans sem.
|
"""Liste des etudiants autorisés à s'inscrire dans sem.
|
||||||
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
|
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
|
||||||
|
ignore_jury: si vrai, considère tous les étudiants comem autorisés, même
|
||||||
|
s'ils n'ont pas de décision de jury.
|
||||||
"""
|
"""
|
||||||
src_sems = list_source_sems(sem, delai=delai)
|
src_sems = list_source_sems(sem, delai=delai)
|
||||||
inscrits = list_inscrits(sem["formsemestre_id"])
|
inscrits = list_inscrits(sem["formsemestre_id"])
|
||||||
@ -59,6 +61,11 @@ def list_authorized_etuds_by_sem(sem, delai=274):
|
|||||||
candidats = {} # etudid : etud (tous les etudiants candidats)
|
candidats = {} # etudid : etud (tous les etudiants candidats)
|
||||||
nb = 0 # debug
|
nb = 0 # debug
|
||||||
for src in src_sems:
|
for src in src_sems:
|
||||||
|
if ignore_jury:
|
||||||
|
# liste de tous les inscrits au semestre (sans dems)
|
||||||
|
liste = list_inscrits(src["formsemestre_id"]).values()
|
||||||
|
else:
|
||||||
|
# liste des étudiants autorisés par le jury à s'inscrire ici
|
||||||
liste = list_etuds_from_sem(src, sem)
|
liste = list_etuds_from_sem(src, sem)
|
||||||
liste_filtree = []
|
liste_filtree = []
|
||||||
for e in liste:
|
for e in liste:
|
||||||
@ -125,7 +132,7 @@ def list_inscrits(formsemestre_id, with_dems=False):
|
|||||||
return inscr
|
return inscr
|
||||||
|
|
||||||
|
|
||||||
def list_etuds_from_sem(src, dst):
|
def list_etuds_from_sem(src, dst) -> list[dict]:
|
||||||
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||||
target = dst["semestre_id"]
|
target = dst["semestre_id"]
|
||||||
dpv = sco_pvjury.dict_pvjury(src["formsemestre_id"])
|
dpv = sco_pvjury.dict_pvjury(src["formsemestre_id"])
|
||||||
@ -224,7 +231,7 @@ def do_desinscrit(sem, etudids):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_source_sems(sem, delai=None):
|
def list_source_sems(sem, delai=None) -> list[dict]:
|
||||||
"""Liste des semestres sources
|
"""Liste des semestres sources
|
||||||
sem est le semestre destination
|
sem est le semestre destination
|
||||||
"""
|
"""
|
||||||
@ -265,6 +272,7 @@ def formsemestre_inscr_passage(
|
|||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
submitted=False,
|
submitted=False,
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
|
ignore_jury=False,
|
||||||
):
|
):
|
||||||
"""Form. pour inscription des etudiants d'un semestre dans un autre
|
"""Form. pour inscription des etudiants d'un semestre dans un autre
|
||||||
(donné par formsemestre_id).
|
(donné par formsemestre_id).
|
||||||
@ -280,6 +288,7 @@ def formsemestre_inscr_passage(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
|
ignore_jury = int(ignore_jury)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
# -- check lock
|
# -- check lock
|
||||||
if not sem["etat"]:
|
if not sem["etat"]:
|
||||||
@ -295,7 +304,9 @@ def formsemestre_inscr_passage(
|
|||||||
elif etuds and isinstance(etuds[0], str):
|
elif etuds and isinstance(etuds[0], str):
|
||||||
etuds = [int(x) for x in etuds]
|
etuds = [int(x) for x in etuds]
|
||||||
|
|
||||||
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem)
|
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(
|
||||||
|
sem, ignore_jury=ignore_jury
|
||||||
|
)
|
||||||
etuds_set = set(etuds)
|
etuds_set = set(etuds)
|
||||||
candidats_set = set(candidats)
|
candidats_set = set(candidats)
|
||||||
inscrits_set = set(inscrits)
|
inscrits_set = set(inscrits)
|
||||||
@ -323,6 +334,7 @@ def formsemestre_inscr_passage(
|
|||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
inscrits_ailleurs,
|
inscrits_ailleurs,
|
||||||
inscrit_groupes=inscrit_groupes,
|
inscrit_groupes=inscrit_groupes,
|
||||||
|
ignore_jury=ignore_jury,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
@ -363,6 +375,7 @@ def formsemestre_inscr_passage(
|
|||||||
"formsemestre_id": formsemestre_id,
|
"formsemestre_id": formsemestre_id,
|
||||||
"etuds": ",".join([str(x) for x in etuds]),
|
"etuds": ",".join([str(x) for x in etuds]),
|
||||||
"inscrit_groupes": inscrit_groupes,
|
"inscrit_groupes": inscrit_groupes,
|
||||||
|
"ignore_jury": ignore_jury,
|
||||||
"submitted": 1,
|
"submitted": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -411,18 +424,23 @@ def build_page(
|
|||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
inscrits_ailleurs,
|
inscrits_ailleurs,
|
||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
|
ignore_jury=False,
|
||||||
):
|
):
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
|
ignore_jury = int(ignore_jury)
|
||||||
if inscrit_groupes:
|
if inscrit_groupes:
|
||||||
inscrit_groupes_checked = " checked"
|
inscrit_groupes_checked = " checked"
|
||||||
else:
|
else:
|
||||||
inscrit_groupes_checked = ""
|
inscrit_groupes_checked = ""
|
||||||
|
if ignore_jury:
|
||||||
|
ignore_jury_checked = " checked"
|
||||||
|
else:
|
||||||
|
ignore_jury_checked = ""
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
"Passages dans le semestre", with_page_header=False
|
"Passages dans le semestre", with_page_header=False
|
||||||
),
|
),
|
||||||
"""<form method="post" action="%s">""" % request.base_url,
|
"""<form name="f" method="post" action="%s">""" % request.base_url,
|
||||||
"""<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
|
"""<input type="hidden" name="formsemestre_id" value="%(formsemestre_id)s"/>
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
<a href="#help">aide</a>
|
<a href="#help">aide</a>
|
||||||
@ -430,6 +448,8 @@ def build_page(
|
|||||||
% sem, # "
|
% sem, # "
|
||||||
"""<input name="inscrit_groupes" type="checkbox" value="1" %s>inscrire aux mêmes groupes</input>"""
|
"""<input name="inscrit_groupes" type="checkbox" value="1" %s>inscrire aux mêmes groupes</input>"""
|
||||||
% inscrit_groupes_checked,
|
% inscrit_groupes_checked,
|
||||||
|
"""<input name="ignore_jury" type="checkbox" value="1" onchange="document.f.submit()" %s>inclure tous les étudiants (même sans décision de jury)</input>"""
|
||||||
|
% ignore_jury_checked,
|
||||||
"""<div class="pas_recap">Actuellement <span id="nbinscrits">%s</span> inscrits
|
"""<div class="pas_recap">Actuellement <span id="nbinscrits">%s</span> inscrits
|
||||||
et %d candidats supplémentaires
|
et %d candidats supplémentaires
|
||||||
</div>"""
|
</div>"""
|
||||||
|
@ -37,7 +37,10 @@ from app.models.moduleimpls import ModuleImpl
|
|||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.comp import res_sem
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
from app.comp.moy_mod import ModuleImplResults
|
||||||
|
from app.comp.res_common import NotesTableCompat
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
@ -432,7 +435,7 @@ def _make_table_notes(
|
|||||||
if is_apc:
|
if is_apc:
|
||||||
# Ajoute une colonne par UE
|
# Ajoute une colonne par UE
|
||||||
_add_apc_columns(
|
_add_apc_columns(
|
||||||
moduleimpl_id,
|
modimpl,
|
||||||
evals_poids,
|
evals_poids,
|
||||||
ues,
|
ues,
|
||||||
rows,
|
rows,
|
||||||
@ -815,7 +818,7 @@ def _add_moymod_column(
|
|||||||
|
|
||||||
|
|
||||||
def _add_apc_columns(
|
def _add_apc_columns(
|
||||||
moduleimpl_id,
|
modimpl,
|
||||||
evals_poids,
|
evals_poids,
|
||||||
ues,
|
ues,
|
||||||
rows,
|
rows,
|
||||||
@ -834,18 +837,23 @@ def _add_apc_columns(
|
|||||||
# => On recharge tout dans les nouveaux modèles
|
# => On recharge tout dans les nouveaux modèles
|
||||||
# rows est une liste de dict avec une clé "etudid"
|
# rows est une liste de dict avec une clé "etudid"
|
||||||
# on va y ajouter une clé par UE du semestre
|
# on va y ajouter une clé par UE du semestre
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
nt: NotesTableCompat = res_sem.load_formsemestre_result(modimpl.formsemestre)
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
||||||
moduleimpl_id
|
|
||||||
)
|
# XXX A ENLEVER TODO
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
# modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
# evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
||||||
|
# moduleimpl_id
|
||||||
|
# )
|
||||||
|
# etuds_moy_module = moy_mod.compute_module_moy(
|
||||||
|
# evals_notes, evals_poids, evaluations, evaluations_completes
|
||||||
|
# )
|
||||||
if is_conforme:
|
if is_conforme:
|
||||||
# valeur des moyennes vers les UEs:
|
# valeur des moyennes vers les UEs:
|
||||||
for row in rows:
|
for row in rows:
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
moy_ue = etuds_moy_module[ue.id].get(row["etudid"], "?")
|
moy_ue = modimpl_results.etuds_moy_module[ue.id].get(row["etudid"], "?")
|
||||||
row[f"moy_ue_{ue.id}"] = scu.fmt_note(moy_ue, keep_numeric=keep_numeric)
|
row[f"moy_ue_{ue.id}"] = scu.fmt_note(moy_ue, keep_numeric=keep_numeric)
|
||||||
row[f"_moy_ue_{ue.id}_class"] = "moy_ue"
|
row[f"_moy_ue_{ue.id}_class"] = "moy_ue"
|
||||||
# Nom et coefs des UE (lignes titres):
|
# Nom et coefs des UE (lignes titres):
|
||||||
|
@ -567,17 +567,17 @@ def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id):
|
|||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT mi.moduleimpl_id
|
"""SELECT mi.id
|
||||||
FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem
|
FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem
|
||||||
WHERE sem.formsemestre_id = %(formsemestre_id)s
|
WHERE sem.id = %(formsemestre_id)s
|
||||||
AND mi.formsemestre_id = sem.formsemestre_id
|
AND mi.formsemestre_id = sem.id
|
||||||
AND mod.module_id = mi.module_id
|
AND mod.id = mi.module_id
|
||||||
AND mod.ue_id = %(ue_id)s
|
AND mod.ue_id = %(ue_id)s
|
||||||
""",
|
""",
|
||||||
{"formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
{"formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
||||||
)
|
)
|
||||||
res = cursor.dictfetchall()
|
res = cursor.dictfetchall()
|
||||||
for moduleimpl_id in [x["moduleimpl_id"] for x in res]:
|
for moduleimpl_id in [x["id"] for x in res]:
|
||||||
sco_moduleimpl.do_moduleimpl_inscription_create(
|
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||||
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
|
@ -171,7 +171,9 @@ def _ue_coefs_html(coefs_lst) -> str:
|
|||||||
"""
|
"""
|
||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
[
|
[
|
||||||
f"""<div style="--coef:{coef}"><div>{coef}</div>{ue.acronyme}</div>"""
|
f"""<div style="--coef:{coef};
|
||||||
|
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||||
|
"><div>{coef}</div>{ue.acronyme}</div>"""
|
||||||
for ue, coef in coefs_lst
|
for ue, coef in coefs_lst
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -286,21 +288,16 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
'<tr><td class="fichetitre2" colspan="4">Règle de calcul: <span class="formula" title="mode de calcul de la moyenne du module">moyenne=<tt>%s</tt></span>'
|
'<tr><td class="fichetitre2" colspan="4">Règle de calcul: <span class="formula" title="mode de calcul de la moyenne du module">moyenne=<tt>%s</tt></span>'
|
||||||
% M["computation_expr"]
|
% M["computation_expr"]
|
||||||
)
|
)
|
||||||
if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
|
H.append('<span class="warning">inutilisée dans cette version de ScoDoc</span>')
|
||||||
H.append(
|
|
||||||
'<span class="fl"><a class="stdlink" href="edit_moduleimpl_expr?moduleimpl_id=%s">modifier</a></span>'
|
|
||||||
% moduleimpl_id
|
|
||||||
)
|
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td colspan="4"><em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
'<tr><td colspan="4">' # <em title="mode de calcul de la moyenne du module">règle de calcul standard</em>'
|
||||||
)
|
|
||||||
if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
|
|
||||||
H.append(
|
|
||||||
' (<a class="stdlink" href="edit_moduleimpl_expr?moduleimpl_id=%s">changer</a>)'
|
|
||||||
% moduleimpl_id
|
|
||||||
)
|
)
|
||||||
|
# if sco_moduleimpl.can_change_ens(moduleimpl_id, raise_exc=False):
|
||||||
|
# H.append(
|
||||||
|
# f' (<a class="stdlink" href="edit_moduleimpl_expr?moduleimpl_id={moduleimpl_id}">changer</a>)'
|
||||||
|
# )
|
||||||
H.append("</td></tr>")
|
H.append("</td></tr>")
|
||||||
H.append(
|
H.append(
|
||||||
'<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink" href="view_module_abs?moduleimpl_id=%s">Absences dans ce module</a></span>'
|
'<tr><td colspan="4"><span class="moduleimpl_abs_link"><a class="stdlink" href="view_module_abs?moduleimpl_id=%s">Absences dans ce module</a></span>'
|
||||||
@ -399,7 +396,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
|||||||
eval_index = len(mod_evals) - 1
|
eval_index = len(mod_evals) - 1
|
||||||
first_eval = True
|
first_eval = True
|
||||||
for eval in mod_evals:
|
for eval in mod_evals:
|
||||||
evaluation = Evaluation.query.get(eval["evaluation_id"]) # TODO unifier
|
evaluation: Evaluation = Evaluation.query.get(
|
||||||
|
eval["evaluation_id"]
|
||||||
|
) # TODO unifier
|
||||||
etat = sco_evaluations.do_evaluation_etat(
|
etat = sco_evaluations.do_evaluation_etat(
|
||||||
eval["evaluation_id"],
|
eval["evaluation_id"],
|
||||||
partition_id=partition_id,
|
partition_id=partition_id,
|
||||||
|
@ -37,7 +37,7 @@ _SCO_PERMISSIONS = (
|
|||||||
(1 << 21, "ScoEditPVJury", "Éditer les PV de jury"),
|
(1 << 21, "ScoEditPVJury", "Éditer les PV de jury"),
|
||||||
# ajouter maquettes Apogee (=> chef dept et secr):
|
# ajouter maquettes Apogee (=> chef dept et secr):
|
||||||
(1 << 22, "ScoEditApo", "Ajouter des maquettes Apogées"),
|
(1 << 22, "ScoEditApo", "Ajouter des maquettes Apogées"),
|
||||||
# application relations entreprises
|
# Application relations entreprises
|
||||||
(1 << 23, "RelationsEntreprisesView", "Voir l'application relations entreprises"),
|
(1 << 23, "RelationsEntreprisesView", "Voir l'application relations entreprises"),
|
||||||
(1 << 24, "RelationsEntreprisesChange", "Modifier les entreprises"),
|
(1 << 24, "RelationsEntreprisesChange", "Modifier les entreprises"),
|
||||||
(
|
(
|
||||||
|
@ -169,7 +169,9 @@ def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=2):
|
|||||||
if doc:
|
if doc:
|
||||||
break
|
break
|
||||||
if not doc:
|
if not doc:
|
||||||
raise ScoValueError("pas de réponse du portail ! (timeout=%s)" % portal_timeout)
|
raise ScoValueError(
|
||||||
|
f"pas de réponse du portail ! <br>(timeout={portal_timeout}, requête: <tt>{req}</tt>)"
|
||||||
|
)
|
||||||
etuds = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req))
|
etuds = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req))
|
||||||
|
|
||||||
# Filtre sur annee inscription Apogee:
|
# Filtre sur annee inscription Apogee:
|
||||||
|
@ -111,8 +111,9 @@ get_base_preferences(formsemestre_id)
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import flask
|
import flask
|
||||||
from flask import g, url_for, request
|
from flask import g, request, current_app
|
||||||
from flask_login import current_user
|
|
||||||
|
# from flask_login import current_user
|
||||||
|
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -1537,7 +1538,7 @@ class BasePreferences(object):
|
|||||||
(
|
(
|
||||||
"email_from_addr",
|
"email_from_addr",
|
||||||
{
|
{
|
||||||
"initvalue": "noreply@scodoc.example.com",
|
"initvalue": current_app.config["SCODOC_MAIL_FROM"],
|
||||||
"title": "adresse mail origine",
|
"title": "adresse mail origine",
|
||||||
"size": 40,
|
"size": 40,
|
||||||
"explanation": "adresse expéditeur pour les envois par mails (bulletins)",
|
"explanation": "adresse expéditeur pour les envois par mails (bulletins)",
|
||||||
@ -2018,7 +2019,7 @@ class BasePreferences(object):
|
|||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Préférences"),
|
html_sco_header.sco_header(page_title="Préférences"),
|
||||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||||
# f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
# f"""<p><a href="{url_for("scodoc.configure_logos", scodoc_dept=g.scodoc_dept)
|
||||||
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
||||||
# if current_user.is_administrator()
|
# if current_user.is_administrator()
|
||||||
# else "",
|
# else "",
|
||||||
|
@ -566,7 +566,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
|
|||||||
if "prev_decision" in row and row["prev_decision"]:
|
if "prev_decision" in row and row["prev_decision"]:
|
||||||
counts[row["prev_decision"]] += 0
|
counts[row["prev_decision"]] += 0
|
||||||
# Légende des codes
|
# Légende des codes
|
||||||
codes = list(counts.keys()) # sco_codes_parcours.CODES_EXPL.keys()
|
codes = list(counts.keys())
|
||||||
codes.sort()
|
codes.sort()
|
||||||
H.append("<h3>Explication des codes</h3>")
|
H.append("<h3>Explication des codes</h3>")
|
||||||
lines = []
|
lines = []
|
||||||
|
@ -58,8 +58,31 @@ SCO_ROLES_DEFAULTS = {
|
|||||||
# il peut ajouter des tags sur les formations:
|
# il peut ajouter des tags sur les formations:
|
||||||
# (doit avoir un rôle Ens en plus !)
|
# (doit avoir un rôle Ens en plus !)
|
||||||
"RespPe": (p.ScoEditFormationTags,),
|
"RespPe": (p.ScoEditFormationTags,),
|
||||||
|
# Rôles pour l'application relations entreprises
|
||||||
|
# ObservateurEntreprise est un observateur de l'application entreprise
|
||||||
|
"ObservateurEntreprise": (p.RelationsEntreprisesView,),
|
||||||
|
# UtilisateurEntreprise est un utilisateur de l'application entreprise (droit de modification)
|
||||||
|
"UtilisateurEntreprise": (p.RelationsEntreprisesView, p.RelationsEntreprisesChange),
|
||||||
|
# AdminEntreprise est un admin de l'application entreprise (toutes les actions possibles de l'application)
|
||||||
|
"AdminEntreprise": (
|
||||||
|
p.RelationsEntreprisesView,
|
||||||
|
p.RelationsEntreprisesChange,
|
||||||
|
p.RelationsEntreprisesExport,
|
||||||
|
p.RelationsEntreprisesSend,
|
||||||
|
p.RelationsEntreprisesValidate,
|
||||||
|
),
|
||||||
# Super Admin est un root: création/suppression de départements
|
# Super Admin est un root: création/suppression de départements
|
||||||
# _tous_ les droits
|
# _tous_ les droits
|
||||||
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
# Afin d'avoir tous les droits, il ne doit pas être asscoié à un département
|
||||||
"SuperAdmin": p.ALL_PERMISSIONS,
|
"SuperAdmin": p.ALL_PERMISSIONS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Les rôles accessibles via la page d'admin utilisateurs
|
||||||
|
# - associés à un département:
|
||||||
|
ROLES_ATTRIBUABLES_DEPT = ("Ens", "Secr", "Admin", "RespPe")
|
||||||
|
# - globaux: (ne peuvent être attribués que par un SuperAdmin)
|
||||||
|
ROLES_ATTRIBUABLES_SCODOC = (
|
||||||
|
"ObservateurEntreprise",
|
||||||
|
"UtilisateurEntreprise",
|
||||||
|
"AdminEntreprise",
|
||||||
|
)
|
||||||
|
@ -153,7 +153,10 @@ def _check_notes(notes, evaluation, mod):
|
|||||||
|
|
||||||
for (etudid, note) in notes:
|
for (etudid, note) in notes:
|
||||||
note = str(note).strip().upper()
|
note = str(note).strip().upper()
|
||||||
|
try:
|
||||||
etudid = int(etudid) #
|
etudid = int(etudid) #
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError(f"Code étudiant ({etudid}) invalide")
|
||||||
if note[:3] == "DEM":
|
if note[:3] == "DEM":
|
||||||
continue # skip !
|
continue # skip !
|
||||||
if note:
|
if note:
|
||||||
@ -487,10 +490,10 @@ def notes_add(
|
|||||||
}
|
}
|
||||||
for (etudid, value) in notes:
|
for (etudid, value) in notes:
|
||||||
if check_inscription and (etudid not in inscrits):
|
if check_inscription and (etudid not in inscrits):
|
||||||
raise NoteProcessError("etudiant non inscrit dans ce module")
|
raise NoteProcessError(f"etudiant {etudid} non inscrit dans ce module")
|
||||||
if not ((value is None) or (type(value) == type(1.0))):
|
if (value is not None) and not isinstance(value, float):
|
||||||
raise NoteProcessError(
|
raise NoteProcessError(
|
||||||
"etudiant %s: valeur de note invalide (%s)" % (etudid, value)
|
f"etudiant {etudid}: valeur de note invalide ({value})"
|
||||||
)
|
)
|
||||||
# Recherche notes existantes
|
# Recherche notes existantes
|
||||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id)
|
||||||
|
@ -181,7 +181,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
r = ndb.SimpleDictFetch(
|
r = ndb.SimpleDictFetch(
|
||||||
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour, u.user_name
|
"""SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour, u.user_name, e.id as evaluation_id
|
||||||
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
|
FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi,
|
||||||
notes_modules mod, identite i, "user" u
|
notes_modules mod, identite i, "user" u
|
||||||
WHERE mi.id = e.moduleimpl_id
|
WHERE mi.id = e.moduleimpl_id
|
||||||
@ -202,6 +202,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"value",
|
"value",
|
||||||
"user_name",
|
"user_name",
|
||||||
"titre",
|
"titre",
|
||||||
|
"evaluation_id",
|
||||||
"description",
|
"description",
|
||||||
"jour",
|
"jour",
|
||||||
"comment",
|
"comment",
|
||||||
@ -214,6 +215,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"):
|
|||||||
"value": "Note",
|
"value": "Note",
|
||||||
"comment": "Remarque",
|
"comment": "Remarque",
|
||||||
"user_name": "Enseignant",
|
"user_name": "Enseignant",
|
||||||
|
"evaluation_id": "evaluation_id",
|
||||||
"titre": "Module",
|
"titre": "Module",
|
||||||
"description": "Evaluation",
|
"description": "Evaluation",
|
||||||
"jour": "Date éval.",
|
"jour": "Date éval.",
|
||||||
|
@ -91,7 +91,8 @@ section>div:nth-child(1){
|
|||||||
/***********************/
|
/***********************/
|
||||||
/* Options d'affichage */
|
/* Options d'affichage */
|
||||||
/***********************/
|
/***********************/
|
||||||
.hide_abs .absences,
|
.hide_abs .absencesRecap,
|
||||||
|
/*.hide_abs .absences,*/
|
||||||
.hide_abs_modules .module>.absences,
|
.hide_abs_modules .module>.absences,
|
||||||
.hide_coef .synthese em,
|
.hide_coef .synthese em,
|
||||||
.hide_coef .eval>em,
|
.hide_coef .eval>em,
|
||||||
@ -100,7 +101,7 @@ section>div:nth-child(1){
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module>.absences,
|
/*.module>.absences,*/
|
||||||
.module .moyenne,
|
.module .moyenne,
|
||||||
.module .info{
|
.module .info{
|
||||||
display: none;
|
display: none;
|
||||||
@ -131,14 +132,15 @@ section>div:nth-child(1){
|
|||||||
/************/
|
/************/
|
||||||
.flex{
|
.flex{
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.infoSemestre{
|
.infoSemestre{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
align-items: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex: none;
|
|
||||||
}
|
}
|
||||||
.infoSemestre>div{
|
.infoSemestre>div{
|
||||||
border: 1px solid var(--couleurIntense);
|
border: 1px solid var(--couleurIntense);
|
||||||
@ -147,6 +149,7 @@ section>div:nth-child(1){
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
column-gap: 4px;
|
column-gap: 4px;
|
||||||
|
flex: none;
|
||||||
}
|
}
|
||||||
.infoSemestre>div:nth-child(1){
|
.infoSemestre>div:nth-child(1){
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -169,6 +172,12 @@ section>div:nth-child(1){
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
/***************/
|
||||||
|
/* Zone custom */
|
||||||
|
/***************/
|
||||||
|
.custom:empty{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/***************/
|
/***************/
|
||||||
/* Synthèse */
|
/* Synthèse */
|
||||||
@ -183,10 +192,19 @@ section>div:nth-child(1){
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
.ueBonus,
|
||||||
|
.ueBonus h3{
|
||||||
|
background: var(--couleurFondTitresSAE) !important;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/***************/
|
/***************/
|
||||||
/* Evaluations */
|
/* Evaluations */
|
||||||
/***************/
|
/***************/
|
||||||
|
.evaluations>div,
|
||||||
|
.sae>div{
|
||||||
|
scroll-margin-top: 60px;
|
||||||
|
}
|
||||||
.module, .ue {
|
.module, .ue {
|
||||||
background: var(--couleurSecondaire);
|
background: var(--couleurSecondaire);
|
||||||
color: #000;
|
color: #000;
|
||||||
@ -201,16 +219,22 @@ section>div:nth-child(1){
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.module::before, .ue::before {
|
.module::before, .ue::before {
|
||||||
content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='black'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='white'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 50%;
|
left: calc(50% - 13px);
|
||||||
margin-left: -13px;
|
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
/* Placer le chevron à gauche au lieu du milieu */
|
||||||
|
.module::before, .ue::before {
|
||||||
|
left: 2px;
|
||||||
|
bottom: calc(50% - 13px);
|
||||||
|
}
|
||||||
|
}
|
||||||
h3{
|
h3{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -284,7 +308,7 @@ h3{
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absences{
|
/*.absences{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
column-gap: 4px;
|
column-gap: 4px;
|
||||||
@ -295,4 +319,4 @@ h3{
|
|||||||
.absences>div:nth-child(1),
|
.absences>div:nth-child(1),
|
||||||
.absences>div:nth-child(2){
|
.absences>div:nth-child(2){
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}*/
|
||||||
|
@ -881,6 +881,19 @@ div.sco_help {
|
|||||||
span.wtf-field ul.errors li {
|
span.wtf-field ul.errors li {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bonus_description {
|
||||||
|
color:rgb(6, 73, 6);
|
||||||
|
padding: 5px;
|
||||||
|
margin-top:5px;
|
||||||
|
border: 2px solid blue;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: cornsilk;
|
||||||
|
}
|
||||||
|
#bonus_description div.bonus_description_head{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.configuration_logo div.img {
|
.configuration_logo div.img {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1308,6 +1321,20 @@ td.formsemestre_status_cell {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.mod_coef_indicator, span.ue_color_indicator {
|
||||||
|
display:inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
span.mod_coef_indicator_zero {
|
||||||
|
display:inline-block;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
border: 1px solid rgb(156, 156, 156);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
span.status_ue_acro { font-weight: bold; }
|
span.status_ue_acro { font-weight: bold; }
|
||||||
span.status_ue_title { font-style: italic; padding-left: 1cm;}
|
span.status_ue_title { font-style: italic; padding-left: 1cm;}
|
||||||
span.status_module_cat { font-weight: bold; }
|
span.status_module_cat { font-weight: bold; }
|
||||||
@ -1499,6 +1526,16 @@ table.moduleimpl_evaluations td.eval_poids {
|
|||||||
color:rgb(0, 0, 255);
|
color:rgb(0, 0, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.eval_coef_ue {
|
||||||
|
color:rgb(6, 73, 6);
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 80%;
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
span.eval_coef_ue_titre {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* Formulaire edition des partitions */
|
/* Formulaire edition des partitions */
|
||||||
form#editpart table {
|
form#editpart table {
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
function submit_form() {
|
function submit_form() {
|
||||||
$("#configuration_form").submit();
|
$("#config_logos_form").submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
@ -3,8 +3,26 @@
|
|||||||
$().ready(function () {
|
$().ready(function () {
|
||||||
update_ue_list();
|
update_ue_list();
|
||||||
$("#tf_ue_code").bind("keyup", update_ue_list);
|
$("#tf_ue_code").bind("keyup", update_ue_list);
|
||||||
|
|
||||||
|
$("select#tf_type").change(function () {
|
||||||
|
update_bonus_description();
|
||||||
|
});
|
||||||
|
update_bonus_description();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function update_bonus_description() {
|
||||||
|
var ue_type = $("#tf_type")[0].value;
|
||||||
|
if (ue_type == "1") { /* UE SPORT */
|
||||||
|
$("#bonus_description").show();
|
||||||
|
var query = "/ScoDoc/get_bonus_description/default";
|
||||||
|
$.get(query, '', function (data) {
|
||||||
|
$("#bonus_description").html(data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#bonus_description").html("");
|
||||||
|
$("#bonus_description").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function update_ue_list() {
|
function update_ue_list() {
|
||||||
var ue_id = $("#tf_ue_id")[0].value;
|
var ue_id = $("#tf_ue_id")[0].value;
|
||||||
|
@ -15,13 +15,10 @@ class releveBUT extends HTMLElement {
|
|||||||
/* Style du module */
|
/* Style du module */
|
||||||
const styles = document.createElement('link');
|
const styles = document.createElement('link');
|
||||||
styles.setAttribute('rel', 'stylesheet');
|
styles.setAttribute('rel', 'stylesheet');
|
||||||
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
|
if (location.href.split("/")[3] == "ScoDoc") {
|
||||||
/* variante "ScoDoc" ou "Passerelle" (ENT) ? */
|
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css'); // Scodoc
|
||||||
if (location.href.split("/")[3] == "ScoDoc") { /* un peu osé... */
|
|
||||||
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
|
|
||||||
} else {
|
} else {
|
||||||
// Passerelle
|
styles.setAttribute('href', '/assets/styles/releve-but.css'); // Passerelle
|
||||||
styles.setAttribute('href', '/assets/styles/releve-but.css');
|
|
||||||
}
|
}
|
||||||
this.shadow.appendChild(styles);
|
this.shadow.appendChild(styles);
|
||||||
}
|
}
|
||||||
@ -49,6 +46,8 @@ class releveBUT extends HTMLElement {
|
|||||||
this.showSynthese(data);
|
this.showSynthese(data);
|
||||||
this.showEvaluations(data);
|
this.showEvaluations(data);
|
||||||
|
|
||||||
|
this.showCustom(data);
|
||||||
|
|
||||||
this.setOptions(data.options);
|
this.setOptions(data.options);
|
||||||
|
|
||||||
this.shadow.querySelectorAll(".CTA_Liste").forEach(e => {
|
this.shadow.querySelectorAll(".CTA_Liste").forEach(e => {
|
||||||
@ -57,7 +56,7 @@ class releveBUT extends HTMLElement {
|
|||||||
this.shadow.querySelectorAll(".ue, .module").forEach(e => {
|
this.shadow.querySelectorAll(".ue, .module").forEach(e => {
|
||||||
e.addEventListener("click", this.moduleOnOff)
|
e.addEventListener("click", this.moduleOnOff)
|
||||||
})
|
})
|
||||||
this.shadow.querySelectorAll(".syntheseModule").forEach(e => {
|
this.shadow.querySelectorAll(":not(.ueBonus)+.syntheseModule").forEach(e => {
|
||||||
e.addEventListener("click", this.goTo)
|
e.addEventListener("click", this.goTo)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -77,6 +76,11 @@ class releveBUT extends HTMLElement {
|
|||||||
<div class=infoEtudiant></div>
|
<div class=infoEtudiant></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!--------------------------------------------------------------------------------------->
|
||||||
|
<!-- Zone spéciale pour que les IUT puisse ajouter des infos locales sur la passerelle -->
|
||||||
|
<!--------------------------------------------------------------------------------------->
|
||||||
|
<section class=custom></section>
|
||||||
|
|
||||||
<!--------------------------->
|
<!--------------------------->
|
||||||
<!-- Semestre -->
|
<!-- Semestre -->
|
||||||
<!--------------------------->
|
<!--------------------------->
|
||||||
@ -169,8 +173,8 @@ class releveBUT extends HTMLElement {
|
|||||||
output += `
|
output += `
|
||||||
</div>
|
</div>
|
||||||
<div class=numerosEtudiant>
|
<div class=numerosEtudiant>
|
||||||
Numéro étudiant : ${data.etudiant.code_nip} -
|
Numéro étudiant : ${data.etudiant.code_nip || "~"} -
|
||||||
Code INE : ${data.etudiant.code_ine}
|
Code INE : ${data.etudiant.code_ine || "~"}
|
||||||
</div>
|
</div>
|
||||||
<div>${data.formation.titre}</div>
|
<div>${data.formation.titre}</div>
|
||||||
`;
|
`;
|
||||||
@ -183,6 +187,13 @@ class releveBUT extends HTMLElement {
|
|||||||
this.shadow.querySelector(".infoEtudiant").innerHTML = output;
|
this.shadow.querySelector(".infoEtudiant").innerHTML = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************/
|
||||||
|
/* Affichage local */
|
||||||
|
/*******************************/
|
||||||
|
showCustom(data) {
|
||||||
|
this.shadow.querySelector(".custom").innerHTML = data.custom || "";
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Information sur le semestre */
|
/* Information sur le semestre */
|
||||||
/*******************************/
|
/*******************************/
|
||||||
@ -196,6 +207,11 @@ class releveBUT extends HTMLElement {
|
|||||||
<div>Max. promo. :</div><div>${data.semestre.notes.max}</div>
|
<div>Max. promo. :</div><div>${data.semestre.notes.max}</div>
|
||||||
<div>Moy. promo. :</div><div>${data.semestre.notes.moy}</div>
|
<div>Moy. promo. :</div><div>${data.semestre.notes.moy}</div>
|
||||||
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
|
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
|
||||||
|
</div>
|
||||||
|
<div class=absencesRecap>
|
||||||
|
<div class=enteteSemestre>Absences</div>
|
||||||
|
<div class=enteteSemestre>N.J. ${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||||
|
<div style="grid-column: 2">Total ${data.semestre.absences?.total ?? "-"}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
/*${data.semestre.groupes.map(groupe => {
|
/*${data.semestre.groupes.map(groupe => {
|
||||||
return `
|
return `
|
||||||
@ -210,7 +226,7 @@ class releveBUT extends HTMLElement {
|
|||||||
}).join("")
|
}).join("")
|
||||||
}*/
|
}*/
|
||||||
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
this.shadow.querySelector(".infoSemestre").innerHTML = output;
|
||||||
/*this.shadow.querySelector(".decision").innerHTML = data.semestre.decision.code;*/
|
this.shadow.querySelector(".decision").innerHTML = data.semestre.decision?.code || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
@ -219,12 +235,22 @@ class releveBUT extends HTMLElement {
|
|||||||
showSynthese(data) {
|
showSynthese(data) {
|
||||||
let output = ``;
|
let output = ``;
|
||||||
Object.entries(data.ues).forEach(([ue, dataUE]) => {
|
Object.entries(data.ues).forEach(([ue, dataUE]) => {
|
||||||
|
if (dataUE.type == 1) { // UE Sport / Bonus
|
||||||
|
output += `
|
||||||
|
<div>
|
||||||
|
<div class="ue ueBonus">
|
||||||
|
<h3>Bonus</h3>
|
||||||
|
<div>${dataUE.bonus_description}</div>
|
||||||
|
</div>
|
||||||
|
${this.ueSport(dataUE.modules)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
output += `
|
output += `
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class=ue>
|
<div class=ue>
|
||||||
<h3>
|
<h3>
|
||||||
${(dataUE.competence) ? dataUE.competence + " - " : ""}${ue}
|
${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""}
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
||||||
@ -235,16 +261,18 @@ class releveBUT extends HTMLElement {
|
|||||||
ECTS : ${dataUE.ECTS.acquis} / ${dataUE.ECTS.total}
|
ECTS : ${dataUE.ECTS.acquis} / ${dataUE.ECTS.total}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
<div class=absences>
|
/*<div class=absences>
|
||||||
<div>Abs N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
|
<div>Abs N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
|
||||||
<div>Total</div><div>${dataUE.absences?.total || 0}</div>
|
<div>Total</div><div>${dataUE.absences?.total || 0}</div>
|
||||||
</div>
|
</div>*/
|
||||||
|
output += `
|
||||||
</div>
|
</div>
|
||||||
${this.synthese(data, dataUE.ressources)}
|
${this.synthese(data, dataUE.ressources)}
|
||||||
${this.synthese(data, dataUE.saes)}
|
${this.synthese(data, dataUE.saes)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.shadow.querySelector(".synthese").innerHTML = output;
|
this.shadow.querySelector(".synthese").innerHTML = output;
|
||||||
}
|
}
|
||||||
@ -252,7 +280,7 @@ class releveBUT extends HTMLElement {
|
|||||||
let output = "";
|
let output = "";
|
||||||
Object.entries(modules).forEach(([module, dataModule]) => {
|
Object.entries(modules).forEach(([module, dataModule]) => {
|
||||||
let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
|
let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
|
||||||
let url = data.ressources[module]?.url || data.saes[module]?.url;
|
//let url = data.ressources[module]?.url || data.saes[module]?.url;
|
||||||
output += `
|
output += `
|
||||||
<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
|
<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
|
||||||
<div>${module} - ${titre}</div>
|
<div>${module} - ${titre}</div>
|
||||||
@ -265,6 +293,23 @@ class releveBUT extends HTMLElement {
|
|||||||
})
|
})
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
ueSport(modules) {
|
||||||
|
let output = "";
|
||||||
|
Object.values(modules).forEach((module) => {
|
||||||
|
Object.values(module.evaluations).forEach((evaluation) => {
|
||||||
|
output += `
|
||||||
|
<div class=syntheseModule>
|
||||||
|
<div>${module.titre} - ${evaluation.description}</div>
|
||||||
|
<div>
|
||||||
|
${evaluation.note.value ?? "-"}
|
||||||
|
<em>Coef. ${evaluation.coef}</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************/
|
/*******************************/
|
||||||
/* Evaluations */
|
/* Evaluations */
|
||||||
@ -305,7 +350,7 @@ class releveBUT extends HTMLElement {
|
|||||||
evaluations.forEach((evaluation) => {
|
evaluations.forEach((evaluation) => {
|
||||||
output += `
|
output += `
|
||||||
<div class=eval>
|
<div class=eval>
|
||||||
<div>${this.URL(evaluation.url, evaluation.description)}</div>
|
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
||||||
<div>
|
<div>
|
||||||
${evaluation.note.value}
|
${evaluation.note.value}
|
||||||
<em>Coef. ${evaluation.coef}</em>
|
<em>Coef. ${evaluation.coef}</em>
|
||||||
|
23
app/templates/config_codes_decisions.html
Normal file
23
app/templates/config_codes_decisions.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Configuration des codes de décision exportés vers Apogée</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="help">
|
||||||
|
<p>Ces codes (ADM, AJ, ...) sont utilisés pour représenter les décisions de jury
|
||||||
|
et les validations de semestres ou d'UE. les valeurs indiquées ici sont utilisées
|
||||||
|
dans les exports Apogée.
|
||||||
|
<p>
|
||||||
|
<p>Ne les modifier que si vous savez ce que vous faites !
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
125
app/templates/config_logos.html
Normal file
125
app/templates/config_logos.html
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% macro render_field(field, with_label=True) %}
|
||||||
|
<div>
|
||||||
|
{% if with_label %}
|
||||||
|
<span class="wtf-field">{{ field.label }} :</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="wtf-field">{{ field(**kwargs)|safe }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<ul class=errors>
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<li>{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_add_logo(add_logo_form) %}
|
||||||
|
<div class="logo-add">
|
||||||
|
<h3>Ajouter un logo</h3>
|
||||||
|
{{ add_logo_form.hidden_tag() }}
|
||||||
|
{{ render_field(add_logo_form.name) }}
|
||||||
|
{{ render_field(add_logo_form.upload) }}
|
||||||
|
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_logo(dept_form, logo_form) %}
|
||||||
|
<div class="logo-edit">
|
||||||
|
{{ logo_form.hidden_tag() }}
|
||||||
|
{% if logo_form.titre %}
|
||||||
|
<tr class="logo-edit">
|
||||||
|
<td colspan="3" class="titre">
|
||||||
|
<div class="nom">
|
||||||
|
<h3>{{ logo_form.titre }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="description">{{ logo_form.description or "" }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr class="logo-edit">
|
||||||
|
<td colspan="3" class="titre">
|
||||||
|
<span class="nom">
|
||||||
|
<h3>Logo personalisé: {{ logo_form.logo_id.data }}</h3>
|
||||||
|
</span>
|
||||||
|
<span class="description">{{ logo_form.description or "" }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td style="padding-right: 20px; ">
|
||||||
|
<div class="img-container">
|
||||||
|
<img src="{{ logo_form.logo.get_url_small() }}" alt="pas de logo chargé" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="img-data">
|
||||||
|
<h3>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h3>
|
||||||
|
Taille: {{ logo_form.logo.size }} px
|
||||||
|
{% if logo_form.logo.mm %} / {{ logo_form.logo.mm }} mm {% endif %}<br />
|
||||||
|
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br />
|
||||||
|
Usage: <span style="font-family: system-ui">{{ logo_form.logo.get_usage() }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="" img-action">
|
||||||
|
<p>Modifier l'image</p>
|
||||||
|
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
|
||||||
|
{% if logo_form.can_delete %}
|
||||||
|
<p>Supprimer l'image</p>
|
||||||
|
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_logos(dept_form) %}
|
||||||
|
<table>
|
||||||
|
{% for logo_entry in dept_form.logos.entries %}
|
||||||
|
{% set logo_form = logo_entry.form %}
|
||||||
|
{{ render_logo(dept_form, logo_form) }}
|
||||||
|
{% else %}
|
||||||
|
<p class="logo-edit">
|
||||||
|
<h3>Aucun logo défini en propre à ce département</h3>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||||
|
<script src="/ScoDoc/static/js/config_logos.js"></script>
|
||||||
|
|
||||||
|
<form id="config_logos_form" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
<div class="configuration_logo">
|
||||||
|
<h1>Bibliothèque de logos</h1>
|
||||||
|
{% for dept_entry in form.depts.entries %}
|
||||||
|
{% set dept_form = dept_entry.form %}
|
||||||
|
{{ dept_entry.form.hidden_tag() }}
|
||||||
|
{% if dept_entry.form.is_local() %}
|
||||||
|
<div class="departement">
|
||||||
|
<h2>Département {{ dept_form.dept_name.data }}</h2>
|
||||||
|
<h3>Logos locaux</h3>
|
||||||
|
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
|
||||||
|
Les logos du département se substituent aux logos de même nom définis globalement:</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="departement">
|
||||||
|
<h2>Logos généraux</h2>
|
||||||
|
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
|
||||||
|
mais peuvent être redéfinies localement au niveau de chaque département
|
||||||
|
(il suffit de définir un logo local de même nom)</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ render_logos(dept_form) }}
|
||||||
|
{{ render_add_logo(dept_form.add_logo.form) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -19,102 +19,53 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro render_add_logo(add_logo_form) %}
|
|
||||||
<div class="logo-add">
|
|
||||||
<h3>Ajouter un logo</h3>
|
|
||||||
{{ add_logo_form.hidden_tag() }}
|
|
||||||
{{ render_field(add_logo_form.name) }}
|
|
||||||
{{ render_field(add_logo_form.upload) }}
|
|
||||||
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro render_logo(dept_form, logo_form) %}
|
|
||||||
<div class="logo-edit">
|
|
||||||
{{ logo_form.hidden_tag() }}
|
|
||||||
{% if logo_form.titre %}
|
|
||||||
<tr class="logo-edit">
|
|
||||||
<td colspan="3" class="titre">
|
|
||||||
<div class="nom"><h3>{{ logo_form.titre }}</h3></div>
|
|
||||||
<div class="description">{{ logo_form.description or "" }}</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% else %}
|
|
||||||
<tr class="logo-edit">
|
|
||||||
<td colspan="3" class="titre">
|
|
||||||
<span class="nom"><h3>Logo personalisé: {{ logo_form.logo_id.data }}</h3></span>
|
|
||||||
<span class="description">{{ logo_form.description or "" }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<td style="padding-right: 20px; ">
|
|
||||||
<div class="img-container">
|
|
||||||
<img src="{{ logo_form.logo.get_url_small() }}" alt="pas de logo chargé" /></div>
|
|
||||||
</td><td class="img-data">
|
|
||||||
<h3>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h3>
|
|
||||||
Taille: {{ logo_form.logo.size }} px
|
|
||||||
{% if logo_form.logo.mm %} / {{ logo_form.logo.mm }} mm {% endif %}<br/>
|
|
||||||
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br/>
|
|
||||||
Usage: <span style="font-family: system-ui">{{ logo_form.logo.get_usage() }}</span>
|
|
||||||
</td><td class=""img-action">
|
|
||||||
<p>Modifier l'image</p>
|
|
||||||
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
|
|
||||||
{% if logo_form.can_delete %}
|
|
||||||
<p>Supprimer l'image</p>
|
|
||||||
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro render_logos(dept_form) %}
|
|
||||||
<table>
|
|
||||||
{% for logo_entry in dept_form.logos.entries %}
|
|
||||||
{% set logo_form = logo_entry.form %}
|
|
||||||
{{ render_logo(dept_form, logo_form) }}
|
|
||||||
{% else %}
|
|
||||||
<p class="logo-edit"><h3>Aucun logo défini en propre à ce département</h3></p>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
|
|
||||||
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
|
||||||
<script src="/ScoDoc/static/js/configuration.js"></script>
|
|
||||||
|
|
||||||
<form id="configuration_form" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
<form id="configuration_form" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
<div class="configuration_logo">
|
|
||||||
<h1>Configuration générale</h1>
|
<h1>Configuration générale</h1>
|
||||||
<div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div>
|
<div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div>
|
||||||
{{ render_field(form.bonus_sport_func_name, onChange="submit_form()")}}
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="bonus_description"></div>
|
||||||
|
|
||||||
|
<h1>Gestion des images: logos, signatures, ...</h1>
|
||||||
|
<div class="sco_help">Ces images peuvent être intégrées dans les documents
|
||||||
|
générés par ScoDoc: bulletins, PV, etc.</div>
|
||||||
|
<p><a href="{{url_for('scodoc.configure_logos')}}">configuration des images et logos</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>Exports Apogée</h1>
|
||||||
|
<p><a href="{{url_for('scodoc.config_codes_decisions')}}">configuration des codes de décision</a></p>
|
||||||
|
|
||||||
<h1>Bibliothèque de logos</h1>
|
|
||||||
{% for dept_entry in form.depts.entries %}
|
|
||||||
{% set dept_form = dept_entry.form %}
|
|
||||||
{{ dept_entry.form.hidden_tag() }}
|
|
||||||
{% if dept_entry.form.is_local() %}
|
|
||||||
<div class="departement">
|
|
||||||
<h2>Département {{ dept_form.dept_name.data }}</h2>
|
|
||||||
<h3>Logos locaux</h3>
|
|
||||||
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br/>
|
|
||||||
Les logos du département se substituent aux logos de même nom définis globalement:</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="departement">
|
|
||||||
<h2>Logos généraux</h2>
|
|
||||||
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
|
|
||||||
mais peuvent être redéfinies localement au niveau de chaque département
|
|
||||||
(il suffit de définir un logo local de même nom)</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{{ render_logos(dept_form) }}
|
|
||||||
{{ render_add_logo(dept_form.add_logo.form) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function update_bonus_description() {
|
||||||
|
var query = "/ScoDoc/get_bonus_description/" + $("#configuration_form select")[0].value;
|
||||||
|
$.get(query, '', function (data) {
|
||||||
|
$("#bonus_description").html(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function()
|
||||||
|
{
|
||||||
|
$("#configuration_form select").change(function(){
|
||||||
|
update_bonus_description();
|
||||||
|
});
|
||||||
|
|
||||||
|
update_bonus_description();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -71,12 +71,12 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if editable and formation.ues.count() and formation.ues[0].matieres.count() %}
|
{% if editable and matiere_parent %}
|
||||||
<li><a class="stdlink" href="{{
|
<li><a class="stdlink" href="{{
|
||||||
url_for("notes.module_create",
|
url_for("notes.module_create",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
module_type=module_type|int,
|
module_type=module_type|int,
|
||||||
matiere_id=formation.ues[0].matieres.first().id
|
matiere_id=matiere_parent.id
|
||||||
)}}"
|
)}}"
|
||||||
>{{create_element_msg}}</a>
|
>{{create_element_msg}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
||||||
|
|
||||||
<span class="ue_type_{{ue.type}}">
|
<span class="ue_type_{{ue.type}}">
|
||||||
|
<span class="ue_color_indicator" style="background:{{
|
||||||
|
ue.color if ue.color is not none else 'blue'}}"></span>
|
||||||
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
<b>{{ue.acronyme}}</b> <a class="discretelink" href="{{
|
||||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||||
>{{ue.titre}}</a>
|
>{{ue.titre}}</a>
|
||||||
|
@ -24,4 +24,24 @@
|
|||||||
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
<a href="https://scodoc.org/BUT" target="_blank">la documentation</a>.
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|
||||||
|
{% if formsemestres %}
|
||||||
|
<p class="help">
|
||||||
|
Ce module est utilisé dans des semestres déjà mis en place, il faut prêter attention
|
||||||
|
aux conséquences des changements effectués ici: par exemple les coefficients vont modifier
|
||||||
|
les notes moyennes calculées. Les modules déjà utilisés ne peuvent pas être changés de semestre, ni détruits.
|
||||||
|
Si vous souhaitez faire cela, allez d'abord modifier les semestres concernés pour déselectionner le module.
|
||||||
|
</p>
|
||||||
|
<h4>Semestres utilisant ce module:</h4>
|
||||||
|
<ul>
|
||||||
|
{%for formsemestre in formsemestres %}
|
||||||
|
<li><a class="stdlink" href="{{
|
||||||
|
url_for('notes.formsemestre_status',
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
|
}}">{{formsemestre.titre_mois()}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{%endif%}
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -290,20 +290,26 @@ def formsemestre_bulletinetud(
|
|||||||
if etudid:
|
if etudid:
|
||||||
etud = models.Identite.query.get_or_404(etudid)
|
etud = models.Identite.query.get_or_404(etudid)
|
||||||
elif code_nip:
|
elif code_nip:
|
||||||
etud = models.Identite.query.filter_by(
|
etud = (
|
||||||
code_nip=str(code_nip)
|
models.Identite.query.filter_by(code_nip=str(code_nip))
|
||||||
).first_or_404()
|
.filter_by(dept_id=formsemestre.dept_id)
|
||||||
|
.first_or_404()
|
||||||
|
)
|
||||||
elif code_ine:
|
elif code_ine:
|
||||||
etud = models.Identite.query.filter_by(
|
etud = (
|
||||||
code_ine=str(code_ine)
|
models.Identite.query.filter_by(code_ine=str(code_ine))
|
||||||
).first_or_404()
|
.filter_by(dept_id=formsemestre.dept_id)
|
||||||
|
.first_or_404()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
|
||||||
)
|
)
|
||||||
if format == "json":
|
if format == "json":
|
||||||
r = bulletin_but.BulletinBUT(formsemestre)
|
r = bulletin_but.BulletinBUT(formsemestre)
|
||||||
return jsonify(r.bulletin_etud(etud, formsemestre))
|
return jsonify(
|
||||||
|
r.bulletin_etud(etud, formsemestre, force_publishing=force_publishing)
|
||||||
|
)
|
||||||
elif format == "html":
|
elif format == "html":
|
||||||
return render_template(
|
return render_template(
|
||||||
"but/bulletin.html",
|
"but/bulletin.html",
|
||||||
@ -314,6 +320,7 @@ def formsemestre_bulletinetud(
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
format="json",
|
format="json",
|
||||||
|
force_publishing=1, # pour ScoDoc lui même
|
||||||
),
|
),
|
||||||
sco=ScoData(),
|
sco=ScoData(),
|
||||||
)
|
)
|
||||||
|
@ -32,50 +32,40 @@ Emmanuel Viennet, 2021
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import io
|
import io
|
||||||
|
import re
|
||||||
import wtforms.validators
|
|
||||||
|
|
||||||
from app.auth.models import User
|
|
||||||
import os
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import abort, flash, url_for, redirect, render_template, send_file
|
from flask import abort, flash, url_for, redirect, render_template, send_file
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask.app import Flask
|
|
||||||
import flask_login
|
import flask_login
|
||||||
from flask_login.utils import login_required, current_user
|
from flask_login.utils import login_required, current_user
|
||||||
from flask_wtf import FlaskForm
|
from PIL import Image as PILImage
|
||||||
from flask_wtf.file import FileField, FileAllowed
|
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, NotFound
|
||||||
from wtforms import SelectField, SubmitField, FormField, validators, Form, FieldList
|
|
||||||
from wtforms.fields import IntegerField
|
|
||||||
from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField
|
|
||||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
|
||||||
|
|
||||||
import app
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.forms.main import config_forms
|
from app.auth.models import User
|
||||||
|
from app.forms.main import config_logos, config_main
|
||||||
from app.forms.main.create_dept import CreateDeptForm
|
from app.forms.main.create_dept import CreateDeptForm
|
||||||
|
from app.forms.main.config_apo import CodesDecisionsForm
|
||||||
|
from app import models
|
||||||
from app.models import Departement, Identite
|
from app.models import Departement, Identite
|
||||||
from app.models import departements
|
from app.models import departements
|
||||||
from app.models import FormSemestre, FormSemestreInscription
|
from app.models import FormSemestre, FormSemestreInscription
|
||||||
import sco_version
|
from app.models import ScoDocSiteConfig
|
||||||
from app.scodoc import sco_logos
|
from app.scodoc import sco_codes_parcours, sco_logos
|
||||||
from app.scodoc import sco_find_etud
|
from app.scodoc import sco_find_etud
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
admin_required,
|
admin_required,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
scodoc,
|
scodoc,
|
||||||
permission_required_compat_scodoc7,
|
|
||||||
permission_required,
|
|
||||||
)
|
)
|
||||||
from app.scodoc.sco_exceptions import AccessDenied
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
from app.scodoc.sco_logos import find_logo
|
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import scodoc_bp as bp
|
from app.views import scodoc_bp as bp
|
||||||
|
import sco_version
|
||||||
from PIL import Image as PILImage
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
@ -133,6 +123,28 @@ def toggle_dept_vis(dept_id):
|
|||||||
return redirect(url_for("scodoc.index"))
|
return redirect(url_for("scodoc.index"))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
|
||||||
|
@admin_required
|
||||||
|
def config_codes_decisions():
|
||||||
|
"""Form config codes decisions"""
|
||||||
|
form = CodesDecisionsForm()
|
||||||
|
if request.method == "POST" and form.cancel.data: # cancel button
|
||||||
|
return redirect(url_for("scodoc.index"))
|
||||||
|
if form.validate_on_submit():
|
||||||
|
for code in models.config.CODES_SCODOC_TO_APO:
|
||||||
|
ScoDocSiteConfig.set_code_apo(code, getattr(form, code).data)
|
||||||
|
flash(f"Codes décisions enregistrés.")
|
||||||
|
return redirect(url_for("scodoc.index"))
|
||||||
|
elif request.method == "GET":
|
||||||
|
for code in models.config.CODES_SCODOC_TO_APO:
|
||||||
|
getattr(form, code).data = ScoDocSiteConfig.get_code_apo(code)
|
||||||
|
return render_template(
|
||||||
|
"config_codes_decisions.html",
|
||||||
|
form=form,
|
||||||
|
title="Configuration des codes de décisions",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def table_etud_in_accessible_depts():
|
def table_etud_in_accessible_depts():
|
||||||
@ -239,10 +251,37 @@ def about(scodoc_dept=None):
|
|||||||
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
|
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def configuration():
|
def configuration():
|
||||||
auth_name = str(current_user)
|
"Page de configuration globale"
|
||||||
if not current_user.is_administrator():
|
if not current_user.is_administrator():
|
||||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % current_user)
|
||||||
return config_forms.configuration()
|
return config_main.configuration()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/get_bonus_description/<bonus_name>", methods=["GET"])
|
||||||
|
def get_bonus_description(bonus_name: str):
|
||||||
|
"description text/html du bonus"
|
||||||
|
if bonus_name == "default":
|
||||||
|
bonus_name = ""
|
||||||
|
bonus_class = ScoDocSiteConfig.get_bonus_sport_class_from_name(bonus_name)
|
||||||
|
text = bonus_class.__doc__
|
||||||
|
fields = re.split(r"\n\n", text, maxsplit=1)
|
||||||
|
if len(fields) > 1:
|
||||||
|
first_line, text = fields
|
||||||
|
else:
|
||||||
|
first_line, text = "", fields[0]
|
||||||
|
|
||||||
|
return f"""<div class="bonus_description_head">{first_line}</div>
|
||||||
|
<div>{text}</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/configure_logos", methods=["GET", "POST"])
|
||||||
|
@admin_required
|
||||||
|
def configure_logos():
|
||||||
|
"Page de configuration des logos (globale)"
|
||||||
|
if not current_user.is_administrator():
|
||||||
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % current_user)
|
||||||
|
return config_logos.config_logos()
|
||||||
|
|
||||||
|
|
||||||
SMALL_SIZE = (200, 200)
|
SMALL_SIZE = (200, 200)
|
||||||
@ -257,14 +296,16 @@ def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
|
|||||||
suffix = logo.suffix
|
suffix = logo.suffix
|
||||||
if small:
|
if small:
|
||||||
with PILImage.open(logo.filepath) as im:
|
with PILImage.open(logo.filepath) as im:
|
||||||
im.thumbnail(SMALL_SIZE)
|
|
||||||
stream = io.BytesIO()
|
|
||||||
# on garde le même format (on pourrait plus simplement générer systématiquement du JPEG)
|
# on garde le même format (on pourrait plus simplement générer systématiquement du JPEG)
|
||||||
fmt = { # adapt suffix to be compliant with PIL save format
|
fmt = { # adapt suffix to be compliant with PIL save format
|
||||||
"PNG": "PNG",
|
"PNG": "PNG",
|
||||||
"JPG": "JPEG",
|
"JPG": "JPEG",
|
||||||
"JPEG": "JPEG",
|
"JPEG": "JPEG",
|
||||||
}[suffix.upper()]
|
}[suffix.upper()]
|
||||||
|
if fmt == "JPEG":
|
||||||
|
im = im.convert("RGB")
|
||||||
|
im.thumbnail(SMALL_SIZE)
|
||||||
|
stream = io.BytesIO()
|
||||||
im.save(stream, fmt)
|
im.save(stream, fmt)
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
return send_file(stream, mimetype=f"image/{fmt}")
|
return send_file(stream, mimetype=f"image/{fmt}")
|
||||||
|
@ -62,7 +62,7 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
|
|
||||||
from app.scodoc import html_sco_header, sco_import_users, sco_excel
|
from app.scodoc import html_sco_header, sco_import_users, sco_excel, sco_roles_default
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
@ -81,7 +81,7 @@ _l = _
|
|||||||
class ChangePasswordForm(FlaskForm):
|
class ChangePasswordForm(FlaskForm):
|
||||||
user_name = HiddenField()
|
user_name = HiddenField()
|
||||||
old_password = PasswordField(_l("Identifiez-vous"))
|
old_password = PasswordField(_l("Identifiez-vous"))
|
||||||
new_password = PasswordField(_l("Nouveau mot de passe"))
|
new_password = PasswordField(_l("Nouveau mot de passe de l'utilisateur"))
|
||||||
bis_password = PasswordField(
|
bis_password = PasswordField(
|
||||||
_l("Répéter"),
|
_l("Répéter"),
|
||||||
validators=[
|
validators=[
|
||||||
@ -150,11 +150,12 @@ def user_info(user_name, format="json"):
|
|||||||
@permission_required(Permission.ScoUsersAdmin)
|
@permission_required(Permission.ScoUsersAdmin)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def create_user_form(user_name=None, edit=0, all_roles=1):
|
def create_user_form(user_name=None, edit=0, all_roles=1):
|
||||||
"form. création ou edition utilisateur"
|
"form. création ou édition utilisateur"
|
||||||
if user_name is not None: # scodoc7func converti en int !
|
if user_name is not None: # scodoc7func converti en int !
|
||||||
user_name = str(user_name)
|
user_name = str(user_name)
|
||||||
|
Role.insert_roles() # assure la mise à jour des rôles en base
|
||||||
auth_dept = current_user.dept
|
auth_dept = current_user.dept
|
||||||
from_mail = current_user.email
|
from_mail = current_app.config["SCODOC_MAIL_FROM"] # current_user.email
|
||||||
initvalues = {}
|
initvalues = {}
|
||||||
edit = int(edit)
|
edit = int(edit)
|
||||||
all_roles = int(all_roles)
|
all_roles = int(all_roles)
|
||||||
@ -191,7 +192,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
else:
|
else:
|
||||||
# Les rôles standards créés à l'initialisation de ScoDoc:
|
# Les rôles standards créés à l'initialisation de ScoDoc:
|
||||||
standard_roles = [
|
standard_roles = [
|
||||||
Role.get_named_role(r) for r in ("Ens", "Secr", "Admin", "RespPe")
|
Role.get_named_role(r) for r in sco_roles_default.ROLES_ATTRIBUABLES_DEPT
|
||||||
]
|
]
|
||||||
# Départements auxquels ont peut associer des rôles via ce dialogue:
|
# Départements auxquels ont peut associer des rôles via ce dialogue:
|
||||||
# si SuperAdmin, tous les rôles standards dans tous les départements
|
# si SuperAdmin, tous les rôles standards dans tous les départements
|
||||||
@ -215,6 +216,11 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
editable_roles_set = {
|
editable_roles_set = {
|
||||||
(r, dept) for r in standard_roles for dept in administrable_dept_acronyms
|
(r, dept) for r in standard_roles for dept in administrable_dept_acronyms
|
||||||
}
|
}
|
||||||
|
if current_user.is_administrator():
|
||||||
|
editable_roles_set |= {
|
||||||
|
(Role.get_named_role(r), "")
|
||||||
|
for r in sco_roles_default.ROLES_ATTRIBUABLES_SCODOC
|
||||||
|
}
|
||||||
#
|
#
|
||||||
if not edit:
|
if not edit:
|
||||||
submitlabel = "Créer utilisateur"
|
submitlabel = "Créer utilisateur"
|
||||||
@ -577,8 +583,8 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
# A: envoi de welcome + procedure de reset
|
# A: envoi de welcome + procedure de reset
|
||||||
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
||||||
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
||||||
if vals["welcome"] == "1":
|
if vals["welcome"] != "1":
|
||||||
if vals["reset_password:list"] == "1":
|
if vals["reset_password"] != "1":
|
||||||
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
||||||
else:
|
else:
|
||||||
mode = Mode.WELCOME_ONLY
|
mode = Mode.WELCOME_ONLY
|
||||||
|
@ -26,6 +26,9 @@ class Config:
|
|||||||
SCODOC_ADMIN_LOGIN = os.environ.get("SCODOC_ADMIN_LOGIN") or "admin"
|
SCODOC_ADMIN_LOGIN = os.environ.get("SCODOC_ADMIN_LOGIN") or "admin"
|
||||||
ADMINS = [SCODOC_ADMIN_MAIL]
|
ADMINS = [SCODOC_ADMIN_MAIL]
|
||||||
SCODOC_ERR_MAIL = os.environ.get("SCODOC_ERR_MAIL")
|
SCODOC_ERR_MAIL = os.environ.get("SCODOC_ERR_MAIL")
|
||||||
|
# Le "from" des mails émis. Attention: peut être remplacée par la préférence email_from_addr:
|
||||||
|
SCODOC_MAIL_FROM = os.environ.get("SCODOC_MAIL_FROM") or ("no-reply@" + MAIL_SERVER)
|
||||||
|
|
||||||
BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL")
|
BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL")
|
||||||
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
|
SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc")
|
||||||
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data")
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
"""augmente taille codes Apogée
|
||||||
|
|
||||||
|
Revision ID: 28874ed6af64
|
||||||
|
Revises: f40fbaf5831c
|
||||||
|
Create Date: 2022-01-19 22:57:59.678313
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "28874ed6af64"
|
||||||
|
down_revision = "f40fbaf5831c"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
|
op.alter_column(
|
||||||
|
"notes_formsemestre_etapes",
|
||||||
|
"etape_apo",
|
||||||
|
existing_type=sa.VARCHAR(length=24),
|
||||||
|
type_=sa.String(length=512),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_formsemestre_inscription",
|
||||||
|
"etape",
|
||||||
|
existing_type=sa.VARCHAR(length=24),
|
||||||
|
type_=sa.String(length=512),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_modules",
|
||||||
|
"code_apogee",
|
||||||
|
existing_type=sa.VARCHAR(length=24),
|
||||||
|
type_=sa.String(length=512),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_ue",
|
||||||
|
"code_apogee",
|
||||||
|
existing_type=sa.VARCHAR(length=24),
|
||||||
|
type_=sa.String(length=512),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"notes_ue",
|
||||||
|
"code_apogee",
|
||||||
|
existing_type=sa.String(length=512),
|
||||||
|
type_=sa.VARCHAR(length=24),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_modules",
|
||||||
|
"code_apogee",
|
||||||
|
existing_type=sa.String(length=512),
|
||||||
|
type_=sa.VARCHAR(length=24),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_formsemestre_inscription",
|
||||||
|
"etape",
|
||||||
|
existing_type=sa.String(length=512),
|
||||||
|
type_=sa.VARCHAR(length=24),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"notes_formsemestre_etapes",
|
||||||
|
"etape_apo",
|
||||||
|
existing_type=sa.String(length=512),
|
||||||
|
type_=sa.VARCHAR(length=24),
|
||||||
|
existing_nullable=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
28
migrations/versions/c95d5a3bf0de_couleur_ue.py
Normal file
28
migrations/versions/c95d5a3bf0de_couleur_ue.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""couleur UE
|
||||||
|
|
||||||
|
Revision ID: c95d5a3bf0de
|
||||||
|
Revises: 28874ed6af64
|
||||||
|
Create Date: 2022-01-24 21:44:55.205544
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "c95d5a3bf0de"
|
||||||
|
down_revision = "28874ed6af64"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("notes_ue", sa.Column("color", sa.Text(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("notes_ue", "color")
|
||||||
|
# ### end Alembic commands ###
|
28
scodoc.py
28
scodoc.py
@ -21,9 +21,8 @@ from app import clear_scodoc_cache
|
|||||||
from app import models
|
from app import models
|
||||||
|
|
||||||
from app.auth.models import User, Role, UserRole
|
from app.auth.models import User, Role, UserRole
|
||||||
from app.models import ScoPreference
|
|
||||||
from app.scodoc.sco_logos import make_logo_local
|
from app.scodoc.sco_logos import make_logo_local
|
||||||
from app.models import Formation, UniteEns, Module
|
from app.models import Formation, UniteEns, Matiere, Module
|
||||||
from app.models import FormSemestre, FormSemestreInscription
|
from app.models import FormSemestre, FormSemestreInscription
|
||||||
from app.models import ModuleImpl, ModuleImplInscription
|
from app.models import ModuleImpl, ModuleImplInscription
|
||||||
from app.models import Identite
|
from app.models import Identite
|
||||||
@ -63,6 +62,7 @@ def make_shell_context():
|
|||||||
"logout_user": logout_user,
|
"logout_user": logout_user,
|
||||||
"mapp": mapp,
|
"mapp": mapp,
|
||||||
"models": models,
|
"models": models,
|
||||||
|
"Matiere": Matiere,
|
||||||
"Module": Module,
|
"Module": Module,
|
||||||
"ModuleImpl": ModuleImpl,
|
"ModuleImpl": ModuleImpl,
|
||||||
"ModuleImplInscription": ModuleImplInscription,
|
"ModuleImplInscription": ModuleImplInscription,
|
||||||
@ -133,7 +133,7 @@ def user_create(username, role, dept, nom=None, prenom=None): # user-create
|
|||||||
"Create a new user"
|
"Create a new user"
|
||||||
r = Role.get_named_role(role)
|
r = Role.get_named_role(role)
|
||||||
if not r:
|
if not r:
|
||||||
sys.stderr.write("user_create: role {r} does not exists\n".format(r=role))
|
sys.stderr.write("user_create: role {r} does not exist\n".format(r=role))
|
||||||
return 1
|
return 1
|
||||||
u = User.query.filter_by(user_name=username).first()
|
u = User.query.filter_by(user_name=username).first()
|
||||||
if u:
|
if u:
|
||||||
@ -289,20 +289,28 @@ def user_role(username, dept_acronym=None, add_role_name=None, remove_role_name=
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def abort_if_false(ctx, param, value):
|
||||||
|
if not value:
|
||||||
|
ctx.abort()
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
|
@click.option(
|
||||||
|
"--yes",
|
||||||
|
is_flag=True,
|
||||||
|
callback=abort_if_false,
|
||||||
|
expose_value=False,
|
||||||
|
prompt=f"""Attention: Cela va effacer toutes les données du département
|
||||||
|
(étudiants, notes, formations, etc)
|
||||||
|
Voulez-vous vraiment continuer ?
|
||||||
|
""",
|
||||||
|
)
|
||||||
@click.argument("dept")
|
@click.argument("dept")
|
||||||
def delete_dept(dept): # delete-dept
|
def delete_dept(dept): # delete-dept
|
||||||
"""Delete existing departement"""
|
"""Delete existing departement"""
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc import sco_dept
|
from app.scodoc import sco_dept
|
||||||
|
|
||||||
click.confirm(
|
|
||||||
f"""Attention: Cela va effacer toutes les données du département {dept}
|
|
||||||
(étudiants, notes, formations, etc)
|
|
||||||
Voulez-vous vraiment continuer ?
|
|
||||||
""",
|
|
||||||
abort=True,
|
|
||||||
)
|
|
||||||
db.reflect()
|
db.reflect()
|
||||||
ndb.open_db_connection()
|
ndb.open_db_connection()
|
||||||
d = models.Departement.query.filter_by(acronym=dept).first()
|
d = models.Departement.query.filter_by(acronym=dept).first()
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
% ************************************************************
|
% ************************************************************
|
||||||
% En-tête de l'avis
|
% En-tête de l'avis
|
||||||
% ************************************************************
|
% ************************************************************
|
||||||
\begin{entete}{logos/logo_header}
|
\begin{entete}{logos/header}
|
||||||
\textbf{\Huge{Avis de Poursuites d'Etudes}} \\
|
\textbf{\Huge{Avis de Poursuites d'Etudes}} \\
|
||||||
\ligne \\
|
\ligne \\
|
||||||
\normalsize{Département **DeptFullName**} \\
|
\normalsize{Département **DeptFullName**} \\
|
||||||
|
@ -170,6 +170,11 @@ def import_scodoc7_dept(dept_id: str, dept_db_uri=None):
|
|||||||
logging.info(f"connecting to database {dept_db_uri}")
|
logging.info(f"connecting to database {dept_db_uri}")
|
||||||
cnx = psycopg2.connect(dept_db_uri)
|
cnx = psycopg2.connect(dept_db_uri)
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
|
# FIX : des dates aberrantes (dans le futur) peuvent tenir en SQL mais pas en Python
|
||||||
|
cursor.execute(
|
||||||
|
"""UPDATE scolar_events SET event_date='2021-09-30' WHERE event_date > '2200-01-01'"""
|
||||||
|
)
|
||||||
|
cnx.commit()
|
||||||
# Create dept:
|
# Create dept:
|
||||||
dept = models.Departement(acronym=dept_id, description="migré de ScoDoc7")
|
dept = models.Departement(acronym=dept_id, description="migré de ScoDoc7")
|
||||||
db.session.add(dept)
|
db.session.add(dept)
|
||||||
@ -374,6 +379,8 @@ def convert_object(
|
|||||||
new_ref = id_from_scodoc7[old_ref]
|
new_ref = id_from_scodoc7[old_ref]
|
||||||
elif (not is_table) and table_name in {
|
elif (not is_table) and table_name in {
|
||||||
"scolog",
|
"scolog",
|
||||||
|
"entreprise_correspondant",
|
||||||
|
"entreprise_contact",
|
||||||
"etud_annotations",
|
"etud_annotations",
|
||||||
"notes_notes_log",
|
"notes_notes_log",
|
||||||
"scolar_news",
|
"scolar_news",
|
||||||
@ -389,7 +396,6 @@ def convert_object(
|
|||||||
new_ref = None
|
new_ref = None
|
||||||
elif is_table and table_name in {
|
elif is_table and table_name in {
|
||||||
"notes_semset_formsemestre",
|
"notes_semset_formsemestre",
|
||||||
"entreprise_contact",
|
|
||||||
}:
|
}:
|
||||||
# pour anciennes installs où des relations n'avait pas été déclarées clés étrangères
|
# pour anciennes installs où des relations n'avait pas été déclarées clés étrangères
|
||||||
# eg: notes_semset_formsemestre.semset_id n'était pas une clé
|
# eg: notes_semset_formsemestre.semset_id n'était pas une clé
|
||||||
|
Loading…
Reference in New Issue
Block a user