forked from ScoDoc/ScoDoc
Compare commits
37 Commits
fdefaa6e1f
...
9d7a75afe4
Author | SHA1 | Date | |
---|---|---|---|
9d7a75afe4 | |||
b6a3bd2388 | |||
e0be0f8fee | |||
b76f44d9f0 | |||
2349d404c8 | |||
8f390cb415 | |||
1a472bd19d | |||
dc9bba3f04 | |||
b26adbeef1 | |||
5f2021d6c1 | |||
2ef0984a91 | |||
78e8b407d2 | |||
4d2d2f20fb | |||
065bcfe1a0 | |||
41b50e0c1c | |||
6fd234ef85 | |||
45a8b31cf1 | |||
8dee5842e1 | |||
25deaaae7f | |||
8ff6a561aa | |||
38d4746f74 | |||
a7ef5d81c5 | |||
5684f57ca5 | |||
0fe5cdb409 | |||
2a6d63cf03 | |||
178016b682 | |||
07d381931a | |||
9477106e9a | |||
0beea0dfc6 | |||
5024658a87 | |||
3b28056278 | |||
4b6f91b304 | |||
adaef9fd24 | |||
dce7dc42cb | |||
d740244d25 | |||
93d026d2ac | |||
30eb738c25 |
@ -199,6 +199,10 @@ def create_app(config_class=DevConfig):
|
|||||||
|
|
||||||
app.register_blueprint(auth_bp, url_prefix="/auth")
|
app.register_blueprint(auth_bp, url_prefix="/auth")
|
||||||
|
|
||||||
|
from app.entreprises import bp as entreprises_bp
|
||||||
|
|
||||||
|
app.register_blueprint(entreprises_bp, url_prefix="/ScoDoc/entreprises")
|
||||||
|
|
||||||
from app.views import scodoc_bp
|
from app.views import scodoc_bp
|
||||||
from app.views import scolar_bp
|
from app.views import scolar_bp
|
||||||
from app.views import notes_bp
|
from app.views import notes_bp
|
||||||
|
@ -4,118 +4,41 @@
|
|||||||
# See LICENSE
|
# See LICENSE
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from collections import defaultdict
|
"""Génération bulletin BUT
|
||||||
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
import numpy as np
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from app import db
|
|
||||||
|
|
||||||
from app.comp import moy_ue, moy_sem, inscr_mod
|
|
||||||
from app.models import ModuleImpl
|
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_cache import ResultatsSemestreBUTCache
|
|
||||||
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_utils import jsnan, fmt_note
|
from app.scodoc.sco_utils import fmt_note
|
||||||
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreBUT:
|
class BulletinBUT(ResultatsSemestreBUT):
|
||||||
"""Structure légère pour stocker les résultats du semestre et
|
"""Génération du bulletin BUT.
|
||||||
générer les bulletins.
|
Cette classe génère des dictionnaires avec toutes les informations
|
||||||
__init__ : charge depuis le cache ou calcule
|
du bulletin, qui sont immédiatement traduisibles en JSON.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_cached_attrs = (
|
|
||||||
"sem_cube",
|
|
||||||
"modimpl_inscr_df",
|
|
||||||
"modimpl_coefs_df",
|
|
||||||
"etud_moy_ue",
|
|
||||||
"modimpls_evals_poids",
|
|
||||||
"modimpls_evals_notes",
|
|
||||||
"etud_moy_gen",
|
|
||||||
"etud_moy_gen_ranks",
|
|
||||||
"modimpls_evaluations_complete",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, formsemestre):
|
|
||||||
self.formsemestre = formsemestre
|
|
||||||
self.ues = formsemestre.query_ues().all()
|
|
||||||
self.modimpls = formsemestre.modimpls.all()
|
|
||||||
self.etuds = self.formsemestre.get_inscrits(include_dem=False)
|
|
||||||
self.etud_index = {e.id: idx for idx, e in enumerate(self.etuds)}
|
|
||||||
self.saes = [
|
|
||||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.SAE
|
|
||||||
]
|
|
||||||
self.ressources = [
|
|
||||||
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
|
||||||
]
|
|
||||||
if not self.load_cached():
|
|
||||||
self.compute()
|
|
||||||
self.store()
|
|
||||||
|
|
||||||
def load_cached(self) -> bool:
|
|
||||||
"Load cached dataframes, returns False si pas en cache"
|
|
||||||
data = ResultatsSemestreBUTCache.get(self.formsemestre.id)
|
|
||||||
if not data:
|
|
||||||
return False
|
|
||||||
for attr in self._cached_attrs:
|
|
||||||
setattr(self, attr, data[attr])
|
|
||||||
return True
|
|
||||||
|
|
||||||
def store(self):
|
|
||||||
"Cache our dataframes"
|
|
||||||
ResultatsSemestreBUTCache.set(
|
|
||||||
self.formsemestre.id,
|
|
||||||
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
|
||||||
)
|
|
||||||
|
|
||||||
def compute(self):
|
|
||||||
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
|
||||||
(
|
|
||||||
self.sem_cube,
|
|
||||||
self.modimpls_evals_poids,
|
|
||||||
self.modimpls_evals_notes,
|
|
||||||
modimpls_evaluations,
|
|
||||||
self.modimpls_evaluations_complete,
|
|
||||||
) = moy_ue.notes_sem_load_cube(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.formsemestre, ues=self.ues, modimpls=self.modimpls
|
|
||||||
)
|
|
||||||
# l'idx de la colonne du mod modimpl.id est
|
|
||||||
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
|
||||||
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
|
||||||
self.etud_moy_ue = moy_ue.compute_ue_moys(
|
|
||||||
self.sem_cube,
|
|
||||||
self.etuds,
|
|
||||||
self.modimpls,
|
|
||||||
self.ues,
|
|
||||||
self.modimpl_inscr_df,
|
|
||||||
self.modimpl_coefs_df,
|
|
||||||
)
|
|
||||||
self.etud_moy_gen = moy_sem.compute_sem_moys(
|
|
||||||
self.etud_moy_ue, self.modimpl_coefs_df
|
|
||||||
)
|
|
||||||
self.etud_moy_gen_ranks = moy_sem.comp_ranks_series(self.etud_moy_gen)
|
|
||||||
|
|
||||||
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
||||||
"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]
|
||||||
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 mi in modimpls:
|
for modimpl in modimpls:
|
||||||
coef = self.modimpl_coefs_df[mi.id][ue.id]
|
coef = self.modimpl_coefs_df[modimpl.id][ue.id]
|
||||||
if coef > 0:
|
if coef > 0:
|
||||||
d[mi.module.code] = {
|
d[modimpl.module.code] = {
|
||||||
"id": mi.id,
|
"id": modimpl.id,
|
||||||
"coef": coef,
|
"coef": coef,
|
||||||
"moyenne": fmt_note(
|
"moyenne": fmt_note(
|
||||||
etud_moy_module[self.modimpl_coefs_df.columns.get_loc(mi.id)][
|
etud_moy_module[
|
||||||
ue_idx
|
self.modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
]
|
][ue_idx]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
@ -149,7 +72,7 @@ class ResultatsSemestreBUT:
|
|||||||
avec évaluations de chacun."""
|
avec évaluations de chacun."""
|
||||||
d = {}
|
d = {}
|
||||||
# etud_idx = self.etud_index[etud.id]
|
# etud_idx = self.etud_index[etud.id]
|
||||||
for mi in modimpls:
|
for modimpl in modimpls:
|
||||||
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
# mod_idx = self.modimpl_coefs_df.columns.get_loc(mi.id)
|
||||||
# # moyennes indicatives (moyennes de moyennes d'UE)
|
# # moyennes indicatives (moyennes de moyennes d'UE)
|
||||||
# try:
|
# try:
|
||||||
@ -163,14 +86,15 @@ class ResultatsSemestreBUT:
|
|||||||
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
# moy_indicative_mod = np.nanmean(self.sem_cube[etud_idx, mod_idx])
|
||||||
# except RuntimeWarning: # all nans in np.nanmean
|
# except RuntimeWarning: # all nans in np.nanmean
|
||||||
# pass
|
# pass
|
||||||
d[mi.module.code] = {
|
modimpl_results = self.modimpls_results[modimpl.id]
|
||||||
"id": mi.id,
|
d[modimpl.module.code] = {
|
||||||
"titre": mi.module.titre,
|
"id": modimpl.id,
|
||||||
"code_apogee": mi.module.code_apogee,
|
"titre": modimpl.module.titre,
|
||||||
|
"code_apogee": modimpl.module.code_apogee,
|
||||||
"url": url_for(
|
"url": url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
moduleimpl_id=mi.id,
|
moduleimpl_id=modimpl.id,
|
||||||
),
|
),
|
||||||
"moyenne": {
|
"moyenne": {
|
||||||
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
# # moyenne indicative de module: moyenne des UE, ignorant celles sans notes (nan)
|
||||||
@ -181,16 +105,17 @@ class ResultatsSemestreBUT:
|
|||||||
},
|
},
|
||||||
"evaluations": [
|
"evaluations": [
|
||||||
self.etud_eval_results(etud, e)
|
self.etud_eval_results(etud, e)
|
||||||
for eidx, e in enumerate(mi.evaluations)
|
for e in modimpl.evaluations
|
||||||
if e.visibulletin
|
if e.visibulletin
|
||||||
and self.modimpls_evaluations_complete[mi.id][eidx]
|
and modimpl_results.evaluations_etat[e.id].is_complete
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def etud_eval_results(self, etud, e) -> dict:
|
def etud_eval_results(self, etud, e) -> dict:
|
||||||
"dict resultats d'un étudiant à une évaluation"
|
"dict resultats d'un étudiant à une évaluation"
|
||||||
eval_notes = self.modimpls_evals_notes[e.moduleimpl_id][e.id] # pd.Series
|
# eval_notes est une pd.Series avec toutes les notes des étudiants inscrits
|
||||||
|
eval_notes = self.modimpls_results[e.moduleimpl_id].evals_notes[e.id]
|
||||||
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
notes_ok = eval_notes.where(eval_notes > scu.NOTES_ABSENCE).dropna()
|
||||||
d = {
|
d = {
|
||||||
"id": e.id,
|
"id": e.id,
|
||||||
@ -202,7 +127,7 @@ class ResultatsSemestreBUT:
|
|||||||
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
"poids": {p.ue.acronyme: p.poids for p in e.ue_poids},
|
||||||
"note": {
|
"note": {
|
||||||
"value": fmt_note(
|
"value": fmt_note(
|
||||||
self.modimpls_evals_notes[e.moduleimpl_id][e.id][etud.id],
|
eval_notes[etud.id],
|
||||||
note_max=e.note_max,
|
note_max=e.note_max,
|
||||||
),
|
),
|
||||||
"min": fmt_note(notes_ok.min()),
|
"min": fmt_note(notes_ok.min()),
|
||||||
@ -233,7 +158,7 @@ class ResultatsSemestreBUT:
|
|||||||
},
|
},
|
||||||
"formsemestre_id": formsemestre.id,
|
"formsemestre_id": formsemestre.id,
|
||||||
"etat_inscription": etat_inscription,
|
"etat_inscription": etat_inscription,
|
||||||
"options": bulletin_option_affichage(formsemestre),
|
"options": sco_preferences.bulletin_option_affichage(formsemestre.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],
|
||||||
@ -244,8 +169,8 @@ class ResultatsSemestreBUT:
|
|||||||
"numero": formsemestre.semestre_id,
|
"numero": formsemestre.semestre_id,
|
||||||
"groupes": [], # XXX TODO
|
"groupes": [], # XXX TODO
|
||||||
"absences": { # XXX TODO
|
"absences": { # XXX TODO
|
||||||
"injustifie": 1,
|
"injustifie": -1,
|
||||||
"total": 33,
|
"total": -1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
semestre_infos.update(
|
semestre_infos.update(
|
||||||
@ -298,125 +223,3 @@ class ResultatsSemestreBUT:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def bulletin_option_affichage(formsemestre):
|
|
||||||
"dict avec les options d'affichages (préférences) pour ce semestre"
|
|
||||||
prefs = sco_preferences.SemPreferences(formsemestre.id)
|
|
||||||
fields = (
|
|
||||||
"bul_show_abs",
|
|
||||||
"bul_show_abs_modules",
|
|
||||||
"bul_show_ects",
|
|
||||||
"bul_show_codemodules",
|
|
||||||
"bul_show_matieres",
|
|
||||||
"bul_show_rangs",
|
|
||||||
"bul_show_ue_rangs",
|
|
||||||
"bul_show_mod_rangs",
|
|
||||||
"bul_show_moypromo",
|
|
||||||
"bul_show_minmax",
|
|
||||||
"bul_show_minmax_mod",
|
|
||||||
"bul_show_minmax_eval",
|
|
||||||
"bul_show_coef",
|
|
||||||
"bul_show_ue_cap_details",
|
|
||||||
"bul_show_ue_cap_current",
|
|
||||||
"bul_show_temporary",
|
|
||||||
"bul_temporary_txt",
|
|
||||||
"bul_show_uevalid",
|
|
||||||
"bul_show_date_inscr",
|
|
||||||
)
|
|
||||||
# on enlève le "bul_" de la clé:
|
|
||||||
return {field[4:]: prefs[field] for field in fields}
|
|
||||||
|
|
||||||
|
|
||||||
# Pour raccorder le code des anciens bulletins qui attendent une NoteTable
|
|
||||||
class APCNotesTableCompat:
|
|
||||||
"""Implementation partielle de NotesTable pour les formations APC
|
|
||||||
Accès aux notes et rangs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, formsemestre):
|
|
||||||
self.results = ResultatsSemestreBUT(formsemestre)
|
|
||||||
nb_etuds = len(self.results.etuds)
|
|
||||||
self.rangs = self.results.etud_moy_gen_ranks
|
|
||||||
self.moy_min = self.results.etud_moy_gen.min()
|
|
||||||
self.moy_max = self.results.etud_moy_gen.max()
|
|
||||||
self.moy_moy = self.results.etud_moy_gen.mean()
|
|
||||||
self.bonus = defaultdict(lambda: 0.0) # XXX
|
|
||||||
self.ue_rangs = {
|
|
||||||
u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.results.ues
|
|
||||||
}
|
|
||||||
self.mod_rangs = {
|
|
||||||
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.results.modimpls
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_ues(self):
|
|
||||||
ues = []
|
|
||||||
for ue in self.results.ues:
|
|
||||||
d = ue.to_dict()
|
|
||||||
d.update(
|
|
||||||
{
|
|
||||||
"max": self.results.etud_moy_ue[ue.id].max(),
|
|
||||||
"min": self.results.etud_moy_ue[ue.id].min(),
|
|
||||||
"moy": self.results.etud_moy_ue[ue.id].mean(),
|
|
||||||
"nb_moy": len(self.results.etud_moy_ue),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
ues.append(d)
|
|
||||||
return ues
|
|
||||||
|
|
||||||
def get_modimpls(self):
|
|
||||||
return [m.to_dict() for m in self.results.modimpls]
|
|
||||||
|
|
||||||
def get_etud_moy_gen(self, etudid):
|
|
||||||
return self.results.etud_moy_gen[etudid]
|
|
||||||
|
|
||||||
def get_moduleimpls_attente(self):
|
|
||||||
return [] # XXX TODO
|
|
||||||
|
|
||||||
def get_etud_rang(self, etudid):
|
|
||||||
return self.rangs[etudid]
|
|
||||||
|
|
||||||
def get_etud_rang_group(self, etudid, group_id):
|
|
||||||
return (None, 0) # XXX unimplemented TODO
|
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid, ue_id):
|
|
||||||
return {
|
|
||||||
"cur_moy_ue": self.results.etud_moy_ue[ue_id][etudid],
|
|
||||||
"is_capitalized": False, # XXX TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_etud_mod_moy(self, moduleimpl_id, etudid):
|
|
||||||
mod_idx = self.results.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
|
|
||||||
etud_idx = self.results.etud_index[etudid]
|
|
||||||
# moyenne sur les UE:
|
|
||||||
self.results.sem_cube[etud_idx, mod_idx].mean()
|
|
||||||
|
|
||||||
def get_mod_stats(self, moduleimpl_id):
|
|
||||||
return {
|
|
||||||
"moy": "-",
|
|
||||||
"max": "-",
|
|
||||||
"min": "-",
|
|
||||||
"nb_notes": "-",
|
|
||||||
"nb_missing": "-",
|
|
||||||
"nb_valid_evals": "-",
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_evals_in_mod(self, moduleimpl_id):
|
|
||||||
mi = ModuleImpl.query.get(moduleimpl_id)
|
|
||||||
evals_results = []
|
|
||||||
for e in mi.evaluations:
|
|
||||||
d = e.to_dict()
|
|
||||||
d["heure_debut"] = e.heure_debut # datetime.time
|
|
||||||
d["heure_fin"] = e.heure_fin
|
|
||||||
d["jour"] = e.jour # datetime
|
|
||||||
d["notes"] = {
|
|
||||||
etud.id: {
|
|
||||||
"etudid": etud.id,
|
|
||||||
"value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
|
||||||
etud.id
|
|
||||||
],
|
|
||||||
}
|
|
||||||
for etud in self.results.etuds
|
|
||||||
}
|
|
||||||
evals_results.append(d)
|
|
||||||
return evals_results
|
|
||||||
|
@ -108,8 +108,8 @@ def bulletin_but_xml_compat(
|
|||||||
code_ine=etud.code_ine or "",
|
code_ine=etud.code_ine or "",
|
||||||
nom=scu.quote_xml_attr(etud.nom),
|
nom=scu.quote_xml_attr(etud.nom),
|
||||||
prenom=scu.quote_xml_attr(etud.prenom),
|
prenom=scu.quote_xml_attr(etud.prenom),
|
||||||
civilite=scu.quote_xml_attr(etud.civilite_str()),
|
civilite=scu.quote_xml_attr(etud.civilite_str),
|
||||||
sexe=scu.quote_xml_attr(etud.civilite_str()), # compat
|
sexe=scu.quote_xml_attr(etud.civilite_str), # compat
|
||||||
photo_url=scu.quote_xml_attr(sco_photos.get_etud_photo_url(etud.id)),
|
photo_url=scu.quote_xml_attr(sco_photos.get_etud_photo_url(etud.id)),
|
||||||
email=scu.quote_xml_attr(etud.get_first_email() or ""),
|
email=scu.quote_xml_attr(etud.get_first_email() or ""),
|
||||||
emailperso=scu.quote_xml_attr(etud.get_first_email("emailperso") or ""),
|
emailperso=scu.quote_xml_attr(etud.get_first_email("emailperso") or ""),
|
||||||
@ -216,9 +216,9 @@ def bulletin_but_xml_compat(
|
|||||||
Element(
|
Element(
|
||||||
"note",
|
"note",
|
||||||
value=scu.fmt_note(
|
value=scu.fmt_note(
|
||||||
results.modimpls_evals_notes[e.moduleimpl_id][
|
results.modimpls_results[
|
||||||
e.id
|
e.moduleimpl_id
|
||||||
][etud.id]
|
].evals_notes[e.id][etud.id]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -314,13 +314,3 @@ def bulletin_but_xml_compat(
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
return sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
formsemestre_id=718
|
|
||||||
etudid=12496
|
|
||||||
from app.but.bulletin_but import *
|
|
||||||
mapp.set_sco_dept("RT")
|
|
||||||
sem = FormSemestre.query.get(formsemestre_id)
|
|
||||||
r = ResultatsSemestreBUT(sem)
|
|
||||||
"""
|
|
||||||
|
37
app/comp/aux.py
Normal file
37
app/comp/aux.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
"""Quelques classes auxiliaires pour les calculs des notes
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class StatsMoyenne:
|
||||||
|
"""Une moyenne d'un ensemble étudiants sur quelque chose
|
||||||
|
(moyenne générale d'un semestre, d'un module, d'un groupe...)
|
||||||
|
et les statistiques associées: min, max, moy, effectif
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vals):
|
||||||
|
"""Calcul les statistiques.
|
||||||
|
Les valeurs NAN ou non numériques sont toujours enlevées.
|
||||||
|
"""
|
||||||
|
self.moy = np.nanmean(vals)
|
||||||
|
self.min = np.nanmin(vals)
|
||||||
|
self.max = np.nanmax(vals)
|
||||||
|
self.size = len(vals)
|
||||||
|
self.nb_vals = self.size - np.count_nonzero(np.isnan(vals))
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"Tous les attributs dans un dict"
|
||||||
|
return {
|
||||||
|
"min": self.min,
|
||||||
|
"max": self.max,
|
||||||
|
"moy": self.moy,
|
||||||
|
"size": self.size,
|
||||||
|
"nb_vals": self.nb_vals,
|
||||||
|
}
|
@ -43,7 +43,7 @@ class ModuleCoefsCache(sco_cache.ScoDocCache):
|
|||||||
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
class EvaluationsPoidsCache(sco_cache.ScoDocCache):
|
||||||
"""Cache for poids evals
|
"""Cache for poids evals
|
||||||
Clé: moduleimpl_id
|
Clé: moduleimpl_id
|
||||||
Valeur: DataFrame (df_load_evaluations_poids)
|
Valeur: DataFrame (load_evaluations_poids)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "EPC"
|
prefix = "EPC"
|
||||||
|
@ -27,21 +27,241 @@
|
|||||||
|
|
||||||
"""Fonctions de calcul des moyennes de modules (modules, ressources ou SAÉ)
|
"""Fonctions de calcul des moyennes de modules (modules, ressources ou SAÉ)
|
||||||
|
|
||||||
|
Pour les formations classiques et le BUT
|
||||||
|
|
||||||
Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une
|
Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une
|
||||||
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
||||||
moyenne générale d'une UE.
|
moyenne générale d'une UE.
|
||||||
"""
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from pandas.core.frame import DataFrame
|
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def df_load_evaluations_poids(
|
@dataclass
|
||||||
|
class EvaluationEtat:
|
||||||
|
"""Classe pour stocker quelques infos sur les résultats d'une évaluation"""
|
||||||
|
|
||||||
|
evaluation_id: int
|
||||||
|
nb_attente: int
|
||||||
|
is_complete: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleImplResults:
|
||||||
|
"""Classe commune à toutes les formations (standard et APC).
|
||||||
|
Les notes des étudiants d'un moduleimpl.
|
||||||
|
Les poids des évals sont à part car on en a besoin sans les notes pour les
|
||||||
|
tableaux de bord.
|
||||||
|
Les attributs sont tous des objets simples cachables dans Redis;
|
||||||
|
les caches sont gérés par ResultatsSemestre.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, moduleimpl: ModuleImpl):
|
||||||
|
self.moduleimpl_id = moduleimpl.id
|
||||||
|
self.module_id = moduleimpl.module.id
|
||||||
|
self.etudids = None
|
||||||
|
"liste des étudiants inscrits au SEMESTRE"
|
||||||
|
self.nb_inscrits_module = None
|
||||||
|
"nombre d'inscrits (non DEM) au module"
|
||||||
|
self.evaluations_completes = []
|
||||||
|
"séquence de booléens, indiquant les évals à prendre en compte."
|
||||||
|
self.evaluations_etat = {}
|
||||||
|
"{ evaluation_id: EvaluationEtat }"
|
||||||
|
#
|
||||||
|
self.evals_notes = None
|
||||||
|
"""DataFrame, colonnes: EVALS, Lignes: etudid
|
||||||
|
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
||||||
|
NOTES_ABSENCE.
|
||||||
|
Les NaN désignent les notes manquantes (non saisies).
|
||||||
|
"""
|
||||||
|
self.etuds_moy_module = None
|
||||||
|
"""DataFrame, colonnes UE, lignes etud
|
||||||
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
|
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||||
|
ne donnent pas de coef vers cette UE.
|
||||||
|
"""
|
||||||
|
self.load_notes()
|
||||||
|
|
||||||
|
def load_notes(self): # ré-écriture de df_load_modimpl_notes
|
||||||
|
"""Charge toutes les notes de toutes les évaluations du module.
|
||||||
|
Dataframe evals_notes
|
||||||
|
colonnes: le nom de la colonne est l'evaluation_id (int)
|
||||||
|
index (lignes): etudid (int)
|
||||||
|
|
||||||
|
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
||||||
|
|
||||||
|
Les notes sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
||||||
|
note : float (valeur enregistrée brute, NON normalisée sur 20)
|
||||||
|
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
||||||
|
absent: NOTES_ABSENCE (NULL en bd)
|
||||||
|
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
||||||
|
attente: NOTES_ATTENTE
|
||||||
|
|
||||||
|
Évaluation "complete" (prise en compte dans les calculs) si:
|
||||||
|
- soit tous les étudiants inscrits au module ont des notes
|
||||||
|
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
||||||
|
|
||||||
|
Évaluation "attente" (prise en compte dans les calculs, mais il y
|
||||||
|
manque des notes) ssi il y a des étudiants inscrits au semestre et au module
|
||||||
|
qui ont des notes ATT.
|
||||||
|
"""
|
||||||
|
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
|
self.etudids = self._etudids()
|
||||||
|
|
||||||
|
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
||||||
|
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||||
|
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
||||||
|
self.etudids
|
||||||
|
)
|
||||||
|
self.nb_inscrits_module = len(inscrits_module)
|
||||||
|
|
||||||
|
# dataFrame vide, index = tous les inscrits au SEMESTRE
|
||||||
|
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||||
|
self.evaluations_completes = []
|
||||||
|
for evaluation in moduleimpl.evaluations:
|
||||||
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
|
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
||||||
|
# ou évaluaton déclarée "à prise en compte immédiate"
|
||||||
|
is_complete = (
|
||||||
|
len(set(eval_df.index).intersection(self.etudids))
|
||||||
|
== self.nb_inscrits_module
|
||||||
|
) or evaluation.publish_incomplete # immédiate
|
||||||
|
self.evaluations_completes.append(is_complete)
|
||||||
|
|
||||||
|
# NULL en base => ABS (= -999)
|
||||||
|
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
||||||
|
# Ce merge ne garde que les étudiants inscrits au module
|
||||||
|
# et met à NULL les notes non présentes
|
||||||
|
# (notes non saisies ou etuds non inscrits au module):
|
||||||
|
evals_notes = evals_notes.merge(
|
||||||
|
eval_df, how="left", left_index=True, right_index=True
|
||||||
|
)
|
||||||
|
# Notes en attente: (on prend dans evals_notes pour ne pas avoir les dem.)
|
||||||
|
nb_att = sum(evals_notes[str(evaluation.id)] == scu.NOTES_ATTENTE)
|
||||||
|
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||||
|
evaluation_id=evaluation.id, nb_attente=nb_att, is_complete=is_complete
|
||||||
|
)
|
||||||
|
|
||||||
|
# Force columns names to integers (evaluation ids)
|
||||||
|
evals_notes.columns = pd.Int64Index(
|
||||||
|
[int(x) for x in evals_notes.columns], dtype="int"
|
||||||
|
)
|
||||||
|
self.evals_notes = evals_notes
|
||||||
|
|
||||||
|
def _load_evaluation_notes(self, evaluation: Evaluation) -> pd.DataFrame:
|
||||||
|
"""Charge les notes de l'évaluation
|
||||||
|
Resultat: dataframe, index: etudid ayant une note, valeur: note brute.
|
||||||
|
"""
|
||||||
|
eval_df = pd.read_sql_query(
|
||||||
|
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
||||||
|
FROM notes_notes n, notes_moduleimpl_inscription i
|
||||||
|
WHERE evaluation_id=%(evaluation_id)s
|
||||||
|
AND n.etudid = i.etudid
|
||||||
|
AND i.moduleimpl_id = %(moduleimpl_id)s
|
||||||
|
""",
|
||||||
|
db.engine,
|
||||||
|
params={
|
||||||
|
"evaluation_id": evaluation.id,
|
||||||
|
"moduleimpl_id": evaluation.moduleimpl.id,
|
||||||
|
},
|
||||||
|
index_col="etudid",
|
||||||
|
)
|
||||||
|
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
||||||
|
return eval_df
|
||||||
|
|
||||||
|
def _etudids(self):
|
||||||
|
"""L'index du dataframe est la liste des étudiants inscrits au semestre,
|
||||||
|
sans les démissionnaires.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
e.etudid
|
||||||
|
for e in ModuleImpl.query.get(self.moduleimpl_id).formsemestre.get_inscrits(
|
||||||
|
include_dem=False
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
||||||
|
"""Coefficients des évaluations, met à zéro ceux des évals incomplètes.
|
||||||
|
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
np.array(
|
||||||
|
[e.coefficient for e in moduleimpl.evaluations],
|
||||||
|
dtype=float,
|
||||||
|
)
|
||||||
|
* self.evaluations_completes
|
||||||
|
).reshape(-1, 1)
|
||||||
|
|
||||||
|
def get_eval_notes_sur_20(self, moduleimpl: ModuleImpl) -> np.array:
|
||||||
|
"""Les notes des évaluations,
|
||||||
|
remplace les ATT, EXC, ABS, NaN par zéro et mets les notes sur 20.
|
||||||
|
Résultat: 2d array of floats, shape nb_etuds x nb_evaluations
|
||||||
|
"""
|
||||||
|
return np.where(
|
||||||
|
self.evals_notes.values > scu.NOTES_ABSENCE, self.evals_notes.values, 0.0
|
||||||
|
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleImplResultsAPC(ModuleImplResults):
|
||||||
|
"Calcul des moyennes de modules à la mode BUT"
|
||||||
|
|
||||||
|
def compute_module_moy(
|
||||||
|
self,
|
||||||
|
evals_poids_df: pd.DataFrame,
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""Calcule les moyennes des étudiants dans ce module
|
||||||
|
|
||||||
|
Argument: evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
||||||
|
|
||||||
|
Résultat: DataFrame, colonnes UE, lignes etud
|
||||||
|
= la note de l'étudiant dans chaque UE pour ce module.
|
||||||
|
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||||
|
ne donnent pas de coef vers cette UE.
|
||||||
|
"""
|
||||||
|
moduleimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
|
nb_etuds, nb_evals = self.evals_notes.shape
|
||||||
|
nb_ues = evals_poids_df.shape[1]
|
||||||
|
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
||||||
|
if nb_etuds == 0:
|
||||||
|
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
||||||
|
evals_coefs = self.get_evaluations_coefs(moduleimpl)
|
||||||
|
evals_poids = evals_poids_df.values * evals_coefs
|
||||||
|
# -> evals_poids shape : (nb_evals, nb_ues)
|
||||||
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
|
evals_notes_20 = self.get_eval_notes_sur_20(moduleimpl)
|
||||||
|
|
||||||
|
# Les poids des évals pour chaque étudiant: là où il a des notes
|
||||||
|
# non neutralisées
|
||||||
|
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
||||||
|
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
||||||
|
# et dans dans evals_poids_etuds
|
||||||
|
# (rappel: la comparaison est toujours false face à un NaN)
|
||||||
|
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||||
|
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
||||||
|
evals_poids_etuds = np.where(
|
||||||
|
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||||
|
poids_stacked,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||||
|
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etuds_moy_module = np.sum(
|
||||||
|
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||||
|
) / np.sum(evals_poids_etuds, axis=1)
|
||||||
|
self.etuds_moy_module = pd.DataFrame(
|
||||||
|
etuds_moy_module,
|
||||||
|
index=self.evals_notes.index,
|
||||||
|
columns=evals_poids_df.columns,
|
||||||
|
)
|
||||||
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
|
||||||
|
def load_evaluations_poids(
|
||||||
moduleimpl_id: int, default_poids=1.0
|
moduleimpl_id: int, default_poids=1.0
|
||||||
) -> tuple[pd.DataFrame, list]:
|
) -> 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
|
||||||
@ -55,23 +275,25 @@ def df_load_evaluations_poids(
|
|||||||
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
ues = modimpl.formsemestre.query_ues(with_sport=False).all()
|
||||||
ue_ids = [ue.id for ue in ues]
|
ue_ids = [ue.id for ue in ues]
|
||||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||||
df = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
evals_poids = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||||
for eval_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):
|
||||||
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
evals_poids[ue_poids.ue_id][ue_poids.evaluation_id] = ue_poids.poids
|
||||||
if default_poids is not None:
|
if default_poids is not None:
|
||||||
df.fillna(value=default_poids, inplace=True)
|
evals_poids.fillna(value=default_poids, inplace=True)
|
||||||
return df, ues
|
return evals_poids, ues
|
||||||
|
|
||||||
|
|
||||||
def check_moduleimpl_conformity(
|
def moduleimpl_is_conforme(
|
||||||
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
||||||
au PN.
|
au PN.
|
||||||
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
||||||
é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
|
||||||
"""
|
"""
|
||||||
nb_evals, nb_ues = evals_poids.shape
|
nb_evals, nb_ues = evals_poids.shape
|
||||||
if nb_evals == 0:
|
if nb_evals == 0:
|
||||||
@ -79,160 +301,52 @@ def check_moduleimpl_conformity(
|
|||||||
if nb_ues == 0:
|
if nb_ues == 0:
|
||||||
return False # situation absurde (pas d'UE)
|
return False # situation absurde (pas d'UE)
|
||||||
if len(modules_coefficients) != nb_ues:
|
if len(modules_coefficients) != nb_ues:
|
||||||
raise ValueError("check_moduleimpl_conformity: nb ue incoherent")
|
raise ValueError("moduleimpl_is_conforme: nb ue incoherent")
|
||||||
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
||||||
check = all(
|
check = all(
|
||||||
(modules_coefficients[moduleimpl.module.id].to_numpy() != 0)
|
(modules_coefficients[moduleimpl.module_id].to_numpy() != 0)
|
||||||
== module_evals_poids
|
== module_evals_poids
|
||||||
)
|
)
|
||||||
return check
|
return check
|
||||||
|
|
||||||
|
|
||||||
def df_load_modimpl_notes(moduleimpl_id: int) -> tuple:
|
class ModuleImplResultsClassic(ModuleImplResults):
|
||||||
"""Construit un dataframe avec toutes les notes de toutes les évaluations du module.
|
"Calcul des moyennes de modules des formations classiques"
|
||||||
colonnes: le nom de la colonne est l'evaluation_id (int)
|
|
||||||
index (lignes): etudid (int)
|
|
||||||
|
|
||||||
Résultat: (evals_notes, liste de évaluations du moduleimpl,
|
def compute_module_moy(self) -> pd.Series:
|
||||||
liste de booleens indiquant si l'évaluation est "complete")
|
"""Calcule les moyennes des étudiants dans ce module
|
||||||
|
|
||||||
L'ensemble des étudiants est celui des inscrits au SEMESTRE.
|
Résultat: Series, lignes etud
|
||||||
|
= la note (moyenne) de l'étudiant pour ce module.
|
||||||
Les notes renvoyées sont "brutes" (séries de floats) et peuvent prendre les valeurs:
|
ou NaN si les évaluations (dans lesquelles l'étudiant a des notes)
|
||||||
note : float (valeur enregistrée brute, non normalisée sur 20)
|
ne donnent pas de coef.
|
||||||
pas de note: NaN (rien en bd, ou étudiant non inscrit au module)
|
"""
|
||||||
absent: NOTES_ABSENCE (NULL en bd)
|
modimpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
excusé: NOTES_NEUTRALISE (voir sco_utils)
|
nb_etuds, nb_evals = self.evals_notes.shape
|
||||||
attente: NOTES_ATTENTE
|
if nb_etuds == 0:
|
||||||
|
return pd.Series()
|
||||||
L'évaluation "complete" (prise en compte dans les calculs) si:
|
evals_coefs = self.get_evaluations_coefs(modimpl).reshape(-1)
|
||||||
- soit tous les étudiants inscrits au module ont des notes
|
assert evals_coefs.shape == (nb_evals,)
|
||||||
- soit elle a été déclarée "à prise ne compte immédiate" (publish_incomplete)
|
evals_notes_20 = self.get_eval_notes_sur_20(modimpl)
|
||||||
|
# Les coefs des évals pour chaque étudiant: là où il a des notes
|
||||||
N'utilise pas de cache ScoDoc.
|
# non neutralisées
|
||||||
"""
|
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
||||||
# L'index du dataframe est la liste des étudiants inscrits au semestre,
|
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
||||||
# sans les démissionnaires
|
# et dans dans evals_poids_etuds
|
||||||
etudids = [
|
# (rappel: la comparaison est toujours False face à un NaN)
|
||||||
e.etudid
|
# shape: (nb_etuds, nb_evals)
|
||||||
for e in ModuleImpl.query.get(moduleimpl_id).formsemestre.get_inscrits(
|
coefs_stacked = np.stack([evals_coefs] * nb_etuds)
|
||||||
include_dem=False
|
evals_coefs_etuds = np.where(
|
||||||
|
self.evals_notes.values > scu.NOTES_NEUTRALISE, coefs_stacked, 0
|
||||||
)
|
)
|
||||||
]
|
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||||
evaluations = Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
# --- Calcul nombre d'inscrits pour détermnier si évaluation "complete":
|
etuds_moy_module = np.sum(
|
||||||
if evaluations:
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
inscrits_module = {
|
|
||||||
ins.etud.id for ins in evaluations[0].moduleimpl.inscriptions
|
self.etuds_moy_module = pd.Series(
|
||||||
}.intersection(etudids)
|
etuds_moy_module,
|
||||||
nb_inscrits_module = len(inscrits_module)
|
index=self.evals_notes.index,
|
||||||
else:
|
|
||||||
nb_inscrits_module = 0
|
|
||||||
# empty df with all students:
|
|
||||||
evals_notes = pd.DataFrame(index=etudids, dtype=float)
|
|
||||||
evaluations_completes = []
|
|
||||||
for evaluation in evaluations:
|
|
||||||
eval_df = pd.read_sql_query(
|
|
||||||
"""SELECT n.etudid, n.value AS "%(evaluation_id)s"
|
|
||||||
FROM notes_notes n, notes_moduleimpl_inscription i
|
|
||||||
WHERE evaluation_id=%(evaluation_id)s
|
|
||||||
AND n.etudid = i.etudid
|
|
||||||
AND i.moduleimpl_id = %(moduleimpl_id)s
|
|
||||||
ORDER BY n.etudid
|
|
||||||
""",
|
|
||||||
db.engine,
|
|
||||||
params={
|
|
||||||
"evaluation_id": evaluation.id,
|
|
||||||
"moduleimpl_id": evaluation.moduleimpl.id,
|
|
||||||
},
|
|
||||||
index_col="etudid",
|
|
||||||
)
|
)
|
||||||
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
return self.etuds_moy_module
|
||||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
|
||||||
is_complete = (
|
|
||||||
len(set(eval_df.index).intersection(etudids)) == nb_inscrits_module
|
|
||||||
) or evaluation.publish_incomplete
|
|
||||||
evaluations_completes.append(is_complete)
|
|
||||||
# NULL en base => ABS (= -999)
|
|
||||||
eval_df.fillna(scu.NOTES_ABSENCE, inplace=True)
|
|
||||||
# Ce merge met à NULL les élements non présents
|
|
||||||
# (notes non saisies ou etuds non inscrits au module):
|
|
||||||
evals_notes = evals_notes.merge(
|
|
||||||
eval_df, how="left", left_index=True, right_index=True
|
|
||||||
)
|
|
||||||
# Force columns names to integers (evaluation ids)
|
|
||||||
evals_notes.columns = pd.Int64Index(
|
|
||||||
[int(x) for x in evals_notes.columns], dtype="int64"
|
|
||||||
)
|
|
||||||
return evals_notes, evaluations, evaluations_completes
|
|
||||||
|
|
||||||
|
|
||||||
def compute_module_moy(
|
|
||||||
evals_notes_df: pd.DataFrame,
|
|
||||||
evals_poids_df: pd.DataFrame,
|
|
||||||
evaluations: list,
|
|
||||||
evaluations_completes: list,
|
|
||||||
) -> pd.DataFrame:
|
|
||||||
"""Calcule les moyennes des étudiants dans ce module
|
|
||||||
|
|
||||||
- evals_notes : DataFrame, colonnes: EVALS, Lignes: etudid
|
|
||||||
valeur: notes brutes, float ou NOTES_ATTENTE, NOTES_NEUTRALISE,
|
|
||||||
NOTES_ABSENCE.
|
|
||||||
Les NaN désignent les notes manquantes (non saisies).
|
|
||||||
|
|
||||||
- evals_poids: DataFrame, colonnes: UEs, Lignes: EVALs
|
|
||||||
|
|
||||||
- evaluations: séquence d'évaluations (utilisées pour le coef et
|
|
||||||
le barème)
|
|
||||||
|
|
||||||
- evaluations_completes: séquence de booléens indiquant les
|
|
||||||
évals à prendre en compte.
|
|
||||||
|
|
||||||
Résultat: DataFrame, colonnes UE, lignes etud
|
|
||||||
= la note de l'étudiant dans chaque UE pour ce module.
|
|
||||||
ou NaN si les évaluations (dans lesquelles l'étudiant à des notes)
|
|
||||||
ne donnent pas de coef vers cette UE.
|
|
||||||
"""
|
|
||||||
nb_etuds, nb_evals = evals_notes_df.shape
|
|
||||||
nb_ues = evals_poids_df.shape[1]
|
|
||||||
assert evals_poids_df.shape[0] == nb_evals # compat notes/poids
|
|
||||||
if nb_etuds == 0:
|
|
||||||
return pd.DataFrame(index=[], columns=evals_poids_df.columns)
|
|
||||||
# Coefficients des évaluations, met à zéro ceux des évals incomplètes:
|
|
||||||
evals_coefs = (
|
|
||||||
np.array(
|
|
||||||
[e.coefficient for e in evaluations],
|
|
||||||
dtype=float,
|
|
||||||
)
|
|
||||||
* evaluations_completes
|
|
||||||
).reshape(-1, 1)
|
|
||||||
evals_poids = evals_poids_df.values * evals_coefs
|
|
||||||
# -> evals_poids shape : (nb_evals, nb_ues)
|
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
|
||||||
# Remplace les notes ATT, EXC, ABS, NaN par zéro et mets les notes sur 20:
|
|
||||||
evals_notes = np.where(
|
|
||||||
evals_notes_df.values > scu.NOTES_ABSENCE, evals_notes_df.values, 0.0
|
|
||||||
) / [e.note_max / 20.0 for e in evaluations]
|
|
||||||
# Les poids des évals pour les étudiant: là où il a des notes non neutralisées
|
|
||||||
# (ABS n'est pas neutralisée, mais ATTENTE et NEUTRALISE oui)
|
|
||||||
# Note: les NaN sont remplacés par des 0 dans evals_notes
|
|
||||||
# et dans dans evals_poids_etuds
|
|
||||||
# (rappel: la comparaison est toujours false face à un NaN)
|
|
||||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
|
||||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
|
||||||
evals_poids_etuds = np.where(
|
|
||||||
np.stack([evals_notes_df.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
|
||||||
poids_stacked,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
|
||||||
evals_notes_stacked = np.stack([evals_notes] * nb_ues, axis=2)
|
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
|
||||||
etuds_moy_module = np.sum(
|
|
||||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
|
||||||
) / np.sum(evals_poids_etuds, axis=1)
|
|
||||||
etuds_moy_module_df = pd.DataFrame(
|
|
||||||
etuds_moy_module, index=evals_notes_df.index, columns=evals_poids_df.columns
|
|
||||||
)
|
|
||||||
return etuds_moy_module_df
|
|
||||||
|
@ -31,7 +31,7 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
def compute_sem_moys(etud_moy_ue_df, modimpl_coefs_df):
|
def compute_sem_moys_apc(etud_moy_ue_df, modimpl_coefs_df):
|
||||||
"""Calcule la moyenne générale indicative
|
"""Calcule la moyenne générale indicative
|
||||||
= 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
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Fonctions de calcul des moyennes d'UE
|
"""Fonctions de calcul des moyennes d'UE (classiques ou BUT)
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -39,9 +39,9 @@ from app.scodoc import sco_codes_parcours
|
|||||||
|
|
||||||
|
|
||||||
def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.DataFrame:
|
def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.DataFrame:
|
||||||
"""Charge les coefs des modules de la formation pour le semestre indiqué.
|
"""Charge les coefs APC des modules de la formation pour le semestre indiqué.
|
||||||
|
|
||||||
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, modules)
|
||||||
DataFrame rows = UEs, columns = modules, value = coef.
|
DataFrame rows = UEs, columns = modules, value = coef.
|
||||||
@ -86,7 +86,7 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||||||
def df_load_modimpl_coefs(
|
def df_load_modimpl_coefs(
|
||||||
formsemestre: models.FormSemestre, ues=None, modimpls=None
|
formsemestre: models.FormSemestre, ues=None, modimpls=None
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Charge les coefs des modules du formsemestre indiqué.
|
"""Charge les coefs APC des modules du formsemestre indiqué.
|
||||||
|
|
||||||
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.
|
||||||
@ -127,45 +127,32 @@ def notes_sem_assemble_cube(modimpls_notes: list[pd.DataFrame]) -> np.ndarray:
|
|||||||
return modimpls_notes.swapaxes(0, 1)
|
return modimpls_notes.swapaxes(0, 1)
|
||||||
|
|
||||||
|
|
||||||
def notes_sem_load_cube(formsemestre):
|
def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
||||||
"""Calcule le cube des notes du semestre
|
"""Calcule le cube des notes du semestre
|
||||||
(charge toutes les notes, calcule les moyenne des modules
|
(charge toutes les notes, calcule les moyenne des modules
|
||||||
et assemble le cube)
|
et assemble le cube)
|
||||||
Resultat:
|
Resultat:
|
||||||
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_evals_notes dict { modimpl.id : evals_notes }
|
modimpls_results dict { modimpl.id : ModuleImplResultsAPC }
|
||||||
modimpls_evaluations dict { modimpl.id : liste des évaluations }
|
|
||||||
modimpls_evaluations_complete: {modimpl_id : liste de booleens (complete/non)}
|
|
||||||
"""
|
"""
|
||||||
|
modimpls_results = {}
|
||||||
modimpls_evals_poids = {}
|
modimpls_evals_poids = {}
|
||||||
modimpls_evals_notes = {}
|
|
||||||
modimpls_evaluations = {}
|
|
||||||
modimpls_evaluations_complete = {}
|
|
||||||
modimpls_notes = []
|
modimpls_notes = []
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls:
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
modimpl.id
|
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||||
)
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(modimpl.id)
|
modimpls_results[modimpl.id] = mod_results
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
|
||||||
modimpls_evals_poids[modimpl.id] = evals_poids
|
|
||||||
modimpls_evals_notes[modimpl.id] = evals_notes
|
|
||||||
modimpls_evaluations[modimpl.id] = evaluations
|
|
||||||
modimpls_evaluations_complete[modimpl.id] = evaluations_completes
|
|
||||||
modimpls_notes.append(etuds_moy_module)
|
modimpls_notes.append(etuds_moy_module)
|
||||||
return (
|
return (
|
||||||
notes_sem_assemble_cube(modimpls_notes),
|
notes_sem_assemble_cube(modimpls_notes),
|
||||||
modimpls_evals_poids,
|
modimpls_evals_poids,
|
||||||
modimpls_evals_notes,
|
modimpls_results,
|
||||||
modimpls_evaluations,
|
|
||||||
modimpls_evaluations_complete,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def compute_ue_moys(
|
def compute_ue_moys_apc(
|
||||||
sem_cube: np.array,
|
sem_cube: np.array,
|
||||||
etuds: list,
|
etuds: list,
|
||||||
modimpls: list,
|
modimpls: list,
|
||||||
@ -173,7 +160,7 @@ def compute_ue_moys(
|
|||||||
modimpl_inscr_df: pd.DataFrame,
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
modimpl_coefs_df: pd.DataFrame,
|
modimpl_coefs_df: pd.DataFrame,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
"""Calcul de la moyenne d'UE
|
"""Calcul de la moyenne d'UE en mode APC (BUT).
|
||||||
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
NI non inscrit à (au moins un) module de cette UE
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
NA pas de notes disponibles
|
NA pas de notes disponibles
|
||||||
@ -182,11 +169,11 @@ def compute_ue_moys(
|
|||||||
sem_cube: notes moyennes aux modules
|
sem_cube: notes moyennes aux modules
|
||||||
ndarray (etuds x modimpls x UEs)
|
ndarray (etuds x modimpls x UEs)
|
||||||
(floats avec des NaN)
|
(floats avec des NaN)
|
||||||
etuds : lites des étudiants (dim. 0 du cube)
|
etuds : listes des étudiants (dim. 0 du cube)
|
||||||
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)
|
||||||
module_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
module_coefs_df: matrice coefficients (UE x modimpl)
|
modimpl_coefs_df: matrice coefficients (UE x modimpl)
|
||||||
|
|
||||||
Resultat: DataFrame columns UE, rows etudid
|
Resultat: DataFrame columns UE, rows etudid
|
||||||
"""
|
"""
|
||||||
@ -228,3 +215,70 @@ def compute_ue_moys(
|
|||||||
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, columns=modimpl_coefs_df.index
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_ue_moys_classic(
|
||||||
|
formsemestre: FormSemestre,
|
||||||
|
sem_matrix: np.array,
|
||||||
|
ues: list,
|
||||||
|
modimpl_inscr_df: pd.DataFrame,
|
||||||
|
modimpl_coefs: np.array,
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
"""Calcul de la moyenne d'UE en mode classique.
|
||||||
|
La moyenne d'UE est un nombre (note/20), ou NI ou NA ou ERR
|
||||||
|
NI non inscrit à (au moins un) module de cette UE
|
||||||
|
NA pas de notes disponibles
|
||||||
|
ERR erreur dans une formule utilisateur. [XXX pas encore gérées ici]
|
||||||
|
|
||||||
|
sem_matrix: notes moyennes aux modules
|
||||||
|
ndarray (etuds x modimpls)
|
||||||
|
(floats avec des NaN)
|
||||||
|
etuds : listes des étudiants (dim. 0 de la matrice)
|
||||||
|
ues : liste des UE
|
||||||
|
modimpl_inscr_df: matrice d'inscription du semestre (etud x modimpl)
|
||||||
|
modimpl_coefs: vecteur des coefficients de modules
|
||||||
|
|
||||||
|
Résultat:
|
||||||
|
- moyennes générales: pd.Series, index etudid
|
||||||
|
- moyennes d'UE: DataFrame columns UE, rows etudid
|
||||||
|
"""
|
||||||
|
nb_etuds, nb_modules = sem_matrix.shape
|
||||||
|
assert len(modimpl_coefs) == nb_modules
|
||||||
|
nb_ues = len(ues)
|
||||||
|
modimpl_inscr = modimpl_inscr_df.values
|
||||||
|
# Enlève les NaN du numérateur:
|
||||||
|
sem_matrix_no_nan = np.nan_to_num(sem_matrix, nan=0.0)
|
||||||
|
# Ne prend pas en compte les notes des étudiants non inscrits au module:
|
||||||
|
# Annule les notes:
|
||||||
|
sem_matrix_inscrits = np.where(modimpl_inscr, sem_matrix_no_nan, 0.0)
|
||||||
|
# Annule les coefs des modules où l'étudiant n'est pas inscrit:
|
||||||
|
modimpl_coefs_etuds = np.where(
|
||||||
|
modimpl_inscr, np.stack([modimpl_coefs.T] * nb_etuds), 0.0
|
||||||
|
)
|
||||||
|
# Annule les coefs des modules NaN (nb_etuds x nb_mods)
|
||||||
|
modimpl_coefs_etuds_no_nan = np.where(
|
||||||
|
np.isnan(sem_matrix), 0.0, modimpl_coefs_etuds
|
||||||
|
)
|
||||||
|
# Calcul des moyennes générales:
|
||||||
|
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)
|
||||||
|
# Calcul des moyennes d'UE
|
||||||
|
ue_modules = np.array(
|
||||||
|
[[m.module.ue == ue for m in formsemestre.modimpls] for ue in ues]
|
||||||
|
)[..., np.newaxis]
|
||||||
|
modimpl_coefs_etuds_no_nan_stacked = np.stack(
|
||||||
|
[modimpl_coefs_etuds_no_nan.T] * nb_ues
|
||||||
|
)
|
||||||
|
# nb_ue x nb_etuds x nb_mods : coefs prenant en compte NaN et inscriptions
|
||||||
|
coefs = (modimpl_coefs_etuds_no_nan_stacked * ue_modules).swapaxes(1, 2)
|
||||||
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
|
etud_moy_ue = (
|
||||||
|
np.sum(coefs * sem_matrix_inscrits, axis=2) / np.sum(coefs, axis=2)
|
||||||
|
).T
|
||||||
|
etud_moy_ue_df = pd.DataFrame(
|
||||||
|
etud_moy_ue, index=modimpl_inscr_df.index, columns=[ue.id for ue in ues]
|
||||||
|
)
|
||||||
|
return etud_moy_gen_s, etud_moy_ue_df
|
||||||
|
65
app/comp/res_but.py
Normal file
65
app/comp/res_but.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Résultats semestres BUT
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app.comp import moy_ue, moy_sem, inscr_mod
|
||||||
|
from app.comp.res_sem import NotesTableCompat
|
||||||
|
|
||||||
|
|
||||||
|
class ResultatsSemestreBUT(NotesTableCompat):
|
||||||
|
"""Résultats BUT: organisation des calculs"""
|
||||||
|
|
||||||
|
_cached_attrs = NotesTableCompat._cached_attrs + (
|
||||||
|
"modimpl_coefs_df",
|
||||||
|
"modimpls_evals_poids",
|
||||||
|
"sem_cube",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, formsemestre):
|
||||||
|
super().__init__(formsemestre)
|
||||||
|
|
||||||
|
if not self.load_cached():
|
||||||
|
self.compute()
|
||||||
|
self.store()
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||||
|
(
|
||||||
|
self.sem_cube,
|
||||||
|
self.modimpls_evals_poids,
|
||||||
|
self.modimpls_results,
|
||||||
|
) = moy_ue.notes_sem_load_cube(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.formsemestre, ues=self.ues, modimpls=self.modimpls
|
||||||
|
)
|
||||||
|
# l'idx de la colonne du mod modimpl.id est
|
||||||
|
# modimpl_coefs_df.columns.get_loc(modimpl.id)
|
||||||
|
# idx de l'UE: modimpl_coefs_df.index.get_loc(ue.id)
|
||||||
|
self.etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
|
self.sem_cube,
|
||||||
|
self.etuds,
|
||||||
|
self.modimpls,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs_df,
|
||||||
|
)
|
||||||
|
self.etud_moy_gen = moy_sem.compute_sem_moys_apc(
|
||||||
|
self.etud_moy_ue, self.modimpl_coefs_df
|
||||||
|
)
|
||||||
|
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:
|
||||||
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
|
En APC, il s'agit d'une moyenne indicative sans valeur.
|
||||||
|
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||||
|
"""
|
||||||
|
mod_idx = self.modimpl_coefs_df.columns.get_loc(moduleimpl_id)
|
||||||
|
etud_idx = self.etud_index[etudid]
|
||||||
|
# moyenne sur les UE:
|
||||||
|
return self.sem_cube[etud_idx, mod_idx].mean()
|
95
app/comp/res_classic.py
Normal file
95
app/comp/res_classic.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""Résultats semestres classiques (non APC)
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from app.comp import moy_mod, moy_ue, moy_sem, inscr_mod
|
||||||
|
from app.comp.res_sem import NotesTableCompat
|
||||||
|
from app.models.formsemestre import FormSemestre
|
||||||
|
|
||||||
|
|
||||||
|
class ResultatsSemestreClassic(NotesTableCompat):
|
||||||
|
"""Résultats du semestre (formation classique): organisation des calculs."""
|
||||||
|
|
||||||
|
_cached_attrs = NotesTableCompat._cached_attrs + (
|
||||||
|
"modimpl_coefs",
|
||||||
|
"modimpl_idx",
|
||||||
|
"sem_matrix",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, formsemestre):
|
||||||
|
super().__init__(formsemestre)
|
||||||
|
|
||||||
|
if not self.load_cached():
|
||||||
|
self.compute()
|
||||||
|
self.store()
|
||||||
|
# recalculé (aussi rapide que de les cacher)
|
||||||
|
self.moy_min = self.etud_moy_gen.min()
|
||||||
|
self.moy_max = self.etud_moy_gen.max()
|
||||||
|
self.moy_moy = self.etud_moy_gen.mean()
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
"Charge les notes et inscriptions et calcule les moyennes d'UE et gen."
|
||||||
|
self.sem_matrix, self.modimpls_results = notes_sem_load_matrix(
|
||||||
|
self.formsemestre
|
||||||
|
)
|
||||||
|
self.modimpl_inscr_df = inscr_mod.df_load_modimpl_inscr(self.formsemestre)
|
||||||
|
self.modimpl_coefs = np.array(
|
||||||
|
[m.module.coefficient for m in self.formsemestre.modimpls]
|
||||||
|
)
|
||||||
|
self.modimpl_idx = {m.id: i for i, m in enumerate(self.formsemestre.modimpls)}
|
||||||
|
"l'idx de la colonne du mod modimpl.id est modimpl_idx[modimpl.id]"
|
||||||
|
|
||||||
|
self.etud_moy_gen, self.etud_moy_ue = moy_ue.compute_ue_moys_classic(
|
||||||
|
self.formsemestre,
|
||||||
|
self.sem_matrix,
|
||||||
|
self.ues,
|
||||||
|
self.modimpl_inscr_df,
|
||||||
|
self.modimpl_coefs,
|
||||||
|
)
|
||||||
|
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:
|
||||||
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
|
Result: valeur float (peut être NaN) ou chaîne "NI" (non inscrit ou DEM)
|
||||||
|
"""
|
||||||
|
return self.modimpls_results[moduleimpl_id].etuds_moy_module.get(etudid, "NI")
|
||||||
|
|
||||||
|
|
||||||
|
def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple:
|
||||||
|
"""Calcule la matrice des notes du semestre
|
||||||
|
(charge toutes les notes, calcule les moyenne des modules
|
||||||
|
et assemble la matrice)
|
||||||
|
Resultat:
|
||||||
|
sem_matrix : 2d-array (etuds x modimpls)
|
||||||
|
modimpls_results dict { modimpl.id : ModuleImplResultsClassic }
|
||||||
|
"""
|
||||||
|
modimpls_results = {}
|
||||||
|
modimpls_notes = []
|
||||||
|
for modimpl in formsemestre.modimpls:
|
||||||
|
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
||||||
|
etuds_moy_module = mod_results.compute_module_moy()
|
||||||
|
modimpls_results[modimpl.id] = mod_results
|
||||||
|
modimpls_notes.append(etuds_moy_module)
|
||||||
|
return (
|
||||||
|
notes_sem_assemble_matrix(modimpls_notes),
|
||||||
|
modimpls_results,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def notes_sem_assemble_matrix(modimpls_notes: list[pd.Series]) -> np.ndarray:
|
||||||
|
"""Réuni les notes moyennes des modules du semestre en une matrice
|
||||||
|
|
||||||
|
modimpls_notes : liste des moyennes de module
|
||||||
|
(Series rendus par compute_module_moy, index: etud)
|
||||||
|
Resultat: ndarray (etud x module)
|
||||||
|
"""
|
||||||
|
modimpls_notes_arr = [s.values for s in modimpls_notes]
|
||||||
|
modimpls_notes = np.stack(modimpls_notes_arr)
|
||||||
|
# passe de (mod x etud) à (etud x mod)
|
||||||
|
return modimpls_notes.T
|
337
app/comp/res_sem.py
Normal file
337
app/comp/res_sem.py
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
##############################################################################
|
||||||
|
# ScoDoc
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
# See LICENSE
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import cached_property
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from app.comp.aux import StatsMoyenne
|
||||||
|
from app.models import FormSemestre, ModuleImpl
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
from app.scodoc.sco_cache import ResultatsSemestreCache
|
||||||
|
from app.scodoc.sco_codes_parcours import UE_SPORT, ATT, DEF
|
||||||
|
|
||||||
|
# Il faut bien distinguer
|
||||||
|
# - ce qui est caché de façon persistente (via redis):
|
||||||
|
# ce sont les attributs listés dans `_cached_attrs`
|
||||||
|
# le stockage et l'invalidation sont gérés dans sco_cache.py
|
||||||
|
#
|
||||||
|
# - les valeurs cachées durant le temps d'une requête
|
||||||
|
# (durée de vie de l'instance de ResultatsSemestre)
|
||||||
|
# qui sont notamment les attributs décorés par `@cached_property``
|
||||||
|
#
|
||||||
|
class ResultatsSemestre:
|
||||||
|
_cached_attrs = (
|
||||||
|
"etud_moy_gen_ranks",
|
||||||
|
"etud_moy_gen",
|
||||||
|
"etud_moy_ue",
|
||||||
|
"modimpl_inscr_df",
|
||||||
|
"modimpls_results",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
|
self.formsemestre = formsemestre
|
||||||
|
# BUT ou standard ? (apc == "approche par compétences")
|
||||||
|
self.is_apc = formsemestre.formation.is_apc()
|
||||||
|
# Attributs "virtuels", définis pas les sous-classes
|
||||||
|
# ResultatsSemestreBUT ou ResultatsSemestreStd
|
||||||
|
self.etud_moy_ue = {}
|
||||||
|
self.etud_moy_gen = {}
|
||||||
|
self.etud_moy_gen_ranks = {}
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
def load_cached(self) -> bool:
|
||||||
|
"Load cached dataframes, returns False si pas en cache"
|
||||||
|
data = ResultatsSemestreCache.get(self.formsemestre.id)
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
for attr in self._cached_attrs:
|
||||||
|
setattr(self, attr, data[attr])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def store(self):
|
||||||
|
"Cache our data"
|
||||||
|
ResultatsSemestreCache.set(
|
||||||
|
self.formsemestre.id,
|
||||||
|
{attr: getattr(self, attr) for attr in self._cached_attrs},
|
||||||
|
)
|
||||||
|
|
||||||
|
def compute(self):
|
||||||
|
"Charge les notes et inscriptions et calcule toutes les moyennes"
|
||||||
|
# voir ce qui est chargé / calculé ici et dans les sous-classes
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def etuds(self):
|
||||||
|
"Liste des inscrits au semestre, sans les démissionnaires"
|
||||||
|
# nb: si la liste des inscrits change, ResultatsSemestre devient invalide
|
||||||
|
return self.formsemestre.get_inscrits(include_dem=False)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def etud_index(self):
|
||||||
|
"dict { etudid : indice dans les inscrits }"
|
||||||
|
return {e.id: idx for idx, e in enumerate(self.etuds)}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def ues(self):
|
||||||
|
"Liste des UE du semestre"
|
||||||
|
return self.formsemestre.query_ues().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
|
||||||
|
def ressources(self):
|
||||||
|
"Liste des ressources du semestre, triées par numéro de module"
|
||||||
|
return [
|
||||||
|
m for m in self.modimpls if m.module.module_type == scu.ModuleType.RESSOURCE
|
||||||
|
]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def saes(self):
|
||||||
|
"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]
|
||||||
|
|
||||||
|
|
||||||
|
# Pour raccorder le code des anciens codes qui attendent une NoteTable
|
||||||
|
class NotesTableCompat(ResultatsSemestre):
|
||||||
|
"""Implementation partielle de NotesTable WIP TODO
|
||||||
|
|
||||||
|
Les méthodes définies dans cette classe sont là
|
||||||
|
pour conserver la compatibilité abvec les codes anciens et
|
||||||
|
il n'est pas recommandé de les utiliser dans de nouveaux
|
||||||
|
développements (API malcommode et peu efficace).
|
||||||
|
"""
|
||||||
|
|
||||||
|
_cached_attrs = ResultatsSemestre._cached_attrs + ()
|
||||||
|
|
||||||
|
def __init__(self, formsemestre: FormSemestre):
|
||||||
|
super().__init__(formsemestre)
|
||||||
|
nb_etuds = len(self.etuds)
|
||||||
|
self.bonus = defaultdict(lambda: 0.0) # XXX TODO
|
||||||
|
self.ue_rangs = {u.id: (defaultdict(lambda: 0.0), nb_etuds) for u in self.ues}
|
||||||
|
self.mod_rangs = {
|
||||||
|
m.id: (defaultdict(lambda: 0), nb_etuds) for m in self.modimpls
|
||||||
|
}
|
||||||
|
self.moy_min = "NA"
|
||||||
|
self.moy_max = "NA"
|
||||||
|
|
||||||
|
def get_etudids(self, sorted=False) -> list[int]:
|
||||||
|
"""Liste des etudids inscrits, incluant les démissionnaires.
|
||||||
|
Si sorted, triée par moy. générale décroissante
|
||||||
|
Sinon, triée par ordre alphabetique de NOM
|
||||||
|
"""
|
||||||
|
# Note: pour avoir les inscrits non triés,
|
||||||
|
# utiliser [ ins.etudid for ins in self.formsemestre.inscriptions ]
|
||||||
|
if sorted:
|
||||||
|
# Tri par moy. generale décroissante
|
||||||
|
return [x[-1] for x in self.T]
|
||||||
|
|
||||||
|
return [x["etudid"] for x in self.inscrlist]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def inscrlist(self) -> list[dict]: # utilisé par PE seulement
|
||||||
|
"""Liste de dict etud, avec démissionnaires
|
||||||
|
classée dans l'ordre alphabétique de noms.
|
||||||
|
"""
|
||||||
|
etuds = self.formsemestre.get_inscrits(include_dem=True)
|
||||||
|
etuds.sort(key=lambda e: e.sort_key)
|
||||||
|
return [e.to_dict_scodoc7() for e in etuds]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def stats_moy_gen(self):
|
||||||
|
"""Stats (moy/min/max) sur la moyenne générale"""
|
||||||
|
return StatsMoyenne(self.etud_moy_gen)
|
||||||
|
|
||||||
|
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
||||||
|
"""Liste des UEs, ordonnée par numero.
|
||||||
|
Si filter_sport, retire les UE de type SPORT.
|
||||||
|
Résultat: liste de dicts { champs UE U stats moyenne UE }
|
||||||
|
"""
|
||||||
|
ues = []
|
||||||
|
for ue in self.ues:
|
||||||
|
if filter_sport and ue.type == UE_SPORT:
|
||||||
|
continue
|
||||||
|
d = ue.to_dict()
|
||||||
|
d.update(StatsMoyenne(self.etud_moy_ue[ue.id]).to_dict())
|
||||||
|
ues.append(d)
|
||||||
|
return ues
|
||||||
|
|
||||||
|
def get_modimpls_dict(self, 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)
|
||||||
|
"""
|
||||||
|
if ue_id is None:
|
||||||
|
return [m.to_dict() for m in self.modimpls]
|
||||||
|
else:
|
||||||
|
return [m.to_dict() for m in self.modimpls if m.module.ue.id == ue_id]
|
||||||
|
|
||||||
|
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.
|
||||||
|
{ 'code' : None|ATT|..., 'assidu' : 0|1, 'event_date' : , compense_formsemestre_id }
|
||||||
|
Si état défaillant, force le code a DEF
|
||||||
|
"""
|
||||||
|
if self.get_etud_etat(etudid) == DEF:
|
||||||
|
return {
|
||||||
|
"code": DEF,
|
||||||
|
"assidu": False,
|
||||||
|
"event_date": "",
|
||||||
|
"compense_formsemestre_id": None,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"code": ATT, # XXX TODO
|
||||||
|
"assidu": True, # XXX TODO
|
||||||
|
"event_date": "",
|
||||||
|
"compense_formsemestre_id": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_etud_etat(self, etudid: int) -> str:
|
||||||
|
"Etat de l'etudiant: 'I', 'D', DEF ou '' (si pas connu dans ce semestre)"
|
||||||
|
ins = self.formsemestre.etuds_inscriptions.get(etudid, None)
|
||||||
|
if ins is None:
|
||||||
|
return ""
|
||||||
|
return ins.etat
|
||||||
|
|
||||||
|
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||||
|
"""Moyenne générale de cet etudiant dans ce semestre.
|
||||||
|
Prend en compte les UE capitalisées. (TODO)
|
||||||
|
Si apc, moyenne indicative.
|
||||||
|
Si pas de notes: 'NA'
|
||||||
|
"""
|
||||||
|
return self.etud_moy_gen[etudid]
|
||||||
|
|
||||||
|
def get_etud_mod_moy(self, moduleimpl_id: int, etudid: int) -> float:
|
||||||
|
"""La moyenne de l'étudiant dans le moduleimpl
|
||||||
|
En APC, il s'agira d'une moyenne indicative sans valeur.
|
||||||
|
Result: valeur float (peut être naN) ou chaîne "NI" (non inscrit ou DEM)
|
||||||
|
"""
|
||||||
|
raise NotImplementedError() # virtual method
|
||||||
|
|
||||||
|
def get_etud_ue_status(self, etudid: int, ue_id: int):
|
||||||
|
return {
|
||||||
|
"cur_moy_ue": self.etud_moy_ue[ue_id][etudid],
|
||||||
|
"is_capitalized": False, # XXX TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_etud_rang(self, etudid: int):
|
||||||
|
return self.etud_moy_gen_ranks.get(etudid, 99999) # XXX
|
||||||
|
|
||||||
|
def get_etud_rang_group(self, etudid: int, group_id: int):
|
||||||
|
return (None, 0) # XXX unimplemented TODO
|
||||||
|
|
||||||
|
def get_evals_in_mod(self, moduleimpl_id: int) -> list[dict]:
|
||||||
|
"liste des évaluations valides dans un module"
|
||||||
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
|
evals_results = []
|
||||||
|
for e in modimpl.evaluations:
|
||||||
|
d = e.to_dict()
|
||||||
|
d["heure_debut"] = e.heure_debut # datetime.time
|
||||||
|
d["heure_fin"] = e.heure_fin
|
||||||
|
d["jour"] = e.jour # datetime
|
||||||
|
d["notes"] = {
|
||||||
|
etud.id: {
|
||||||
|
"etudid": etud.id,
|
||||||
|
"value": self.results.modimpls_evals_notes[e.moduleimpl_id][e.id][
|
||||||
|
etud.id
|
||||||
|
],
|
||||||
|
}
|
||||||
|
for etud in self.results.etuds
|
||||||
|
}
|
||||||
|
evals_results.append(d)
|
||||||
|
return evals_results
|
||||||
|
|
||||||
|
def get_moduleimpls_attente(self):
|
||||||
|
return [] # XXX TODO
|
||||||
|
|
||||||
|
def get_mod_stats(self, moduleimpl_id):
|
||||||
|
return {
|
||||||
|
"moy": "-",
|
||||||
|
"max": "-",
|
||||||
|
"min": "-",
|
||||||
|
"nb_notes": "-",
|
||||||
|
"nb_missing": "-",
|
||||||
|
"nb_valid_evals": "-",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_nom_short(self, etudid):
|
||||||
|
"formatte nom d'un etud (pour table recap)"
|
||||||
|
etud = self.identdict[etudid]
|
||||||
|
return (
|
||||||
|
(etud["nom_usuel"] or etud["nom"]).upper()
|
||||||
|
+ " "
|
||||||
|
+ etud["prenom"].capitalize()[:2]
|
||||||
|
+ "."
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def T(self):
|
||||||
|
return self.get_table_moyennes_triees()
|
||||||
|
|
||||||
|
def get_table_moyennes_triees(self) -> list:
|
||||||
|
"""Result: liste de tuples
|
||||||
|
moy_gen, moy_ue_0, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||||
|
"""
|
||||||
|
table_moyennes = []
|
||||||
|
etuds_inscriptions = self.formsemestre.etuds_inscriptions
|
||||||
|
|
||||||
|
for etudid in etuds_inscriptions:
|
||||||
|
moy_gen = self.etud_moy_gen.get(etudid, False)
|
||||||
|
if moy_gen is False:
|
||||||
|
# pas de moyenne: démissionnaire ou def
|
||||||
|
t = ["-"] + ["0.00"] * len(self.ues) + ["NI"] * len(self.modimpls)
|
||||||
|
else:
|
||||||
|
moy_ues = self.etud_moy_ue.loc[etudid]
|
||||||
|
t = [moy_gen] + list(moy_ues)
|
||||||
|
# TODO UE capitalisées: ne pas afficher moyennes modules
|
||||||
|
for modimpl in self.modimpls:
|
||||||
|
val = self.get_etud_mod_moy(modimpl.id, etudid)
|
||||||
|
t.append(val)
|
||||||
|
t.append(etudid)
|
||||||
|
table_moyennes.append(t)
|
||||||
|
# tri par moyennes décroissantes,
|
||||||
|
# en laissant les démissionnaires à la fin, par ordre alphabetique
|
||||||
|
etuds = [ins.etud for ins in etuds_inscriptions.values()]
|
||||||
|
etuds.sort(key=lambda e: e.sort_key)
|
||||||
|
self._rang_alpha = {e.id: i for i, e in enumerate(etuds)}
|
||||||
|
table_moyennes.sort(key=self._row_key)
|
||||||
|
return table_moyennes
|
||||||
|
|
||||||
|
def _row_key(self, x):
|
||||||
|
"""clé de tri par moyennes décroissantes,
|
||||||
|
en laissant les demissionnaires à la fin, par ordre alphabetique.
|
||||||
|
(moy_gen, rang_alpha)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
moy = -float(x[0])
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
moy = 1000.0
|
||||||
|
return (moy, self._rang_alpha[x[-1]])
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def identdict(self) -> dict:
|
||||||
|
"""{ etudid : etud_dict } pour tous les inscrits au semestre"""
|
||||||
|
return {
|
||||||
|
ins.etud.id: ins.etud.to_dict_scodoc7()
|
||||||
|
for ins in self.formsemestre.inscriptions
|
||||||
|
}
|
29
app/entreprises/__init__.py
Normal file
29
app/entreprises/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""entreprises.__init__
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint
|
||||||
|
from app.scodoc import sco_etud
|
||||||
|
from app.auth.models import User
|
||||||
|
|
||||||
|
bp = Blueprint("entreprises", __name__)
|
||||||
|
|
||||||
|
LOGS_LEN = 10
|
||||||
|
|
||||||
|
|
||||||
|
@bp.app_template_filter()
|
||||||
|
def format_prenom(s):
|
||||||
|
return sco_etud.format_prenom(s)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.app_template_filter()
|
||||||
|
def format_nom(s):
|
||||||
|
return sco_etud.format_nom(s)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.app_template_filter()
|
||||||
|
def get_nomcomplet(s):
|
||||||
|
user = User.query.filter_by(user_name=s).first()
|
||||||
|
return user.get_nomcomplet()
|
||||||
|
|
||||||
|
|
||||||
|
from app.entreprises import routes
|
289
app/entreprises/forms.py
Normal file
289
app/entreprises/forms.py
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gestion scolarite IUT
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import FileField, FileAllowed, FileRequired
|
||||||
|
from markupsafe import Markup
|
||||||
|
from sqlalchemy import text
|
||||||
|
from wtforms import StringField, SubmitField, TextAreaField, SelectField, HiddenField
|
||||||
|
from wtforms.fields import EmailField, DateField
|
||||||
|
from wtforms.validators import ValidationError, DataRequired, Email
|
||||||
|
|
||||||
|
from app.entreprises.models import Entreprise, EntrepriseContact
|
||||||
|
from app.models import Identite
|
||||||
|
from app.auth.models import User
|
||||||
|
|
||||||
|
CHAMP_REQUIS = "Ce champ est requis"
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseCreationForm(FlaskForm):
|
||||||
|
siret = StringField(
|
||||||
|
"SIRET",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
render_kw={"placeholder": "Numéro composé de 14 chiffres", "maxlength": "14"},
|
||||||
|
)
|
||||||
|
nom_entreprise = StringField(
|
||||||
|
"Nom de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
adresse = StringField(
|
||||||
|
"Adresse de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
codepostal = StringField(
|
||||||
|
"Code postal de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
ville = StringField(
|
||||||
|
"Ville de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
pays = StringField(
|
||||||
|
"Pays de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
render_kw={"style": "margin-bottom: 50px;"},
|
||||||
|
)
|
||||||
|
|
||||||
|
nom_contact = StringField(
|
||||||
|
"Nom du contact", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
prenom_contact = StringField(
|
||||||
|
"Prénom du contact",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
telephone = StringField(
|
||||||
|
"Téléphone du contact",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
mail = EmailField(
|
||||||
|
"Mail du contact",
|
||||||
|
validators=[
|
||||||
|
DataRequired(message=CHAMP_REQUIS),
|
||||||
|
Email(message="Adresse e-mail invalide"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
poste = StringField("Poste du contact", validators=[])
|
||||||
|
service = StringField("Service du contact", validators=[])
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
def validate_siret(self, siret):
|
||||||
|
siret = siret.data.strip()
|
||||||
|
if re.match("^\d{14}$", siret) == None:
|
||||||
|
raise ValidationError("Format incorrect")
|
||||||
|
req = requests.get(
|
||||||
|
f"https://entreprise.data.gouv.fr/api/sirene/v1/siret/{siret}"
|
||||||
|
)
|
||||||
|
if req.status_code != 200:
|
||||||
|
raise ValidationError("SIRET inexistant")
|
||||||
|
entreprise = Entreprise.query.filter_by(siret=siret).first()
|
||||||
|
if entreprise is not None:
|
||||||
|
lien = f'<a href="/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}">ici</a>'
|
||||||
|
raise ValidationError(
|
||||||
|
Markup(f"Entreprise déjà présent, lien vers la fiche : {lien}")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseModificationForm(FlaskForm):
|
||||||
|
siret = StringField("SIRET", validators=[], render_kw={"disabled": ""})
|
||||||
|
nom = StringField(
|
||||||
|
"Nom de l'entreprise",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
adresse = StringField("Adresse", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
codepostal = StringField(
|
||||||
|
"Code postal", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
ville = StringField("Ville", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
pays = StringField("Pays", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
|
||||||
|
class OffreCreationForm(FlaskForm):
|
||||||
|
intitule = StringField("Intitulé", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
description = TextAreaField(
|
||||||
|
"Description", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
type_offre = SelectField(
|
||||||
|
"Type de l'offre",
|
||||||
|
choices=[("Stage"), ("Alternance")],
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
missions = TextAreaField(
|
||||||
|
"Missions", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
duree = StringField("Durée", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
|
||||||
|
class OffreModificationForm(FlaskForm):
|
||||||
|
intitule = StringField("Intitulé", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
description = TextAreaField(
|
||||||
|
"Description", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
type_offre = SelectField(
|
||||||
|
"Type de l'offre",
|
||||||
|
choices=[("Stage"), ("Alternance")],
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
missions = TextAreaField(
|
||||||
|
"Missions", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
duree = StringField("Durée", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
|
||||||
|
class ContactCreationForm(FlaskForm):
|
||||||
|
hidden_entreprise_id = HiddenField()
|
||||||
|
nom = StringField("Nom", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
prenom = StringField("Prénom", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
telephone = StringField(
|
||||||
|
"Téléphone", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
mail = EmailField(
|
||||||
|
"Mail",
|
||||||
|
validators=[
|
||||||
|
DataRequired(message=CHAMP_REQUIS),
|
||||||
|
Email(message="Adresse e-mail invalide"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
poste = StringField("Poste", validators=[])
|
||||||
|
service = StringField("Service", validators=[])
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
rv = FlaskForm.validate(self)
|
||||||
|
if not rv:
|
||||||
|
return False
|
||||||
|
|
||||||
|
contact = EntrepriseContact.query.filter_by(
|
||||||
|
entreprise_id=self.hidden_entreprise_id.data,
|
||||||
|
nom=self.nom.data,
|
||||||
|
prenom=self.prenom.data,
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if contact is not None:
|
||||||
|
self.nom.errors.append("Ce contact existe déjà (même nom et prénom)")
|
||||||
|
self.prenom.errors.append("")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class ContactModificationForm(FlaskForm):
|
||||||
|
nom = StringField("Nom", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
prenom = StringField("Prénom", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
telephone = StringField(
|
||||||
|
"Téléphone", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
mail = EmailField(
|
||||||
|
"Mail",
|
||||||
|
validators=[
|
||||||
|
DataRequired(message=CHAMP_REQUIS),
|
||||||
|
Email(message="Adresse e-mail invalide"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
poste = StringField("Poste", validators=[])
|
||||||
|
service = StringField("Service", validators=[])
|
||||||
|
submit = SubmitField("Modifier", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
|
||||||
|
class HistoriqueCreationForm(FlaskForm):
|
||||||
|
etudiant = StringField(
|
||||||
|
"Étudiant",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
render_kw={"placeholder": "Tapez le nom de l'étudiant puis selectionnez"},
|
||||||
|
)
|
||||||
|
type_offre = SelectField(
|
||||||
|
"Type de l'offre",
|
||||||
|
choices=[("Stage"), ("Alternance")],
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
date_debut = DateField(
|
||||||
|
"Date début", validators=[DataRequired(message=CHAMP_REQUIS)]
|
||||||
|
)
|
||||||
|
date_fin = DateField("Date fin", validators=[DataRequired(message=CHAMP_REQUIS)])
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
rv = FlaskForm.validate(self)
|
||||||
|
if not rv:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.date_debut.data > self.date_fin.data:
|
||||||
|
self.date_debut.errors.append("Les dates sont incompatibles")
|
||||||
|
self.date_fin.errors.append("Les dates sont incompatibles")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_etudiant(self, etudiant):
|
||||||
|
etudiant_data = etudiant.data.upper().strip()
|
||||||
|
stm = text(
|
||||||
|
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||||
|
)
|
||||||
|
etudiant = (
|
||||||
|
Identite.query.from_statement(stm).params(nom_prenom=etudiant_data).first()
|
||||||
|
)
|
||||||
|
if etudiant is None:
|
||||||
|
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||||
|
|
||||||
|
|
||||||
|
class EnvoiOffreForm(FlaskForm):
|
||||||
|
responsable = StringField(
|
||||||
|
"Responsable de formation",
|
||||||
|
validators=[DataRequired(message=CHAMP_REQUIS)],
|
||||||
|
)
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
def validate_responsable(self, responsable):
|
||||||
|
responsable_data = responsable.data.upper().strip()
|
||||||
|
stm = text(
|
||||||
|
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:responsable_data"
|
||||||
|
)
|
||||||
|
responsable = (
|
||||||
|
User.query.from_statement(stm)
|
||||||
|
.params(responsable_data=responsable_data)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if responsable is None:
|
||||||
|
raise ValidationError("Champ incorrect (selectionnez dans la liste)")
|
||||||
|
|
||||||
|
|
||||||
|
class AjoutFichierForm(FlaskForm):
|
||||||
|
fichier = FileField(
|
||||||
|
"Fichier",
|
||||||
|
validators=[
|
||||||
|
FileRequired(message=CHAMP_REQUIS),
|
||||||
|
FileAllowed(["pdf", "docx"], "Fichier .pdf ou .docx uniquement"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
submit = SubmitField("Envoyer", render_kw={"style": "margin-bottom: 10px;"})
|
||||||
|
|
||||||
|
|
||||||
|
class SuppressionConfirmationForm(FlaskForm):
|
||||||
|
submit = SubmitField("Supprimer", render_kw={"style": "margin-bottom: 10px;"})
|
102
app/entreprises/models.py
Normal file
102
app/entreprises/models.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
|
class Entreprise(db.Model):
|
||||||
|
__tablename__ = "entreprises"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
siret = db.Column(db.Text)
|
||||||
|
nom = db.Column(db.Text)
|
||||||
|
adresse = db.Column(db.Text)
|
||||||
|
codepostal = db.Column(db.Text)
|
||||||
|
ville = db.Column(db.Text)
|
||||||
|
pays = db.Column(db.Text)
|
||||||
|
contacts = db.relationship(
|
||||||
|
"EntrepriseContact",
|
||||||
|
backref="entreprise",
|
||||||
|
lazy="dynamic",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
)
|
||||||
|
offres = db.relationship(
|
||||||
|
"EntrepriseOffre",
|
||||||
|
backref="entreprise",
|
||||||
|
lazy="dynamic",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"siret": self.siret,
|
||||||
|
"nom": self.nom,
|
||||||
|
"adresse": self.adresse,
|
||||||
|
"codepostal": self.codepostal,
|
||||||
|
"ville": self.ville,
|
||||||
|
"pays": self.pays,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseContact(db.Model):
|
||||||
|
__tablename__ = "entreprise_contact"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
entreprise_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade")
|
||||||
|
)
|
||||||
|
nom = db.Column(db.Text)
|
||||||
|
prenom = db.Column(db.Text)
|
||||||
|
telephone = db.Column(db.Text)
|
||||||
|
mail = db.Column(db.Text)
|
||||||
|
poste = db.Column(db.Text)
|
||||||
|
service = db.Column(db.Text)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"nom": self.nom,
|
||||||
|
"prenom": self.prenom,
|
||||||
|
"telephone": self.telephone,
|
||||||
|
"mail": self.mail,
|
||||||
|
"poste": self.poste,
|
||||||
|
"service": self.service,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseOffre(db.Model):
|
||||||
|
__tablename__ = "entreprise_offre"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
entreprise_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey("entreprises.id", ondelete="cascade")
|
||||||
|
)
|
||||||
|
date_ajout = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
intitule = db.Column(db.Text)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
type_offre = db.Column(db.Text)
|
||||||
|
missions = db.Column(db.Text)
|
||||||
|
duree = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseLog(db.Model):
|
||||||
|
__tablename__ = "entreprise_log"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
authenticated_user = db.Column(db.Text)
|
||||||
|
object = db.Column(db.Integer)
|
||||||
|
text = db.Column(db.Text)
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseEtudiant(db.Model):
|
||||||
|
__tablename__ = "entreprise_etudiant"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
|
||||||
|
etudid = db.Column(db.Integer)
|
||||||
|
type_offre = db.Column(db.Text)
|
||||||
|
date_debut = db.Column(db.Date)
|
||||||
|
date_fin = db.Column(db.Date)
|
||||||
|
formation_text = db.Column(db.Text)
|
||||||
|
formation_scodoc = db.Column(db.Integer)
|
||||||
|
|
||||||
|
|
||||||
|
class EntrepriseEnvoiOffre(db.Model):
|
||||||
|
__tablename__ = "entreprise_envoi_offre"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sender_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
|
receiver_id = db.Column(db.Integer, db.ForeignKey("user.id"))
|
||||||
|
offre_id = db.Column(db.Integer, db.ForeignKey("entreprise_offre.id"))
|
||||||
|
date_envoi = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
630
app/entreprises/routes.py
Normal file
630
app/entreprises/routes.py
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
import os
|
||||||
|
from config import Config
|
||||||
|
from datetime import datetime
|
||||||
|
import glob
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from flask import render_template, redirect, url_for, request, flash, send_file, abort
|
||||||
|
from flask.json import jsonify
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app.decorators import permission_required
|
||||||
|
|
||||||
|
from app.entreprises import LOGS_LEN
|
||||||
|
from app.entreprises.forms import (
|
||||||
|
EntrepriseCreationForm,
|
||||||
|
EntrepriseModificationForm,
|
||||||
|
SuppressionConfirmationForm,
|
||||||
|
OffreCreationForm,
|
||||||
|
OffreModificationForm,
|
||||||
|
ContactCreationForm,
|
||||||
|
ContactModificationForm,
|
||||||
|
HistoriqueCreationForm,
|
||||||
|
EnvoiOffreForm,
|
||||||
|
AjoutFichierForm,
|
||||||
|
)
|
||||||
|
from app.entreprises import bp
|
||||||
|
from app.entreprises.models import (
|
||||||
|
Entreprise,
|
||||||
|
EntrepriseOffre,
|
||||||
|
EntrepriseContact,
|
||||||
|
EntrepriseLog,
|
||||||
|
EntrepriseEtudiant,
|
||||||
|
EntrepriseEnvoiOffre,
|
||||||
|
)
|
||||||
|
from app.models import Identite
|
||||||
|
from app.auth.models import User
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
from app.scodoc import sco_etud, sco_excel
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
from sqlalchemy import text
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["GET"])
|
||||||
|
def index():
|
||||||
|
entreprises = Entreprise.query.all()
|
||||||
|
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
|
||||||
|
return render_template(
|
||||||
|
"entreprises/entreprises.html",
|
||||||
|
title=("Entreprises"),
|
||||||
|
entreprises=entreprises,
|
||||||
|
logs=logs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/contacts", methods=["GET"])
|
||||||
|
def contacts():
|
||||||
|
contacts = (
|
||||||
|
db.session.query(EntrepriseContact, Entreprise)
|
||||||
|
.join(Entreprise, EntrepriseContact.entreprise_id == Entreprise.id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
logs = EntrepriseLog.query.order_by(EntrepriseLog.date.desc()).limit(LOGS_LEN).all()
|
||||||
|
return render_template(
|
||||||
|
"entreprises/contacts.html", title=("Contacts"), contacts=contacts, logs=logs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/fiche_entreprise/<int:id>", methods=["GET"])
|
||||||
|
def fiche_entreprise(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
offres = entreprise.offres
|
||||||
|
offres_with_files = []
|
||||||
|
for offre in offres:
|
||||||
|
files = []
|
||||||
|
path = os.path.join(
|
||||||
|
Config.SCODOC_VAR_DIR,
|
||||||
|
"entreprises",
|
||||||
|
f"{offre.entreprise_id}",
|
||||||
|
f"{offre.id}",
|
||||||
|
)
|
||||||
|
if os.path.exists(path):
|
||||||
|
for dir in glob.glob(
|
||||||
|
f"{path}/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"
|
||||||
|
):
|
||||||
|
for file in glob.glob(f"{dir}/*"):
|
||||||
|
file = [os.path.basename(dir), os.path.basename(file)]
|
||||||
|
files.append(file)
|
||||||
|
offres_with_files.append([offre, files])
|
||||||
|
contacts = entreprise.contacts
|
||||||
|
logs = (
|
||||||
|
EntrepriseLog.query.order_by(EntrepriseLog.date.desc())
|
||||||
|
.filter_by(object=id)
|
||||||
|
.limit(LOGS_LEN)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
historique = (
|
||||||
|
db.session.query(EntrepriseEtudiant, Identite)
|
||||||
|
.order_by(EntrepriseEtudiant.date_debut.desc())
|
||||||
|
.filter(EntrepriseEtudiant.entreprise_id == id)
|
||||||
|
.join(Identite, Identite.id == EntrepriseEtudiant.etudid)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"entreprises/fiche_entreprise.html",
|
||||||
|
title=("Fiche entreprise"),
|
||||||
|
entreprise=entreprise,
|
||||||
|
contacts=contacts,
|
||||||
|
offres=offres_with_files,
|
||||||
|
logs=logs,
|
||||||
|
historique=historique,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/offres", methods=["GET"])
|
||||||
|
def offres():
|
||||||
|
offres_recus = (
|
||||||
|
db.session.query(EntrepriseEnvoiOffre, EntrepriseOffre)
|
||||||
|
.filter(EntrepriseEnvoiOffre.receiver_id == current_user.id)
|
||||||
|
.join(EntrepriseOffre, EntrepriseOffre.id == EntrepriseEnvoiOffre.offre_id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"entreprises/offres.html", title=("Offres"), offres_recus=offres_recus
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/add_entreprise", methods=["GET", "POST"])
|
||||||
|
def add_entreprise():
|
||||||
|
form = EntrepriseCreationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
entreprise = Entreprise(
|
||||||
|
nom=form.nom_entreprise.data.strip(),
|
||||||
|
siret=form.siret.data.strip(),
|
||||||
|
adresse=form.adresse.data.strip(),
|
||||||
|
codepostal=form.codepostal.data.strip(),
|
||||||
|
ville=form.ville.data.strip(),
|
||||||
|
pays=form.pays.data.strip(),
|
||||||
|
)
|
||||||
|
db.session.add(entreprise)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.refresh(entreprise)
|
||||||
|
contact = EntrepriseContact(
|
||||||
|
entreprise_id=entreprise.id,
|
||||||
|
nom=form.nom_contact.data.strip(),
|
||||||
|
prenom=form.prenom_contact.data.strip(),
|
||||||
|
telephone=form.telephone.data.strip(),
|
||||||
|
mail=form.mail.data.strip(),
|
||||||
|
poste=form.poste.data.strip(),
|
||||||
|
service=form.service.data.strip(),
|
||||||
|
)
|
||||||
|
db.session.add(contact)
|
||||||
|
nom_entreprise = f"<a href=/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}>{entreprise.nom}</a>"
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
text=f"{nom_entreprise} - Création de la fiche entreprise ({entreprise.nom}) avec un contact",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'entreprise a été ajouté à la liste.")
|
||||||
|
return redirect(url_for("entreprises.index"))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/ajout_entreprise.html",
|
||||||
|
title=("Ajout entreprise + contact"),
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/edit_entreprise/<int:id>", methods=["GET", "POST"])
|
||||||
|
def edit_entreprise(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
form = EntrepriseModificationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
nom_entreprise = f"<a href=/ScoDoc/entreprises/fiche_entreprise/{entreprise.id}>{form.nom.data.strip()}</a>"
|
||||||
|
if entreprise.nom != form.nom.data.strip():
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"{nom_entreprise} - Modification du nom (ancien nom : {entreprise.nom})",
|
||||||
|
)
|
||||||
|
entreprise.nom = form.nom.data.strip()
|
||||||
|
db.session.add(log)
|
||||||
|
if entreprise.adresse != form.adresse.data.strip():
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"{nom_entreprise} - Modification de l'adresse (ancienne adresse : {entreprise.adresse})",
|
||||||
|
)
|
||||||
|
entreprise.adresse = form.adresse.data.strip()
|
||||||
|
db.session.add(log)
|
||||||
|
if entreprise.codepostal != form.codepostal.data.strip():
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"{nom_entreprise} - Modification du code postal (ancien code postal : {entreprise.codepostal})",
|
||||||
|
)
|
||||||
|
entreprise.codepostal = form.codepostal.data.strip()
|
||||||
|
db.session.add(log)
|
||||||
|
if entreprise.ville != form.ville.data.strip():
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"{nom_entreprise} - Modification de la ville (ancienne ville : {entreprise.ville})",
|
||||||
|
)
|
||||||
|
entreprise.ville = form.ville.data.strip()
|
||||||
|
db.session.add(log)
|
||||||
|
if entreprise.pays != form.pays.data.strip():
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"{nom_entreprise} - Modification du pays (ancien pays : {entreprise.pays})",
|
||||||
|
)
|
||||||
|
entreprise.pays = form.pays.data.strip()
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'entreprise a été modifié.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||||
|
elif request.method == "GET":
|
||||||
|
form.siret.data = entreprise.siret
|
||||||
|
form.nom.data = entreprise.nom
|
||||||
|
form.adresse.data = entreprise.adresse
|
||||||
|
form.codepostal.data = entreprise.codepostal
|
||||||
|
form.ville.data = entreprise.ville
|
||||||
|
form.pays.data = entreprise.pays
|
||||||
|
return render_template(
|
||||||
|
"entreprises/form.html", title=("Modification entreprise"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/delete_entreprise/<int:id>", methods=["GET", "POST"])
|
||||||
|
def delete_entreprise(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
form = SuppressionConfirmationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
db.session.delete(entreprise)
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text=f"Suppression de la fiche entreprise ({entreprise.nom})",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'entreprise a été supprimé de la liste.")
|
||||||
|
return redirect(url_for("entreprises.index"))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/delete_confirmation.html",
|
||||||
|
title=("Supression entreprise"),
|
||||||
|
form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/add_offre/<int:id>", methods=["GET", "POST"])
|
||||||
|
def add_offre(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
form = OffreCreationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
offre = EntrepriseOffre(
|
||||||
|
entreprise_id=entreprise.id,
|
||||||
|
intitule=form.intitule.data.strip(),
|
||||||
|
description=form.description.data.strip(),
|
||||||
|
type_offre=form.type_offre.data.strip(),
|
||||||
|
missions=form.missions.data.strip(),
|
||||||
|
duree=form.duree.data.strip(),
|
||||||
|
)
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text="Création d'une offre",
|
||||||
|
)
|
||||||
|
db.session.add(offre)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'offre a été ajouté à la fiche entreprise.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||||
|
return render_template("entreprises/form.html", title=("Ajout offre"), form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/edit_offre/<int:id>", methods=["GET", "POST"])
|
||||||
|
def edit_offre(id):
|
||||||
|
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
|
||||||
|
form = OffreModificationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
offre.intitule = form.intitule.data.strip()
|
||||||
|
offre.description = form.description.data.strip()
|
||||||
|
offre.type_offre = form.type_offre.data.strip()
|
||||||
|
offre.missions = form.missions.data.strip()
|
||||||
|
offre.duree = form.duree.data.strip()
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=offre.entreprise_id,
|
||||||
|
text="Modification d'une offre",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'offre a été modifié.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise.id))
|
||||||
|
elif request.method == "GET":
|
||||||
|
form.intitule.data = offre.intitule
|
||||||
|
form.description.data = offre.description
|
||||||
|
form.type_offre.data = offre.type_offre
|
||||||
|
form.missions.data = offre.missions
|
||||||
|
form.duree.data = offre.duree
|
||||||
|
return render_template(
|
||||||
|
"entreprises/form.html", title=("Modification offre"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/delete_offre/<int:id>", methods=["GET", "POST"])
|
||||||
|
def delete_offre(id):
|
||||||
|
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
|
||||||
|
entreprise_id = offre.entreprise.id
|
||||||
|
form = SuppressionConfirmationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
db.session.delete(offre)
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=offre.entreprise_id,
|
||||||
|
text="Suppression d'une offre",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'offre a été supprimé de la fiche entreprise.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/delete_confirmation.html", title=("Supression offre"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/add_contact/<int:id>", methods=["GET", "POST"])
|
||||||
|
def add_contact(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
form = ContactCreationForm(hidden_entreprise_id=entreprise.id)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
contact = EntrepriseContact(
|
||||||
|
entreprise_id=entreprise.id,
|
||||||
|
nom=form.nom.data.strip(),
|
||||||
|
prenom=form.prenom.data.strip(),
|
||||||
|
telephone=form.telephone.data.strip(),
|
||||||
|
mail=form.mail.data.strip(),
|
||||||
|
poste=form.poste.data.strip(),
|
||||||
|
service=form.service.data.strip(),
|
||||||
|
)
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=entreprise.id,
|
||||||
|
text="Création d'un contact",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.add(contact)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Le contact a été ajouté à la fiche entreprise.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||||
|
return render_template("entreprises/form.html", title=("Ajout contact"), form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/edit_contact/<int:id>", methods=["GET", "POST"])
|
||||||
|
def edit_contact(id):
|
||||||
|
contact = EntrepriseContact.query.filter_by(id=id).first_or_404()
|
||||||
|
form = ContactModificationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
contact.nom = form.nom.data.strip()
|
||||||
|
contact.prenom = form.prenom.data.strip()
|
||||||
|
contact.telephone = form.telephone.data.strip()
|
||||||
|
contact.mail = form.mail.data.strip()
|
||||||
|
contact.poste = form.poste.data.strip()
|
||||||
|
contact.service = form.service.data.strip()
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=contact.entreprise_id,
|
||||||
|
text="Modification d'un contact",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Le contact a été modifié.")
|
||||||
|
return redirect(
|
||||||
|
url_for("entreprises.fiche_entreprise", id=contact.entreprise.id)
|
||||||
|
)
|
||||||
|
elif request.method == "GET":
|
||||||
|
form.nom.data = contact.nom
|
||||||
|
form.prenom.data = contact.prenom
|
||||||
|
form.telephone.data = contact.telephone
|
||||||
|
form.mail.data = contact.mail
|
||||||
|
form.poste.data = contact.poste
|
||||||
|
form.service.data = contact.service
|
||||||
|
return render_template(
|
||||||
|
"entreprises/form.html", title=("Modification contact"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/delete_contact/<int:id>", methods=["GET", "POST"])
|
||||||
|
def delete_contact(id):
|
||||||
|
contact = EntrepriseContact.query.filter_by(id=id).first_or_404()
|
||||||
|
entreprise_id = contact.entreprise.id
|
||||||
|
form = SuppressionConfirmationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
contact_count = EntrepriseContact.query.filter_by(
|
||||||
|
entreprise_id=contact.entreprise.id
|
||||||
|
).count()
|
||||||
|
if contact_count == 1:
|
||||||
|
flash(
|
||||||
|
"Le contact n'a pas été supprimé de la fiche entreprise. (1 contact minimum)"
|
||||||
|
)
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id))
|
||||||
|
else:
|
||||||
|
db.session.delete(contact)
|
||||||
|
log = EntrepriseLog(
|
||||||
|
authenticated_user=current_user.user_name,
|
||||||
|
object=contact.entreprise_id,
|
||||||
|
text="Suppression d'un contact",
|
||||||
|
)
|
||||||
|
db.session.add(log)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Le contact a été supprimé de la fiche entreprise.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise_id))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/delete_confirmation.html", title=("Supression contact"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/add_historique/<int:id>", methods=["GET", "POST"])
|
||||||
|
def add_historique(id):
|
||||||
|
entreprise = Entreprise.query.filter_by(id=id).first_or_404()
|
||||||
|
form = HistoriqueCreationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
etudiant_nomcomplet = form.etudiant.data.upper().strip()
|
||||||
|
stm = text(
|
||||||
|
"SELECT id, CONCAT(nom, ' ', prenom) as nom_prenom FROM Identite WHERE CONCAT(nom, ' ', prenom)=:nom_prenom"
|
||||||
|
)
|
||||||
|
etudiant = (
|
||||||
|
Identite.query.from_statement(stm)
|
||||||
|
.params(nom_prenom=etudiant_nomcomplet)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
formation = etudiant.inscription_courante_date(
|
||||||
|
form.date_debut.data, form.date_fin.data
|
||||||
|
)
|
||||||
|
historique = EntrepriseEtudiant(
|
||||||
|
entreprise_id=entreprise.id,
|
||||||
|
etudid=etudiant.id,
|
||||||
|
type_offre=form.type_offre.data.strip(),
|
||||||
|
date_debut=form.date_debut.data,
|
||||||
|
date_fin=form.date_fin.data,
|
||||||
|
formation_text=formation.formsemestre.titre if formation else None,
|
||||||
|
formation_scodoc=formation.formsemestre.formsemestre_id
|
||||||
|
if formation
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
db.session.add(historique)
|
||||||
|
db.session.commit()
|
||||||
|
flash("L'étudiant a été ajouté sur la fiche entreprise.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=entreprise.id))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/ajout_historique.html", title=("Ajout historique"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/envoyer_offre/<int:id>", methods=["GET", "POST"])
|
||||||
|
def envoyer_offre(id):
|
||||||
|
offre = EntrepriseOffre.query.filter_by(id=id).first_or_404()
|
||||||
|
form = EnvoiOffreForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
responsable_data = form.responsable.data.upper().strip()
|
||||||
|
stm = text(
|
||||||
|
"SELECT id, UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')')) FROM \"user\" WHERE UPPER(CONCAT(nom, ' ', prenom, ' ', '(', user_name, ')'))=:responsable_data"
|
||||||
|
)
|
||||||
|
responsable = (
|
||||||
|
User.query.from_statement(stm)
|
||||||
|
.params(responsable_data=responsable_data)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
envoi_offre = EntrepriseEnvoiOffre(
|
||||||
|
sender_id=current_user.id, receiver_id=responsable.id, offre_id=offre.id
|
||||||
|
)
|
||||||
|
db.session.add(envoi_offre)
|
||||||
|
db.session.commit()
|
||||||
|
flash(f"L'offre a été envoyé à {responsable.get_nomplogin()}.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/envoi_offre_form.html", title=("Envoyer une offre"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/etudiants")
|
||||||
|
def json_etudiants():
|
||||||
|
term = request.args.get("term").strip()
|
||||||
|
etudiants = Identite.query.filter(Identite.nom.ilike(f"%{term}%")).all()
|
||||||
|
list = []
|
||||||
|
content = {}
|
||||||
|
for etudiant in etudiants:
|
||||||
|
value = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
||||||
|
if etudiant.inscription_courante() is not None:
|
||||||
|
content = {
|
||||||
|
"id": f"{etudiant.id}",
|
||||||
|
"value": value,
|
||||||
|
"info": f"{etudiant.inscription_courante().formsemestre.titre}",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
content = {"id": f"{etudiant.id}", "value": value}
|
||||||
|
list.append(content)
|
||||||
|
content = {}
|
||||||
|
return jsonify(results=list)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/responsables")
|
||||||
|
def json_responsables():
|
||||||
|
term = request.args.get("term").strip()
|
||||||
|
responsables = User.query.filter(
|
||||||
|
User.nom.ilike(f"%{term}%"), User.nom.is_not(None), User.prenom.is_not(None)
|
||||||
|
).all()
|
||||||
|
list = []
|
||||||
|
content = {}
|
||||||
|
for responsable in responsables:
|
||||||
|
value = f"{responsable.get_nomplogin()}"
|
||||||
|
content = {"id": f"{responsable.id}", "value": value, "info": ""}
|
||||||
|
list.append(content)
|
||||||
|
content = {}
|
||||||
|
return jsonify(results=list)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/export_entreprises")
|
||||||
|
def export_entreprises():
|
||||||
|
entreprises = Entreprise.query.all()
|
||||||
|
if entreprises:
|
||||||
|
keys = ["siret", "nom", "adresse", "ville", "codepostal", "pays"]
|
||||||
|
titles = keys[:]
|
||||||
|
L = [
|
||||||
|
[entreprise.to_dict().get(k, "") for k in keys]
|
||||||
|
for entreprise in entreprises
|
||||||
|
]
|
||||||
|
title = "entreprises"
|
||||||
|
xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
|
||||||
|
filename = title
|
||||||
|
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/export_contacts")
|
||||||
|
def export_contacts():
|
||||||
|
contacts = EntrepriseContact.query.all()
|
||||||
|
if contacts:
|
||||||
|
keys = ["nom", "prenom", "telephone", "mail", "poste", "service"]
|
||||||
|
titles = keys[:]
|
||||||
|
L = [[contact.to_dict().get(k, "") for k in keys] for contact in contacts]
|
||||||
|
title = "contacts"
|
||||||
|
xlsx = sco_excel.excel_simple_table(titles=titles, lines=L, sheet_name=title)
|
||||||
|
filename = title
|
||||||
|
return scu.send_file(xlsx, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route(
|
||||||
|
"/get_offre_file/<int:entreprise_id>/<int:offre_id>/<string:filedir>/<string:filename>"
|
||||||
|
)
|
||||||
|
def get_offre_file(entreprise_id, offre_id, filedir, filename):
|
||||||
|
if os.path.isfile(
|
||||||
|
os.path.join(
|
||||||
|
Config.SCODOC_VAR_DIR,
|
||||||
|
"entreprises",
|
||||||
|
f"{entreprise_id}",
|
||||||
|
f"{offre_id}",
|
||||||
|
f"{filedir}",
|
||||||
|
f"{filename}",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return send_file(
|
||||||
|
os.path.join(
|
||||||
|
Config.SCODOC_VAR_DIR,
|
||||||
|
"entreprises",
|
||||||
|
f"{entreprise_id}",
|
||||||
|
f"{offre_id}",
|
||||||
|
f"{filedir}",
|
||||||
|
f"{filename}",
|
||||||
|
),
|
||||||
|
as_attachment=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/add_offre_file/<int:offre_id>", methods=["GET", "POST"])
|
||||||
|
def add_offre_file(offre_id):
|
||||||
|
offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404()
|
||||||
|
form = AjoutFichierForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
date = f"{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}"
|
||||||
|
path = os.path.join(
|
||||||
|
Config.SCODOC_VAR_DIR,
|
||||||
|
"entreprises",
|
||||||
|
f"{offre.entreprise_id}",
|
||||||
|
f"{offre.id}",
|
||||||
|
f"{date}",
|
||||||
|
)
|
||||||
|
os.makedirs(path)
|
||||||
|
file = form.fichier.data
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
file.save(os.path.join(path, filename))
|
||||||
|
flash("Le fichier a été ajouté a l'offre.")
|
||||||
|
return redirect(url_for("entreprises.fiche_entreprise", id=offre.entreprise_id))
|
||||||
|
return render_template(
|
||||||
|
"entreprises/form.html", title=("Ajout fichier à une offre"), form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/delete_offre_file/<int:offre_id>/<string:filedir>", methods=["GET", "POST"])
|
||||||
|
def delete_offre_file(offre_id, filedir):
|
||||||
|
offre = EntrepriseOffre.query.filter_by(id=offre_id).first_or_404()
|
||||||
|
form = SuppressionConfirmationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
path = os.path.join(
|
||||||
|
Config.SCODOC_VAR_DIR,
|
||||||
|
"entreprises",
|
||||||
|
f"{offre.entreprise_id}",
|
||||||
|
f"{offre_id}",
|
||||||
|
f"{filedir}",
|
||||||
|
)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
shutil.rmtree(path)
|
||||||
|
flash("Le fichier relié à l'offre a été supprimé.")
|
||||||
|
return redirect(
|
||||||
|
url_for("entreprises.fiche_entreprise", id=offre.entreprise_id)
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"entreprises/delete_confirmation.html",
|
||||||
|
title=("Suppression fichier d'une offre"),
|
||||||
|
form=form,
|
||||||
|
)
|
@ -14,12 +14,6 @@ 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.entreprises import (
|
|
||||||
Entreprise,
|
|
||||||
EntrepriseCorrespondant,
|
|
||||||
EntrepriseContact,
|
|
||||||
)
|
|
||||||
from app.models.etudiants import (
|
from app.models.etudiants import (
|
||||||
Identite,
|
Identite,
|
||||||
Adresse,
|
Adresse,
|
||||||
|
@ -19,7 +19,7 @@ class Departement(db.Model):
|
|||||||
db.Boolean(), nullable=False, default=True, server_default="true"
|
db.Boolean(), nullable=False, default=True, server_default="true"
|
||||||
) # sur page d'accueil
|
) # sur page d'accueil
|
||||||
|
|
||||||
entreprises = db.relationship("Entreprise", lazy="dynamic", backref="departement")
|
# entreprises = db.relationship("Entreprise", lazy="dynamic", backref="departement")
|
||||||
etudiants = db.relationship("Identite", lazy="dynamic", backref="departement")
|
etudiants = db.relationship("Identite", lazy="dynamic", backref="departement")
|
||||||
formations = db.relationship("Formation", lazy="dynamic", backref="departement")
|
formations = db.relationship("Formation", lazy="dynamic", backref="departement")
|
||||||
formsemestres = db.relationship(
|
formsemestres = db.relationship(
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
# -*- coding: UTF-8 -*
|
|
||||||
|
|
||||||
"""Gestion des absences
|
|
||||||
"""
|
|
||||||
|
|
||||||
from app import db
|
|
||||||
|
|
||||||
|
|
||||||
class Entreprise(db.Model):
|
|
||||||
"""une entreprise"""
|
|
||||||
|
|
||||||
__tablename__ = "entreprises"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
entreprise_id = db.synonym("id")
|
|
||||||
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
|
|
||||||
nom = db.Column(db.Text)
|
|
||||||
adresse = db.Column(db.Text)
|
|
||||||
ville = db.Column(db.Text)
|
|
||||||
codepostal = db.Column(db.Text)
|
|
||||||
pays = db.Column(db.Text)
|
|
||||||
contact_origine = db.Column(db.Text)
|
|
||||||
secteur = db.Column(db.Text)
|
|
||||||
note = db.Column(db.Text)
|
|
||||||
privee = db.Column(db.Text)
|
|
||||||
localisation = db.Column(db.Text)
|
|
||||||
# -1 inconnue, 0, 25, 50, 75, 100:
|
|
||||||
qualite_relation = db.Column(db.Integer)
|
|
||||||
plus10salaries = db.Column(db.Boolean())
|
|
||||||
date_creation = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
|
||||||
|
|
||||||
|
|
||||||
class EntrepriseCorrespondant(db.Model):
|
|
||||||
"""Personne contact en entreprise"""
|
|
||||||
|
|
||||||
__tablename__ = "entreprise_correspondant"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
entreprise_corresp_id = db.synonym("id")
|
|
||||||
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
|
|
||||||
nom = db.Column(db.Text)
|
|
||||||
prenom = db.Column(db.Text)
|
|
||||||
civilite = db.Column(db.Text)
|
|
||||||
fonction = db.Column(db.Text)
|
|
||||||
phone1 = db.Column(db.Text)
|
|
||||||
phone2 = db.Column(db.Text)
|
|
||||||
mobile = db.Column(db.Text)
|
|
||||||
mail1 = db.Column(db.Text)
|
|
||||||
mail2 = db.Column(db.Text)
|
|
||||||
fax = db.Column(db.Text)
|
|
||||||
note = db.Column(db.Text)
|
|
||||||
|
|
||||||
|
|
||||||
class EntrepriseContact(db.Model):
|
|
||||||
"""Evènement (contact) avec une entreprise"""
|
|
||||||
|
|
||||||
__tablename__ = "entreprise_contact"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
entreprise_contact_id = db.synonym("id")
|
|
||||||
date = db.Column(db.DateTime(timezone=True))
|
|
||||||
type_contact = db.Column(db.Text)
|
|
||||||
entreprise_id = db.Column(db.Integer, db.ForeignKey("entreprises.id"))
|
|
||||||
entreprise_corresp_id = db.Column(
|
|
||||||
db.Integer, db.ForeignKey("entreprise_correspondant.id")
|
|
||||||
)
|
|
||||||
etudid = db.Column(db.Integer) # sans contrainte pour garder logs après suppression
|
|
||||||
description = db.Column(db.Text)
|
|
||||||
enseignant = db.Column(db.Text)
|
|
@ -4,11 +4,15 @@
|
|||||||
et données rattachées (adresses, annotations, ...)
|
et données rattachées (adresses, annotations, ...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import g, url_for
|
from functools import cached_property
|
||||||
|
from flask import abort, url_for
|
||||||
|
from flask import g, request
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
|
|
||||||
|
from app.scodoc import notesdb as ndb
|
||||||
|
|
||||||
|
|
||||||
class Identite(db.Model):
|
class Identite(db.Model):
|
||||||
"""étudiant"""
|
"""étudiant"""
|
||||||
@ -50,14 +54,24 @@ class Identite(db.Model):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
return f"<Etud {self.id} {self.nom} {self.prenom}>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_request(cls, etudid=None, code_nip=None):
|
||||||
|
"""Etudiant à partir de l'etudid ou du code_nip, soit
|
||||||
|
passés en argument soit retrouvés directement dans la requête web.
|
||||||
|
Erreur 404 si inexistant.
|
||||||
|
"""
|
||||||
|
args = make_etud_args(etudid=etudid, code_nip=code_nip)
|
||||||
|
return Identite.query.filter_by(**args).first_or_404()
|
||||||
|
|
||||||
|
@property
|
||||||
def civilite_str(self):
|
def civilite_str(self):
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||||
personnes ne souhaitant pas d'affichage).
|
personnes ne souhaitant pas d'affichage).
|
||||||
"""
|
"""
|
||||||
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
return {"M": "M.", "F": "Mme", "X": ""}[self.civilite]
|
||||||
|
|
||||||
def nom_disp(self):
|
def nom_disp(self) -> str:
|
||||||
"nom à afficher"
|
"Nom à afficher"
|
||||||
if self.nom_usuel:
|
if self.nom_usuel:
|
||||||
return (
|
return (
|
||||||
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
(self.nom_usuel + " (" + self.nom + ")") if self.nom else self.nom_usuel
|
||||||
@ -65,10 +79,50 @@ class Identite(db.Model):
|
|||||||
else:
|
else:
|
||||||
return self.nom
|
return self.nom
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def nomprenom(self, reverse=False) -> str:
|
||||||
|
"""Civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||||
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
|
"""
|
||||||
|
nom = self.nom_usuel or self.nom
|
||||||
|
prenom = self.prenom_str
|
||||||
|
if reverse:
|
||||||
|
fields = (nom, prenom)
|
||||||
|
else:
|
||||||
|
fields = (self.civilite_str, prenom, nom)
|
||||||
|
return " ".join([x for x in fields if x])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def prenom_str(self):
|
||||||
|
"""Prénom à afficher. Par exemple: "Jean-Christophe" """
|
||||||
|
if not self.prenom:
|
||||||
|
return ""
|
||||||
|
frags = self.prenom.split()
|
||||||
|
r = []
|
||||||
|
for frag in frags:
|
||||||
|
fields = frag.split("-")
|
||||||
|
r.append("-".join([x.lower().capitalize() for x in fields]))
|
||||||
|
return " ".join(r)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sort_key(self) -> tuple:
|
||||||
|
"clé pour tris par ordre alphabétique"
|
||||||
|
return (self.nom_usuel or self.nom).lower(), self.prenom.lower()
|
||||||
|
|
||||||
def get_first_email(self, field="email") -> str:
|
def get_first_email(self, field="email") -> str:
|
||||||
"le mail associé à la première adrese de l'étudiant, ou None"
|
"Le mail associé à la première adrese de l'étudiant, ou None"
|
||||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||||
|
|
||||||
|
def to_dict_scodoc7(self):
|
||||||
|
"""Représentation dictionnaire,
|
||||||
|
compatible ScoDoc7 mais sans infos admission
|
||||||
|
"""
|
||||||
|
e = dict(self.__dict__)
|
||||||
|
e.pop("_sa_instance_state", None)
|
||||||
|
# ScoDoc7 output_formators: (backward compat)
|
||||||
|
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
||||||
|
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
||||||
|
|
||||||
def to_dict_bul(self, include_urls=True):
|
def to_dict_bul(self, include_urls=True):
|
||||||
"""Infos exportées dans les bulletins"""
|
"""Infos exportées dans les bulletins"""
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
@ -104,6 +158,17 @@ class Identite(db.Model):
|
|||||||
]
|
]
|
||||||
return r[0] if r else None
|
return r[0] if r else None
|
||||||
|
|
||||||
|
def inscription_courante_date(self, date_debut, date_fin):
|
||||||
|
"""La première inscription à un formsemestre incluant la
|
||||||
|
période [date_debut, date_fin]
|
||||||
|
"""
|
||||||
|
r = [
|
||||||
|
ins
|
||||||
|
for ins in self.formsemestre_inscriptions
|
||||||
|
if ins.formsemestre.contient_periode(date_debut, date_fin)
|
||||||
|
]
|
||||||
|
return r[0] if r else None
|
||||||
|
|
||||||
def etat_inscription(self, formsemestre_id):
|
def etat_inscription(self, formsemestre_id):
|
||||||
"""etat de l'inscription de cet étudiant au semestre:
|
"""etat de l'inscription de cet étudiant au semestre:
|
||||||
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
||||||
@ -117,6 +182,42 @@ class Identite(db.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def make_etud_args(
|
||||||
|
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||||
|
) -> dict:
|
||||||
|
"""forme args dict pour requete recherche etudiant
|
||||||
|
On peut specifier etudid
|
||||||
|
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
||||||
|
(dans cet ordre).
|
||||||
|
|
||||||
|
Résultat: dict avec soit "etudid", soit "code_nip", soit "code_ine"
|
||||||
|
"""
|
||||||
|
args = None
|
||||||
|
if etudid:
|
||||||
|
args = {"etudid": etudid}
|
||||||
|
elif code_nip:
|
||||||
|
args = {"code_nip": code_nip}
|
||||||
|
elif use_request: # use form from current request (Flask global)
|
||||||
|
if request.method == "POST":
|
||||||
|
vals = request.form
|
||||||
|
elif request.method == "GET":
|
||||||
|
vals = request.args
|
||||||
|
else:
|
||||||
|
vals = {}
|
||||||
|
if "etudid" in vals:
|
||||||
|
args = {"etudid": int(vals["etudid"])}
|
||||||
|
elif "code_nip" in vals:
|
||||||
|
args = {"code_nip": str(vals["code_nip"])}
|
||||||
|
elif "code_ine" in vals:
|
||||||
|
args = {"code_ine": str(vals["code_ine"])}
|
||||||
|
if not args:
|
||||||
|
if abort_404:
|
||||||
|
abort(404, "pas d'étudiant sélectionné")
|
||||||
|
elif raise_exc:
|
||||||
|
raise ValueError("make_etud_args: pas d'étudiant sélectionné !")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
class Adresse(db.Model):
|
class Adresse(db.Model):
|
||||||
"""Adresse d'un étudiant
|
"""Adresse d'un étudiant
|
||||||
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
|
(le modèle permet plusieurs adresses, mais l'UI n'en gère qu'une seule)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"""ScoDoc models: formsemestre
|
"""ScoDoc models: formsemestre
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
import flask_sqlalchemy
|
import flask_sqlalchemy
|
||||||
|
|
||||||
@ -84,7 +85,11 @@ class FormSemestre(db.Model):
|
|||||||
etapes = db.relationship(
|
etapes = db.relationship(
|
||||||
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
|
"FormSemestreEtape", cascade="all,delete", backref="formsemestre"
|
||||||
)
|
)
|
||||||
modimpls = db.relationship("ModuleImpl", backref="formsemestre", lazy="dynamic")
|
modimpls = db.relationship(
|
||||||
|
"ModuleImpl",
|
||||||
|
backref="formsemestre",
|
||||||
|
lazy="dynamic",
|
||||||
|
)
|
||||||
etuds = db.relationship(
|
etuds = db.relationship(
|
||||||
"Identite",
|
"Identite",
|
||||||
secondary="notes_formsemestre_inscription",
|
secondary="notes_formsemestre_inscription",
|
||||||
@ -146,6 +151,13 @@ class FormSemestre(db.Model):
|
|||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
return (self.date_debut <= today) and (today <= self.date_fin)
|
return (self.date_debut <= today) and (today <= self.date_fin)
|
||||||
|
|
||||||
|
def contient_periode(self, date_debut, date_fin) -> bool:
|
||||||
|
"""Vrai si l'intervalle [date_debut, date_fin] est
|
||||||
|
inclus dans le semestre.
|
||||||
|
(les dates de début et fin sont incluses)
|
||||||
|
"""
|
||||||
|
return (self.date_debut <= date_debut) and (date_fin <= self.date_fin)
|
||||||
|
|
||||||
def est_decale(self):
|
def est_decale(self):
|
||||||
"""Vrai si semestre "décalé"
|
"""Vrai si semestre "décalé"
|
||||||
c'est à dire semestres impairs commençant entre janvier et juin
|
c'est à dire semestres impairs commençant entre janvier et juin
|
||||||
@ -240,7 +252,7 @@ class FormSemestre(db.Model):
|
|||||||
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
etudid, self.date_debut.isoformat(), self.date_fin.isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_inscrits(self, include_dem=False) -> list:
|
def get_inscrits(self, include_dem=False) -> list[Identite]:
|
||||||
"""Liste des étudiants inscrits à ce semestre
|
"""Liste des étudiants inscrits à ce semestre
|
||||||
Si all, tous les étudiants, avec les démissionnaires.
|
Si all, tous les étudiants, avec les démissionnaires.
|
||||||
"""
|
"""
|
||||||
@ -249,6 +261,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 etuds_inscriptions(self) -> dict:
|
||||||
|
"""Map { etudid : inscription }"""
|
||||||
|
return {ins.etud.id: ins for ins in self.inscriptions}
|
||||||
|
|
||||||
|
|
||||||
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
|
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
|
||||||
notes_formsemestre_responsables = db.Table(
|
notes_formsemestre_responsables = db.Table(
|
||||||
|
@ -50,7 +50,7 @@ class ModuleImpl(db.Model):
|
|||||||
if evaluations_poids is None:
|
if evaluations_poids is None:
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
|
||||||
evaluations_poids, _ = moy_mod.df_load_evaluations_poids(self.id)
|
evaluations_poids, _ = moy_mod.load_evaluations_poids(self.id)
|
||||||
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
df_cache.EvaluationsPoidsCache.set(self.id, evaluations_poids)
|
||||||
return evaluations_poids
|
return evaluations_poids
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class ModuleImpl(db.Model):
|
|||||||
return True
|
return True
|
||||||
from app.comp import moy_mod
|
from app.comp import moy_mod
|
||||||
|
|
||||||
return moy_mod.check_moduleimpl_conformity(
|
return moy_mod.moduleimpl_is_conforme(
|
||||||
self,
|
self,
|
||||||
self.get_evaluations_poids(),
|
self.get_evaluations_poids(),
|
||||||
self.module.formation.get_module_coefs(self.module.semestre_id),
|
self.module.formation.get_module_coefs(self.module.semestre_id),
|
||||||
|
@ -68,7 +68,7 @@ class TableTag(object):
|
|||||||
self.taglist = []
|
self.taglist = []
|
||||||
|
|
||||||
self.resultats = {}
|
self.resultats = {}
|
||||||
self.rangs = {}
|
self.etud_moy_gen_ranks = {}
|
||||||
self.statistiques = {}
|
self.statistiques = {}
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
@ -117,15 +117,15 @@ class TableTag(object):
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------
|
||||||
def get_moy_from_stats(self, tag):
|
def get_moy_from_stats(self, tag):
|
||||||
""" Renvoie la moyenne des notes calculées pour d'un tag donné"""
|
"""Renvoie la moyenne des notes calculées pour d'un tag donné"""
|
||||||
return self.statistiques[tag][0] if tag in self.statistiques else None
|
return self.statistiques[tag][0] if tag in self.statistiques else None
|
||||||
|
|
||||||
def get_min_from_stats(self, tag):
|
def get_min_from_stats(self, tag):
|
||||||
""" Renvoie la plus basse des notes calculées pour d'un tag donné"""
|
"""Renvoie la plus basse des notes calculées pour d'un tag donné"""
|
||||||
return self.statistiques[tag][1] if tag in self.statistiques else None
|
return self.statistiques[tag][1] if tag in self.statistiques else None
|
||||||
|
|
||||||
def get_max_from_stats(self, tag):
|
def get_max_from_stats(self, tag):
|
||||||
""" Renvoie la plus haute des notes calculées pour d'un tag donné"""
|
"""Renvoie la plus haute des notes calculées pour d'un tag donné"""
|
||||||
return self.statistiques[tag][2] if tag in self.statistiques else None
|
return self.statistiques[tag][2] if tag in self.statistiques else None
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------
|
||||||
@ -236,7 +236,7 @@ class TableTag(object):
|
|||||||
return moystr
|
return moystr
|
||||||
|
|
||||||
def str_res_d_un_etudiant(self, etudid, delim=";"):
|
def str_res_d_un_etudiant(self, etudid, delim=";"):
|
||||||
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique). """
|
"""Renvoie sur une ligne les résultats d'un étudiant à tous les tags (par ordre alphabétique)."""
|
||||||
return delim.join(
|
return delim.join(
|
||||||
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
|
[self.str_resTag_d_un_etudiant(tag, etudid) for tag in self.get_all_tags()]
|
||||||
)
|
)
|
||||||
@ -256,7 +256,7 @@ class TableTag(object):
|
|||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
def str_tagtable(self, delim=";", decimal_sep=","):
|
def str_tagtable(self, delim=";", decimal_sep=","):
|
||||||
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags. """
|
"""Renvoie une chaine de caractère listant toutes les moyennes, les rangs des étudiants pour tous les tags."""
|
||||||
entete = ["etudid", "nom", "prenom"]
|
entete = ["etudid", "nom", "prenom"]
|
||||||
for tag in self.get_all_tags():
|
for tag in self.get_all_tags():
|
||||||
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
|
entete += [titre + "_" + tag for titre in ["note", "rang", "nb_inscrit"]]
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Calculs sur les notes et cache des resultats
|
"""Calculs sur les notes et cache des résultats
|
||||||
|
|
||||||
|
Ancien code ScoDoc 7 en cours de rénovation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
@ -102,7 +104,7 @@ def comp_ranks(T):
|
|||||||
|
|
||||||
def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
|
def get_sem_ues_modimpls(formsemestre_id, modimpls=None):
|
||||||
"""Get liste des UE du semestre (à partir des moduleimpls)
|
"""Get liste des UE du semestre (à partir des moduleimpls)
|
||||||
(utilisé quand on ne peut pas construire nt et faire nt.get_ues())
|
(utilisé quand on ne peut pas construire nt et faire nt.get_ues_stat_dict())
|
||||||
"""
|
"""
|
||||||
if modimpls is None:
|
if modimpls is None:
|
||||||
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
@ -200,7 +202,7 @@ class NotesTable:
|
|||||||
self.inscrlist.sort(key=itemgetter("nomp"))
|
self.inscrlist.sort(key=itemgetter("nomp"))
|
||||||
|
|
||||||
# { etudid : rang dans l'ordre alphabetique }
|
# { etudid : rang dans l'ordre alphabetique }
|
||||||
self.rang_alpha = {e["etudid"]: i for i, e in enumerate(self.inscrlist)}
|
self._rang_alpha = {e["etudid"]: i for i, e in enumerate(self.inscrlist)}
|
||||||
|
|
||||||
self.bonus = scu.DictDefault(defaultvalue=0)
|
self.bonus = scu.DictDefault(defaultvalue=0)
|
||||||
# Notes dans les modules { moduleimpl_id : { etudid: note_moyenne_dans_ce_module } }
|
# Notes dans les modules { moduleimpl_id : { etudid: note_moyenne_dans_ce_module } }
|
||||||
@ -294,7 +296,7 @@ class NotesTable:
|
|||||||
for ue in self._ues:
|
for ue in self._ues:
|
||||||
is_cap[ue["ue_id"]] = ue_status[ue["ue_id"]]["is_capitalized"]
|
is_cap[ue["ue_id"]] = ue_status[ue["ue_id"]]["is_capitalized"]
|
||||||
|
|
||||||
for modimpl in self.get_modimpls():
|
for modimpl in self.get_modimpls_dict():
|
||||||
val = self.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
val = self.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
|
||||||
if is_cap[modimpl["module"]["ue_id"]]:
|
if is_cap[modimpl["module"]["ue_id"]]:
|
||||||
t.append("-c-")
|
t.append("-c-")
|
||||||
@ -316,7 +318,7 @@ class NotesTable:
|
|||||||
self.moy_min = self.moy_max = "NA"
|
self.moy_min = self.moy_max = "NA"
|
||||||
|
|
||||||
# calcul rangs (/ moyenne generale)
|
# calcul rangs (/ moyenne generale)
|
||||||
self.rangs = comp_ranks(T)
|
self.etud_moy_gen_ranks = comp_ranks(T)
|
||||||
|
|
||||||
self.rangs_groupes = (
|
self.rangs_groupes = (
|
||||||
{}
|
{}
|
||||||
@ -364,7 +366,7 @@ class NotesTable:
|
|||||||
moy = -float(x[0])
|
moy = -float(x[0])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
moy = 1000.0
|
moy = 1000.0
|
||||||
return (moy, self.rang_alpha[x[-1]])
|
return (moy, self._rang_alpha[x[-1]])
|
||||||
|
|
||||||
def get_etudids(self, sorted=False):
|
def get_etudids(self, sorted=False):
|
||||||
if sorted:
|
if sorted:
|
||||||
@ -417,46 +419,17 @@ class NotesTable:
|
|||||||
else:
|
else:
|
||||||
return ' <font color="red">(%s)</font> ' % etat
|
return ' <font color="red">(%s)</font> ' % etat
|
||||||
|
|
||||||
def get_ues(self, filter_sport=False, filter_non_inscrit=False, etudid=None):
|
def get_ues_stat_dict(self, filter_sport=False): # was get_ues()
|
||||||
"""liste des ue, ordonnée par numero.
|
"""Liste des UEs, ordonnée par numero.
|
||||||
Si filter_non_inscrit, retire les UE dans lesquelles l'etudiant n'est
|
|
||||||
inscrit à aucun module.
|
|
||||||
Si filter_sport, retire les UE de type SPORT
|
Si filter_sport, retire les UE de type SPORT
|
||||||
"""
|
"""
|
||||||
if not filter_sport and not filter_non_inscrit:
|
if not filter_sport:
|
||||||
return self._ues
|
return self._ues
|
||||||
|
|
||||||
if filter_sport:
|
|
||||||
ues_src = [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
|
||||||
else:
|
else:
|
||||||
ues_src = self._ues
|
return [ue for ue in self._ues if ue["type"] != UE_SPORT]
|
||||||
if not filter_non_inscrit:
|
|
||||||
return ues_src
|
|
||||||
ues = []
|
|
||||||
for ue in ues_src:
|
|
||||||
if self.get_etud_ue_status(etudid, ue["ue_id"])["is_capitalized"]:
|
|
||||||
# garde toujours les UE capitalisees
|
|
||||||
has_note = True
|
|
||||||
else:
|
|
||||||
has_note = False
|
|
||||||
# verifie que l'etud. est inscrit a au moins un module de l'UE
|
|
||||||
# (en fait verifie qu'il a une note)
|
|
||||||
modimpls = self.get_modimpls(ue["ue_id"])
|
|
||||||
|
|
||||||
for modi in modimpls:
|
def get_modimpls_dict(self, ue_id=None):
|
||||||
moy = self.get_etud_mod_moy(modi["moduleimpl_id"], etudid)
|
"Liste des modules pour une UE (ou toutes si ue_id==None), triés par matières."
|
||||||
try:
|
|
||||||
float(moy)
|
|
||||||
has_note = True
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if has_note:
|
|
||||||
ues.append(ue)
|
|
||||||
return ues
|
|
||||||
|
|
||||||
def get_modimpls(self, ue_id=None):
|
|
||||||
"liste des modules pour une UE (ou toutes si ue_id==None), triés par matières."
|
|
||||||
if ue_id is None:
|
if ue_id is None:
|
||||||
r = self._modimpls
|
r = self._modimpls
|
||||||
else:
|
else:
|
||||||
@ -522,7 +495,7 @@ class NotesTable:
|
|||||||
|
|
||||||
Les moyennes d'UE ne tiennent pas compte des capitalisations.
|
Les moyennes d'UE ne tiennent pas compte des capitalisations.
|
||||||
"""
|
"""
|
||||||
ues = self.get_ues()
|
ues = self.get_ues_stat_dict()
|
||||||
sum_moy = 0 # la somme des moyennes générales valides
|
sum_moy = 0 # la somme des moyennes générales valides
|
||||||
nb_moy = 0 # le nombre de moyennes générales valides
|
nb_moy = 0 # le nombre de moyennes générales valides
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
@ -561,9 +534,9 @@ class NotesTable:
|
|||||||
i = 0
|
i = 0
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
i += 1
|
i += 1
|
||||||
ue["nb_moy"] = len(ue["_notes"])
|
ue["nb_vals"] = len(ue["_notes"])
|
||||||
if ue["nb_moy"] > 0:
|
if ue["nb_vals"] > 0:
|
||||||
ue["moy"] = sum(ue["_notes"]) / ue["nb_moy"]
|
ue["moy"] = sum(ue["_notes"]) / ue["nb_vals"]
|
||||||
ue["max"] = max(ue["_notes"])
|
ue["max"] = max(ue["_notes"])
|
||||||
ue["min"] = min(ue["_notes"])
|
ue["min"] = min(ue["_notes"])
|
||||||
else:
|
else:
|
||||||
@ -591,7 +564,7 @@ class NotesTable:
|
|||||||
Si non inscrit, moy == 'NI' et sum_coefs==0
|
Si non inscrit, moy == 'NI' et sum_coefs==0
|
||||||
"""
|
"""
|
||||||
assert ue_id
|
assert ue_id
|
||||||
modimpls = self.get_modimpls(ue_id)
|
modimpls = self.get_modimpls_dict(ue_id)
|
||||||
nb_notes = 0 # dans cette UE
|
nb_notes = 0 # dans cette UE
|
||||||
sum_notes = 0.0
|
sum_notes = 0.0
|
||||||
sum_coefs = 0.0
|
sum_coefs = 0.0
|
||||||
@ -767,7 +740,7 @@ class NotesTable:
|
|||||||
sem_ects_pot_fond = 0.0
|
sem_ects_pot_fond = 0.0
|
||||||
sem_ects_pot_pro = 0.0
|
sem_ects_pot_pro = 0.0
|
||||||
|
|
||||||
for ue in self.get_ues():
|
for ue in self.get_ues_stat_dict():
|
||||||
# - On calcule la moyenne d'UE courante:
|
# - On calcule la moyenne d'UE courante:
|
||||||
if not block_computation:
|
if not block_computation:
|
||||||
mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx)
|
mu = self.comp_etud_moy_ue(etudid, ue_id=ue["ue_id"], cnx=cnx)
|
||||||
@ -948,7 +921,7 @@ class NotesTable:
|
|||||||
|
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def get_etud_moy_gen(self, etudid):
|
def get_etud_moy_gen(self, etudid): # -> float | str
|
||||||
"""Moyenne generale de cet etudiant dans ce semestre.
|
"""Moyenne generale de cet etudiant dans ce semestre.
|
||||||
Prend en compte les UE capitalisées.
|
Prend en compte les UE capitalisées.
|
||||||
Si pas de notes: 'NA'
|
Si pas de notes: 'NA'
|
||||||
@ -981,7 +954,7 @@ class NotesTable:
|
|||||||
return self.T
|
return self.T
|
||||||
|
|
||||||
def get_etud_rang(self, etudid) -> str:
|
def get_etud_rang(self, etudid) -> str:
|
||||||
return self.rangs.get(etudid, "999")
|
return self.etud_moy_gen_ranks.get(etudid, "999")
|
||||||
|
|
||||||
def get_etud_rang_group(self, etudid, group_id):
|
def get_etud_rang_group(self, etudid, group_id):
|
||||||
"""Returns rank of etud in this group and number of etuds in group.
|
"""Returns rank of etud in this group and number of etuds in group.
|
||||||
@ -1347,7 +1320,7 @@ class NotesTable:
|
|||||||
|
|
||||||
# Rappel des épisodes précédents: T est une liste de liste
|
# Rappel des épisodes précédents: T est une liste de liste
|
||||||
# Colonnes: 0 moy_gen, moy_ue1, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
# Colonnes: 0 moy_gen, moy_ue1, ..., moy_ue_n, moy_mod1, ..., moy_mod_n, etudid
|
||||||
ues = self.get_ues() # incluant le(s) UE de sport
|
ues = self.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||||
for t in self.T:
|
for t in self.T:
|
||||||
etudid = t[-1]
|
etudid = t[-1]
|
||||||
if etudid in results.etud_moy_gen: # evite les démissionnaires
|
if etudid in results.etud_moy_gen: # evite les démissionnaires
|
||||||
@ -1358,4 +1331,4 @@ class NotesTable:
|
|||||||
# 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:
|
||||||
self.rangs = results.etud_moy_gen_ranks
|
self.etud_moy_gen_ranks = results.etud_moy_gen_ranks
|
||||||
|
@ -32,6 +32,8 @@ import datetime
|
|||||||
|
|
||||||
from flask import url_for, g, request, abort
|
from flask import url_for, g, request, abort
|
||||||
|
|
||||||
|
from app import log
|
||||||
|
from app.models import Identite
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
@ -46,7 +48,6 @@ from app.scodoc import sco_groups
|
|||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app import log
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
@ -71,8 +72,8 @@ def doSignaleAbsence(
|
|||||||
etudid: etudiant concerné. Si non spécifié, cherche dans
|
etudid: etudiant concerné. Si non spécifié, cherche dans
|
||||||
les paramètres de la requête courante.
|
les paramètres de la requête courante.
|
||||||
"""
|
"""
|
||||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
etud = Identite.from_request(etudid)
|
||||||
etudid = etud["etudid"]
|
|
||||||
if not moduleimpl_id:
|
if not moduleimpl_id:
|
||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
description_abs = description
|
description_abs = description
|
||||||
@ -82,7 +83,7 @@ def doSignaleAbsence(
|
|||||||
for jour in dates:
|
for jour in dates:
|
||||||
if demijournee == 2:
|
if demijournee == 2:
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
False,
|
False,
|
||||||
estjust,
|
estjust,
|
||||||
@ -90,7 +91,7 @@ def doSignaleAbsence(
|
|||||||
moduleimpl_id,
|
moduleimpl_id,
|
||||||
)
|
)
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
True,
|
True,
|
||||||
estjust,
|
estjust,
|
||||||
@ -100,7 +101,7 @@ def doSignaleAbsence(
|
|||||||
nbadded += 2
|
nbadded += 2
|
||||||
else:
|
else:
|
||||||
sco_abs.add_absence(
|
sco_abs.add_absence(
|
||||||
etudid,
|
etud.id,
|
||||||
jour,
|
jour,
|
||||||
demijournee,
|
demijournee,
|
||||||
estjust,
|
estjust,
|
||||||
@ -113,27 +114,27 @@ def doSignaleAbsence(
|
|||||||
J = ""
|
J = ""
|
||||||
else:
|
else:
|
||||||
J = "NON "
|
J = "NON "
|
||||||
M = ""
|
indication_module = ""
|
||||||
if moduleimpl_id and moduleimpl_id != "NULL":
|
if moduleimpl_id and moduleimpl_id != "NULL":
|
||||||
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
mod = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
||||||
formsemestre_id = mod["formsemestre_id"]
|
formsemestre_id = mod["formsemestre_id"]
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
ues = nt.get_ues(etudid=etudid)
|
ues = nt.get_ues_stat_dict()
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
if modimpl["moduleimpl_id"] == moduleimpl_id:
|
||||||
M = "dans le module %s" % modimpl["module"]["code"]
|
indication_module = "dans le module %s" % modimpl["module"]["code"]
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(
|
html_sco_header.sco_header(
|
||||||
page_title="Signalement d'une absence pour %(nomprenom)s" % etud,
|
page_title=f"Signalement d'une absence pour {etud.nomprenom}",
|
||||||
),
|
),
|
||||||
"""<h2>Signalement d'absences</h2>""",
|
"""<h2>Signalement d'absences</h2>""",
|
||||||
]
|
]
|
||||||
if dates:
|
if dates:
|
||||||
H.append(
|
H.append(
|
||||||
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
|
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
|
||||||
% (nbadded, J, datedebut, datefin, M)
|
% (nbadded, J, datedebut, datefin, indication_module)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(
|
H.append(
|
||||||
@ -142,11 +143,18 @@ def doSignaleAbsence(
|
|||||||
)
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<ul><li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Autre absence pour <b>%(nomprenom)s</b></a></li>
|
f"""<ul>
|
||||||
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
|
<li><a href="{url_for("absences.SignaleAbsenceEtud",
|
||||||
</ul>
|
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
<hr>"""
|
)}">Autre absence pour <b>{etud.nomprenom}</b></a>
|
||||||
% etud
|
</li>
|
||||||
|
<li><a href="{url_for("absences.CalAbs",
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
|
)}">Calendrier de ses absences</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
H.append(sco_find_etud.form_search_etud())
|
H.append(sco_find_etud.form_search_etud())
|
||||||
H.append(html_sco_header.sco_footer())
|
H.append(html_sco_header.sco_footer())
|
||||||
@ -175,7 +183,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
|||||||
"abs_require_module", formsemestre_id
|
"abs_require_module", formsemestre_id
|
||||||
)
|
)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
ues = nt.get_ues(etudid=etudid)
|
ues = nt.get_ues_stat_dict()
|
||||||
if require_module:
|
if require_module:
|
||||||
menu_module = """
|
menu_module = """
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -200,7 +208,7 @@ def SignaleAbsenceEtud(): # etudid implied
|
|||||||
menu_module += """<option value="" selected>(Module)</option>"""
|
menu_module += """<option value="" selected>(Module)</option>"""
|
||||||
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls = nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
menu_module += (
|
menu_module += (
|
||||||
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
|
||||||
|
@ -437,7 +437,7 @@ class ApoEtud(dict):
|
|||||||
|
|
||||||
# Elements UE
|
# Elements UE
|
||||||
decisions_ue = nt.get_etud_decision_ues(etudid)
|
decisions_ue = nt.get_etud_decision_ues(etudid)
|
||||||
for ue in nt.get_ues():
|
for ue in nt.get_ues_stat_dict():
|
||||||
if code in ue["code_apogee"].split(","):
|
if code in ue["code_apogee"].split(","):
|
||||||
if self.export_res_ues:
|
if self.export_res_ues:
|
||||||
if decisions_ue and ue["ue_id"] in decisions_ue:
|
if decisions_ue and ue["ue_id"] in decisions_ue:
|
||||||
@ -456,7 +456,7 @@ class ApoEtud(dict):
|
|||||||
return VOID_APO_RES
|
return VOID_APO_RES
|
||||||
|
|
||||||
# Elements Modules
|
# Elements Modules
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
module_code_found = False
|
module_code_found = False
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if code in modimpl["module"]["code_apogee"].split(","):
|
if code in modimpl["module"]["code_apogee"].split(","):
|
||||||
@ -973,12 +973,12 @@ class ApoData(object):
|
|||||||
continue
|
continue
|
||||||
# associé à une UE:
|
# associé à une UE:
|
||||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
for ue in nt.get_ues():
|
for ue in nt.get_ues_stat_dict():
|
||||||
if code in ue["code_apogee"].split(","):
|
if code in ue["code_apogee"].split(","):
|
||||||
s.add(code)
|
s.add(code)
|
||||||
continue
|
continue
|
||||||
# associé à un module:
|
# associé à un module:
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
if code in modimpl["module"]["code_apogee"].split(","):
|
if code in modimpl["module"]["code_apogee"].split(","):
|
||||||
s.add(code)
|
s.add(code)
|
||||||
|
@ -218,10 +218,10 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
] # deprecated / keep it for backward compat in templates
|
] # deprecated / keep it for backward compat in templates
|
||||||
|
|
||||||
# --- Notes
|
# --- Notes
|
||||||
ues = nt.get_ues()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
moy_gen = nt.get_etud_moy_gen(etudid)
|
moy_gen = nt.get_etud_moy_gen(etudid)
|
||||||
I["nb_inscrits"] = len(nt.rangs)
|
I["nb_inscrits"] = len(nt.etud_moy_gen_ranks)
|
||||||
I["moy_gen"] = scu.fmt_note(moy_gen)
|
I["moy_gen"] = scu.fmt_note(moy_gen)
|
||||||
I["moy_min"] = scu.fmt_note(nt.moy_min)
|
I["moy_min"] = scu.fmt_note(nt.moy_min)
|
||||||
I["moy_max"] = scu.fmt_note(nt.moy_max)
|
I["moy_max"] = scu.fmt_note(nt.moy_max)
|
||||||
@ -265,7 +265,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
I["rang_gr"] = rang_gr
|
I["rang_gr"] = rang_gr
|
||||||
I["gr_name"] = gr_name
|
I["gr_name"] = gr_name
|
||||||
I["ninscrits_gr"] = ninscrits_gr
|
I["ninscrits_gr"] = ninscrits_gr
|
||||||
I["nbetuds"] = len(nt.rangs)
|
I["nbetuds"] = len(nt.etud_moy_gen_ranks)
|
||||||
I["nb_demissions"] = nt.nb_demissions
|
I["nb_demissions"] = nt.nb_demissions
|
||||||
I["nb_defaillants"] = nt.nb_defaillants
|
I["nb_defaillants"] = nt.nb_defaillants
|
||||||
if prefs["bul_show_rangs"]:
|
if prefs["bul_show_rangs"]:
|
||||||
@ -352,7 +352,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
etudid,
|
etudid,
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
ue_status["capitalized_ue_id"],
|
ue_status["capitalized_ue_id"],
|
||||||
nt_cap.get_modimpls(),
|
nt_cap.get_modimpls_dict(),
|
||||||
nt_cap,
|
nt_cap,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
|
@ -153,9 +153,9 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
pid = partition["partition_id"]
|
pid = partition["partition_id"]
|
||||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||||
|
|
||||||
ues = nt.get_ues()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
nbetuds = len(nt.rangs)
|
nbetuds = len(nt.etud_moy_gen_ranks)
|
||||||
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
||||||
if (
|
if (
|
||||||
nt.get_moduleimpls_attente()
|
nt.get_moduleimpls_attente()
|
||||||
|
@ -151,9 +151,9 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||||
|
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||||
ues = nt.get_ues()
|
ues = nt.get_ues_stat_dict()
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
nbetuds = len(nt.rangs)
|
nbetuds = len(nt.etud_moy_gen_ranks)
|
||||||
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
mg = scu.fmt_note(nt.get_etud_moy_gen(etudid))
|
||||||
if (
|
if (
|
||||||
nt.get_moduleimpls_attente()
|
nt.get_moduleimpls_attente()
|
||||||
|
@ -155,14 +155,14 @@ class EvaluationCache(ScoDocCache):
|
|||||||
cls.delete_many(evaluation_ids)
|
cls.delete_many(evaluation_ids)
|
||||||
|
|
||||||
|
|
||||||
class ResultatsSemestreBUTCache(ScoDocCache):
|
class ResultatsSemestreCache(ScoDocCache):
|
||||||
"""Cache pour les résultats ResultatsSemestreBUT.
|
"""Cache pour les résultats ResultatsSemestre.
|
||||||
Clé: formsemestre_id
|
Clé: formsemestre_id
|
||||||
Valeur: { un paquet de dataframes }
|
Valeur: { un paquet de dataframes }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RBUT"
|
prefix = "RSEM"
|
||||||
timeout = 1 * 60 # ttl 1 minutes (en phase de mise au point)
|
timeout = 60 * 60 # ttl 1 heure (en phase de mise au point)
|
||||||
|
|
||||||
|
|
||||||
class AbsSemEtudCache(ScoDocCache):
|
class AbsSemEtudCache(ScoDocCache):
|
||||||
@ -299,7 +299,7 @@ def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=Fa
|
|||||||
SemInscriptionsCache.delete_many(formsemestre_ids)
|
SemInscriptionsCache.delete_many(formsemestre_ids)
|
||||||
|
|
||||||
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
SemBulletinsPDFCache.invalidate_sems(formsemestre_ids)
|
||||||
ResultatsSemestreBUTCache.delete_many(formsemestre_ids)
|
ResultatsSemestreCache.delete_many(formsemestre_ids)
|
||||||
|
|
||||||
|
|
||||||
class DefferedSemCacheManager:
|
class DefferedSemCacheManager:
|
||||||
|
@ -38,7 +38,7 @@ from flask_mail import Message
|
|||||||
|
|
||||||
from app import email
|
from app import email
|
||||||
from app import log
|
from app import log
|
||||||
|
from app.models.etudiants import make_etud_args
|
||||||
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.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
@ -87,6 +87,8 @@ def force_uppercase(s):
|
|||||||
def format_nomprenom(etud, reverse=False):
|
def format_nomprenom(etud, reverse=False):
|
||||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||||
Si reverse, "Dupont Pierre", sans civilité.
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
|
|
||||||
|
DEPRECATED: utiliser Identite.nomprenom
|
||||||
"""
|
"""
|
||||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||||
prenom = format_prenom(etud["prenom"])
|
prenom = format_prenom(etud["prenom"])
|
||||||
@ -99,7 +101,9 @@ def format_nomprenom(etud, reverse=False):
|
|||||||
|
|
||||||
|
|
||||||
def format_prenom(s):
|
def format_prenom(s):
|
||||||
"Formatte prenom etudiant pour affichage"
|
"""Formatte prenom etudiant pour affichage
|
||||||
|
DEPRECATED: utiliser Identite.prenom_str
|
||||||
|
"""
|
||||||
if not s:
|
if not s:
|
||||||
return ""
|
return ""
|
||||||
frags = s.split()
|
frags = s.split()
|
||||||
@ -590,35 +594,6 @@ etudident_edit = _etudidentEditor.edit
|
|||||||
etudident_create = _etudidentEditor.create
|
etudident_create = _etudidentEditor.create
|
||||||
|
|
||||||
|
|
||||||
def make_etud_args(etudid=None, code_nip=None, use_request=True, raise_exc=True):
|
|
||||||
"""forme args dict pour requete recherche etudiant
|
|
||||||
On peut specifier etudid
|
|
||||||
ou bien (si use_request) cherche dans la requete http: etudid, code_nip, code_ine
|
|
||||||
(dans cet ordre).
|
|
||||||
"""
|
|
||||||
args = None
|
|
||||||
if etudid:
|
|
||||||
args = {"etudid": etudid}
|
|
||||||
elif code_nip:
|
|
||||||
args = {"code_nip": code_nip}
|
|
||||||
elif use_request: # use form from current request (Flask global)
|
|
||||||
if request.method == "POST":
|
|
||||||
vals = request.form
|
|
||||||
elif request.method == "GET":
|
|
||||||
vals = request.args
|
|
||||||
else:
|
|
||||||
vals = {}
|
|
||||||
if "etudid" in vals:
|
|
||||||
args = {"etudid": int(vals["etudid"])}
|
|
||||||
elif "code_nip" in vals:
|
|
||||||
args = {"code_nip": str(vals["code_nip"])}
|
|
||||||
elif "code_ine" in vals:
|
|
||||||
args = {"code_ine": str(vals["code_ine"])}
|
|
||||||
if not args and raise_exc:
|
|
||||||
raise ValueError("getEtudInfo: no parameter !")
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def log_unknown_etud():
|
def log_unknown_etud():
|
||||||
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
|
"""Log request: cas ou getEtudInfo n'a pas ramene de resultat"""
|
||||||
etud_args = make_etud_args(raise_exc=False)
|
etud_args = make_etud_args(raise_exc=False)
|
||||||
|
@ -51,6 +51,7 @@ from app.scodoc import sco_formsemestre_edit
|
|||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_formsemestre_status
|
from app.scodoc import sco_formsemestre_status
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
|
from app.scodoc.sco_parcours_dut import etud_est_inscrit_ue
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_pvjury
|
from app.scodoc import sco_pvjury
|
||||||
@ -543,7 +544,7 @@ def formsemestre_recap_parcours_table(
|
|||||||
|
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(
|
||||||
sem["formsemestre_id"]
|
sem["formsemestre_id"]
|
||||||
) # > get_ues, get_etud_moy_gen, get_etud_ue_status
|
) # > get_ues_stat_dict, get_etud_moy_gen, get_etud_ue_status
|
||||||
if is_cur:
|
if is_cur:
|
||||||
type_sem = "*" # now unused
|
type_sem = "*" # now unused
|
||||||
class_sem = "sem_courant"
|
class_sem = "sem_courant"
|
||||||
@ -582,8 +583,17 @@ def formsemestre_recap_parcours_table(
|
|||||||
else:
|
else:
|
||||||
H.append('<td colspan="%d"><em>en cours</em></td>')
|
H.append('<td colspan="%d"><em>en cours</em></td>')
|
||||||
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
H.append('<td class="rcp_nonass">%s</td>' % ass) # abs
|
||||||
# acronymes UEs
|
# acronymes UEs auxquelles l'étudiant est inscrit:
|
||||||
ues = nt.get_ues(filter_sport=True, filter_non_inscrit=True, etudid=etudid)
|
# XXX il est probable que l'on doive ici ajouter les
|
||||||
|
# XXX UE capitalisées
|
||||||
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
ues = [
|
||||||
|
ue
|
||||||
|
for ue in ues
|
||||||
|
if etud_est_inscrit_ue(cnx, etudid, sem["formsemestre_id"], ue["ue_id"])
|
||||||
|
]
|
||||||
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"])
|
H.append('<td class="ue_acro"><span>%s</span></td>' % ue["acronyme"])
|
||||||
if len(ues) < Se.nb_max_ue:
|
if len(ues) < Se.nb_max_ue:
|
||||||
|
@ -240,7 +240,7 @@ def _make_table_notes(
|
|||||||
if is_apc:
|
if is_apc:
|
||||||
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
is_conforme = modimpl.check_apc_conformity()
|
is_conforme = modimpl.check_apc_conformity()
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||||
if not ues:
|
if not ues:
|
||||||
is_apc = False
|
is_apc = False
|
||||||
else:
|
else:
|
||||||
|
@ -479,11 +479,13 @@ def get_etuds_with_capitalized_ue(formsemestre_id):
|
|||||||
returns { ue_id : [ { infos } ] }
|
returns { ue_id : [ { infos } ] }
|
||||||
"""
|
"""
|
||||||
UECaps = scu.DictDefault(defaultvalue=[])
|
UECaps = scu.DictDefault(defaultvalue=[])
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status
|
nt = sco_cache.NotesTableCache.get(
|
||||||
|
formsemestre_id
|
||||||
|
) # > get_ues_stat_dict, get_etud_ue_status
|
||||||
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
)
|
)
|
||||||
ues = nt.get_ues()
|
ues = nt.get_ues_stat_dict()
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
for etud in inscrits:
|
for etud in inscrits:
|
||||||
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
||||||
|
@ -36,6 +36,7 @@ from flask_login import current_user
|
|||||||
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.models.etudiants import make_etud_args
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_archives_etud
|
from app.scodoc import sco_archives_etud
|
||||||
@ -156,7 +157,7 @@ def ficheEtud(etudid=None):
|
|||||||
# la sidebar est differente s'il y a ou pas un etudid
|
# la sidebar est differente s'il y a ou pas un etudid
|
||||||
# voir html_sidebar.sidebar()
|
# voir html_sidebar.sidebar()
|
||||||
g.etudid = etudid
|
g.etudid = etudid
|
||||||
args = sco_etud.make_etud_args(etudid=etudid)
|
args = make_etud_args(etudid=etudid)
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
etuds = sco_etud.etudident_list(cnx, args)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
|
log("ficheEtud: etudid=%s request.args=%s" % (etudid, request.args))
|
||||||
|
@ -109,7 +109,7 @@ def SituationEtudParcours(etud, formsemestre_id):
|
|||||||
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
|
"""renvoie une instance de SituationEtudParcours (ou sous-classe spécialisée)"""
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(
|
||||||
formsemestre_id
|
formsemestre_id
|
||||||
) # > get_etud_decision_sem, get_etud_moy_gen, get_ues, get_etud_ue_status, etud_check_conditions_ues
|
) # > get_etud_decision_sem, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, etud_check_conditions_ues
|
||||||
parcours = nt.parcours
|
parcours = nt.parcours
|
||||||
#
|
#
|
||||||
if parcours.ECTS_ONLY:
|
if parcours.ECTS_ONLY:
|
||||||
@ -330,8 +330,10 @@ class SituationEtudParcoursGeneric(object):
|
|||||||
ue_acros = {} # acronyme ue : 1
|
ue_acros = {} # acronyme ue : 1
|
||||||
nb_max_ue = 0
|
nb_max_ue = 0
|
||||||
for sem in sems:
|
for sem in sems:
|
||||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"]) # > get_ues
|
nt = sco_cache.NotesTableCache.get(
|
||||||
ues = nt.get_ues(filter_sport=True)
|
sem["formsemestre_id"]
|
||||||
|
) # > get_ues_stat_dict
|
||||||
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
ue_acros[ue["acronyme"]] = 1
|
ue_acros[ue["acronyme"]] = 1
|
||||||
nb_ue = len(ues)
|
nb_ue = len(ues)
|
||||||
@ -419,9 +421,7 @@ class SituationEtudParcoursGeneric(object):
|
|||||||
self.moy_gen >= (self.parcours.BARRE_MOY - scu.NOTES_TOLERANCE)
|
self.moy_gen >= (self.parcours.BARRE_MOY - scu.NOTES_TOLERANCE)
|
||||||
)
|
)
|
||||||
# conserve etat UEs
|
# conserve etat UEs
|
||||||
ue_ids = [
|
ue_ids = [x["ue_id"] for x in self.nt.get_ues_stat_dict(filter_sport=True)]
|
||||||
x["ue_id"] for x in self.nt.get_ues(etudid=self.etudid, filter_sport=True)
|
|
||||||
]
|
|
||||||
self.ues_status = {} # ue_id : status
|
self.ues_status = {} # ue_id : status
|
||||||
for ue_id in ue_ids:
|
for ue_id in ue_ids:
|
||||||
self.ues_status[ue_id] = self.nt.get_etud_ue_status(self.etudid, ue_id)
|
self.ues_status[ue_id] = self.nt.get_etud_ue_status(self.etudid, ue_id)
|
||||||
@ -903,8 +903,10 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
|
|||||||
"""
|
"""
|
||||||
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
|
valid_semestre = CODES_SEM_VALIDES.get(code_etat_sem, False)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > get_ues, get_etud_ue_status
|
nt = sco_cache.NotesTableCache.get(
|
||||||
ue_ids = [x["ue_id"] for x in nt.get_ues(etudid=etudid, filter_sport=True)]
|
formsemestre_id
|
||||||
|
) # > get_ues_stat_dict, get_etud_ue_status
|
||||||
|
ue_ids = [x["ue_id"] for x in nt.get_ues_stat_dict(filter_sport=True)]
|
||||||
for ue_id in ue_ids:
|
for ue_id in ue_ids:
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
if not assiduite:
|
if not assiduite:
|
||||||
@ -1000,7 +1002,7 @@ def formsemestre_has_decisions(formsemestre_id):
|
|||||||
|
|
||||||
|
|
||||||
def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id):
|
def etud_est_inscrit_ue(cnx, etudid, formsemestre_id, ue_id):
|
||||||
"""Vrai si l'étudiant est inscrit a au moins un module de cette UE dans ce semestre"""
|
"""Vrai si l'étudiant est inscrit à au moins un module de cette UE dans ce semestre"""
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT mi.*
|
"""SELECT mi.*
|
||||||
|
@ -61,7 +61,7 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
nt = sco_cache.NotesTableCache.get(s["formsemestre_id"])
|
nt = sco_cache.NotesTableCache.get(s["formsemestre_id"])
|
||||||
dec = nt.get_etud_decision_sem(etudid)
|
dec = nt.get_etud_decision_sem(etudid)
|
||||||
# Moyennes et rangs des UE
|
# Moyennes et rangs des UE
|
||||||
ues = nt.get_ues(filter_sport=True)
|
ues = nt.get_ues_stat_dict(filter_sport=True)
|
||||||
moy_ues = [
|
moy_ues = [
|
||||||
(
|
(
|
||||||
ue["acronyme"],
|
ue["acronyme"],
|
||||||
@ -75,7 +75,7 @@ def etud_get_poursuite_info(sem, etud):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Moyennes et rang des modules
|
# Moyennes et rang des modules
|
||||||
modimpls = nt.get_modimpls() # recupération des modules
|
modimpls = nt.get_modimpls_dict() # recupération des modules
|
||||||
modules = []
|
modules = []
|
||||||
rangs = []
|
rangs = []
|
||||||
for ue in ues: # on parcourt chaque UE
|
for ue in ues: # on parcourt chaque UE
|
||||||
|
@ -2266,3 +2266,31 @@ def doc_preferences():
|
|||||||
)
|
)
|
||||||
|
|
||||||
return "\n".join([" | ".join(x) for x in L])
|
return "\n".join([" | ".join(x) for x in L])
|
||||||
|
|
||||||
|
|
||||||
|
def bulletin_option_affichage(formsemestre_id: int) -> dict:
|
||||||
|
"dict avec les options d'affichages (préférences) pour ce semestre"
|
||||||
|
prefs = SemPreferences(formsemestre_id)
|
||||||
|
fields = (
|
||||||
|
"bul_show_abs",
|
||||||
|
"bul_show_abs_modules",
|
||||||
|
"bul_show_ects",
|
||||||
|
"bul_show_codemodules",
|
||||||
|
"bul_show_matieres",
|
||||||
|
"bul_show_rangs",
|
||||||
|
"bul_show_ue_rangs",
|
||||||
|
"bul_show_mod_rangs",
|
||||||
|
"bul_show_moypromo",
|
||||||
|
"bul_show_minmax",
|
||||||
|
"bul_show_minmax_mod",
|
||||||
|
"bul_show_minmax_eval",
|
||||||
|
"bul_show_coef",
|
||||||
|
"bul_show_ue_cap_details",
|
||||||
|
"bul_show_ue_cap_current",
|
||||||
|
"bul_show_temporary",
|
||||||
|
"bul_temporary_txt",
|
||||||
|
"bul_show_uevalid",
|
||||||
|
"bul_show_date_inscr",
|
||||||
|
)
|
||||||
|
# on enlève le "bul_" de la clé:
|
||||||
|
return {field[4:]: prefs[field] for field in fields}
|
||||||
|
@ -52,7 +52,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
"Feuille excel pour preparation des jurys"
|
"Feuille excel pour preparation des jurys"
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(
|
||||||
formsemestre_id
|
formsemestre_id
|
||||||
) # > get_etudids, get_etud_moy_gen, get_ues, get_etud_ue_status, get_etud_decision_sem, identdict,
|
) # > get_etudids, get_etud_moy_gen, get_ues_stat_dict, get_etud_ue_status, get_etud_decision_sem, identdict,
|
||||||
etudids = nt.get_etudids(sorted=True) # tri par moy gen
|
etudids = nt.get_etudids(sorted=True) # tri par moy gen
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
|
|
||||||
@ -85,8 +85,8 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
if Se.prev:
|
if Se.prev:
|
||||||
ntp = sco_cache.NotesTableCache.get(
|
ntp = sco_cache.NotesTableCache.get(
|
||||||
Se.prev["formsemestre_id"]
|
Se.prev["formsemestre_id"]
|
||||||
) # > get_ues, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
|
) # > get_ues_stat_dict, get_etud_ue_status, get_etud_moy_gen, get_etud_decision_sem
|
||||||
for ue in ntp.get_ues(filter_sport=True):
|
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||||
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = ntp.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
ue_code_s = (
|
ue_code_s = (
|
||||||
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
|
ue["ue_code"] + "_%s" % ntp.sem["semestre_id"]
|
||||||
@ -102,7 +102,7 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
prev_code[etudid] += "+" # indique qu'il a servi a compenser
|
prev_code[etudid] += "+" # indique qu'il a servi a compenser
|
||||||
|
|
||||||
moy[etudid] = nt.get_etud_moy_gen(etudid)
|
moy[etudid] = nt.get_etud_moy_gen(etudid)
|
||||||
for ue in nt.get_ues(filter_sport=True):
|
for ue in nt.get_ues_stat_dict(filter_sport=True):
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
ue_status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
|
ue_code_s = ue["ue_code"] + "_%s" % nt.sem["semestre_id"]
|
||||||
moy_ue[ue_code_s][etudid] = ue_status["moy"]
|
moy_ue[ue_code_s][etudid] = ue_status["moy"]
|
||||||
@ -310,9 +310,9 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
ws.append_blank_row()
|
ws.append_blank_row()
|
||||||
ws.append_single_cell_row("Titre des UE")
|
ws.append_single_cell_row("Titre des UE")
|
||||||
if prev_moy:
|
if prev_moy:
|
||||||
for ue in ntp.get_ues(filter_sport=True):
|
for ue in ntp.get_ues_stat_dict(filter_sport=True):
|
||||||
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
||||||
for ue in nt.get_ues(filter_sport=True):
|
for ue in nt.get_ues_stat_dict(filter_sport=True):
|
||||||
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
ws.append_row(ws.make_row(["", "", "", ue["acronyme"], ue["titre"]]))
|
||||||
#
|
#
|
||||||
ws.append_blank_row()
|
ws.append_blank_row()
|
||||||
|
@ -161,7 +161,7 @@ def _comp_ects_by_ue_code_and_type(nt, decision_ues):
|
|||||||
|
|
||||||
def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
def _comp_ects_capitalises_by_ue_code(nt, etudid):
|
||||||
"""Calcul somme des ECTS des UE capitalisees"""
|
"""Calcul somme des ECTS des UE capitalisees"""
|
||||||
ues = nt.get_ues()
|
ues = nt.get_ues_stat_dict()
|
||||||
ects_by_ue_code = {}
|
ects_by_ue_code = {}
|
||||||
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"])
|
||||||
|
@ -37,6 +37,7 @@ from flask import make_response
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.but import bulletin_but
|
from app.but import bulletin_but
|
||||||
|
from app.comp.res_classic import ResultatsSemestreClassic
|
||||||
from app.models import FormSemestre
|
from app.models import FormSemestre
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
@ -302,11 +303,11 @@ def make_formsemestre_recapcomplet(
|
|||||||
sem = sco_formsemestre.do_formsemestre_list(
|
sem = sco_formsemestre.do_formsemestre_list(
|
||||||
args={"formsemestre_id": formsemestre_id}
|
args={"formsemestre_id": formsemestre_id}
|
||||||
)[0]
|
)[0]
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
formsemestre_id
|
# XXX EXPERIMENTAL
|
||||||
) # > get_modimpls, get_ues, get_table_moyennes_triees, get_etud_decision_sem, get_etud_etat, get_etud_rang, get_nom_short, get_mod_stats, nt.moy_moy, get_etud_decision_sem,
|
# nt = ResultatsSemestreClassic(formsemestre)
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
ues = nt.get_ues() # incluant le(s) UE de sport
|
ues = nt.get_ues_stat_dict() # incluant le(s) UE de sport
|
||||||
#
|
#
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
nt.apc_recompute_moyennes()
|
nt.apc_recompute_moyennes()
|
||||||
@ -964,7 +965,7 @@ def _formsemestre_recapcomplet_json(
|
|||||||
etudid = t[-1]
|
etudid = t[-1]
|
||||||
if is_apc:
|
if is_apc:
|
||||||
etud = Identite.query.get(etudid)
|
etud = Identite.query.get(etudid)
|
||||||
r = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
r = bulletin_but.BulletinBUT(formsemestre)
|
||||||
bul = r.bulletin_etud(etud, formsemestre)
|
bul = r.bulletin_etud(etud, formsemestre)
|
||||||
else:
|
else:
|
||||||
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
bul = sco_bulletins_json.formsemestre_bulletinetud_published_dict(
|
||||||
|
@ -270,7 +270,7 @@ def get_etud_tagged_modules(etudid, tagname):
|
|||||||
R = []
|
R = []
|
||||||
for sem in etud["sems"]:
|
for sem in etud["sems"]:
|
||||||
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
modimpls = nt.get_modimpls()
|
modimpls = nt.get_modimpls_dict()
|
||||||
for modimpl in modimpls:
|
for modimpl in modimpls:
|
||||||
tags = module_tag_list(module_id=modimpl["module_id"])
|
tags = module_tag_list(module_id=modimpl["module_id"])
|
||||||
if tagname in tags:
|
if tagname in tags:
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
@ -5,7 +6,7 @@
|
|||||||
<h1>Changez votre mot de passe ScoDoc</h1>
|
<h1>Changez votre mot de passe ScoDoc</h1>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 30px;">
|
<div class="row" style="margin-top: 30px;">
|
||||||
<div class="col-md-4">Votre identifiant: <b>{{user.user_name}}</b></div>
|
<div class="col-md-4">Votre identifiant: <b>{{user.user_name}}</b></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 30px;">
|
<div class="row" style="margin-top: 30px;">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
@ -5,16 +6,16 @@
|
|||||||
|
|
||||||
<h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2>
|
<h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2>
|
||||||
<p>
|
<p>
|
||||||
<b>Login :</b> {{user.user_name}}<br/>
|
<b>Login :</b> {{user.user_name}}<br>
|
||||||
<b>Nom :</b> {{user.nom or ""}}<br/>
|
<b>Nom :</b> {{user.nom or ""}}<br>
|
||||||
<b>Prénom :</b> {{user.prenom or ""}}<br/>
|
<b>Prénom :</b> {{user.prenom or ""}}<br>
|
||||||
<b>Mail :</b> {{user.email}}<br/>
|
<b>Mail :</b> {{user.email}}<br>
|
||||||
<b>Roles :</b> {{user.get_roles_string()}}<br/>
|
<b>Roles :</b> {{user.get_roles_string()}}<br>
|
||||||
<b>Dept :</b> {{user.dept or ""}}<br/>
|
<b>Dept :</b> {{user.dept or ""}}<br>
|
||||||
<b>Dernière modif mot de passe:</b>
|
<b>Dernière modif mot de passe:</b>
|
||||||
{{user.date_modif_passwd.isoformat() if user.date_modif_passwd else ""}}<br/>
|
{{user.date_modif_passwd.isoformat() if user.date_modif_passwd else ""}}<br>
|
||||||
<b>Date d'expiration:</b>
|
<b>Date d'expiration:</b>
|
||||||
{{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}}
|
{{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}}
|
||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a class="stdlink" href="{{
|
<li><a class="stdlink" href="{{
|
||||||
@ -27,33 +28,33 @@
|
|||||||
url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
|
url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
|
||||||
user_name=user.user_name, edit=1)
|
user_name=user.user_name, edit=1)
|
||||||
}}">modifier ce compte</a>
|
}}">modifier ce compte</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="stdlink" href="{{
|
<li><a class="stdlink" href="{{
|
||||||
url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
|
url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
|
||||||
user_name=user.user_name)
|
user_name=user.user_name)
|
||||||
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
|
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if current_user.id == user.id %}
|
{% if current_user.id == user.id %}
|
||||||
<p><b>Se déconnecter:
|
<p><b>Se déconnecter:
|
||||||
<a class="stdlink" href="{{url_for('auth.logout')}}">logout</a>
|
<a class="stdlink" href="{{url_for('auth.logout')}}">logout</a>
|
||||||
</b></p>
|
</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Liste des permissions #}
|
{# Liste des permissions #}
|
||||||
<div class="permissions">
|
<div class="permissions">
|
||||||
<p>Permissions de cet utilisateur dans le département {dept}:</p>
|
<p>Permissions de cet utilisateur dans le département {dept}:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for p in Permission.description %}
|
{% for p in Permission.description %}
|
||||||
<li>{{Permission.description[p]}} :
|
<li>{{Permission.description[p]}} :
|
||||||
{{
|
{{
|
||||||
"oui" if user.has_permission(Permission.get_by_name(p), dept) else "non"
|
"oui" if user.has_permission(Permission.get_by_name(p), dept) else "non"
|
||||||
}}
|
}}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if current_user.has_permission(Permission.ScoUsersAdmin, dept) %}
|
{% if current_user.has_permission(Permission.ScoUsersAdmin, dept) %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'bootstrap/base.html' %}
|
{% extends 'bootstrap/base.html' %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "sco_page.html" %}
|
{% extends "sco_page.html" %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "sco_page.html" %}
|
{% extends "sco_page.html" %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "sco_page.html" %}
|
{% extends "sco_page.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% macro render_field(field) %}
|
{% macro render_field(field) %}
|
||||||
<div>
|
<div>
|
||||||
<span class="wtf-field">{{ field.label }} :</span>
|
<span class="wtf-field">{{ field.label }} :</span>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
14
app/templates/entreprises/_contact.html
Normal file
14
app/templates/entreprises/_contact.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Nom : {{ contact.nom }}<br>
|
||||||
|
Prénom : {{ contact.prenom }}<br>
|
||||||
|
Téléphone : {{ contact.telephone }}<br>
|
||||||
|
Mail : {{ contact.mail }}<br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_contact', id=contact.id) }}">Modifier contact</a>
|
||||||
|
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_contact', id=contact.id) }}">Supprimer contact</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
21
app/templates/entreprises/_offre.html
Normal file
21
app/templates/entreprises/_offre.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Intitulé : {{ offre[0].intitule }}<br>
|
||||||
|
Description : {{ offre[0].description }}<br>
|
||||||
|
Type de l'offre : {{ offre[0].type_offre }}<br>
|
||||||
|
Missions : {{ offre[0].missions }}<br>
|
||||||
|
Durée : {{ offre[0].duree }}<br>
|
||||||
|
{% for fichier in offre[1] %}
|
||||||
|
<a href="{{ url_for('entreprises.get_offre_file', entreprise_id=entreprise.id, offre_id=offre[0].id, filedir=fichier[0], filename=fichier[1] )}}">{{ fichier[1] }}</a>
|
||||||
|
<a href="{{ url_for('entreprises.delete_offre_file', offre_id=offre[0].id, filedir=fichier[0] )}}" style="margin-left: 5px;"><img title="Supprimer fichier" alt="supprimer" width="10" height="9" border="0" src="/ScoDoc/static/icons/delete_small_img.png" /></a><br>
|
||||||
|
{% endfor %}
|
||||||
|
<a href="{{ url_for('entreprises.add_offre_file', offre_id=offre[0].id) }}">Ajoutez un fichier</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_offre', id=offre[0].id) }}">Modifier l'offre</a>
|
||||||
|
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_offre', id=offre[0].id) }}">Supprimer l'offre</a>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.envoyer_offre', id=offre[0].id) }}">Envoyer l'offre</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
54
app/templates/entreprises/ajout_entreprise.html
Normal file
54
app/templates/entreprises/ajout_entreprise.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<p>
|
||||||
|
Les champs s'autocomplète selon le SIRET
|
||||||
|
</p>
|
||||||
|
{{ wtf.quick_form(form, novalidate=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.onload = function(e){
|
||||||
|
document.getElementById("siret").addEventListener("keyup", autocomplete);
|
||||||
|
|
||||||
|
function autocomplete() {
|
||||||
|
var input = document.getElementById("siret").value;
|
||||||
|
data = null
|
||||||
|
if(input.length == 14) {
|
||||||
|
fetch("https://entreprise.data.gouv.fr/api/sirene/v1/siret/" + input)
|
||||||
|
.then(response => {
|
||||||
|
if(response.ok)
|
||||||
|
return response.json()
|
||||||
|
else {
|
||||||
|
emptyForm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => fillForm(response))
|
||||||
|
.catch(err => err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillForm(response) {
|
||||||
|
document.getElementById("nom_entreprise").value = response.etablissement.l1_normalisee
|
||||||
|
document.getElementById("adresse").value = response.etablissement.l4_normalisee
|
||||||
|
document.getElementById("codepostal").value = response.etablissement.code_postal
|
||||||
|
document.getElementById("ville").value = response.etablissement.libelle_commune
|
||||||
|
document.getElementById("pays").value = 'FRANCE'
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyForm() {
|
||||||
|
document.getElementById("nom_entreprise").value = ''
|
||||||
|
document.getElementById("adresse").value = ''
|
||||||
|
document.getElementById("codepostal").value = ''
|
||||||
|
document.getElementById("ville").value = ''
|
||||||
|
document.getElementById("pays").value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
32
app/templates/entreprises/ajout_historique.html
Normal file
32
app/templates/entreprises/ajout_historique.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
||||||
|
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form, novalidate=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.onload = function(e) {
|
||||||
|
var etudiants_options = {
|
||||||
|
script: "/ScoDoc/entreprises/etudiants?",
|
||||||
|
varname: "term",
|
||||||
|
json: true,
|
||||||
|
noresults: "Valeur invalide !",
|
||||||
|
minchars: 2,
|
||||||
|
timeout: 60000
|
||||||
|
};
|
||||||
|
var as_etudiants = new bsn.AutoSuggest('etudiant', etudiants_options);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
47
app/templates/entreprises/contacts.html
Normal file
47
app/templates/entreprises/contacts.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
{% if logs %}
|
||||||
|
<div class="container">
|
||||||
|
<h3>Dernières opérations</h3>
|
||||||
|
<ul>
|
||||||
|
{% for log in logs %}
|
||||||
|
<li><span style="margin-right: 10px;">{{ log.date.strftime('%d %b %Hh%M') }}</span><span>{{ log.text|safe }} par {{ log.authenticated_user|get_nomcomplet }}</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Liste des contacts</h1>
|
||||||
|
{% if contacts %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<tr>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Prenom</th>
|
||||||
|
<th>Telephone</th>
|
||||||
|
<th>Mail</th>
|
||||||
|
<th>Entreprise</th>
|
||||||
|
</tr>
|
||||||
|
{% for contact in contacts %}
|
||||||
|
<tr class="table-row active">
|
||||||
|
<th>{{ contact[0].nom }}</th>
|
||||||
|
<th>{{ contact[0].prenom }}</th>
|
||||||
|
<th>{{ contact[0].telephone }}</th>
|
||||||
|
<th>{{ contact[0].mail }}</th>
|
||||||
|
<th><a href="{{ url_for('entreprises.fiche_entreprise', id=contact[1].id) }}">{{ contact[1].nom }}</a></th>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div>Aucun contact présent dans la base</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% if contacts %}
|
||||||
|
<a class="btn btn-default" href="{{ url_for('entreprises.export_contacts') }}">Exporter la liste des contacts</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
15
app/templates/entreprises/delete_confirmation.html
Normal file
15
app/templates/entreprises/delete_confirmation.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
<div style="color:red">Cliquez sur le bouton supprimer pour confirmer votre supression</div>
|
||||||
|
<br>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
63
app/templates/entreprises/entreprises.html
Normal file
63
app/templates/entreprises/entreprises.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
{% if logs %}
|
||||||
|
<div class="container">
|
||||||
|
<h3>Dernières opérations</h3>
|
||||||
|
<ul>
|
||||||
|
{% for log in logs %}
|
||||||
|
<li><span style="margin-right: 10px;">{{ log.date.strftime('%d %b %Hh%M') }}</span><span>{{ log.text|safe }} par {{ log.authenticated_user|get_nomcomplet }}</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Liste des entreprises</h1>
|
||||||
|
{% if entreprises %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<tr>
|
||||||
|
<th>SIRET</th>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Adresse</th>
|
||||||
|
<th>Code postal</th>
|
||||||
|
<th>Ville</th>
|
||||||
|
<th>Pays</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
{% for entreprise in entreprises %}
|
||||||
|
<tr class="table-row active">
|
||||||
|
<th><a href="{{ url_for('entreprises.fiche_entreprise', id=entreprise.id) }}">{{ entreprise.siret }}</a></th>
|
||||||
|
<th>{{ entreprise.nom }}</th>
|
||||||
|
<th>{{ entreprise.adresse }}</th>
|
||||||
|
<th>{{ entreprise.codepostal }}</th>
|
||||||
|
<th>{{ entreprise.ville }}</th>
|
||||||
|
<th>{{ entreprise.pays }}</th>
|
||||||
|
<th>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a class="btn btn-default dropdown-toggle" data-toggle="dropdown" href="#">Action
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu pull-right">
|
||||||
|
<li><a href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a></li>
|
||||||
|
<li><a href="{{ url_for('entreprises.delete_entreprise', id=entreprise.id) }}" style="color:red">Supprimer</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div>Aucune entreprise présent dans la base</div>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<a class="btn btn-default" href="{{ url_for('entreprises.add_entreprise') }}">Ajouter une entreprise</a>
|
||||||
|
{% if entreprises %}
|
||||||
|
<a class="btn btn-default" href="{{ url_for('entreprises.export_entreprises') }}">Exporter la liste des entreprises</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
32
app/templates/entreprises/envoi_offre_form.html
Normal file
32
app/templates/entreprises/envoi_offre_form.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
||||||
|
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form, novalidate=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.onload = function(e) {
|
||||||
|
var responsables_options = {
|
||||||
|
script: "/ScoDoc/entreprises/responsables?",
|
||||||
|
varname: "term",
|
||||||
|
json: true,
|
||||||
|
noresults: "Valeur invalide !",
|
||||||
|
minchars: 2,
|
||||||
|
timeout: 60000
|
||||||
|
};
|
||||||
|
var as_responsables = new bsn.AutoSuggest('responsable', responsables_options);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
76
app/templates/entreprises/fiche_entreprise.html
Normal file
76
app/templates/entreprises/fiche_entreprise.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
{% if logs %}
|
||||||
|
<div class="container">
|
||||||
|
<h3>Dernières opérations sur cette fiche</h3>
|
||||||
|
<ul>
|
||||||
|
{% for log in logs %}
|
||||||
|
<li>
|
||||||
|
<span style="margin-right: 10px;">{{ log.date.strftime('%d %b %Hh%M') }}</span>
|
||||||
|
<span>{{ log.text|safe }} par {{ log.authenticated_user|get_nomcomplet }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if historique %}
|
||||||
|
<div class="container">
|
||||||
|
<h3>Historique</h3>
|
||||||
|
<ul>
|
||||||
|
{% for data in historique %}
|
||||||
|
<li>
|
||||||
|
<span style="margin-right: 10px;">{{ data[0].date_debut.strftime('%d/%m/%Y') }} - {{
|
||||||
|
data[0].date_fin.strftime('%d/%m/%Y') }}</span>
|
||||||
|
<span style="margin-right: 10px;">
|
||||||
|
{{ data[0].type_offre }} réalisé par {{ data[1].nom|format_nom }} {{ data[1].prenom|format_prenom }} en
|
||||||
|
{{ data[0].formation_text }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="container">
|
||||||
|
<h2>Fiche entreprise - {{ entreprise.nom }} ({{ entreprise.siret }})</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
SIRET : {{ entreprise.siret }}<br>
|
||||||
|
Nom : {{ entreprise.nom }}<br>
|
||||||
|
Adresse : {{ entreprise.adresse }}<br>
|
||||||
|
Code postal : {{ entreprise.codepostal }}<br>
|
||||||
|
Ville : {{ entreprise.ville }}<br>
|
||||||
|
Pays : {{ entreprise.pays }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if contacts %}
|
||||||
|
<div>
|
||||||
|
{% for contact in contacts %}
|
||||||
|
Contact {{loop.index}}
|
||||||
|
{% include 'entreprises/_contact.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if offres %}
|
||||||
|
<div>
|
||||||
|
{% for offre in offres %}
|
||||||
|
Offre {{loop.index}} (ajouté le {{offre[0].date_ajout.strftime('%d/%m/%Y') }})
|
||||||
|
{% include 'entreprises/_offre.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.edit_entreprise', id=entreprise.id) }}">Modifier</a>
|
||||||
|
<a class="btn btn-danger" href="{{ url_for('entreprises.delete_entreprise', id=entreprise.id) }}">Supprimer</a>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.add_offre', id=entreprise.id) }}">Ajouter offre</a>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.add_contact', id=entreprise.id) }}">Ajouter contact</a>
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('entreprises.add_historique', id=entreprise.id) }}">Ajouter
|
||||||
|
historique</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
19
app/templates/entreprises/form.html
Normal file
19
app/templates/entreprises/form.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/css/autosuggest_inquisitor.css" />
|
||||||
|
<script src="/ScoDoc/static/libjs/AutoSuggest.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<br>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
{{ wtf.quick_form(form, novalidate=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
29
app/templates/entreprises/offres.html
Normal file
29
app/templates/entreprises/offres.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
{% if offres_recus %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div>
|
||||||
|
{% for offre in offres_recus %}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Date envoi : {{ offre[0].date_envoi.strftime('%d/%m/%Y %H:%M') }}<br>
|
||||||
|
Intitulé : {{ offre[1].intitule }}<br>
|
||||||
|
Description : {{ offre[1].description }}<br>
|
||||||
|
Type de l'offre : {{ offre[1].type_offre }}<br>
|
||||||
|
Missions : {{ offre[1].missions }}<br>
|
||||||
|
Durée : {{ offre[1].duree }}<br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
<div>Aucune offre reçue</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,11 +1,13 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% block title %}Une erreur est survenue !{% endblock %}
|
{% block title %}Une erreur est survenue !{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Une erreur est survenue !</h1>
|
<h1>Une erreur est survenue !</h1>
|
||||||
<p>Oups...</tt> <span style="color:red;"><b>ScoDoc version <span style="font-size: 120%;">{{SCOVERSION}}</span></b></span> a
|
<p>Oups...</tt> <span style="color:red;"><b>ScoDoc version
|
||||||
un problème, désolé.</p>
|
<span style="font-size: 120%;">{{SCOVERSION}}</span></b></span>
|
||||||
|
a un problème, désolé.</p>
|
||||||
<p><tt style="font-size:60%">{{date}}</tt></p>
|
<p><tt style="font-size:60%">{{date}}</tt></p>
|
||||||
|
|
||||||
<p> Si le problème persiste, contacter l'administrateur de votre site,
|
<p> Si le problème persiste, contacter l'administrateur de votre site,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{# Description un semestre (barre de menu et infos) #}
|
{# Description un semestre (barre de menu et infos) #}
|
||||||
<!-- formsemestre_header -->
|
<!-- formsemestre_header -->
|
||||||
<div class="formsemestre_page_title">
|
<div class="formsemestre_page_title">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{# Chapeau description d'une formation #}
|
{# Chapeau description d'une formation #}
|
||||||
|
|
||||||
<div class="formation_descr">
|
<div class="formation_descr">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{# Édition liste modules APC (SAÉ ou ressources) #}
|
{# Édition liste modules APC (SAÉ ou ressources) #}
|
||||||
|
|
||||||
<div class="formation_list_modules formation_list_modules_{{module_type.name}}">
|
<div class="formation_list_modules formation_list_modules_{{module_type.name}}">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
<h2>Édition des coefficients des modules vers les UEs</h2>
|
<h2>Édition des coefficients des modules vers les UEs</h2>
|
||||||
<div class="help">
|
<div class="help">
|
||||||
Double-cliquer pour changer une valeur.
|
Double-cliquer pour changer une valeur.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{# Édition liste UEs APC #}
|
{# Édition liste UEs APC #}
|
||||||
<div class="formation_list_ues">
|
<div class="formation_list_ues">
|
||||||
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{# Informations sur une UE #}
|
{# Informations sur une UE #}
|
||||||
{% extends "sco_page.html" %}
|
{% extends "sco_page.html" %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'bootstrap/base.html' %}
|
{% extends 'bootstrap/base.html' %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
@ -23,7 +24,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if current_user.is_administrator() %}
|
{% if current_user.is_administrator() %}
|
||||||
<li><a href="{{ url_for('scodoc.create_dept') }}">créer un nouveau département</a></li>
|
<li><a href="{{ url_for('scodoc.create_dept') }}">créer un nouveau département</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-raw -*- #}
|
||||||
Importation des photo effectuée
|
Importation des photo effectuée
|
||||||
|
|
||||||
{% if ignored_zipfiles %}
|
{% if ignored_zipfiles %}
|
||||||
|
@ -1,98 +1,95 @@
|
|||||||
{# Barre marge gauche ScoDoc #}
|
{# Barre marge gauche ScoDoc #}
|
||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
<!-- sidebar -->
|
<!-- sidebar -->
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
{# sidebar_common #}
|
{# sidebar_common #}
|
||||||
<a class="scodoc_title" href="{{
|
<a class="scodoc_title" href="{{
|
||||||
url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
|
url_for(" scodoc.index", scodoc_dept=g.scodoc_dept) }}">ScoDoc 9.2a</a>
|
||||||
}}">ScoDoc 9.1</a>
|
|
||||||
<div id="authuser"><a id="authuserlink" href="{{
|
<div id="authuser"><a id="authuserlink" href="{{
|
||||||
url_for("users.user_info_page",
|
url_for(" users.user_info_page", scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||||
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
}}">{{current_user.user_name}}</a>
|
||||||
}}">{{current_user.user_name}}</a>
|
<br /><a id="deconnectlink" href="{{url_for(" auth.logout")}}">déconnexion</a>
|
||||||
<br/><a id="deconnectlink" href="{{url_for("auth.logout")}}">déconnexion</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% block sidebar_dept %}
|
{% block sidebar_dept %}
|
||||||
<h2 class="insidebar">Dépt. {{ sco.prefs["DeptName"] }}</h2>
|
<h2 class="insidebar">Dépt. {{ sco.prefs["DeptName"] }}</h2>
|
||||||
<a href="{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}" class="sidebar">Accueil</a> <br />
|
<a href="{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}" class="sidebar">Accueil</a> <br />
|
||||||
{% if sco.prefs["DeptIntranetURL"] %}
|
{% if sco.prefs["DeptIntranetURL"] %}
|
||||||
<a href="{{ sco.prefs["DeptIntranetURL"] }}" class="sidebar">
|
<a href="{{ sco.prefs[" DeptIntranetURL"] }}" class="sidebar">
|
||||||
{{ sco.prefs["DeptIntranetTitle"] }}</a>
|
{{ sco.prefs["DeptIntranetTitle"] }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br />
|
<br>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<h2 class="insidebar">Scolarité</h2>
|
<h2 class="insidebar">Scolarité</h2>
|
||||||
<a href="{{url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br/>
|
<a href="{{url_for(" scolar.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||||
<a href="{{url_for("notes.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br/>
|
<a href="{{url_for(" notes.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||||
<a href="{{url_for("absences.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br/>
|
<a href="{{url_for(" absences.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br>
|
||||||
|
|
||||||
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
|
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
|
||||||
or current_user.has_permission(sco.Permission.ScoUsersView)
|
or current_user.has_permission(sco.Permission.ScoUsersView)
|
||||||
%}
|
%}
|
||||||
<a href="{{url_for("users.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Utilisateurs</a> <br/>
|
<a href="{{url_for(" users.index_html", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Utilisateurs</a> <br />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.has_permission(sco.Permission.ScoChangePreferences) %}
|
{% if current_user.has_permission(sco.Permission.ScoChangePreferences) %}
|
||||||
<a href="{{url_for("scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}}"
|
<a href="{{url_for(" scolar.edit_preferences", scodoc_dept=g.scodoc_dept)}}" class="sidebar">Paramétrage</a> <br>
|
||||||
class="sidebar">Paramétrage</a> <br/>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# /sidebar_common #}
|
{# /sidebar_common #}
|
||||||
|
|
||||||
<div class="box-chercheetud">Chercher étudiant:<br/>
|
<div class="box-chercheetud">Chercher étudiant:<br>
|
||||||
<form method="get" id="form-chercheetud"
|
<form method="get" id="form-chercheetud"
|
||||||
action="{{ url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }}">
|
action="{{ url_for('scolar.search_etud_in_dept', scodoc_dept=g.scodoc_dept) }}">
|
||||||
<div>
|
<div>
|
||||||
<input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"/>
|
<input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="etud-insidebar">
|
<div class="etud-insidebar">
|
||||||
{% if sco.etud %}
|
{% if sco.etud %}
|
||||||
<h2 id="insidebar-etud"><a href="{{url_for(
|
<h2 id="insidebar-etud"><a href="{{url_for(
|
||||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id
|
" scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=sco.etud.id )}}" class="sidebar">
|
||||||
)}}" class="sidebar">
|
<span class="fontred">{{sco.etud.nomprenom}}</span></a>
|
||||||
<span class="fontred">{{sco.etud.civilite_str()}} {{sco.etud.nom_disp()}}</span></a>
|
</h2>
|
||||||
</h2>
|
<b>Absences</b>
|
||||||
<b>Absences</b>
|
|
||||||
{% if sco.etud_cur_sem %}
|
{% if sco.etud_cur_sem %}
|
||||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
||||||
au {{ sco.etud_cur_sem['date_fin'] }}">(1/2 j.)
|
au {{ sco.etud_cur_sem['date_fin'] }}">(1/2 j.)
|
||||||
<br/>{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
{% if current_user.has_permission(sco.Permission.ScoAbsChange) %}
|
{% if current_user.has_permission(sco.Permission.ScoAbsChange) %}
|
||||||
<li><a href="{{ url_for('absences.SignaleAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.SignaleAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Ajouter</a></li>
|
etudid=sco.etud.id) }}">Ajouter</a></li>
|
||||||
<li><a href="{{ url_for('absences.JustifAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.JustifAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Justifier</a></li>
|
etudid=sco.etud.id) }}">Justifier</a></li>
|
||||||
<li><a href="{{ url_for('absences.AnnuleAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.AnnuleAbsenceEtud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Supprimer</a></li>
|
etudid=sco.etud.id) }}">Supprimer</a></li>
|
||||||
{% if sco.prefs["handle_billets_abs"] %}
|
{% if sco.prefs["handle_billets_abs"] %}
|
||||||
<li><a href="{{ url_for('absences.listeBilletsEtud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.listeBilletsEtud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Billets</a></li>
|
etudid=sco.etud.id) }}">Billets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.CalAbs', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Calendrier</a></li>
|
etudid=sco.etud.id) }}">Calendrier</a></li>
|
||||||
<li><a href="{{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('absences.ListeAbsEtud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Liste</a></li>
|
etudid=sco.etud.id) }}">Liste</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div> {# /etud-insidebar #}
|
</div> {# /etud-insidebar #}
|
||||||
|
|
||||||
{# LOGO #}
|
{# LOGO #}
|
||||||
<div class="logo-insidebar">
|
<div class="logo-insidebar">
|
||||||
<div class="sidebar-bottom"><a href="{{ url_for( 'scodoc.about',
|
<div class="sidebar-bottom"><a href="{{ url_for( 'scodoc.about',
|
||||||
scodoc_dept=g.scodoc_dept ) }}" class="sidebar">À propos</a>
|
scodoc_dept=g.scodoc_dept ) }}" class="sidebar">À propos</a>
|
||||||
<br/>
|
<br />
|
||||||
<a href="{{ sco.scu.SCO_USER_MANUAL }}" target="_blank" class="sidebar">Aide</a>
|
<a href="{{ sco.scu.SCO_USER_MANUAL }}" target="_blank" class="sidebar">Aide</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="logo-logo">
|
<div class="logo-logo">
|
||||||
<a href="{{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }}">
|
<a href="{{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }}">
|
||||||
{{ sco.scu.icontag("scologo_img", no_size=True) | safe}}</a>
|
{{ sco.scu.icontag("scologo_img", no_size=True) | safe}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- end of sidebar -->
|
<!-- end of sidebar -->
|
@ -1,3 +1,4 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
<h2 class="insidebar"><a href="{{
|
<h2 class="insidebar"><a href="{{
|
||||||
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
|
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
|
||||||
}}">Dépt. {{ prefs["DeptName"] }}</a>
|
}}">Dépt. {{ prefs["DeptName"] }}</a>
|
||||||
|
@ -429,18 +429,9 @@ def SignaleAbsenceGrHebdo(
|
|||||||
]
|
]
|
||||||
#
|
#
|
||||||
modimpls_list = []
|
modimpls_list = []
|
||||||
# Initialize with first student
|
ues = nt.get_ues_stat_dict()
|
||||||
ues = nt.get_ues(etudid=etuds[0]["etudid"])
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
|
|
||||||
# Add modules other students are subscribed to
|
|
||||||
for etud in etuds[1:]:
|
|
||||||
modimpls_etud = []
|
|
||||||
ues = nt.get_ues(etudid=etud["etudid"])
|
|
||||||
for ue in ues:
|
|
||||||
modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"])
|
|
||||||
modimpls_list += [m for m in modimpls_etud if m not in modimpls_list]
|
|
||||||
|
|
||||||
menu_module = ""
|
menu_module = ""
|
||||||
for modimpl in modimpls_list:
|
for modimpl in modimpls_list:
|
||||||
@ -606,18 +597,9 @@ def SignaleAbsenceGrSemestre(
|
|||||||
#
|
#
|
||||||
if etuds:
|
if etuds:
|
||||||
modimpls_list = []
|
modimpls_list = []
|
||||||
# Initialize with first student
|
ues = nt.get_ues_stat_dict()
|
||||||
ues = nt.get_ues(etudid=etuds[0]["etudid"])
|
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"])
|
modimpls_list += nt.get_modimpls_dict(ue_id=ue["ue_id"])
|
||||||
|
|
||||||
# Add modules other students are subscribed to
|
|
||||||
for etud in etuds[1:]:
|
|
||||||
modimpls_etud = []
|
|
||||||
ues = nt.get_ues(etudid=etud["etudid"])
|
|
||||||
for ue in ues:
|
|
||||||
modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"])
|
|
||||||
modimpls_list += [m for m in modimpls_etud if m not in modimpls_list]
|
|
||||||
|
|
||||||
menu_module = ""
|
menu_module = ""
|
||||||
for modimpl in modimpls_list:
|
for modimpl in modimpls_list:
|
||||||
@ -750,8 +732,8 @@ def _gen_form_saisie_groupe(
|
|||||||
if etud["cursem"]:
|
if etud["cursem"]:
|
||||||
nt = sco_cache.NotesTableCache.get(
|
nt = sco_cache.NotesTableCache.get(
|
||||||
etud["cursem"]["formsemestre_id"]
|
etud["cursem"]["formsemestre_id"]
|
||||||
) # > get_ues, get_etud_ue_status
|
) # > get_ues_stat_dict, get_etud_ue_status
|
||||||
for ue in nt.get_ues():
|
for ue in nt.get_ues_stat_dict():
|
||||||
status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
status = nt.get_etud_ue_status(etudid, ue["ue_id"])
|
||||||
if status["is_capitalized"]:
|
if status["is_capitalized"]:
|
||||||
cap.append(ue["acronyme"])
|
cap.append(ue["acronyme"])
|
||||||
|
@ -296,7 +296,7 @@ def formsemestre_bulletinetud(
|
|||||||
code_nip=str(code_nip)
|
code_nip=str(code_nip)
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
if format == "json":
|
if format == "json":
|
||||||
r = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
r = bulletin_but.BulletinBUT(formsemestre)
|
||||||
return jsonify(r.bulletin_etud(etud, formsemestre))
|
return jsonify(r.bulletin_etud(etud, formsemestre))
|
||||||
elif format == "html":
|
elif format == "html":
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -41,6 +41,7 @@ from flask_wtf import FlaskForm
|
|||||||
from flask_wtf.file import FileField, FileAllowed
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
from wtforms import SubmitField
|
from wtforms import SubmitField
|
||||||
|
|
||||||
|
from app import log
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
scodoc7func,
|
scodoc7func,
|
||||||
@ -50,12 +51,12 @@ from app.decorators import (
|
|||||||
login_required,
|
login_required,
|
||||||
)
|
)
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
from app.models.etudiants import make_etud_args
|
||||||
|
|
||||||
from app.views import scolar_bp as bp
|
from app.views import scolar_bp as bp
|
||||||
|
|
||||||
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.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
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 (
|
||||||
@ -455,7 +456,7 @@ def etud_info(etudid=None, format="xml"):
|
|||||||
if not format in ("xml", "json"):
|
if not format in ("xml", "json"):
|
||||||
raise ScoValueError("format demandé non supporté par cette fonction.")
|
raise ScoValueError("format demandé non supporté par cette fonction.")
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
args = sco_etud.make_etud_args(etudid=etudid)
|
args = make_etud_args(etudid=etudid)
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
etuds = sco_etud.etudident_list(cnx, args)
|
etuds = sco_etud.etudident_list(cnx, args)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
|
@ -0,0 +1,255 @@
|
|||||||
|
"""creation tables relations entreprises
|
||||||
|
|
||||||
|
Revision ID: f3b62d64efa3
|
||||||
|
Revises: 91be8a06d423
|
||||||
|
Create Date: 2021-12-24 10:36:27.150085
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "f3b62d64efa3"
|
||||||
|
down_revision = "91be8a06d423"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"entreprise_log",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column(
|
||||||
|
"date",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("authenticated_user", sa.Text(), nullable=True),
|
||||||
|
sa.Column("object", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("text", sa.Text(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
"entreprise_etudiant",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("etudid", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("type_offre", sa.Text(), nullable=True),
|
||||||
|
sa.Column("date_debut", sa.Date(), nullable=True),
|
||||||
|
sa.Column("date_fin", sa.Date(), nullable=True),
|
||||||
|
sa.Column("formation_text", sa.Text(), nullable=True),
|
||||||
|
sa.Column("formation_scodoc", sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["entreprise_id"], ["entreprises.id"], ondelete="cascade"
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
"entreprise_offre",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("entreprise_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"date_ajout",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column("intitule", sa.Text(), nullable=True),
|
||||||
|
sa.Column("description", sa.Text(), nullable=True),
|
||||||
|
sa.Column("type_offre", sa.Text(), nullable=True),
|
||||||
|
sa.Column("missions", sa.Text(), nullable=True),
|
||||||
|
sa.Column("duree", sa.Text(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["entreprise_id"], ["entreprises.id"], ondelete="cascade"
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
"entreprise_envoi_offre",
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("sender_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("receiver_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("offre_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"date_envoi",
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["offre_id"],
|
||||||
|
["entreprise_offre.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["sender_id"],
|
||||||
|
["user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["receiver_id"],
|
||||||
|
["user.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
op.drop_constraint(
|
||||||
|
"entreprise_contact_entreprise_corresp_id_fkey",
|
||||||
|
"entreprise_contact",
|
||||||
|
type_="foreignkey",
|
||||||
|
)
|
||||||
|
op.drop_table("entreprise_correspondant")
|
||||||
|
op.add_column("entreprise_contact", sa.Column("nom", sa.Text(), nullable=True))
|
||||||
|
op.add_column("entreprise_contact", sa.Column("prenom", sa.Text(), nullable=True))
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact", sa.Column("telephone", sa.Text(), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column("entreprise_contact", sa.Column("mail", sa.Text(), nullable=True))
|
||||||
|
op.add_column("entreprise_contact", sa.Column("poste", sa.Text(), nullable=True))
|
||||||
|
op.add_column("entreprise_contact", sa.Column("service", sa.Text(), nullable=True))
|
||||||
|
op.drop_column("entreprise_contact", "description")
|
||||||
|
op.drop_column("entreprise_contact", "enseignant")
|
||||||
|
op.drop_column("entreprise_contact", "date")
|
||||||
|
op.drop_column("entreprise_contact", "type_contact")
|
||||||
|
op.drop_column("entreprise_contact", "etudid")
|
||||||
|
op.drop_column("entreprise_contact", "entreprise_corresp_id")
|
||||||
|
|
||||||
|
op.add_column("entreprises", sa.Column("siret", sa.Text(), nullable=True))
|
||||||
|
op.drop_index("ix_entreprises_dept_id", table_name="entreprises")
|
||||||
|
op.drop_constraint("entreprises_dept_id_fkey", "entreprises", type_="foreignkey")
|
||||||
|
op.drop_column("entreprises", "qualite_relation")
|
||||||
|
op.drop_column("entreprises", "note")
|
||||||
|
op.drop_column("entreprises", "contact_origine")
|
||||||
|
op.drop_column("entreprises", "plus10salaries")
|
||||||
|
op.drop_column("entreprises", "privee")
|
||||||
|
op.drop_column("entreprises", "secteur")
|
||||||
|
op.drop_column("entreprises", "date_creation")
|
||||||
|
op.drop_column("entreprises", "dept_id")
|
||||||
|
op.drop_column("entreprises", "localisation")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("localisation", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("dept_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column(
|
||||||
|
"date_creation",
|
||||||
|
postgresql.TIMESTAMP(timezone=True),
|
||||||
|
server_default=sa.text("now()"),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("secteur", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("privee", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("plus10salaries", sa.BOOLEAN(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("contact_origine", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises", sa.Column("note", sa.TEXT(), autoincrement=False, nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprises",
|
||||||
|
sa.Column("qualite_relation", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.create_foreign_key(
|
||||||
|
"entreprises_dept_id_fkey", "entreprises", "departement", ["dept_id"], ["id"]
|
||||||
|
)
|
||||||
|
op.create_index("ix_entreprises_dept_id", "entreprises", ["dept_id"], unique=False)
|
||||||
|
op.drop_column("entreprises", "siret")
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column(
|
||||||
|
"entreprise_corresp_id", sa.INTEGER(), autoincrement=False, nullable=True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column("etudid", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column("type_contact", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column(
|
||||||
|
"date",
|
||||||
|
postgresql.TIMESTAMP(timezone=True),
|
||||||
|
autoincrement=False,
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column("enseignant", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"entreprise_contact",
|
||||||
|
sa.Column("description", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"entreprise_correspondant",
|
||||||
|
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column("entreprise_id", sa.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("nom", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("prenom", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("civilite", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("fonction", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("phone1", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("phone2", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("mobile", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("mail1", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("mail2", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("fax", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.Column("note", sa.TEXT(), autoincrement=False, nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["entreprise_id"],
|
||||||
|
["entreprises.id"],
|
||||||
|
name="entreprise_correspondant_entreprise_id_fkey",
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id", name="entreprise_correspondant_pkey"),
|
||||||
|
)
|
||||||
|
op.create_foreign_key(
|
||||||
|
"entreprise_contact_entreprise_corresp_id_fkey",
|
||||||
|
"entreprise_contact",
|
||||||
|
"entreprise_correspondant",
|
||||||
|
["entreprise_corresp_id"],
|
||||||
|
["id"],
|
||||||
|
)
|
||||||
|
op.drop_column("entreprise_contact", "service")
|
||||||
|
op.drop_column("entreprise_contact", "poste")
|
||||||
|
op.drop_column("entreprise_contact", "mail")
|
||||||
|
op.drop_column("entreprise_contact", "telephone")
|
||||||
|
op.drop_column("entreprise_contact", "prenom")
|
||||||
|
op.drop_column("entreprise_contact", "nom")
|
||||||
|
|
||||||
|
op.drop_table("entreprise_envoi_offre")
|
||||||
|
op.drop_table("entreprise_offre")
|
||||||
|
op.drop_table("entreprise_etudiant")
|
||||||
|
op.drop_table("entreprise_log")
|
||||||
|
# ### end Alembic commands ###
|
2
pylintrc
2
pylintrc
@ -19,3 +19,5 @@ ignored-classes=Permission,
|
|||||||
# and thus existing member attributes cannot be deduced by static analysis). It
|
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||||
# supports qualified module names, as well as Unix pattern matching.
|
# supports qualified module names, as well as Unix pattern matching.
|
||||||
ignored-modules=entreprises
|
ignored-modules=entreprises
|
||||||
|
|
||||||
|
good-names=d,e,f,i,j,k,t,u,v,x,y,z,H,F,ue
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.1.16"
|
SCOVERSION = "9.2.0a"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
13
scodoc.py
13
scodoc.py
@ -29,7 +29,6 @@ from app.models import ModuleImpl, ModuleImplInscription
|
|||||||
from app.models import Identite
|
from app.models import Identite
|
||||||
from app.models import departements
|
from app.models import departements
|
||||||
from app.models.evaluations import Evaluation
|
from app.models.evaluations import Evaluation
|
||||||
from app.scodoc.sco_etud import identite_create
|
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import notes, scolar
|
from app.views import notes, scolar
|
||||||
import tools
|
import tools
|
||||||
@ -249,6 +248,18 @@ def edit_role(rolename, addpermissionname=None, removepermissionname=None): # e
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.argument("rolename")
|
||||||
|
def delete_role(rolename):
|
||||||
|
"""Delete a role"""
|
||||||
|
role = Role.query.filter_by(name=rolename).first()
|
||||||
|
if role is None:
|
||||||
|
sys.stderr.write(f"delete_role: role {rolename} does not exists\n")
|
||||||
|
return 1
|
||||||
|
db.session.delete(role)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@click.argument("username")
|
@click.argument("username")
|
||||||
@click.option("-d", "--dept", "dept_acronym")
|
@click.option("-d", "--dept", "dept_acronym")
|
||||||
|
@ -4,6 +4,8 @@ et calcul moyennes modules
|
|||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from app.models.modules import Module
|
||||||
|
from app.models.moduleimpls import ModuleImpl
|
||||||
|
|
||||||
from tests.unit import setup
|
from tests.unit import setup
|
||||||
from app import db
|
from app import db
|
||||||
@ -135,70 +137,72 @@ def test_module_conformity(test_client):
|
|||||||
)
|
)
|
||||||
assert isinstance(modules_coefficients, pd.DataFrame)
|
assert isinstance(modules_coefficients, pd.DataFrame)
|
||||||
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(evaluation.moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(evaluation.moduleimpl_id)
|
||||||
assert isinstance(evals_poids, pd.DataFrame)
|
assert isinstance(evals_poids, pd.DataFrame)
|
||||||
assert len(ues) == nb_ues
|
assert len(ues) == nb_ues
|
||||||
assert all(evals_poids.dtypes == np.float64)
|
assert all(evals_poids.dtypes == np.float64)
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
assert not moy_mod.check_moduleimpl_conformity(
|
assert not moy_mod.moduleimpl_is_conforme(
|
||||||
evaluation.moduleimpl, evals_poids, modules_coefficients
|
evaluation.moduleimpl, evals_poids, modules_coefficients
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_module_moy_elem(test_client):
|
# En ScoDoc 9.2 test ne peut plus exister car compute_module_moy
|
||||||
"""Vérification calcul moyenne d'un module
|
# est maintenant incorporé dans la classe ModuleImplResultsAPC
|
||||||
(notes entrées dans un DataFrame sans passer par ScoDoc)
|
# def test_module_moy_elem(test_client):
|
||||||
"""
|
# """Vérification calcul moyenne d'un module
|
||||||
# Création de deux évaluations:
|
# (notes entrées dans un DataFrame sans passer par ScoDoc)
|
||||||
e1 = Evaluation(note_max=20.0, coefficient=1.0)
|
# """
|
||||||
e2 = Evaluation(note_max=20.0, coefficient=1.0)
|
# # Création de deux évaluations:
|
||||||
db.session.add(e1)
|
# e1 = Evaluation(note_max=20.0, coefficient=1.0)
|
||||||
db.session.add(e2)
|
# e2 = Evaluation(note_max=20.0, coefficient=1.0)
|
||||||
db.session.commit()
|
# db.session.add(e1)
|
||||||
# Repris du notebook CalculNotesBUT.ipynb
|
# db.session.add(e2)
|
||||||
data = [ # Les notes de chaque étudiant dans les 2 evals:
|
# db.session.flush()
|
||||||
{
|
# # Repris du notebook CalculNotesBUT.ipynb
|
||||||
e1.id: 11.0,
|
# data = [ # Les notes de chaque étudiant dans les 2 evals:
|
||||||
e2.id: 16.0,
|
# {
|
||||||
},
|
# e1.id: 11.0,
|
||||||
{
|
# e2.id: 16.0,
|
||||||
e1.id: None, # une absence
|
# },
|
||||||
e2.id: 17.0,
|
# {
|
||||||
},
|
# e1.id: None, # une absence
|
||||||
{
|
# e2.id: 17.0,
|
||||||
e1.id: 13.0,
|
# },
|
||||||
e2.id: NOTES_NEUTRALISE, # une abs EXC
|
# {
|
||||||
},
|
# e1.id: 13.0,
|
||||||
{
|
# e2.id: NOTES_NEUTRALISE, # une abs EXC
|
||||||
e1.id: 14.0,
|
# },
|
||||||
e2.id: 19.0,
|
# {
|
||||||
},
|
# e1.id: 14.0,
|
||||||
{
|
# e2.id: 19.0,
|
||||||
e1.id: NOTES_ATTENTE, # une ATT (traitée comme EXC)
|
# },
|
||||||
e2.id: None, # et une ABS
|
# {
|
||||||
},
|
# e1.id: NOTES_ATTENTE, # une ATT (traitée comme EXC)
|
||||||
]
|
# e2.id: None, # et une ABS
|
||||||
evals_notes_df = pd.DataFrame(
|
# },
|
||||||
data, index=["etud1", "etud2", "etud3", "etud4", "etud5"]
|
# ]
|
||||||
)
|
# evals_notes_df = pd.DataFrame(
|
||||||
# Poids des évaluations (1 ligne / évaluation)
|
# data, index=["etud1", "etud2", "etud3", "etud4", "etud5"]
|
||||||
data = [
|
# )
|
||||||
{"UE1": 1, "UE2": 0, "UE3": 0},
|
# # Poids des évaluations (1 ligne / évaluation)
|
||||||
{"UE1": 2, "UE2": 5, "UE3": 0},
|
# data = [
|
||||||
]
|
# {"UE1": 1, "UE2": 0, "UE3": 0},
|
||||||
evals_poids_df = pd.DataFrame(data, index=[e1.id, e2.id], dtype=float)
|
# {"UE1": 2, "UE2": 5, "UE3": 0},
|
||||||
evaluations = [e1, e2]
|
# ]
|
||||||
etuds_moy_module_df = moy_mod.compute_module_moy(
|
# evals_poids_df = pd.DataFrame(data, index=[e1.id, e2.id], dtype=float)
|
||||||
evals_notes_df.fillna(0.0), evals_poids_df, evaluations, [True, True]
|
# evaluations = [e1, e2]
|
||||||
)
|
# etuds_moy_module_df = moy_mod.compute_module_moy(
|
||||||
NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
# evals_notes_df.fillna(0.0), evals_poids_df, evaluations, [True, True]
|
||||||
r = etuds_moy_module_df.fillna(NAN)
|
# )
|
||||||
assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
# NAN = 666.0 # pour pouvoir comparer NaN et NaN (car NaN != NaN)
|
||||||
assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
# r = etuds_moy_module_df.fillna(NAN)
|
||||||
assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
# assert tuple(r.loc["etud1"]) == (14 + 1 / 3, 16.0, NAN)
|
||||||
assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
# assert tuple(r.loc["etud2"]) == (11 + 1 / 3, 17.0, NAN)
|
||||||
assert tuple(r.loc["etud5"]) == (0.0, 0.0, NAN)
|
# assert tuple(r.loc["etud3"]) == (13, NAN, NAN)
|
||||||
# note: les notes UE3 sont toutes NAN car les poids vers l'UE3 sont nuls
|
# assert tuple(r.loc["etud4"]) == (17 + 1 / 3, 19, NAN)
|
||||||
|
# assert tuple(r.loc["etud5"]) == (0.0, 0.0, NAN)
|
||||||
|
# # note: les notes UE3 sont toutes NAN car les poids vers l'UE3 sont nuls
|
||||||
|
|
||||||
|
|
||||||
def test_module_moy(test_client):
|
def test_module_moy(test_client):
|
||||||
@ -237,7 +241,7 @@ def test_module_moy(test_client):
|
|||||||
nb_evals = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
|
nb_evals = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).count()
|
||||||
assert nb_evals == 2
|
assert nb_evals == 2
|
||||||
nb_ues = 3
|
nb_ues = 3
|
||||||
|
modimpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
# --- Change les notes et recalcule les moyennes du module
|
# --- Change les notes et recalcule les moyennes du module
|
||||||
# (rappel: on a deux évaluations: evaluation1, evaluation2, et un seul étudiant)
|
# (rappel: on a deux évaluations: evaluation1, evaluation2, et un seul étudiant)
|
||||||
def change_notes(n1, n2):
|
def change_notes(n1, n2):
|
||||||
@ -245,17 +249,14 @@ def test_module_moy(test_client):
|
|||||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
|
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
|
||||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
||||||
# Calcul de la moyenne du module
|
# Calcul de la moyenne du module
|
||||||
evals_poids, ues = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||||
evals_notes, evaluations, evaluations_completes = moy_mod.df_load_modimpl_notes(
|
|
||||||
moduleimpl_id
|
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||||
)
|
evals_notes = mod_results.evals_notes
|
||||||
assert evals_notes[evaluations[0].id].dtype == np.float64
|
assert evals_notes[evaluation1.id].dtype == np.float64
|
||||||
assert evaluation1.id == evaluations[0].id
|
|
||||||
assert evaluation2.id == evaluations[1].id
|
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||||
etuds_moy_module = moy_mod.compute_module_moy(
|
|
||||||
evals_notes, evals_poids, evaluations, evaluations_completes
|
|
||||||
)
|
|
||||||
return etuds_moy_module
|
return etuds_moy_module
|
||||||
|
|
||||||
# --- Notes ordinaires:
|
# --- Notes ordinaires:
|
||||||
|
@ -69,9 +69,9 @@ def test_ue_moy(test_client):
|
|||||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
|
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
|
||||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
||||||
# Recalcul des moyennes
|
# Recalcul des moyennes
|
||||||
sem_cube, _, _, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
sem_cube, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
||||||
etuds = formsemestre.etuds.all()
|
etuds = formsemestre.etuds.all()
|
||||||
etud_moy_ue = moy_ue.compute_ue_moys(
|
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||||
)
|
)
|
||||||
return etud_moy_ue
|
return etud_moy_ue
|
||||||
@ -112,9 +112,9 @@ def test_ue_moy(test_client):
|
|||||||
exception_raised = True
|
exception_raised = True
|
||||||
assert exception_raised
|
assert exception_raised
|
||||||
# Recalcule les notes:
|
# Recalcule les notes:
|
||||||
sem_cube, _, _, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
sem_cube, _, _ = moy_ue.notes_sem_load_cube(formsemestre)
|
||||||
etuds = formsemestre.etuds.all()
|
etuds = formsemestre.etuds.all()
|
||||||
etud_moy_ue = moy_ue.compute_ue_moys(
|
etud_moy_ue = moy_ue.compute_ue_moys_apc(
|
||||||
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
sem_cube, etuds, modimpls, ues, modimpl_inscr_df, modimpl_coefs_df
|
||||||
)
|
)
|
||||||
assert etud_moy_ue[ue1.id][etudid] == n1
|
assert etud_moy_ue[ue1.id][etudid] == n1
|
||||||
|
@ -64,7 +64,7 @@ fi
|
|||||||
|
|
||||||
# ------------ LIEN VERS .env
|
# ------------ LIEN VERS .env
|
||||||
# Pour conserver le .env entre les mises à jour, on le génère dans
|
# Pour conserver le .env entre les mises à jour, on le génère dans
|
||||||
# /opt/scodoc-data/;env et on le lie:
|
# /opt/scodoc-data/.env et on le lie:
|
||||||
if [ ! -e "$SCODOC_DIR/.env" ] && [ ! -L "$SCODOC_DIR/.env" ]
|
if [ ! -e "$SCODOC_DIR/.env" ] && [ ! -L "$SCODOC_DIR/.env" ]
|
||||||
then
|
then
|
||||||
ln -s "$SCODOC_VAR_DIR/.env" "$SCODOC_DIR"
|
ln -s "$SCODOC_VAR_DIR/.env" "$SCODOC_DIR"
|
||||||
|
Loading…
Reference in New Issue
Block a user