forked from ScoDoc/ScoDoc
Merge branch 'scodoc-master' into pe-but-v4
This commit is contained in:
commit
8acd9a12d4
@ -104,9 +104,11 @@ class BulletinBUT:
|
|||||||
"competence": None, # XXX TODO lien avec référentiel
|
"competence": None, # XXX TODO lien avec référentiel
|
||||||
"moyenne": None,
|
"moyenne": None,
|
||||||
# Le bonus sport appliqué sur cette UE
|
# Le bonus sport appliqué sur cette UE
|
||||||
"bonus": fmt_note(res.bonus_ues[ue.id][etud.id])
|
"bonus": (
|
||||||
|
fmt_note(res.bonus_ues[ue.id][etud.id])
|
||||||
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
if res.bonus_ues is not None and ue.id in res.bonus_ues
|
||||||
else fmt_note(0.0),
|
else fmt_note(0.0)
|
||||||
|
),
|
||||||
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
"malus": fmt_note(res.malus[ue.id][etud.id]),
|
||||||
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
|
"capitalise": None, # "AAAA-MM-JJ" TODO #sco93
|
||||||
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
"ressources": self.etud_ue_mod_results(etud, ue, res.ressources),
|
||||||
@ -181,14 +183,16 @@ class BulletinBUT:
|
|||||||
"is_external": ue_capitalisee.is_external,
|
"is_external": ue_capitalisee.is_external,
|
||||||
"date_capitalisation": ue_capitalisee.event_date,
|
"date_capitalisation": ue_capitalisee.event_date,
|
||||||
"formsemestre_id": ue_capitalisee.formsemestre_id,
|
"formsemestre_id": ue_capitalisee.formsemestre_id,
|
||||||
"bul_orig_url": url_for(
|
"bul_orig_url": (
|
||||||
|
url_for(
|
||||||
"notes.formsemestre_bulletinetud",
|
"notes.formsemestre_bulletinetud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
formsemestre_id=ue_capitalisee.formsemestre_id,
|
formsemestre_id=ue_capitalisee.formsemestre_id,
|
||||||
)
|
)
|
||||||
if ue_capitalisee.formsemestre_id
|
if ue_capitalisee.formsemestre_id
|
||||||
else None,
|
else None
|
||||||
|
),
|
||||||
"ressources": {}, # sans détail en BUT
|
"ressources": {}, # sans détail en BUT
|
||||||
"saes": {},
|
"saes": {},
|
||||||
}
|
}
|
||||||
@ -227,13 +231,15 @@ class BulletinBUT:
|
|||||||
"id": modimpl.id,
|
"id": modimpl.id,
|
||||||
"titre": modimpl.module.titre,
|
"titre": modimpl.module.titre,
|
||||||
"code_apogee": modimpl.module.code_apogee,
|
"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=modimpl.id,
|
moduleimpl_id=modimpl.id,
|
||||||
)
|
)
|
||||||
if has_request_context()
|
if has_request_context()
|
||||||
else "na",
|
else "na"
|
||||||
|
),
|
||||||
"moyenne": {
|
"moyenne": {
|
||||||
# # moyenne indicative de module: moyenne des UE,
|
# # moyenne indicative de module: moyenne des UE,
|
||||||
# # ignorant celles sans notes (nan)
|
# # ignorant celles sans notes (nan)
|
||||||
@ -242,7 +248,8 @@ class BulletinBUT:
|
|||||||
# "max": fmt_note(moyennes_etuds.max()),
|
# "max": fmt_note(moyennes_etuds.max()),
|
||||||
# "moy": fmt_note(moyennes_etuds.mean()),
|
# "moy": fmt_note(moyennes_etuds.mean()),
|
||||||
},
|
},
|
||||||
"evaluations": [
|
"evaluations": (
|
||||||
|
[
|
||||||
self.etud_eval_results(etud, e)
|
self.etud_eval_results(etud, e)
|
||||||
for e in modimpl.evaluations
|
for e in modimpl.evaluations
|
||||||
if (e.visibulletin or version == "long")
|
if (e.visibulletin or version == "long")
|
||||||
@ -253,7 +260,8 @@ class BulletinBUT:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
if version != "short"
|
if version != "short"
|
||||||
else [],
|
else []
|
||||||
|
),
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -274,14 +282,17 @@ class BulletinBUT:
|
|||||||
poids = collections.defaultdict(lambda: 0.0)
|
poids = collections.defaultdict(lambda: 0.0)
|
||||||
d = {
|
d = {
|
||||||
"id": e.id,
|
"id": e.id,
|
||||||
"coef": fmt_note(e.coefficient)
|
"coef": (
|
||||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
fmt_note(e.coefficient)
|
||||||
else None,
|
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||||
|
else None
|
||||||
|
),
|
||||||
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
"date_debut": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
"date_fin": e.date_fin.isoformat() if e.date_fin else None,
|
||||||
"description": e.description,
|
"description": e.description,
|
||||||
"evaluation_type": e.evaluation_type,
|
"evaluation_type": e.evaluation_type,
|
||||||
"note": {
|
"note": (
|
||||||
|
{
|
||||||
"value": fmt_note(
|
"value": fmt_note(
|
||||||
eval_notes[etud.id],
|
eval_notes[etud.id],
|
||||||
note_max=e.note_max,
|
note_max=e.note_max,
|
||||||
@ -289,20 +300,25 @@ class BulletinBUT:
|
|||||||
"min": fmt_note(notes_ok.min(), note_max=e.note_max),
|
"min": fmt_note(notes_ok.min(), note_max=e.note_max),
|
||||||
"max": fmt_note(notes_ok.max(), note_max=e.note_max),
|
"max": fmt_note(notes_ok.max(), note_max=e.note_max),
|
||||||
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
|
"moy": fmt_note(notes_ok.mean(), note_max=e.note_max),
|
||||||
},
|
}
|
||||||
|
if not e.is_blocked()
|
||||||
|
else {}
|
||||||
|
),
|
||||||
"poids": poids,
|
"poids": poids,
|
||||||
"url": url_for(
|
"url": (
|
||||||
|
url_for(
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
evaluation_id=e.id,
|
evaluation_id=e.id,
|
||||||
)
|
)
|
||||||
if has_request_context()
|
if has_request_context()
|
||||||
else "na",
|
else "na"
|
||||||
|
),
|
||||||
# deprecated (supprimer avant #sco9.7)
|
# deprecated (supprimer avant #sco9.7)
|
||||||
"date": e.date_debut.isoformat() if e.date_debut else None,
|
"date": e.date_debut.isoformat() if e.date_debut else None,
|
||||||
"heure_debut": e.date_debut.time().isoformat("minutes")
|
"heure_debut": (
|
||||||
if e.date_debut
|
e.date_debut.time().isoformat("minutes") if e.date_debut else None
|
||||||
else None,
|
),
|
||||||
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
"heure_fin": e.date_fin.time().isoformat("minutes") if e.date_fin else None,
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
@ -524,9 +540,9 @@ class BulletinBUT:
|
|||||||
|
|
||||||
d.update(infos)
|
d.update(infos)
|
||||||
# --- Rangs
|
# --- Rangs
|
||||||
d[
|
d["rang_nt"] = (
|
||||||
"rang_nt"
|
f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
)
|
||||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||||
|
|
||||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||||
|
@ -119,6 +119,12 @@ def _build_bulletin_but_infos(
|
|||||||
refcomp = formsemestre.formation.referentiel_competence
|
refcomp = formsemestre.formation.referentiel_competence
|
||||||
if refcomp is None:
|
if refcomp is None:
|
||||||
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
raise ScoNoReferentielCompetences(formation=formsemestre.formation)
|
||||||
|
|
||||||
|
warn_html = cursus_but.formsemestre_warning_apc_setup(
|
||||||
|
formsemestre, bulletins_sem.res
|
||||||
|
)
|
||||||
|
if warn_html:
|
||||||
|
raise ScoValueError("<b>Formation mal configurée pour le BUT</b>" + warn_html)
|
||||||
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
||||||
refcomp, etud
|
refcomp, etud
|
||||||
)
|
)
|
||||||
|
@ -24,7 +24,7 @@ from reportlab.lib.colors import blue
|
|||||||
from reportlab.lib.units import cm, mm
|
from reportlab.lib.units import cm, mm
|
||||||
from reportlab.platypus import Paragraph, Spacer
|
from reportlab.platypus import Paragraph, Spacer
|
||||||
|
|
||||||
from app.models import ScoDocSiteConfig
|
from app.models import Evaluation, ScoDocSiteConfig
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
from app.scodoc import gen_tables
|
from app.scodoc import gen_tables
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
@ -422,7 +422,11 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
|||||||
def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
|
def evaluations_rows(self, rows, evaluations: list[dict], ue_acros=()):
|
||||||
"lignes des évaluations"
|
"lignes des évaluations"
|
||||||
for e in evaluations:
|
for e in evaluations:
|
||||||
coef = e["coef"] if e["evaluation_type"] == scu.EVALUATION_NORMALE else "*"
|
coef = (
|
||||||
|
e["coef"]
|
||||||
|
if e["evaluation_type"] == Evaluation.EVALUATION_NORMALE
|
||||||
|
else "*"
|
||||||
|
)
|
||||||
t = {
|
t = {
|
||||||
"titre": f"{e['description'] or ''}",
|
"titre": f"{e['description'] or ''}",
|
||||||
"moyenne": e["note"]["value"],
|
"moyenne": e["note"]["value"],
|
||||||
@ -431,7 +435,10 @@ class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
|||||||
),
|
),
|
||||||
"coef": coef,
|
"coef": coef,
|
||||||
"_coef_pdf": Paragraph(
|
"_coef_pdf": Paragraph(
|
||||||
f"<para align=right fontSize={self.small_fontsize}><i>{coef}</i></para>"
|
f"""<para align=right fontSize={self.small_fontsize}><i>{
|
||||||
|
coef if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||||
|
else "bonus"
|
||||||
|
}</i></para>"""
|
||||||
),
|
),
|
||||||
"_pdf_style": [
|
"_pdf_style": [
|
||||||
(
|
(
|
||||||
|
@ -23,29 +23,21 @@ from app.comp.res_but import ResultatsSemestreBUT
|
|||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
|
|
||||||
from app.models.but_refcomp import (
|
from app.models.but_refcomp import (
|
||||||
ApcAnneeParcours,
|
|
||||||
ApcCompetence,
|
ApcCompetence,
|
||||||
ApcNiveau,
|
ApcNiveau,
|
||||||
ApcParcours,
|
ApcParcours,
|
||||||
ApcParcoursNiveauCompetence,
|
|
||||||
ApcReferentielCompetences,
|
ApcReferentielCompetences,
|
||||||
)
|
)
|
||||||
from app.models import Scolog, ScolarAutorisationInscription
|
from app.models.ues import UEParcours
|
||||||
from app.models.but_validations import (
|
from app.models.but_validations import ApcValidationRCUE
|
||||||
ApcValidationAnnee,
|
|
||||||
ApcValidationRCUE,
|
|
||||||
)
|
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formations import Formation
|
from app.models.formations import Formation
|
||||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
from app.models.formsemestre import FormSemestre
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
from app.models.validations import ScolarFormSemestreValidation
|
from app.models.validations import ScolarFormSemestreValidation
|
||||||
from app.scodoc import codes_cursus as sco_codes
|
from app.scodoc import codes_cursus as sco_codes
|
||||||
from app.scodoc.codes_cursus import code_ue_validant, CODES_UE_VALIDES, UE_STANDARD
|
from app.scodoc.codes_cursus import code_ue_validant, CODES_UE_VALIDES, UE_STANDARD
|
||||||
|
|
||||||
from app.scodoc import sco_utils as scu
|
|
||||||
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
from app.scodoc.sco_exceptions import ScoNoReferentielCompetences, ScoValueError
|
||||||
|
|
||||||
from app.scodoc import sco_cursus_dut
|
from app.scodoc import sco_cursus_dut
|
||||||
|
|
||||||
|
|
||||||
@ -440,11 +432,16 @@ def formsemestre_warning_apc_setup(
|
|||||||
"""
|
"""
|
||||||
if not formsemestre.formation.is_apc():
|
if not formsemestre.formation.is_apc():
|
||||||
return ""
|
return ""
|
||||||
|
url_formation = url_for(
|
||||||
|
"notes.ue_table",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formation_id=formsemestre.formation.id,
|
||||||
|
semestre_idx=formsemestre.semestre_id,
|
||||||
|
)
|
||||||
if formsemestre.formation.referentiel_competence is None:
|
if formsemestre.formation.referentiel_competence is None:
|
||||||
return f"""<div class="formsemestre_status_warning">
|
return f"""<div class="formsemestre_status_warning">
|
||||||
La <a class="stdlink" href="{
|
La <a class="stdlink" href="{url_formation}">formation
|
||||||
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)
|
n'est pas associée à un référentiel de compétence.</a>
|
||||||
}">formation n'est pas associée à un référentiel de compétence.</a>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
H = []
|
H = []
|
||||||
@ -462,7 +459,9 @@ def formsemestre_warning_apc_setup(
|
|||||||
)
|
)
|
||||||
if nb_ues_sans_parcours != nb_ues_tot:
|
if nb_ues_sans_parcours != nb_ues_tot:
|
||||||
H.append(
|
H.append(
|
||||||
f"""Le semestre n'est associé à aucun parcours, mais les UEs de la formation ont des parcours"""
|
"""Le semestre n'est associé à aucun parcours,
|
||||||
|
mais les UEs de la formation ont des parcours
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
# Vérifie les niveaux de chaque parcours
|
# Vérifie les niveaux de chaque parcours
|
||||||
for parcour in formsemestre.parcours or [None]:
|
for parcour in formsemestre.parcours or [None]:
|
||||||
@ -489,7 +488,8 @@ def formsemestre_warning_apc_setup(
|
|||||||
if not H:
|
if not H:
|
||||||
return ""
|
return ""
|
||||||
return f"""<div class="formsemestre_status_warning">
|
return f"""<div class="formsemestre_status_warning">
|
||||||
Problème dans la configuration de la formation:
|
Problème dans la
|
||||||
|
<a class="stdlink" href="{url_formation}">configuration de la formation</a>:
|
||||||
<ul>
|
<ul>
|
||||||
<li>{ '</li><li>'.join(H) }</li>
|
<li>{ '</li><li>'.join(H) }</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -502,6 +502,78 @@ def formsemestre_warning_apc_setup(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int) -> str:
|
||||||
|
"""Vérifie que tous les niveaux de compétences de cette année de formation
|
||||||
|
ont bien des UEs.
|
||||||
|
Afin de ne pas générer trop de messages, on ne considère que les parcours
|
||||||
|
du référentiel de compétences pour lesquels au moins une UE a été associée.
|
||||||
|
|
||||||
|
Renvoie fragment de html
|
||||||
|
"""
|
||||||
|
annee = (semestre_idx - 1) // 2 + 1 # année BUT
|
||||||
|
ref_comp: ApcReferentielCompetences = formation.referentiel_competence
|
||||||
|
if not ref_comp:
|
||||||
|
return "" # détecté ailleurs...
|
||||||
|
niveaux_sans_ue_by_parcour = {} # { parcour.code : [ ue ... ] }
|
||||||
|
parcours_ids = {
|
||||||
|
uep.parcours_id
|
||||||
|
for uep in UEParcours.query.join(UniteEns).filter_by(
|
||||||
|
formation_id=formation.id, type=UE_STANDARD
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for parcour in ref_comp.parcours:
|
||||||
|
if parcour.id not in parcours_ids:
|
||||||
|
continue # saute parcours associés à aucune UE (tous semestres)
|
||||||
|
niveaux_sans_ue = []
|
||||||
|
niveaux = ApcNiveau.niveaux_annee_de_parcours(parcour, annee, ref_comp)
|
||||||
|
# print(f"\n# Parcours {parcour.code} : {len(niveaux)} niveaux")
|
||||||
|
for niveau in niveaux:
|
||||||
|
ues = [ue for ue in formation.ues if ue.niveau_competence_id == niveau.id]
|
||||||
|
if not ues:
|
||||||
|
niveaux_sans_ue.append(niveau)
|
||||||
|
# print( niveau.competence.titre + " " + str(niveau.ordre) + "\t" + str(ue) )
|
||||||
|
if niveaux_sans_ue:
|
||||||
|
niveaux_sans_ue_by_parcour[parcour.code] = niveaux_sans_ue
|
||||||
|
#
|
||||||
|
H = []
|
||||||
|
for parcour_code, niveaux in niveaux_sans_ue_by_parcour.items():
|
||||||
|
H.append(
|
||||||
|
f"""<li>Parcours {parcour_code} : {
|
||||||
|
len(niveaux)} niveaux sans UEs
|
||||||
|
<span>
|
||||||
|
{ ', '.join( f'{niveau.competence.titre} {niveau.ordre}'
|
||||||
|
for niveau in niveaux
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# Combien de compétences de tronc commun ?
|
||||||
|
_, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
|
||||||
|
nb_niveaux_tc = len(niveaux_by_parcours["TC"])
|
||||||
|
nb_ues_tc = len(
|
||||||
|
formation.query_ues_parcour(None)
|
||||||
|
.filter(UniteEns.semestre_idx == semestre_idx)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
if nb_niveaux_tc != nb_ues_tc:
|
||||||
|
H.append(
|
||||||
|
f"""<li>{nb_niveaux_tc} niveaux de compétences de tronc commun,
|
||||||
|
mais {nb_ues_tc} UEs de tronc commun !</li>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if H:
|
||||||
|
return f"""<div class="formation_semestre_niveaux_warning">
|
||||||
|
<div>Problèmes détectés à corriger :</div>
|
||||||
|
<ul>
|
||||||
|
{"".join(H)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
return "" # no problem detected
|
||||||
|
|
||||||
|
|
||||||
def ue_associee_au_niveau_du_parcours(
|
def ue_associee_au_niveau_du_parcours(
|
||||||
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
|
ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S"
|
||||||
) -> UniteEns:
|
) -> UniteEns:
|
||||||
|
@ -77,7 +77,7 @@ from app.models.but_refcomp import (
|
|||||||
ApcNiveau,
|
ApcNiveau,
|
||||||
ApcParcours,
|
ApcParcours,
|
||||||
)
|
)
|
||||||
from app.models import Scolog, ScolarAutorisationInscription
|
from app.models import Evaluation, Scolog, ScolarAutorisationInscription
|
||||||
from app.models.but_validations import (
|
from app.models.but_validations import (
|
||||||
ApcValidationAnnee,
|
ApcValidationAnnee,
|
||||||
ApcValidationRCUE,
|
ApcValidationRCUE,
|
||||||
@ -260,11 +260,11 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
else []
|
else []
|
||||||
)
|
)
|
||||||
# ---- Niveaux et RCUEs
|
# ---- Niveaux et RCUEs
|
||||||
niveaux_by_parcours = (
|
niveaux_by_parcours = formsemestre.formation.referentiel_competence.get_niveaux_by_parcours(
|
||||||
formsemestre.formation.referentiel_competence.get_niveaux_by_parcours(
|
|
||||||
self.annee_but, [self.parcour] if self.parcour else None
|
self.annee_but, [self.parcour] if self.parcour else None
|
||||||
)[1]
|
)[
|
||||||
)
|
1
|
||||||
|
]
|
||||||
self.niveaux_competences = niveaux_by_parcours["TC"] + (
|
self.niveaux_competences = niveaux_by_parcours["TC"] + (
|
||||||
niveaux_by_parcours[self.parcour.id] if self.parcour else []
|
niveaux_by_parcours[self.parcour.id] if self.parcour else []
|
||||||
)
|
)
|
||||||
@ -358,13 +358,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
# self.codes = [] # pas de décision annuelle sur semestres impairs
|
# self.codes = [] # pas de décision annuelle sur semestres impairs
|
||||||
elif self.inscription_etat != scu.INSCRIT:
|
elif self.inscription_etat != scu.INSCRIT:
|
||||||
self.codes = [
|
self.codes = [
|
||||||
|
(
|
||||||
sco_codes.DEM
|
sco_codes.DEM
|
||||||
if self.inscription_etat == scu.DEMISSION
|
if self.inscription_etat == scu.DEMISSION
|
||||||
else sco_codes.DEF,
|
else sco_codes.DEF
|
||||||
|
),
|
||||||
# propose aussi d'autres codes, au cas où...
|
# propose aussi d'autres codes, au cas où...
|
||||||
|
(
|
||||||
sco_codes.DEM
|
sco_codes.DEM
|
||||||
if self.inscription_etat != scu.DEMISSION
|
if self.inscription_etat != scu.DEMISSION
|
||||||
else sco_codes.DEF,
|
else sco_codes.DEF
|
||||||
|
),
|
||||||
sco_codes.ABAN,
|
sco_codes.ABAN,
|
||||||
sco_codes.ABL,
|
sco_codes.ABL,
|
||||||
sco_codes.EXCLU,
|
sco_codes.EXCLU,
|
||||||
@ -595,11 +599,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
# Ordonne par numéro d'UE
|
# Ordonne par numéro d'UE
|
||||||
niv_rcue = sorted(
|
niv_rcue = sorted(
|
||||||
self.rcue_by_niveau.items(),
|
self.rcue_by_niveau.items(),
|
||||||
key=lambda x: x[1].ue_1.numero
|
key=lambda x: (
|
||||||
if x[1].ue_1
|
x[1].ue_1.numero if x[1].ue_1 else x[1].ue_2.numero if x[1].ue_2 else 0
|
||||||
else x[1].ue_2.numero
|
),
|
||||||
if x[1].ue_2
|
|
||||||
else 0,
|
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
niveau_id: DecisionsProposeesRCUE(self, rcue, self.inscription_etat)
|
niveau_id: DecisionsProposeesRCUE(self, rcue, self.inscription_etat)
|
||||||
@ -816,8 +818,14 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
Return: True si au moins un code modifié et enregistré.
|
Return: True si au moins un code modifié et enregistré.
|
||||||
"""
|
"""
|
||||||
modif = False
|
modif = False
|
||||||
# Vérification notes en attente dans formsemestre origine
|
if only_validantes:
|
||||||
if only_validantes and self.has_notes_en_attente():
|
if self.has_notes_en_attente():
|
||||||
|
# notes en attente dans formsemestre origine
|
||||||
|
return False
|
||||||
|
if Evaluation.get_evaluations_blocked_for_etud(
|
||||||
|
self.formsemestre, self.etud
|
||||||
|
):
|
||||||
|
# évaluation(s) qui seront débloquées dans le futur
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Toujours valider dans l'ordre UE, RCUE, Année
|
# Toujours valider dans l'ordre UE, RCUE, Année
|
||||||
@ -1488,9 +1496,11 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||||||
self.validation = None # cache toute validation
|
self.validation = None # cache toute validation
|
||||||
self.explanation = "non inscrit (dem. ou déf.)"
|
self.explanation = "non inscrit (dem. ou déf.)"
|
||||||
self.codes = [
|
self.codes = [
|
||||||
|
(
|
||||||
sco_codes.DEM
|
sco_codes.DEM
|
||||||
if res.get_etud_etat(etud.id) == scu.DEMISSION
|
if res.get_etud_etat(etud.id) == scu.DEMISSION
|
||||||
else sco_codes.DEF
|
else sco_codes.DEF
|
||||||
|
)
|
||||||
]
|
]
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -331,250 +331,6 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def jury_but_semestriel(
|
|
||||||
formsemestre: FormSemestre,
|
|
||||||
etud: Identite,
|
|
||||||
read_only: bool,
|
|
||||||
navigation_div: str = "",
|
|
||||||
) -> str:
|
|
||||||
"""Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)."""
|
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
|
||||||
parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
|
|
||||||
inscription_etat = etud.inscription_etat(formsemestre.id)
|
|
||||||
semestre_terminal = (
|
|
||||||
formsemestre.semestre_id >= formsemestre.formation.get_cursus().NB_SEM
|
|
||||||
)
|
|
||||||
autorisations_passage = ScolarAutorisationInscription.query.filter_by(
|
|
||||||
etudid=etud.id,
|
|
||||||
origin_formsemestre_id=formsemestre.id,
|
|
||||||
).all()
|
|
||||||
# Par défaut: autorisé à passer dans le semestre suivant si sem. impair,
|
|
||||||
# ou si décision déjà enregistrée:
|
|
||||||
est_autorise_a_passer = (formsemestre.semestre_id % 2) or (
|
|
||||||
formsemestre.semestre_id + 1
|
|
||||||
) in (a.semestre_id for a in autorisations_passage)
|
|
||||||
decisions_ues = {
|
|
||||||
ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat)
|
|
||||||
for ue in ues
|
|
||||||
}
|
|
||||||
for dec_ue in decisions_ues.values():
|
|
||||||
dec_ue.compute_codes()
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
if not read_only:
|
|
||||||
for key in request.form:
|
|
||||||
code = request.form[key]
|
|
||||||
# Codes d'UE
|
|
||||||
code_match = re.match(r"^code_ue_(\d+)$", key)
|
|
||||||
if code_match:
|
|
||||||
ue_id = int(code_match.group(1))
|
|
||||||
dec_ue = decisions_ues.get(ue_id)
|
|
||||||
if not dec_ue:
|
|
||||||
raise ScoValueError(f"UE invalide ue_id={ue_id}")
|
|
||||||
dec_ue.record(code)
|
|
||||||
db.session.commit()
|
|
||||||
flash("codes enregistrés")
|
|
||||||
if not semestre_terminal:
|
|
||||||
if request.form.get("autorisation_passage"):
|
|
||||||
if not formsemestre.semestre_id + 1 in (
|
|
||||||
a.semestre_id for a in autorisations_passage
|
|
||||||
):
|
|
||||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
|
||||||
etud.id, formsemestre.id
|
|
||||||
)
|
|
||||||
ScolarAutorisationInscription.autorise_etud(
|
|
||||||
etud.id,
|
|
||||||
formsemestre.formation.formation_code,
|
|
||||||
formsemestre.id,
|
|
||||||
formsemestre.semestre_id + 1,
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
flash(
|
|
||||||
f"""autorisation de passage en S{formsemestre.semestre_id + 1
|
|
||||||
} enregistrée"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if est_autorise_a_passer:
|
|
||||||
ScolarAutorisationInscription.delete_autorisation_etud(
|
|
||||||
etud.id, formsemestre.id
|
|
||||||
)
|
|
||||||
db.session.commit()
|
|
||||||
flash(
|
|
||||||
f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée"
|
|
||||||
)
|
|
||||||
ScolarNews.add(
|
|
||||||
typ=ScolarNews.NEWS_JURY,
|
|
||||||
obj=formsemestre.id,
|
|
||||||
text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""",
|
|
||||||
url=url_for(
|
|
||||||
"notes.formsemestre_status",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return flask.redirect(
|
|
||||||
url_for(
|
|
||||||
"notes.formsemestre_validation_but",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
etudid=etud.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# GET
|
|
||||||
if formsemestre.semestre_id % 2 == 0:
|
|
||||||
warning = f"""<div class="warning">
|
|
||||||
Cet étudiant de S{formsemestre.semestre_id} ne peut pas passer
|
|
||||||
en jury BUT annuel car il lui manque le semestre précédent.
|
|
||||||
</div>"""
|
|
||||||
else:
|
|
||||||
warning = ""
|
|
||||||
H = [
|
|
||||||
html_sco_header.sco_header(
|
|
||||||
page_title=f"Validation BUT S{formsemestre.semestre_id}",
|
|
||||||
formsemestre_id=formsemestre.id,
|
|
||||||
etudid=etud.id,
|
|
||||||
cssstyles=("css/jury_but.css",),
|
|
||||||
javascripts=("js/jury_but.js",),
|
|
||||||
),
|
|
||||||
f"""
|
|
||||||
<div class="jury_but">
|
|
||||||
<div>
|
|
||||||
<div class="bull_head">
|
|
||||||
<div>
|
|
||||||
<div class="titre_parcours">Jury BUT S{formsemestre.id}
|
|
||||||
- Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
|
|
||||||
</div>
|
|
||||||
<div class="nom_etud">{etud.nomprenom}</div>
|
|
||||||
</div>
|
|
||||||
<div class="bull_photo"><a href="{
|
|
||||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
|
||||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
|
|
||||||
{warning}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post" class="jury_but_box" id="jury_but">
|
|
||||||
""",
|
|
||||||
]
|
|
||||||
|
|
||||||
erase_span = ""
|
|
||||||
if not read_only:
|
|
||||||
# Requête toutes les validations (pas seulement celles du deca courant),
|
|
||||||
# au cas où: changement d'architecture, saisie en mode classique, ...
|
|
||||||
validations = ScolarFormSemestreValidation.query.filter_by(
|
|
||||||
etudid=etud.id, formsemestre_id=formsemestre.id
|
|
||||||
).all()
|
|
||||||
if validations:
|
|
||||||
erase_span = f"""<a href="{
|
|
||||||
url_for("notes.formsemestre_jury_but_erase",
|
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
|
|
||||||
etudid=etud.id, only_one_sem=1)
|
|
||||||
}" class="stdlink">effacer les décisions enregistrées</a>"""
|
|
||||||
else:
|
|
||||||
erase_span = (
|
|
||||||
"Cet étudiant n'a aucune décision enregistrée pour ce semestre."
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<div class="but_section_annee">
|
|
||||||
</div>
|
|
||||||
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
if not ues:
|
|
||||||
H.append(
|
|
||||||
"""<div class="warning">Aucune UE ! Vérifiez votre programme de
|
|
||||||
formation, et l'association UEs / Niveaux de compétences</div>"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append(
|
|
||||||
"""
|
|
||||||
<div class="but_annee">
|
|
||||||
<div class="titre"></div>
|
|
||||||
<div class="titre"></div>
|
|
||||||
<div class="titre"></div>
|
|
||||||
<div class="titre"></div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
for ue in ues:
|
|
||||||
dec_ue = decisions_ues[ue.id]
|
|
||||||
H.append("""<div class="but_niveau_titre"><div></div></div>""")
|
|
||||||
H.append(
|
|
||||||
_gen_but_niveau_ue(
|
|
||||||
ue,
|
|
||||||
dec_ue,
|
|
||||||
disabled=read_only,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
H.append(
|
|
||||||
"""<div style=""></div>
|
|
||||||
<div class=""></div>"""
|
|
||||||
)
|
|
||||||
H.append("</div>") # but_annee
|
|
||||||
|
|
||||||
div_autorisations_passage = (
|
|
||||||
f"""
|
|
||||||
<div class="but_autorisations_passage">
|
|
||||||
<span>Autorisé à passer en :</span>
|
|
||||||
{ ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
if autorisations_passage
|
|
||||||
else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
|
|
||||||
)
|
|
||||||
H.append(div_autorisations_passage)
|
|
||||||
|
|
||||||
if read_only:
|
|
||||||
H.append(
|
|
||||||
f"""<div class="but_explanation">
|
|
||||||
{"Vous n'avez pas la permission de modifier ces décisions."
|
|
||||||
if formsemestre.etat
|
|
||||||
else "Semestre verrouillé."}
|
|
||||||
Les champs entourés en vert sont enregistrés.
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if formsemestre.semestre_id < formsemestre.formation.get_cursus().NB_SEM:
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<div class="but_settings">
|
|
||||||
<input type="checkbox" name="autorisation_passage" value="1" {
|
|
||||||
"checked" if est_autorise_a_passer else ""}>
|
|
||||||
<em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
|
|
||||||
</input>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
H.append("""<div class="help">dernier semestre de la formation.</div>""")
|
|
||||||
H.append(
|
|
||||||
f"""
|
|
||||||
<div class="but_buttons">
|
|
||||||
<span><input type="submit" value="Enregistrer ces décisions"></span>
|
|
||||||
<span>{erase_span}</span>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
H.append(navigation_div)
|
|
||||||
H.append("</div>")
|
|
||||||
H.append(
|
|
||||||
render_template(
|
|
||||||
"but/documentation_codes_jury.j2",
|
|
||||||
nom_univ=f"""Export {sco_preferences.get_preference("InstituteName")
|
|
||||||
or sco_preferences.get_preference("UnivName")
|
|
||||||
or "Apogée"}""",
|
|
||||||
codes=ScoDocSiteConfig.get_codes_apo_dict(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return "\n".join(H)
|
|
||||||
|
|
||||||
|
|
||||||
# -------------
|
# -------------
|
||||||
def infos_fiche_etud_html(etudid: int) -> str:
|
def infos_fiche_etud_html(etudid: int) -> str:
|
||||||
"""Section html pour fiche etudiant
|
"""Section html pour fiche etudiant
|
||||||
|
@ -35,7 +35,6 @@ moyenne générale d'une UE.
|
|||||||
"""
|
"""
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
@ -151,17 +150,18 @@ class ModuleImplResults:
|
|||||||
self.evaluations_completes_dict = {}
|
self.evaluations_completes_dict = {}
|
||||||
for evaluation in moduleimpl.evaluations:
|
for evaluation in moduleimpl.evaluations:
|
||||||
eval_df = self._load_evaluation_notes(evaluation)
|
eval_df = self._load_evaluation_notes(evaluation)
|
||||||
# is_complete ssi tous les inscrits (non dem) au semestre ont une note
|
# is_complete ssi
|
||||||
|
# tous les inscrits (non dem) au module ont une note
|
||||||
# ou évaluation déclarée "à prise en compte immédiate"
|
# ou évaluation déclarée "à prise en compte immédiate"
|
||||||
# Les évaluations de rattrapage et 2eme session sont toujours complètes
|
# ou rattrapage, 2eme session, bonus
|
||||||
|
# ET pas bloquée par date (is_blocked)
|
||||||
|
|
||||||
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
|
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
|
||||||
is_complete = (
|
is_complete = (
|
||||||
(evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE)
|
(evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||||
or (evaluation.evaluation_type == scu.EVALUATION_SESSION2)
|
|
||||||
or (evaluation.publish_incomplete)
|
or (evaluation.publish_incomplete)
|
||||||
or (not etudids_sans_note)
|
or (not etudids_sans_note)
|
||||||
)
|
) and not evaluation.is_blocked()
|
||||||
self.evaluations_completes.append(is_complete)
|
self.evaluations_completes.append(is_complete)
|
||||||
self.evaluations_completes_dict[evaluation.id] = is_complete
|
self.evaluations_completes_dict[evaluation.id] = is_complete
|
||||||
self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note
|
self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note
|
||||||
@ -186,7 +186,7 @@ class ModuleImplResults:
|
|||||||
].index
|
].index
|
||||||
)
|
)
|
||||||
if evaluation.publish_incomplete:
|
if evaluation.publish_incomplete:
|
||||||
# et en "imédiat", tous ceux sans note
|
# et en "immédiat", tous ceux sans note
|
||||||
eval_etudids_attente |= etudids_sans_note
|
eval_etudids_attente |= etudids_sans_note
|
||||||
# Synthèse pour état du module:
|
# Synthèse pour état du module:
|
||||||
self.etudids_attente |= eval_etudids_attente
|
self.etudids_attente |= eval_etudids_attente
|
||||||
@ -240,19 +240,20 @@ class ModuleImplResults:
|
|||||||
).formsemestre.inscriptions
|
).formsemestre.inscriptions
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_evaluations_coefs(self, moduleimpl: ModuleImpl) -> np.array:
|
def get_evaluations_coefs(self, modimpl: ModuleImpl) -> np.array:
|
||||||
"""Coefficients des évaluations.
|
"""Coefficients des évaluations.
|
||||||
Les coefs des évals incomplètes et non "normales" (session 2, rattrapage)
|
Les coefs des évals incomplètes, rattrapage, session 2, bonus sont forcés à zéro.
|
||||||
sont zéro.
|
|
||||||
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
Résultat: 2d-array of floats, shape (nb_evals, 1)
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
np.array(
|
np.array(
|
||||||
[
|
[
|
||||||
|
(
|
||||||
e.coefficient
|
e.coefficient
|
||||||
if e.evaluation_type == scu.EVALUATION_NORMALE
|
if e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||||
else 0.0
|
else 0.0
|
||||||
for e in moduleimpl.evaluations
|
)
|
||||||
|
for e in modimpl.evaluations
|
||||||
],
|
],
|
||||||
dtype=float,
|
dtype=float,
|
||||||
)
|
)
|
||||||
@ -276,7 +277,7 @@ class ModuleImplResults:
|
|||||||
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
) / [e.note_max / 20.0 for e in moduleimpl.evaluations]
|
||||||
|
|
||||||
def get_eval_notes_dict(self, evaluation_id: int) -> dict:
|
def get_eval_notes_dict(self, evaluation_id: int) -> dict:
|
||||||
"""Notes d'une évaulation, brutes, sous forme d'un dict
|
"""Notes d'une évaluation, brutes, sous forme d'un dict
|
||||||
{ etudid : valeur }
|
{ etudid : valeur }
|
||||||
avec les valeurs float, ou "ABS" ou EXC
|
avec les valeurs float, ou "ABS" ou EXC
|
||||||
"""
|
"""
|
||||||
@ -285,7 +286,7 @@ class ModuleImplResults:
|
|||||||
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
for (etudid, x) in self.evals_notes[evaluation_id].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl):
|
def get_evaluation_rattrapage(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||||
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
"""L'évaluation de rattrapage de ce module, ou None s'il n'en a pas.
|
||||||
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
Rattrapage: la moyenne du module est la meilleure note entre moyenne
|
||||||
des autres évals et la note eval rattrapage.
|
des autres évals et la note eval rattrapage.
|
||||||
@ -293,25 +294,41 @@ class ModuleImplResults:
|
|||||||
eval_list = [
|
eval_list = [
|
||||||
e
|
e
|
||||||
for e in moduleimpl.evaluations
|
for e in moduleimpl.evaluations
|
||||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE
|
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE
|
||||||
]
|
]
|
||||||
if eval_list:
|
if eval_list:
|
||||||
return eval_list[0]
|
return eval_list[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_evaluation_session2(self, moduleimpl: ModuleImpl):
|
def get_evaluation_session2(self, moduleimpl: ModuleImpl) -> Evaluation | None:
|
||||||
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
|
"""L'évaluation de deuxième session de ce module, ou None s'il n'en a pas.
|
||||||
Session 2: remplace la note de moyenne des autres évals.
|
Session 2: remplace la note de moyenne des autres évals.
|
||||||
"""
|
"""
|
||||||
eval_list = [
|
eval_list = [
|
||||||
e
|
e
|
||||||
for e in moduleimpl.evaluations
|
for e in moduleimpl.evaluations
|
||||||
if e.evaluation_type == scu.EVALUATION_SESSION2
|
if e.evaluation_type == Evaluation.EVALUATION_SESSION2
|
||||||
]
|
]
|
||||||
if eval_list:
|
if eval_list:
|
||||||
return eval_list[0]
|
return eval_list[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_evaluations_bonus(self, modimpl: ModuleImpl) -> list[Evaluation]:
|
||||||
|
"""Les évaluations bonus de ce module, ou liste vide s'il n'en a pas."""
|
||||||
|
return [
|
||||||
|
e
|
||||||
|
for e in modimpl.evaluations
|
||||||
|
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_evaluations_bonus_idx(self, modimpl: ModuleImpl) -> list[int]:
|
||||||
|
"""Les indices des évaluations bonus"""
|
||||||
|
return [
|
||||||
|
i
|
||||||
|
for (i, e) in enumerate(modimpl.evaluations)
|
||||||
|
if e.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ModuleImplResultsAPC(ModuleImplResults):
|
class ModuleImplResultsAPC(ModuleImplResults):
|
||||||
"Calcul des moyennes de modules à la mode BUT"
|
"Calcul des moyennes de modules à la mode BUT"
|
||||||
@ -356,7 +373,7 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
# et dans dans evals_poids_etuds
|
# et dans dans evals_poids_etuds
|
||||||
# (rappel: la comparaison est toujours false face à un NaN)
|
# (rappel: la comparaison est toujours false face à un NaN)
|
||||||
# shape: (nb_etuds, nb_evals, nb_ues)
|
# shape: (nb_etuds, nb_evals, nb_ues)
|
||||||
poids_stacked = np.stack([evals_poids] * nb_etuds)
|
poids_stacked = np.stack([evals_poids] * nb_etuds) # nb_etuds, nb_evals, nb_ues
|
||||||
evals_poids_etuds = np.where(
|
evals_poids_etuds = np.where(
|
||||||
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
np.stack([self.evals_notes.values] * nb_ues, axis=2) > scu.NOTES_NEUTRALISE,
|
||||||
poids_stacked,
|
poids_stacked,
|
||||||
@ -364,10 +381,20 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
)
|
)
|
||||||
# Calcule la moyenne pondérée sur les notes disponibles:
|
# Calcule la moyenne pondérée sur les notes disponibles:
|
||||||
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
evals_notes_stacked = np.stack([evals_notes_20] * nb_ues, axis=2)
|
||||||
|
# evals_notes_stacked shape: nb_etuds, nb_evals, nb_ues
|
||||||
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
with np.errstate(invalid="ignore"): # ignore les 0/0 (-> NaN)
|
||||||
etuds_moy_module = np.sum(
|
etuds_moy_module = np.sum(
|
||||||
evals_poids_etuds * evals_notes_stacked, axis=1
|
evals_poids_etuds * evals_notes_stacked, axis=1
|
||||||
) / np.sum(evals_poids_etuds, axis=1)
|
) / np.sum(evals_poids_etuds, axis=1)
|
||||||
|
# etuds_moy_module shape: nb_etuds x nb_ues
|
||||||
|
|
||||||
|
# Application des évaluations bonus:
|
||||||
|
etuds_moy_module = self.apply_bonus(
|
||||||
|
etuds_moy_module,
|
||||||
|
modimpl,
|
||||||
|
evals_poids_df,
|
||||||
|
evals_notes_stacked,
|
||||||
|
)
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||||
@ -416,6 +443,30 @@ class ModuleImplResultsAPC(ModuleImplResults):
|
|||||||
)
|
)
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def apply_bonus(
|
||||||
|
self,
|
||||||
|
etuds_moy_module: pd.DataFrame,
|
||||||
|
modimpl: ModuleImpl,
|
||||||
|
evals_poids_df: pd.DataFrame,
|
||||||
|
evals_notes_stacked: np.ndarray,
|
||||||
|
):
|
||||||
|
"""Ajoute les points des évaluations bonus.
|
||||||
|
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||||
|
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||||
|
"""
|
||||||
|
evals_bonus = self.get_evaluations_bonus(modimpl)
|
||||||
|
if not evals_bonus:
|
||||||
|
return etuds_moy_module
|
||||||
|
poids_stacked = np.stack([evals_poids_df.values] * len(etuds_moy_module))
|
||||||
|
for evaluation in evals_bonus:
|
||||||
|
eval_idx = evals_poids_df.index.get_loc(evaluation.id)
|
||||||
|
etuds_moy_module += (
|
||||||
|
evals_notes_stacked[:, eval_idx, :] * poids_stacked[:, eval_idx, :]
|
||||||
|
)
|
||||||
|
# Clip dans [0,20]
|
||||||
|
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||||
|
return etuds_moy_module
|
||||||
|
|
||||||
|
|
||||||
def load_evaluations_poids(moduleimpl_id: int) -> tuple[pd.DataFrame, list]:
|
def load_evaluations_poids(moduleimpl_id: int) -> 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
|
||||||
@ -532,6 +583,13 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
evals_coefs_etuds * evals_notes_20, axis=1
|
evals_coefs_etuds * evals_notes_20, axis=1
|
||||||
) / np.sum(evals_coefs_etuds, axis=1)
|
) / np.sum(evals_coefs_etuds, axis=1)
|
||||||
|
|
||||||
|
# Application des évaluations bonus:
|
||||||
|
etuds_moy_module = self.apply_bonus(
|
||||||
|
etuds_moy_module,
|
||||||
|
modimpl,
|
||||||
|
evals_notes_20,
|
||||||
|
)
|
||||||
|
|
||||||
# Session2 : quand elle existe, remplace la note de module
|
# Session2 : quand elle existe, remplace la note de module
|
||||||
eval_session2 = self.get_evaluation_session2(modimpl)
|
eval_session2 = self.get_evaluation_session2(modimpl)
|
||||||
if eval_session2:
|
if eval_session2:
|
||||||
@ -571,3 +629,22 @@ class ModuleImplResultsClassic(ModuleImplResults):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return self.etuds_moy_module
|
return self.etuds_moy_module
|
||||||
|
|
||||||
|
def apply_bonus(
|
||||||
|
self,
|
||||||
|
etuds_moy_module: np.ndarray,
|
||||||
|
modimpl: ModuleImpl,
|
||||||
|
evals_notes_20: np.ndarray,
|
||||||
|
):
|
||||||
|
"""Ajoute les points des évaluations bonus.
|
||||||
|
Il peut y avoir un nb quelconque d'évaluations bonus.
|
||||||
|
Les points sont directement ajoutés (ils peuvent être négatifs).
|
||||||
|
"""
|
||||||
|
evals_bonus_idx = self.get_evaluations_bonus_idx(modimpl)
|
||||||
|
if not evals_bonus_idx:
|
||||||
|
return etuds_moy_module
|
||||||
|
for eval_idx in evals_bonus_idx:
|
||||||
|
etuds_moy_module += evals_notes_20[:, eval_idx]
|
||||||
|
# Clip dans [0,20]
|
||||||
|
etuds_moy_module.clip(0, 20, out=etuds_moy_module)
|
||||||
|
return etuds_moy_module
|
||||||
|
@ -205,11 +205,12 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"coefficient" : float, # 0 si None
|
"coefficient" : float, # 0 si None
|
||||||
"description" : str, # de l'évaluation, "" si None
|
"description" : str, # de l'évaluation, "" si None
|
||||||
"etat" {
|
"etat" {
|
||||||
|
"blocked" : bool, # vrai si prise en compte bloquée
|
||||||
"evalcomplete" : bool,
|
"evalcomplete" : bool,
|
||||||
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
||||||
"nb_notes" : int, # nb notes d'étudiants inscrits
|
"nb_notes" : int, # nb notes d'étudiants inscrits
|
||||||
},
|
},
|
||||||
"evaluatiuon_id" : int,
|
"evaluation_id" : int,
|
||||||
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
||||||
"publish_incomplete" : bool,
|
"publish_incomplete" : bool,
|
||||||
}
|
}
|
||||||
@ -230,15 +231,16 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
date_modif = cursor.one_or_none()
|
date_modif = cursor.one_or_none()
|
||||||
last_modif = date_modif[0] if date_modif else None
|
last_modif = date_modif[0] if date_modif else None
|
||||||
return {
|
return {
|
||||||
"coefficient": evaluation.coefficient or 0.0,
|
"coefficient": evaluation.coefficient,
|
||||||
"description": evaluation.description or "",
|
"description": evaluation.description,
|
||||||
"evaluation_id": evaluation.id,
|
|
||||||
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
|
||||||
"etat": {
|
"etat": {
|
||||||
|
"blocked": evaluation.is_blocked(),
|
||||||
"evalcomplete": etat.is_complete,
|
"evalcomplete": etat.is_complete,
|
||||||
"nb_notes": etat.nb_notes,
|
"nb_notes": etat.nb_notes,
|
||||||
"last_modif": last_modif,
|
"last_modif": last_modif,
|
||||||
},
|
},
|
||||||
|
"evaluation_id": evaluation.id,
|
||||||
|
"jour": evaluation.date_debut or datetime.datetime(1900, 1, 1),
|
||||||
"publish_incomplete": evaluation.publish_incomplete,
|
"publish_incomplete": evaluation.publish_incomplete,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,9 +434,24 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
ue_cap_dict["compense_formsemestre_id"] = None
|
ue_cap_dict["compense_formsemestre_id"] = None
|
||||||
return ue_cap_dict
|
return ue_cap_dict
|
||||||
|
|
||||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict:
|
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict | None:
|
||||||
"""L'état de l'UE pour cet étudiant.
|
"""L'état de l'UE pour cet étudiant.
|
||||||
Result: dict, ou None si l'UE n'est pas dans ce semestre.
|
Result: dict, ou None si l'UE n'est pas dans ce semestre.
|
||||||
|
{
|
||||||
|
"is_capitalized": # vrai si la version capitalisée est celle prise en compte (meilleure)
|
||||||
|
"was_capitalized":# si elle a été capitalisée (meilleure ou pas)
|
||||||
|
"is_external": # si UE externe
|
||||||
|
"coef_ue": 0.0,
|
||||||
|
"cur_moy_ue": 0.0, # moyenne de l'UE courante
|
||||||
|
"moy": 0.0, # moyenne prise en compte
|
||||||
|
"event_date": # date de la capiltalisation éventuelle (ou None)
|
||||||
|
"ue": ue_dict, # l'UE, comme un dict
|
||||||
|
"formsemestre_id": None,
|
||||||
|
"capitalized_ue_id": None, # l'id de l'UE capitalisée, ou None
|
||||||
|
"ects_pot": 0.0, # deprecated (les ECTS liés à cette UE)
|
||||||
|
"ects": 0.0, # les ECTS acquis grace à cette UE
|
||||||
|
"ects_ue": # les ECTS liés à cette UE
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
ue: UniteEns = db.session.get(UniteEns, ue_id)
|
ue: UniteEns = db.session.get(UniteEns, ue_id)
|
||||||
ue_dict = ue.to_dict()
|
ue_dict = ue.to_dict()
|
||||||
@ -455,7 +472,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"ects": 0.0,
|
"ects": 0.0,
|
||||||
"ects_ue": ue.ects,
|
"ects_ue": ue.ects,
|
||||||
}
|
}
|
||||||
if not ue_id in self.etud_moy_ue:
|
if not ue_id in self.etud_moy_ue or not etudid in self.etud_moy_ue[ue_id]:
|
||||||
return None
|
return None
|
||||||
if not self.validations:
|
if not self.validations:
|
||||||
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
|
||||||
@ -512,11 +529,13 @@ class ResultatsSemestre(ResultatsCache):
|
|||||||
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
"is_external": ue_cap["is_external"] if is_capitalized else ue.is_external,
|
||||||
"coef_ue": coef_ue,
|
"coef_ue": coef_ue,
|
||||||
"ects_pot": ue.ects or 0.0,
|
"ects_pot": ue.ects or 0.0,
|
||||||
"ects": self.validations.decisions_jury_ues.get(etudid, {})
|
"ects": (
|
||||||
|
self.validations.decisions_jury_ues.get(etudid, {})
|
||||||
.get(ue.id, {})
|
.get(ue.id, {})
|
||||||
.get("ects", 0.0)
|
.get("ects", 0.0)
|
||||||
if self.validations.decisions_jury_ues
|
if self.validations.decisions_jury_ues
|
||||||
else 0.0,
|
else 0.0
|
||||||
|
),
|
||||||
"ects_ue": ue.ects,
|
"ects_ue": ue.ects,
|
||||||
"cur_moy_ue": cur_moy_ue,
|
"cur_moy_ue": cur_moy_ue,
|
||||||
"moy": moy_ue,
|
"moy": moy_ue,
|
||||||
|
@ -125,7 +125,7 @@ class Identite(models.ScoDocModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Champs "protégés" par ViewEtudData (RGPD)
|
# Champs "protégés" par ViewEtudData (RGPD)
|
||||||
protected_attrs = {"boursier"}
|
protected_attrs = {"boursier", "nationalite"}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
|
@ -10,6 +10,7 @@ from flask_login import current_user
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
from app import models
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.events import ScolarNews
|
from app.models.events import ScolarNews
|
||||||
from app.models.notes import NotesNotes
|
from app.models.notes import NotesNotes
|
||||||
@ -23,10 +24,8 @@ MAX_EVALUATION_DURATION = datetime.timedelta(days=365)
|
|||||||
NOON = datetime.time(12, 00)
|
NOON = datetime.time(12, 00)
|
||||||
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
DEFAULT_EVALUATION_TIME = datetime.time(8, 0)
|
||||||
|
|
||||||
VALID_EVALUATION_TYPES = {0, 1, 2}
|
|
||||||
|
|
||||||
|
class Evaluation(models.ScoDocModel):
|
||||||
class Evaluation(db.Model):
|
|
||||||
"""Evaluation (contrôle, examen, ...)"""
|
"""Evaluation (contrôle, examen, ...)"""
|
||||||
|
|
||||||
__tablename__ = "notes_evaluation"
|
__tablename__ = "notes_evaluation"
|
||||||
@ -38,9 +37,9 @@ class Evaluation(db.Model):
|
|||||||
)
|
)
|
||||||
date_debut = db.Column(db.DateTime(timezone=True), nullable=True)
|
date_debut = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
date_fin = db.Column(db.DateTime(timezone=True), nullable=True)
|
date_fin = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text, nullable=False)
|
||||||
note_max = db.Column(db.Float)
|
note_max = db.Column(db.Float, nullable=False)
|
||||||
coefficient = db.Column(db.Float)
|
coefficient = db.Column(db.Float, nullable=False)
|
||||||
visibulletin = db.Column(
|
visibulletin = db.Column(
|
||||||
db.Boolean, nullable=False, default=True, server_default="true"
|
db.Boolean, nullable=False, default=True, server_default="true"
|
||||||
)
|
)
|
||||||
@ -48,15 +47,30 @@ class Evaluation(db.Model):
|
|||||||
publish_incomplete = db.Column(
|
publish_incomplete = db.Column(
|
||||||
db.Boolean, nullable=False, default=False, server_default="false"
|
db.Boolean, nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
# type d'evaluation: 0 normale, 1 rattrapage, 2 "2eme session"
|
"prise en compte immédiate"
|
||||||
evaluation_type = db.Column(
|
evaluation_type = db.Column(
|
||||||
db.Integer, nullable=False, default=0, server_default="0"
|
db.Integer, nullable=False, default=0, server_default="0"
|
||||||
)
|
)
|
||||||
|
"type d'evaluation: 0 normale, 1 rattrapage, 2 2eme session, 3 bonus"
|
||||||
|
blocked_until = db.Column(db.DateTime(timezone=True), nullable=True)
|
||||||
|
"date de prise en compte"
|
||||||
|
BLOCKED_FOREVER = datetime.datetime(2666, 12, 31, tzinfo=scu.TIME_ZONE)
|
||||||
# ordre de presentation (par défaut, le plus petit numero
|
# ordre de presentation (par défaut, le plus petit numero
|
||||||
# est la plus ancienne eval):
|
# est la plus ancienne eval):
|
||||||
numero = db.Column(db.Integer, nullable=False, default=0)
|
numero = db.Column(db.Integer, nullable=False, default=0)
|
||||||
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
ues = db.relationship("UniteEns", secondary="evaluation_ue_poids", viewonly=True)
|
||||||
|
|
||||||
|
EVALUATION_NORMALE = 0 # valeurs stockées en base, ne pas changer !
|
||||||
|
EVALUATION_RATTRAPAGE = 1
|
||||||
|
EVALUATION_SESSION2 = 2
|
||||||
|
EVALUATION_BONUS = 3
|
||||||
|
VALID_EVALUATION_TYPES = {
|
||||||
|
EVALUATION_NORMALE,
|
||||||
|
EVALUATION_RATTRAPAGE,
|
||||||
|
EVALUATION_SESSION2,
|
||||||
|
EVALUATION_BONUS,
|
||||||
|
}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""<Evaluation {self.id} {
|
return f"""<Evaluation {self.id} {
|
||||||
self.date_debut.isoformat() if self.date_debut else ''} "{
|
self.date_debut.isoformat() if self.date_debut else ''} "{
|
||||||
@ -70,13 +84,14 @@ class Evaluation(db.Model):
|
|||||||
date_fin: datetime.datetime = None,
|
date_fin: datetime.datetime = None,
|
||||||
description=None,
|
description=None,
|
||||||
note_max=None,
|
note_max=None,
|
||||||
|
blocked_until=None,
|
||||||
coefficient=None,
|
coefficient=None,
|
||||||
visibulletin=None,
|
visibulletin=None,
|
||||||
publish_incomplete=None,
|
publish_incomplete=None,
|
||||||
evaluation_type=None,
|
evaluation_type=None,
|
||||||
numero=None,
|
numero=None,
|
||||||
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
**kw, # ceci pour absorber les éventuel arguments excedentaires
|
||||||
):
|
) -> "Evaluation":
|
||||||
"""Create an evaluation. Check permission and all arguments.
|
"""Create an evaluation. Check permission and all arguments.
|
||||||
Ne crée pas les poids vers les UEs.
|
Ne crée pas les poids vers les UEs.
|
||||||
Add to session, do not commit.
|
Add to session, do not commit.
|
||||||
@ -88,7 +103,7 @@ class Evaluation(db.Model):
|
|||||||
args = locals()
|
args = locals()
|
||||||
del args["cls"]
|
del args["cls"]
|
||||||
del args["kw"]
|
del args["kw"]
|
||||||
check_convert_evaluation_args(moduleimpl, args)
|
check_and_convert_evaluation_args(args, moduleimpl)
|
||||||
# Check numeros
|
# Check numeros
|
||||||
Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True)
|
Evaluation.moduleimpl_evaluation_renumber(moduleimpl, only_if_unumbered=True)
|
||||||
if not "numero" in args or args["numero"] is None:
|
if not "numero" in args or args["numero"] is None:
|
||||||
@ -199,6 +214,10 @@ class Evaluation(db.Model):
|
|||||||
def to_dict_api(self) -> dict:
|
def to_dict_api(self) -> dict:
|
||||||
"Représentation dict pour API JSON"
|
"Représentation dict pour API JSON"
|
||||||
return {
|
return {
|
||||||
|
"blocked": self.is_blocked(),
|
||||||
|
"blocked_until": (
|
||||||
|
self.blocked_until.isoformat() if self.blocked_until else ""
|
||||||
|
),
|
||||||
"coefficient": self.coefficient,
|
"coefficient": self.coefficient,
|
||||||
"date_debut": self.date_debut.isoformat() if self.date_debut else "",
|
"date_debut": self.date_debut.isoformat() if self.date_debut else "",
|
||||||
"date_fin": self.date_fin.isoformat() if self.date_fin else "",
|
"date_fin": self.date_fin.isoformat() if self.date_fin else "",
|
||||||
@ -235,15 +254,6 @@ class Evaluation(db.Model):
|
|||||||
|
|
||||||
return e_dict
|
return e_dict
|
||||||
|
|
||||||
def from_dict(self, data):
|
|
||||||
"""Set evaluation attributes from given dict values."""
|
|
||||||
check_convert_evaluation_args(self.moduleimpl, data)
|
|
||||||
if data.get("numero") is None:
|
|
||||||
data["numero"] = Evaluation.get_max_numero(self.moduleimpl.id) + 1
|
|
||||||
for k in self.__dict__:
|
|
||||||
if k != "_sa_instance_state" and k != "id" and k in data:
|
|
||||||
setattr(self, k, data[k])
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_evaluation(
|
def get_evaluation(
|
||||||
cls, evaluation_id: int | str, dept_id: int = None
|
cls, evaluation_id: int | str, dept_id: int = None
|
||||||
@ -361,19 +371,6 @@ class Evaluation(db.Model):
|
|||||||
Chaine vide si non renseignée."""
|
Chaine vide si non renseignée."""
|
||||||
return self.date_fin.time().isoformat("minutes") if self.date_fin else ""
|
return self.date_fin.time().isoformat("minutes") if self.date_fin else ""
|
||||||
|
|
||||||
def clone(self, not_copying=()):
|
|
||||||
"""Clone, not copying the given attrs
|
|
||||||
Attention: la copie n'a pas d'id avant le prochain commit
|
|
||||||
"""
|
|
||||||
d = dict(self.__dict__)
|
|
||||||
d.pop("id") # get rid of id
|
|
||||||
d.pop("_sa_instance_state") # get rid of SQLAlchemy special attr
|
|
||||||
for k in not_copying:
|
|
||||||
d.pop(k)
|
|
||||||
copy = self.__class__(**d)
|
|
||||||
db.session.add(copy)
|
|
||||||
return copy
|
|
||||||
|
|
||||||
def is_matin(self) -> bool:
|
def is_matin(self) -> bool:
|
||||||
"Evaluation commençant le matin (faux si pas de date)"
|
"Evaluation commençant le matin (faux si pas de date)"
|
||||||
if not self.date_debut:
|
if not self.date_debut:
|
||||||
@ -386,6 +383,14 @@ class Evaluation(db.Model):
|
|||||||
return False
|
return False
|
||||||
return self.date_debut.time() >= NOON
|
return self.date_debut.time() >= NOON
|
||||||
|
|
||||||
|
def is_blocked(self, now=None) -> bool:
|
||||||
|
"True si prise en compte bloquée"
|
||||||
|
if self.blocked_until is None:
|
||||||
|
return False
|
||||||
|
if now is None:
|
||||||
|
now = datetime.datetime.now(scu.TIME_ZONE)
|
||||||
|
return self.blocked_until > now
|
||||||
|
|
||||||
def set_default_poids(self) -> bool:
|
def set_default_poids(self) -> bool:
|
||||||
"""Initialize les poids vers les UE à leurs valeurs par défaut
|
"""Initialize les poids vers les UE à leurs valeurs par défaut
|
||||||
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
C'est à dire à 1 si le coef. module/UE est non nul, 0 sinon.
|
||||||
@ -474,6 +479,29 @@ class Evaluation(db.Model):
|
|||||||
"""
|
"""
|
||||||
return NotesNotes.query.filter_by(etudid=etud.id, evaluation_id=self.id).first()
|
return NotesNotes.query.filter_by(etudid=etud.id, evaluation_id=self.id).first()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_evaluations_blocked_for_etud(
|
||||||
|
cls, formsemestre, etud: Identite
|
||||||
|
) -> list["Evaluation"]:
|
||||||
|
"""Liste des évaluations de ce semestre avec note pour cet étudiant et date blocage
|
||||||
|
et date blocage < FOREVER.
|
||||||
|
Si non vide, une note apparaitra dans le futur pour cet étudiant: il faut
|
||||||
|
donc interdire la saisie du jury.
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.now(scu.TIME_ZONE)
|
||||||
|
return (
|
||||||
|
Evaluation.query.filter(
|
||||||
|
Evaluation.blocked_until != None, # pylint: disable=C0121
|
||||||
|
Evaluation.blocked_until >= now,
|
||||||
|
)
|
||||||
|
.join(ModuleImpl)
|
||||||
|
.filter_by(formsemestre_id=formsemestre.id)
|
||||||
|
.join(ModuleImplInscription)
|
||||||
|
.filter_by(etudid=etud.id)
|
||||||
|
.join(NotesNotes)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EvaluationUEPoids(db.Model):
|
class EvaluationUEPoids(db.Model):
|
||||||
"""Poids des évaluations (BUT)
|
"""Poids des évaluations (BUT)
|
||||||
@ -531,7 +559,7 @@ def evaluation_enrich_dict(e: Evaluation, e_dict: dict):
|
|||||||
return e_dict
|
return e_dict
|
||||||
|
|
||||||
|
|
||||||
def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
def check_and_convert_evaluation_args(data: dict, moduleimpl: "ModuleImpl"):
|
||||||
"""Check coefficient, dates and duration, raises exception if invalid.
|
"""Check coefficient, dates and duration, raises exception if invalid.
|
||||||
Convert date and time strings to date and time objects.
|
Convert date and time strings to date and time objects.
|
||||||
|
|
||||||
@ -546,7 +574,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
|||||||
# --- evaluation_type
|
# --- evaluation_type
|
||||||
try:
|
try:
|
||||||
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
data["evaluation_type"] = int(data.get("evaluation_type", 0) or 0)
|
||||||
if not data["evaluation_type"] in VALID_EVALUATION_TYPES:
|
if not data["evaluation_type"] in Evaluation.VALID_EVALUATION_TYPES:
|
||||||
raise ScoValueError("invalid evaluation_type value")
|
raise ScoValueError("invalid evaluation_type value")
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ScoValueError("invalid evaluation_type value") from exc
|
raise ScoValueError("invalid evaluation_type value") from exc
|
||||||
@ -571,7 +599,7 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
|||||||
if coef < 0:
|
if coef < 0:
|
||||||
raise ScoValueError("invalid coefficient value (must be positive or null)")
|
raise ScoValueError("invalid coefficient value (must be positive or null)")
|
||||||
data["coefficient"] = coef
|
data["coefficient"] = coef
|
||||||
# --- date de l'évaluation
|
# --- date de l'évaluation dans le semestre ?
|
||||||
formsemestre = moduleimpl.formsemestre
|
formsemestre = moduleimpl.formsemestre
|
||||||
date_debut = data.get("date_debut", None)
|
date_debut = data.get("date_debut", None)
|
||||||
if date_debut:
|
if date_debut:
|
||||||
@ -612,6 +640,8 @@ def check_convert_evaluation_args(moduleimpl: "ModuleImpl", data: dict):
|
|||||||
"Heures de l'évaluation incohérentes !",
|
"Heures de l'évaluation incohérentes !",
|
||||||
dest_url="javascript:history.back();",
|
dest_url="javascript:history.back();",
|
||||||
)
|
)
|
||||||
|
if "blocked_until" in data:
|
||||||
|
data["blocked_until"] = data["blocked_until"] or None
|
||||||
|
|
||||||
|
|
||||||
def heure_to_time(heure: str) -> datetime.time:
|
def heure_to_time(heure: str) -> datetime.time:
|
||||||
@ -641,3 +671,6 @@ def _moduleimpl_evaluation_insert_before(
|
|||||||
db.session.add(e)
|
db.session.add(e)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
from app.models.moduleimpls import ModuleImpl, ModuleImplInscription
|
||||||
|
@ -93,6 +93,10 @@ class FormSemestre(db.Model):
|
|||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
"Si vrai, la moyenne générale indicative BUT n'est pas calculée"
|
||||||
|
mode_calcul_moyennes = db.Column(
|
||||||
|
db.Integer, nullable=False, default=0, server_default="0"
|
||||||
|
)
|
||||||
|
"pour usage futur"
|
||||||
gestion_semestrielle = db.Column(
|
gestion_semestrielle = db.Column(
|
||||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,12 @@ from flask import current_app, g
|
|||||||
from app import db
|
from app import db
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models.but_refcomp import ApcParcours, app_critiques_modules, parcours_modules
|
from app.models.but_refcomp import (
|
||||||
|
ApcParcours,
|
||||||
|
ApcReferentielCompetences,
|
||||||
|
app_critiques_modules,
|
||||||
|
parcours_modules,
|
||||||
|
)
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.codes_cursus import UE_SPORT
|
from app.scodoc.codes_cursus import UE_SPORT
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
@ -100,6 +105,33 @@ class Module(models.ScoDocModel):
|
|||||||
|
|
||||||
return args_dict
|
return args_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||||
|
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||||
|
Add 'id' to excluded."""
|
||||||
|
# on ne peut pas affecter directement parcours
|
||||||
|
return super().filter_model_attributes(data, (excluded or set()) | {"parcours"})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_dict(cls, data: dict) -> "Module":
|
||||||
|
"""Create from given dict, add parcours"""
|
||||||
|
mod = super().create_from_dict(data)
|
||||||
|
for p in data.get("parcours", []) or []:
|
||||||
|
if isinstance(p, ApcParcours):
|
||||||
|
parcour: ApcParcours = p
|
||||||
|
else:
|
||||||
|
pid = int(p)
|
||||||
|
query = ApcParcours.query.filter_by(id=pid)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(ApcReferentielCompetences).filter_by(
|
||||||
|
dept_id=g.scodoc_dept_id
|
||||||
|
)
|
||||||
|
parcour: ApcParcours = query.first()
|
||||||
|
if parcour is None:
|
||||||
|
raise ScoValueError("Parcours invalide")
|
||||||
|
mod.parcours.append(parcour)
|
||||||
|
return mod
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""Create a new copy of this module."""
|
"""Create a new copy of this module."""
|
||||||
mod = Module(
|
mod = Module(
|
||||||
|
@ -126,7 +126,7 @@ class ScolarFormSemestreValidation(db.Model):
|
|||||||
def ects(self) -> float:
|
def ects(self) -> float:
|
||||||
"Les ECTS acquis par cette validation. (0 si ce n'est pas une validation d'UE)"
|
"Les ECTS acquis par cette validation. (0 si ce n'est pas une validation d'UE)"
|
||||||
return (
|
return (
|
||||||
self.ue.ects
|
self.ue.ects or 0.0
|
||||||
if (self.ue is not None) and (self.code in CODES_UE_VALIDES)
|
if (self.ue is not None) and (self.code in CODES_UE_VALIDES)
|
||||||
else 0.0
|
else 0.0
|
||||||
)
|
)
|
||||||
|
@ -85,17 +85,6 @@ UE_ELECTIVE = 4 # UE "élective" dans certains cursus (UCAC?, ISCID)
|
|||||||
UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
|
UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
|
||||||
UE_OPTIONNELLE = 6 # UE non fondamentales (ILEPS, ...)
|
UE_OPTIONNELLE = 6 # UE non fondamentales (ILEPS, ...)
|
||||||
|
|
||||||
|
|
||||||
def ue_is_fondamentale(ue_type):
|
|
||||||
return ue_type in (UE_STANDARD, UE_STAGE_LP, UE_PROFESSIONNELLE)
|
|
||||||
|
|
||||||
|
|
||||||
def ue_is_professionnelle(ue_type):
|
|
||||||
return (
|
|
||||||
ue_type == UE_PROFESSIONNELLE
|
|
||||||
) # NB: les UE_PROFESSIONNELLE sont à la fois fondamentales et pro
|
|
||||||
|
|
||||||
|
|
||||||
UE_TYPE_NAME = {
|
UE_TYPE_NAME = {
|
||||||
UE_STANDARD: "Standard",
|
UE_STANDARD: "Standard",
|
||||||
UE_SPORT: "Sport/Culture (points bonus)",
|
UE_SPORT: "Sport/Culture (points bonus)",
|
||||||
@ -104,8 +93,6 @@ UE_TYPE_NAME = {
|
|||||||
UE_ELECTIVE: "Elective (ISCID)",
|
UE_ELECTIVE: "Elective (ISCID)",
|
||||||
UE_PROFESSIONNELLE: "Professionnelle (ISCID)",
|
UE_PROFESSIONNELLE: "Professionnelle (ISCID)",
|
||||||
UE_OPTIONNELLE: "Optionnelle",
|
UE_OPTIONNELLE: "Optionnelle",
|
||||||
# UE_FONDAMENTALE : '"Fondamentale" (eg UCAC)',
|
|
||||||
# UE_OPTIONNELLE : '"Optionnelle" (UCAC)'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Couleurs RGB (dans [0.,1.]) des UE pour les bulletins:
|
# Couleurs RGB (dans [0.,1.]) des UE pour les bulletins:
|
||||||
|
@ -186,7 +186,7 @@ def sidebar(etudid: int = None):
|
|||||||
formsemestre.date_fin.strftime("%d/%m/%Y")
|
formsemestre.date_fin.strftime("%d/%m/%Y")
|
||||||
}">({
|
}">({
|
||||||
sco_preferences.get_preference("assi_metrique", None)})
|
sco_preferences.get_preference("assi_metrique", None)})
|
||||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
<br>{nbabsjust:1.0f} J., {nbabsnj:1.0f} N.J.</span>"""
|
||||||
)
|
)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
if current_user.has_permission(Permission.AbsChange):
|
if current_user.has_permission(Permission.AbsChange):
|
||||||
|
@ -124,9 +124,9 @@ def table_billets(
|
|||||||
else:
|
else:
|
||||||
billet_dict["nomprenom"] = billet.etudiant.nomprenom
|
billet_dict["nomprenom"] = billet.etudiant.nomprenom
|
||||||
billet_dict["_nomprenom_order"] = billet.etudiant.sort_key
|
billet_dict["_nomprenom_order"] = billet.etudiant.sort_key
|
||||||
billet_dict[
|
billet_dict["_nomprenom_td_attrs"] = (
|
||||||
"_nomprenom_td_attrs"
|
f'id="{billet.etudiant.id}" class="etudinfo"'
|
||||||
] = f'id="{billet.etudiant.id}" class="etudinfo"'
|
)
|
||||||
if with_links:
|
if with_links:
|
||||||
billet_dict["_nomprenom_target"] = url_for(
|
billet_dict["_nomprenom_target"] = url_for(
|
||||||
"scolar.fiche_etud",
|
"scolar.fiche_etud",
|
||||||
|
@ -34,7 +34,7 @@ Il suffit d'appeler abs_notify() après chaque ajout d'absence.
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from flask import g, url_for
|
from flask import flash, g, url_for
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -46,7 +46,6 @@ from app.models.etudiants import Identite
|
|||||||
from app.models.events import Scolog
|
from app.models.events import Scolog
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc import sco_etud
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -283,10 +282,17 @@ def abs_notification_message(
|
|||||||
)
|
)
|
||||||
|
|
||||||
template = prefs["abs_notification_mail_tmpl"]
|
template = prefs["abs_notification_mail_tmpl"]
|
||||||
|
txt = ""
|
||||||
if template:
|
if template:
|
||||||
|
try:
|
||||||
txt = prefs["abs_notification_mail_tmpl"] % values
|
txt = prefs["abs_notification_mail_tmpl"] % values
|
||||||
|
except KeyError:
|
||||||
|
flash("Mail non envoyé: format invalide (voir paramétrage)")
|
||||||
|
log("abs_notification_message: invalid key in abs_notification_mail_tmpl")
|
||||||
|
txt = ""
|
||||||
else:
|
else:
|
||||||
log("abs_notification_message: empty template, not sending message")
|
log("abs_notification_message: empty template, not sending message")
|
||||||
|
if not txt:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
|
subject = f"""[ScoDoc] Trop d'absences pour {etud.nomprenom}"""
|
||||||
|
@ -55,7 +55,7 @@ from app.models import (
|
|||||||
ScoDocSiteConfig,
|
ScoDocSiteConfig,
|
||||||
)
|
)
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoTemporaryError
|
||||||
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_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
@ -318,7 +318,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
if nt.bonus_ues is not None:
|
if nt.bonus_ues is not None:
|
||||||
u["cur_moy_ue_txt"] += " (+ues)"
|
u["cur_moy_ue_txt"] += " (+ues)"
|
||||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||||
if ue_status["coef_ue"] != None:
|
if ue_status["coef_ue"] is not None:
|
||||||
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
u["coef_ue_txt"] = scu.fmt_coef(ue_status["coef_ue"])
|
||||||
else:
|
else:
|
||||||
u["coef_ue_txt"] = "-"
|
u["coef_ue_txt"] = "-"
|
||||||
@ -346,14 +346,14 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
# auparavant on filtrait les modules sans notes
|
# auparavant on filtrait les modules sans notes
|
||||||
# si ue_status['cur_moy_ue'] != 'NA' alors u['modules'] = [] (pas de moyenne => pas de modules)
|
# si ue_status['cur_moy_ue'] != 'NA' alors u['modules'] = [] (pas de moyenne => pas de modules)
|
||||||
|
|
||||||
u[
|
u["modules_capitalized"] = (
|
||||||
"modules_capitalized"
|
[]
|
||||||
] = [] # modules de l'UE capitalisée (liste vide si pas capitalisée)
|
) # modules de l'UE capitalisée (liste vide si pas capitalisée)
|
||||||
if ue_status["is_capitalized"] and ue_status["formsemestre_id"] is not None:
|
if ue_status["is_capitalized"] and ue_status["formsemestre_id"] is not None:
|
||||||
sem_origin = db.session.get(FormSemestre, ue_status["formsemestre_id"])
|
sem_origin = db.session.get(FormSemestre, ue_status["formsemestre_id"])
|
||||||
u[
|
u["ue_descr_txt"] = (
|
||||||
"ue_descr_txt"
|
f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
|
||||||
] = f'capitalisée le {ndb.DateISOtoDMY(ue_status["event_date"])}'
|
)
|
||||||
u["ue_descr_html"] = (
|
u["ue_descr_html"] = (
|
||||||
f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
|
f"""<a href="{ url_for( 'notes.formsemestre_bulletinetud',
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin.id, etudid=etudid)}"
|
scodoc_dept=g.scodoc_dept, formsemestre_id=sem_origin.id, etudid=etudid)}"
|
||||||
@ -558,6 +558,8 @@ def _ue_mod_bulletin(
|
|||||||
).order_by(Evaluation.numero, Evaluation.date_debut)
|
).order_by(Evaluation.numero, Evaluation.date_debut)
|
||||||
# (plus ancienne d'abord)
|
# (plus ancienne d'abord)
|
||||||
for e in all_evals:
|
for e in all_evals:
|
||||||
|
if e.is_blocked():
|
||||||
|
continue # ignore évaluations bloquées
|
||||||
if not e.visibulletin and version != "long":
|
if not e.visibulletin and version != "long":
|
||||||
continue
|
continue
|
||||||
is_complete = e.id in complete_eval_ids
|
is_complete = e.id in complete_eval_ids
|
||||||
@ -610,19 +612,22 @@ def _ue_mod_bulletin(
|
|||||||
e_dict["coef_txt"] = ""
|
e_dict["coef_txt"] = ""
|
||||||
else:
|
else:
|
||||||
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
|
e_dict["coef_txt"] = scu.fmt_coef(e.coefficient)
|
||||||
if e.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
if e.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||||
e_dict["coef_txt"] = "rat."
|
e_dict["coef_txt"] = "rat."
|
||||||
elif e.evaluation_type == scu.EVALUATION_SESSION2:
|
elif e.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||||
e_dict["coef_txt"] = "Ses. 2"
|
e_dict["coef_txt"] = "Ses. 2"
|
||||||
|
|
||||||
if modimpl_results.evaluations_etat[e.id].nb_attente:
|
if modimpl_results.evaluations_etat[e.id].nb_attente:
|
||||||
mod_attente = True # une eval en attente dans ce module
|
mod_attente = True # une eval en attente dans ce module
|
||||||
|
|
||||||
if ((not is_malus) or (val != "NP")) and (
|
if ((not is_malus) or (val != "NP")) and (
|
||||||
(e.evaluation_type == scu.EVALUATION_NORMALE or not np.isnan(val))
|
(
|
||||||
|
e.evaluation_type == Evaluation.EVALUATION_NORMALE
|
||||||
|
or not np.isnan(val)
|
||||||
|
)
|
||||||
):
|
):
|
||||||
# ne liste pas les eval malus sans notes
|
# ne liste pas les eval malus sans notes
|
||||||
# ni les rattrapages et sessions 2 si pas de note
|
# ni les rattrapages, sessions 2 et bonus si pas de note
|
||||||
if e.id in complete_eval_ids:
|
if e.id in complete_eval_ids:
|
||||||
mod["evaluations"].append(e_dict)
|
mod["evaluations"].append(e_dict)
|
||||||
else:
|
else:
|
||||||
@ -731,7 +736,11 @@ def etud_descr_situation_semestre(
|
|||||||
infos["refcomp_specialite_long"] = ""
|
infos["refcomp_specialite_long"] = ""
|
||||||
if formsemestre.formation.is_apc():
|
if formsemestre.formation.is_apc():
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
try:
|
||||||
parcour_id = res.etuds_parcour_id[etudid]
|
parcour_id = res.etuds_parcour_id[etudid]
|
||||||
|
except KeyError as exc:
|
||||||
|
log("sco_bulletins: ScoTemporaryError 240222")
|
||||||
|
raise ScoTemporaryError() from exc
|
||||||
parcour: ApcParcours = (
|
parcour: ApcParcours = (
|
||||||
db.session.get(ApcParcours, parcour_id) if parcour_id is not None else None
|
db.session.get(ApcParcours, parcour_id) if parcour_id is not None else None
|
||||||
)
|
)
|
||||||
|
@ -50,14 +50,11 @@ import traceback
|
|||||||
|
|
||||||
import reportlab
|
import reportlab
|
||||||
from reportlab.platypus import (
|
from reportlab.platypus import (
|
||||||
SimpleDocTemplate,
|
|
||||||
DocIf,
|
DocIf,
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Spacer,
|
|
||||||
Frame,
|
|
||||||
PageBreak,
|
PageBreak,
|
||||||
)
|
)
|
||||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
from reportlab.platypus import Table, KeepInFrame
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -213,7 +210,7 @@ class BulletinGenerator:
|
|||||||
story.append(PageBreak()) # insert page break at end
|
story.append(PageBreak()) # insert page break at end
|
||||||
|
|
||||||
return story
|
return story
|
||||||
else:
|
|
||||||
# Generation du document PDF
|
# Generation du document PDF
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
report = io.BytesIO() # in-memory document, no disk file
|
report = io.BytesIO() # in-memory document, no disk file
|
||||||
@ -221,8 +218,8 @@ class BulletinGenerator:
|
|||||||
document.addPageTemplates(
|
document.addPageTemplates(
|
||||||
sco_pdf.ScoDocPageTemplate(
|
sco_pdf.ScoDocPageTemplate(
|
||||||
document,
|
document,
|
||||||
author="%s %s (E. Viennet) [%s]"
|
author=f"""{sco_version.SCONAME} {
|
||||||
% (sco_version.SCONAME, sco_version.SCOVERSION, self.description),
|
sco_version.SCOVERSION} (E. Viennet) [{self.description}]""",
|
||||||
title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
|
title=f"""Bulletin {sem["titremois"]} de {etat_civil}""",
|
||||||
subject="Bulletin de note",
|
subject="Bulletin de note",
|
||||||
margins=self.margins,
|
margins=self.margins,
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Génération du bulletin en format JSON
|
"""Génération du bulletin en format JSON (formations classiques)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -62,10 +62,12 @@ from flask import g, request
|
|||||||
from app import log, ScoValueError
|
from app import log, ScoValueError
|
||||||
from app.comp.res_but import ResultatsSemestreBUT
|
from app.comp.res_but import ResultatsSemestreBUT
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import (
|
||||||
from app.scodoc import codes_cursus
|
codes_cursus,
|
||||||
from app.scodoc import sco_pdf
|
sco_cache,
|
||||||
from app.scodoc import sco_preferences
|
sco_pdf,
|
||||||
|
sco_preferences,
|
||||||
|
)
|
||||||
from app.scodoc.sco_logos import find_logo
|
from app.scodoc.sco_logos import find_logo
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
@ -111,7 +113,8 @@ def assemble_bulletins_pdf(
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def replacement_function(match):
|
def replacement_function(match) -> str:
|
||||||
|
"remplace logo par balise html img"
|
||||||
balise = match.group(1)
|
balise = match.group(1)
|
||||||
name = match.group(3)
|
name = match.group(3)
|
||||||
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||||
@ -211,7 +214,11 @@ def process_field(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
def get_formsemestre_bulletins_pdf(
|
||||||
|
formsemestre_id,
|
||||||
|
version="selectedevals",
|
||||||
|
groups_infos=None, # si indiqué, ne prend que ces groupes
|
||||||
|
):
|
||||||
"Document pdf avec tous les bulletins du semestre, et filename"
|
"Document pdf avec tous les bulletins du semestre, et filename"
|
||||||
from app.but import bulletin_but_court
|
from app.but import bulletin_but_court
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
@ -226,13 +233,22 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
|||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"get_formsemestre_bulletins_pdf: version de bulletin demandée invalide !"
|
"get_formsemestre_bulletins_pdf: version de bulletin demandée invalide !"
|
||||||
)
|
)
|
||||||
cached = sco_cache.SemBulletinsPDFCache.get(str(formsemestre_id) + "_" + version)
|
|
||||||
|
etuds = formsemestre.get_inscrits(include_demdef=True, order=True)
|
||||||
|
if groups_infos is None:
|
||||||
|
gr_key = ""
|
||||||
|
else:
|
||||||
|
etudids = {m["etudid"] for m in groups_infos.members}
|
||||||
|
etuds = [etud for etud in etuds if etud.id in etudids]
|
||||||
|
gr_key = groups_infos.get_groups_key()
|
||||||
|
|
||||||
|
cache_key = str(formsemestre_id) + "_" + version + "_" + gr_key
|
||||||
|
cached = sco_cache.SemBulletinsPDFCache.get(cache_key)
|
||||||
if cached:
|
if cached:
|
||||||
return cached[1], cached[0]
|
return cached[1], cached[0]
|
||||||
fragments = []
|
fragments = []
|
||||||
# Make each bulletin
|
# Make each bulletin
|
||||||
|
for etud in etuds:
|
||||||
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
|
|
||||||
if version == "butcourt":
|
if version == "butcourt":
|
||||||
frag = bulletin_but_court.bulletin_but_court_pdf_frag(etud, formsemestre)
|
frag = bulletin_but_court.bulletin_but_court_pdf_frag(etud, formsemestre)
|
||||||
else:
|
else:
|
||||||
@ -262,7 +278,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
|||||||
sco_pdf.PDFLOCK.release()
|
sco_pdf.PDFLOCK.release()
|
||||||
#
|
#
|
||||||
date_iso = time.strftime("%Y-%m-%d")
|
date_iso = time.strftime("%Y-%m-%d")
|
||||||
filename = "bul-%s-%s.pdf" % (formsemestre.titre_num(), date_iso)
|
filename = f"bul-{formsemestre.titre_num()}-{date_iso}.pdf"
|
||||||
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
|
filename = scu.unescape_html(filename).replace(" ", "_").replace("&", "")
|
||||||
# fill cache
|
# fill cache
|
||||||
sco_cache.SemBulletinsPDFCache.set(
|
sco_cache.SemBulletinsPDFCache.set(
|
||||||
|
@ -51,7 +51,7 @@ from reportlab.lib.colors import Color, blue
|
|||||||
from reportlab.lib.units import cm, mm
|
from reportlab.lib.units import cm, mm
|
||||||
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
||||||
|
|
||||||
from app.models import BulAppreciations
|
from app.models import BulAppreciations, Evaluation
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import (
|
from app.scodoc import (
|
||||||
gen_tables,
|
gen_tables,
|
||||||
@ -715,9 +715,15 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
eval_style = ""
|
eval_style = ""
|
||||||
t = {
|
t = {
|
||||||
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
"module": '<bullet indent="2mm">•</bullet> ' + e["name"],
|
||||||
"coef": ("<i>" + e["coef_txt"] + "</i>")
|
"coef": (
|
||||||
|
(
|
||||||
|
f"<i>{e['coef_txt']}</i>"
|
||||||
|
if e["evaluation_type"] != Evaluation.EVALUATION_BONUS
|
||||||
|
else "bonus"
|
||||||
|
)
|
||||||
if prefs["bul_show_coef"]
|
if prefs["bul_show_coef"]
|
||||||
else "",
|
else ""
|
||||||
|
),
|
||||||
"_hidden": hidden,
|
"_hidden": hidden,
|
||||||
"_module_target": e["target_html"],
|
"_module_target": e["target_html"],
|
||||||
# '_module_help' : ,
|
# '_module_help' : ,
|
||||||
|
@ -67,7 +67,7 @@ class ScoDocCache:
|
|||||||
keys are prefixed by the current departement: g.scodoc_dept MUST be set.
|
keys are prefixed by the current departement: g.scodoc_dept MUST be set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeout = None # ttl, infinite by default
|
timeout = 3600 # ttl, one hour by default
|
||||||
prefix = ""
|
prefix = ""
|
||||||
verbose = False # if true, verbose logging (debug)
|
verbose = False # if true, verbose logging (debug)
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ class AbsSemEtudCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "ABSE"
|
prefix = "ABSE"
|
||||||
timeout = 60 * 60 # ttl 60 minutes
|
timeout = 600 # ttl 10 minutes
|
||||||
|
|
||||||
|
|
||||||
class SemBulletinsPDFCache(ScoDocCache):
|
class SemBulletinsPDFCache(ScoDocCache):
|
||||||
@ -233,7 +233,6 @@ class SemInscriptionsCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "SI"
|
prefix = "SI"
|
||||||
duration = 12 * 60 * 60 # ttl 12h
|
|
||||||
|
|
||||||
|
|
||||||
class TableRecapCache(ScoDocCache):
|
class TableRecapCache(ScoDocCache):
|
||||||
@ -243,7 +242,6 @@ class TableRecapCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RECAP"
|
prefix = "RECAP"
|
||||||
duration = 12 * 60 * 60 # ttl 12h
|
|
||||||
|
|
||||||
|
|
||||||
class TableRecapWithEvalsCache(ScoDocCache):
|
class TableRecapWithEvalsCache(ScoDocCache):
|
||||||
@ -253,7 +251,6 @@ class TableRecapWithEvalsCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RECAPWITHEVALS"
|
prefix = "RECAPWITHEVALS"
|
||||||
duration = 12 * 60 * 60 # ttl 12h
|
|
||||||
|
|
||||||
|
|
||||||
class TableJuryCache(ScoDocCache):
|
class TableJuryCache(ScoDocCache):
|
||||||
@ -263,7 +260,6 @@ class TableJuryCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RECAPJURY"
|
prefix = "RECAPJURY"
|
||||||
duration = 12 * 60 * 60 # ttl 12h
|
|
||||||
|
|
||||||
|
|
||||||
class TableJuryWithEvalsCache(ScoDocCache):
|
class TableJuryWithEvalsCache(ScoDocCache):
|
||||||
@ -273,7 +269,6 @@ class TableJuryWithEvalsCache(ScoDocCache):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prefix = "RECAPJURYWITHEVALS"
|
prefix = "RECAPJURYWITHEVALS"
|
||||||
duration = 12 * 60 * 60 # ttl 12h
|
|
||||||
|
|
||||||
|
|
||||||
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
def invalidate_formsemestre( # was inval_cache(formsemestre_id=None, pdfonly=False)
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
from flask.templating import render_template
|
from flask.templating import render_template
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.but import apc_edit_ue
|
|
||||||
from app.models import UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
from app.models import UniteEns, Matiere, Module, FormSemestre, ModuleImpl
|
||||||
from app.models.validations import ScolarFormSemestreValidation
|
from app.models.validations import ScolarFormSemestreValidation
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
@ -48,6 +47,8 @@ def html_edit_formation_apc(
|
|||||||
- Les ressources
|
- Les ressources
|
||||||
- Les SAÉs
|
- Les SAÉs
|
||||||
"""
|
"""
|
||||||
|
from app.but import cursus_but
|
||||||
|
|
||||||
cursus = formation.get_cursus()
|
cursus = formation.get_cursus()
|
||||||
assert cursus.APC_SAE
|
assert cursus.APC_SAE
|
||||||
|
|
||||||
@ -101,18 +102,26 @@ def html_edit_formation_apc(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html_ue_warning = {
|
||||||
|
semestre_idx: cursus_but.formation_semestre_niveaux_warning(
|
||||||
|
formation, semestre_idx
|
||||||
|
)
|
||||||
|
for semestre_idx in semestre_ids
|
||||||
|
}
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
render_template(
|
render_template(
|
||||||
"pn/form_ues.j2",
|
"pn/form_ues.j2",
|
||||||
formation=formation,
|
|
||||||
semestre_ids=semestre_ids,
|
|
||||||
editable=editable,
|
|
||||||
tag_editable=tag_editable,
|
|
||||||
icons=icons,
|
|
||||||
ues_by_sem=ues_by_sem,
|
|
||||||
ects_by_sem=ects_by_sem,
|
|
||||||
scu=scu,
|
|
||||||
codes_cursus=codes_cursus,
|
codes_cursus=codes_cursus,
|
||||||
|
ects_by_sem=ects_by_sem,
|
||||||
|
editable=editable,
|
||||||
|
formation=formation,
|
||||||
|
html_ue_warning=html_ue_warning,
|
||||||
|
icons=icons,
|
||||||
|
scu=scu,
|
||||||
|
semestre_ids=semestre_ids,
|
||||||
|
tag_editable=tag_editable,
|
||||||
|
ues_by_sem=ues_by_sem,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
for semestre_idx in semestre_ids:
|
for semestre_idx in semestre_ids:
|
||||||
|
@ -682,8 +682,11 @@ def module_edit(
|
|||||||
"input_type": "checkbox",
|
"input_type": "checkbox",
|
||||||
"vertical": True,
|
"vertical": True,
|
||||||
"dom_id": "tf_module_parcours",
|
"dom_id": "tf_module_parcours",
|
||||||
"labels": [parcour.libelle for parcour in ref_comp.parcours]
|
"labels": [
|
||||||
+ ["Tous (tronc commun)"],
|
f" {parcour.libelle} (<b>{parcour.code}</b>)"
|
||||||
|
for parcour in ref_comp.parcours
|
||||||
|
]
|
||||||
|
+ [" Tous (tronc commun)"],
|
||||||
"allowed_values": [
|
"allowed_values": [
|
||||||
str(parcour.id) for parcour in ref_comp.parcours
|
str(parcour.id) for parcour in ref_comp.parcours
|
||||||
]
|
]
|
||||||
|
@ -892,7 +892,9 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
|||||||
<form>
|
<form>
|
||||||
<input type="checkbox" class="sco_tag_checkbox"
|
<input type="checkbox" class="sco_tag_checkbox"
|
||||||
{'checked' if show_tags else ''}
|
{'checked' if show_tags else ''}
|
||||||
> Montrer les tags des modules voire en ajouter <i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i></input>
|
> Montrer les tags des modules voire en ajouter
|
||||||
|
<i>(ceux correspondant aux titres des compétences étant ajoutés par défaut)</i>
|
||||||
|
</input>
|
||||||
</form>
|
</form>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -31,96 +31,15 @@
|
|||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
|
||||||
from app.models import Evaluation
|
from app.models import Evaluation
|
||||||
from app.models.evaluations import check_convert_evaluation_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 AccessDenied
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
|
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_moduleimpl
|
|
||||||
|
|
||||||
|
|
||||||
_evaluationEditor = ndb.EditableTable(
|
|
||||||
"notes_evaluation",
|
|
||||||
"evaluation_id",
|
|
||||||
(
|
|
||||||
"evaluation_id",
|
|
||||||
"moduleimpl_id",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"description",
|
|
||||||
"note_max",
|
|
||||||
"coefficient",
|
|
||||||
"visibulletin",
|
|
||||||
"publish_incomplete",
|
|
||||||
"evaluation_type",
|
|
||||||
"numero",
|
|
||||||
),
|
|
||||||
sortkey="numero, date_debut desc", # plus recente d'abord
|
|
||||||
output_formators={
|
|
||||||
"numero": ndb.int_null_is_zero,
|
|
||||||
},
|
|
||||||
input_formators={
|
|
||||||
"visibulletin": bool,
|
|
||||||
"publish_incomplete": bool,
|
|
||||||
"evaluation_type": int,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_evaluations_dict(args: dict) -> list[dict]:
|
|
||||||
"""Liste evaluations, triées numero (or most recent date first).
|
|
||||||
Fonction de transition pour ancien code ScoDoc7.
|
|
||||||
|
|
||||||
Ajoute les champs:
|
|
||||||
'duree' : '2h30'
|
|
||||||
'matin' : 1 (commence avant 12:00) ou 0
|
|
||||||
'apresmidi' : 1 (termine après 12:00) ou 0
|
|
||||||
'descrheure' : ' de 15h00 à 16h30'
|
|
||||||
"""
|
|
||||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
|
||||||
return [
|
|
||||||
e.to_dict()
|
|
||||||
for e in Evaluation.query.filter_by(**args).order_by(
|
|
||||||
sa.desc(Evaluation.numero), sa.desc(Evaluation.date_debut)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
|
||||||
"list evaluations in this formsemestre"
|
|
||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
|
||||||
evals = []
|
|
||||||
for modimpl in mods:
|
|
||||||
evals += get_evaluations_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
|
||||||
return evals
|
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_edit(args):
|
|
||||||
"edit an evaluation"
|
|
||||||
evaluation_id = args["evaluation_id"]
|
|
||||||
evaluation: Evaluation = db.session.get(Evaluation, evaluation_id)
|
|
||||||
if evaluation is None:
|
|
||||||
raise ValueError("evaluation inexistante !")
|
|
||||||
|
|
||||||
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
|
||||||
raise AccessDenied(
|
|
||||||
f"Modification évaluation impossible pour {current_user.get_nomplogin()}"
|
|
||||||
)
|
|
||||||
args["moduleimpl_id"] = evaluation.moduleimpl.id
|
|
||||||
check_convert_evaluation_args(evaluation.moduleimpl, args)
|
|
||||||
|
|
||||||
cnx = ndb.GetDBConnexion()
|
|
||||||
_evaluationEditor.edit(cnx, args)
|
|
||||||
# inval cache pour ce semestre
|
|
||||||
sco_cache.invalidate_formsemestre(
|
|
||||||
formsemestre_id=evaluation.moduleimpl.formsemestre_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ancien _notes_getall
|
# ancien _notes_getall
|
||||||
|
@ -31,14 +31,12 @@ import datetime
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, render_template
|
from flask import g, render_template, request, url_for
|
||||||
from flask import g
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask import request
|
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Evaluation, Module, ModuleImpl
|
from app.models import Evaluation, Module, ModuleImpl
|
||||||
from app.models.evaluations import heure_to_time
|
from app.models.evaluations import heure_to_time, check_and_convert_evaluation_args
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
@ -108,7 +106,7 @@ def evaluation_create_form(
|
|||||||
raise ValueError("missing evaluation_id parameter")
|
raise ValueError("missing evaluation_id parameter")
|
||||||
initvalues = evaluation.to_dict()
|
initvalues = evaluation.to_dict()
|
||||||
moduleimpl_id = initvalues["moduleimpl_id"]
|
moduleimpl_id = initvalues["moduleimpl_id"]
|
||||||
submitlabel = "Modifier les données"
|
submitlabel = "Modifier l'évaluation"
|
||||||
action = "Modification d'une évaluation"
|
action = "Modification d'une évaluation"
|
||||||
link = ""
|
link = ""
|
||||||
# Note maximale actuelle dans cette éval ?
|
# Note maximale actuelle dans cette éval ?
|
||||||
@ -142,6 +140,15 @@ def evaluation_create_form(
|
|||||||
else:
|
else:
|
||||||
poids = 0.0
|
poids = 0.0
|
||||||
initvalues[f"poids_{ue.id}"] = poids
|
initvalues[f"poids_{ue.id}"] = poids
|
||||||
|
# Blocage
|
||||||
|
if edit:
|
||||||
|
initvalues["blocked"] = evaluation.is_blocked()
|
||||||
|
initvalues["blocked_until"] = (
|
||||||
|
evaluation.blocked_until.strftime("%d/%m/%Y")
|
||||||
|
if evaluation.blocked_until
|
||||||
|
and evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
|
||||||
|
else ""
|
||||||
|
)
|
||||||
#
|
#
|
||||||
form = [
|
form = [
|
||||||
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
("evaluation_id", {"default": evaluation_id, "input_type": "hidden"}),
|
||||||
@ -183,7 +190,8 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"size": 6,
|
"size": 6,
|
||||||
"type": "float", # peut être négatif (!)
|
"type": "float", # peut être négatif (!)
|
||||||
"explanation": "coef. dans le module (choisi librement par l'enseignant, non utilisé pour rattrapage et 2ème session)",
|
"explanation": """coef. dans le module (choisi librement par
|
||||||
|
l'enseignant, non utilisé pour rattrapage, 2ème session et bonus)""",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -195,7 +203,7 @@ def evaluation_create_form(
|
|||||||
"size": 4,
|
"size": 4,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"title": "Notes de 0 à",
|
"title": "Notes de 0 à",
|
||||||
"explanation": f"barème (note max actuelle: {min_note_max_str})",
|
"explanation": f"""barème (note max actuelle: {min_note_max_str}).""",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
"max_value": scu.NOTES_MAX,
|
"max_value": scu.NOTES_MAX,
|
||||||
"min_value": min_note_max,
|
"min_value": min_note_max,
|
||||||
@ -206,7 +214,8 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"size": 36,
|
"size": 36,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"explanation": """type d'évaluation, apparait sur le bulletins longs. Exemples: "contrôle court", "examen de TP", "examen final".""",
|
"explanation": """type d'évaluation, apparait sur le bulletins longs.
|
||||||
|
Exemples: "contrôle court", "examen de TP", "examen final".""",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -230,16 +239,20 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
"title": "Modalité",
|
"title": "Modalité",
|
||||||
"allowed_values": (
|
"allowed_values": Evaluation.VALID_EVALUATION_TYPES,
|
||||||
scu.EVALUATION_NORMALE,
|
|
||||||
scu.EVALUATION_RATTRAPAGE,
|
|
||||||
scu.EVALUATION_SESSION2,
|
|
||||||
),
|
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"labels": (
|
"labels": (
|
||||||
"Normale",
|
"Normale",
|
||||||
"Rattrapage (remplace si meilleure note)",
|
"Rattrapage (remplace si meilleure note)",
|
||||||
"Deuxième session (remplace toujours)",
|
"Deuxième session (remplace toujours)",
|
||||||
|
(
|
||||||
|
"Bonus "
|
||||||
|
+ (
|
||||||
|
"(pondéré par poids et ajouté aux moyennes de ce module)"
|
||||||
|
if is_apc
|
||||||
|
else "(ajouté à la moyenne de ce module)"
|
||||||
|
)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -251,8 +264,10 @@ def evaluation_create_form(
|
|||||||
{
|
{
|
||||||
"size": 6,
|
"size": 6,
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"explanation": "importance de l'évaluation (multiplie les poids ci-dessous)",
|
"explanation": """importance de l'évaluation (multiplie les poids ci-dessous).
|
||||||
|
Non utilisé pour les bonus.""",
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
|
"dom_id": "evaluation-edit-coef",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -294,6 +309,28 @@ def evaluation_create_form(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
# Bloquage / date prise en compte
|
||||||
|
form += [
|
||||||
|
(
|
||||||
|
"blocked",
|
||||||
|
{
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"title": "Bloquer la prise en compte",
|
||||||
|
"explanation": """empêche la prise en compte
|
||||||
|
(ne sera pas visible sur les bulletins ni dans les tableaux)""",
|
||||||
|
"dom_id": "evaluation-edit-blocked",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"blocked_until",
|
||||||
|
{
|
||||||
|
"input_type": "datedmy",
|
||||||
|
"title": "Date déblocage",
|
||||||
|
"size": 12,
|
||||||
|
"explanation": "sera débloquée à partir de cette date",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
request.base_url,
|
request.base_url,
|
||||||
vals,
|
vals,
|
||||||
@ -324,7 +361,9 @@ def evaluation_create_form(
|
|||||||
+ "\n".join(H)
|
+ "\n".join(H)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ tf[1]
|
+ tf[1]
|
||||||
+ render_template("scodoc/help/evaluations.j2", is_apc=is_apc)
|
+ render_template(
|
||||||
|
"scodoc/help/evaluations.j2", is_apc=is_apc, modimpl=modimpl
|
||||||
|
)
|
||||||
+ render_template("sco_timepicker.j2")
|
+ render_template("sco_timepicker.j2")
|
||||||
+ html_sco_header.sco_footer()
|
+ html_sco_header.sco_footer()
|
||||||
)
|
)
|
||||||
@ -342,6 +381,8 @@ def evaluation_create_form(
|
|||||||
raise ScoValueError("Date (j/m/a) invalide") from exc
|
raise ScoValueError("Date (j/m/a) invalide") from exc
|
||||||
else:
|
else:
|
||||||
date_debut = None
|
date_debut = None
|
||||||
|
args["date_debut"] = date_debut
|
||||||
|
args["date_fin"] = date_debut # même jour
|
||||||
args.pop("jour", None)
|
args.pop("jour", None)
|
||||||
if date_debut and args.get("heure_debut"):
|
if date_debut and args.get("heure_debut"):
|
||||||
try:
|
try:
|
||||||
@ -350,7 +391,8 @@ def evaluation_create_form(
|
|||||||
raise ScoValueError("Heure début invalide") from exc
|
raise ScoValueError("Heure début invalide") from exc
|
||||||
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
|
args["date_debut"] = datetime.datetime.combine(date_debut, heure_debut)
|
||||||
args.pop("heure_debut", None)
|
args.pop("heure_debut", None)
|
||||||
# note: ce formulaire ne permet de créer que des évaluation avec debut et fin sur le même jour.
|
# note: ce formulaire ne permet de créer que des évaluations
|
||||||
|
# avec debut et fin sur le même jour.
|
||||||
if date_debut and args.get("heure_fin"):
|
if date_debut and args.get("heure_fin"):
|
||||||
try:
|
try:
|
||||||
heure_fin = heure_to_time(args["heure_fin"])
|
heure_fin = heure_to_time(args["heure_fin"])
|
||||||
@ -358,8 +400,22 @@ def evaluation_create_form(
|
|||||||
raise ScoValueError("Heure fin invalide") from exc
|
raise ScoValueError("Heure fin invalide") from exc
|
||||||
args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
|
args["date_fin"] = datetime.datetime.combine(date_debut, heure_fin)
|
||||||
args.pop("heure_fin", None)
|
args.pop("heure_fin", None)
|
||||||
|
# Blocage:
|
||||||
|
if args.get("blocked"):
|
||||||
|
if args.get("blocked_until"):
|
||||||
|
try:
|
||||||
|
args["blocked_until"] = datetime.datetime.strptime(
|
||||||
|
args["blocked_until"], "%d/%m/%Y"
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Date déblocage (j/m/a) invalide") from exc
|
||||||
|
else: # bloquage coché sans date
|
||||||
|
args["blocked_until"] = Evaluation.BLOCKED_FOREVER
|
||||||
|
else: # si pas coché, efface date déblocage
|
||||||
|
args["blocked_until"] = None
|
||||||
#
|
#
|
||||||
if edit:
|
if edit:
|
||||||
|
check_and_convert_evaluation_args(args, modimpl)
|
||||||
evaluation.from_dict(args)
|
evaluation.from_dict(args)
|
||||||
else:
|
else:
|
||||||
# création d'une evaluation
|
# création d'une evaluation
|
||||||
|
@ -40,16 +40,14 @@ from app import db
|
|||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import Evaluation, FormSemestre, ModuleImpl
|
from app.models import Evaluation, FormSemestre, ModuleImpl, Module
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc.sco_utils import ModuleType
|
from app.scodoc.sco_utils import ModuleType
|
||||||
import app.scodoc.notesdb as ndb
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_cal
|
from app.scodoc import sco_cal
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_edit_module
|
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
@ -114,9 +112,10 @@ def do_evaluation_etat(
|
|||||||
nb_neutre,
|
nb_neutre,
|
||||||
nb_att,
|
nb_att,
|
||||||
moy, median, mini, maxi : # notes, en chaine, sur 20
|
moy, median, mini, maxi : # notes, en chaine, sur 20
|
||||||
last_modif: datetime,
|
maxi_num : note max, numérique
|
||||||
|
last_modif: datetime, *
|
||||||
gr_complets, gr_incomplets,
|
gr_complets, gr_incomplets,
|
||||||
evalcomplete
|
evalcomplete *
|
||||||
}
|
}
|
||||||
evalcomplete est vrai si l'eval est complete (tous les inscrits
|
evalcomplete est vrai si l'eval est complete (tous les inscrits
|
||||||
à ce module ont des notes)
|
à ce module ont des notes)
|
||||||
@ -130,11 +129,12 @@ def do_evaluation_etat(
|
|||||||
) # { etudid : note }
|
) # { etudid : note }
|
||||||
|
|
||||||
# ---- Liste des groupes complets et incomplets
|
# ---- Liste des groupes complets et incomplets
|
||||||
E = sco_evaluation_db.get_evaluations_dict(args={"evaluation_id": evaluation_id})[0]
|
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
modimpl: ModuleImpl = evaluation.moduleimpl
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
module: Module = modimpl.module
|
||||||
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
|
||||||
formsemestre_id = M["formsemestre_id"]
|
is_malus = module.module_type == ModuleType.MALUS # True si module de malus
|
||||||
|
formsemestre_id = modimpl.formsemestre_id
|
||||||
# Si partition_id is None, prend 'all' ou bien la premiere:
|
# Si partition_id is None, prend 'all' ou bien la premiere:
|
||||||
if partition_id is None:
|
if partition_id is None:
|
||||||
if select_first_partition:
|
if select_first_partition:
|
||||||
@ -150,9 +150,7 @@ def do_evaluation_etat(
|
|||||||
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
||||||
formsemestre_id
|
formsemestre_id
|
||||||
)
|
)
|
||||||
insmod = sco_moduleimpl.do_moduleimpl_inscription_list(
|
insmod = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=modimpl.id)
|
||||||
moduleimpl_id=E["moduleimpl_id"]
|
|
||||||
)
|
|
||||||
insmodset = {x["etudid"] for x in insmod}
|
insmodset = {x["etudid"] for x in insmod}
|
||||||
# retire de insem ceux qui ne sont pas inscrits au module
|
# retire de insem ceux qui ne sont pas inscrits au module
|
||||||
ins = [i for i in insem if i["etudid"] in insmodset]
|
ins = [i for i in insem if i["etudid"] in insmodset]
|
||||||
@ -175,9 +173,9 @@ def do_evaluation_etat(
|
|||||||
maxi_num = None
|
maxi_num = None
|
||||||
else:
|
else:
|
||||||
median = scu.fmt_note(median_num)
|
median = scu.fmt_note(median_num)
|
||||||
moy = scu.fmt_note(moy_num, E["note_max"])
|
moy = scu.fmt_note(moy_num, evaluation.note_max)
|
||||||
mini = scu.fmt_note(mini_num, E["note_max"])
|
mini = scu.fmt_note(mini_num, evaluation.note_max)
|
||||||
maxi = scu.fmt_note(maxi_num, E["note_max"])
|
maxi = scu.fmt_note(maxi_num, evaluation.note_max)
|
||||||
# cherche date derniere modif note
|
# cherche date derniere modif note
|
||||||
if len(etuds_notes_dict):
|
if len(etuds_notes_dict):
|
||||||
t = [x["date"] for x in etuds_notes_dict.values()]
|
t = [x["date"] for x in etuds_notes_dict.values()]
|
||||||
@ -218,25 +216,17 @@ def do_evaluation_etat(
|
|||||||
|
|
||||||
gr_incomplets = list(group_nb_missing.keys())
|
gr_incomplets = list(group_nb_missing.keys())
|
||||||
gr_incomplets.sort()
|
gr_incomplets.sort()
|
||||||
if (
|
|
||||||
(total_nb_missing > 0)
|
|
||||||
and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE)
|
|
||||||
and (E["evaluation_type"] != scu.EVALUATION_SESSION2)
|
|
||||||
):
|
|
||||||
complete = False
|
|
||||||
else:
|
|
||||||
complete = True
|
|
||||||
|
|
||||||
complete = (
|
complete = (
|
||||||
(total_nb_missing == 0)
|
(total_nb_missing == 0)
|
||||||
or (E["evaluation_type"] == scu.EVALUATION_RATTRAPAGE)
|
or (evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||||
or (E["evaluation_type"] == scu.EVALUATION_SESSION2)
|
and not evaluation.is_blocked()
|
||||||
)
|
)
|
||||||
evalattente = (total_nb_missing > 0) and (
|
evalattente = (total_nb_missing > 0) and (
|
||||||
(total_nb_missing == total_nb_att) or E["publish_incomplete"]
|
(total_nb_missing == total_nb_att) or evaluation.publish_incomplete
|
||||||
)
|
)
|
||||||
# mais ne met pas en attente les evals immediates sans aucune notes:
|
# mais ne met pas en attente les evals immediates sans aucune notes:
|
||||||
if E["publish_incomplete"] and nb_notes == 0:
|
if evaluation.publish_incomplete and nb_notes == 0:
|
||||||
evalattente = False
|
evalattente = False
|
||||||
|
|
||||||
# Calcul moyenne dans chaque groupe de TD
|
# Calcul moyenne dans chaque groupe de TD
|
||||||
@ -247,10 +237,10 @@ def do_evaluation_etat(
|
|||||||
{
|
{
|
||||||
"group_id": group_id,
|
"group_id": group_id,
|
||||||
"group_name": group_by_id[group_id]["group_name"],
|
"group_name": group_by_id[group_id]["group_name"],
|
||||||
"gr_moy": scu.fmt_note(gr_moy, E["note_max"]),
|
"gr_moy": scu.fmt_note(gr_moy, evaluation.note_max),
|
||||||
"gr_median": scu.fmt_note(gr_median, E["note_max"]),
|
"gr_median": scu.fmt_note(gr_median, evaluation.note_max),
|
||||||
"gr_mini": scu.fmt_note(gr_mini, E["note_max"]),
|
"gr_mini": scu.fmt_note(gr_mini, evaluation.note_max),
|
||||||
"gr_maxi": scu.fmt_note(gr_maxi, E["note_max"]),
|
"gr_maxi": scu.fmt_note(gr_maxi, evaluation.note_max),
|
||||||
"gr_nb_notes": len(notes),
|
"gr_nb_notes": len(notes),
|
||||||
"gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]),
|
"gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]),
|
||||||
}
|
}
|
||||||
@ -283,8 +273,9 @@ def do_evaluation_etat(
|
|||||||
def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
||||||
"""Synthétise les états d'une liste d'évaluations
|
"""Synthétise les états d'une liste d'évaluations
|
||||||
evals: list of mappings (etats),
|
evals: list of mappings (etats),
|
||||||
utilise e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
utilise e["blocked"], e["etat"]["evalcomplete"], e["etat"]["nb_notes"], e["etat"]["last_modif"]
|
||||||
->
|
->
|
||||||
|
nb_evals : nb total qcq soit état
|
||||||
nb_eval_completes (= prises en compte)
|
nb_eval_completes (= prises en compte)
|
||||||
nb_evals_en_cours (= avec des notes, mais pas complete)
|
nb_evals_en_cours (= avec des notes, mais pas complete)
|
||||||
nb_evals_vides (= sans aucune note)
|
nb_evals_vides (= sans aucune note)
|
||||||
@ -292,14 +283,16 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
|||||||
|
|
||||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||||
"""
|
"""
|
||||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides = 0, 0, 0
|
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, nb_evals_blocked = 0, 0, 0, 0
|
||||||
dates = []
|
dates = []
|
||||||
for e in etat_evals:
|
for e in etat_evals:
|
||||||
|
if e["etat"]["blocked"]:
|
||||||
|
nb_evals_blocked += 1
|
||||||
if e["etat"]["evalcomplete"]:
|
if e["etat"]["evalcomplete"]:
|
||||||
nb_evals_completes += 1
|
nb_evals_completes += 1
|
||||||
elif e["etat"]["nb_notes"] == 0:
|
elif e["etat"]["nb_notes"] == 0:
|
||||||
nb_evals_vides += 1
|
nb_evals_vides += 1
|
||||||
else:
|
elif not e["etat"]["blocked"]:
|
||||||
nb_evals_en_cours += 1
|
nb_evals_en_cours += 1
|
||||||
last_modif = e["etat"]["last_modif"]
|
last_modif = e["etat"]["last_modif"]
|
||||||
if last_modif is not None:
|
if last_modif is not None:
|
||||||
@ -309,6 +302,8 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
|||||||
last_modif = sorted(dates)[-1] if dates else ""
|
last_modif = sorted(dates)[-1] if dates else ""
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"nb_evals": len(etat_evals),
|
||||||
|
"nb_evals_blocked": nb_evals_blocked,
|
||||||
"nb_evals_completes": nb_evals_completes,
|
"nb_evals_completes": nb_evals_completes,
|
||||||
"nb_evals_en_cours": nb_evals_en_cours,
|
"nb_evals_en_cours": nb_evals_en_cours,
|
||||||
"nb_evals_vides": nb_evals_vides,
|
"nb_evals_vides": nb_evals_vides,
|
||||||
@ -499,13 +494,14 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
|||||||
"""Experimental: un tableau indiquant pour chaque évaluation
|
"""Experimental: un tableau indiquant pour chaque évaluation
|
||||||
le nombre de jours avant la publication des notes.
|
le nombre de jours avant la publication des notes.
|
||||||
|
|
||||||
N'indique pas les évaluations de rattrapage ni celles des modules de bonus/malus.
|
N'indique que les évaluations "normales" (pas rattrapage, ni bonus, ni session2,
|
||||||
|
ni celles des modules de bonus/malus).
|
||||||
"""
|
"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
evaluations = formsemestre.get_evaluations()
|
evaluations = formsemestre.get_evaluations()
|
||||||
rows = []
|
rows = []
|
||||||
for e in evaluations:
|
for e in evaluations:
|
||||||
if (e.evaluation_type != scu.EVALUATION_NORMALE) or (
|
if (e.evaluation_type != Evaluation.EVALUATION_NORMALE) or (
|
||||||
e.moduleimpl.module.module_type == ModuleType.MALUS
|
e.moduleimpl.module.module_type == ModuleType.MALUS
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
@ -519,9 +515,9 @@ def formsemestre_evaluations_delai_correction(formsemestre_id, fmt="html"):
|
|||||||
{
|
{
|
||||||
"date_first_complete": date_first_complete,
|
"date_first_complete": date_first_complete,
|
||||||
"delai_correction": delai_correction,
|
"delai_correction": delai_correction,
|
||||||
"jour": e.date_debut.strftime("%d/%m/%Y")
|
"jour": (
|
||||||
if e.date_debut
|
e.date_debut.strftime("%d/%m/%Y") if e.date_debut else "sans date"
|
||||||
else "sans date",
|
),
|
||||||
"_jour_target": url_for(
|
"_jour_target": url_for(
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -611,13 +607,17 @@ def evaluation_describe(evaluation_id="", edit_in_place=True, link_saisie=True)
|
|||||||
# Indique l'UE
|
# Indique l'UE
|
||||||
ue = modimpl.module.ue
|
ue = modimpl.module.ue
|
||||||
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
|
H.append(f"<p><b>UE : {ue.acronyme}</b></p>")
|
||||||
|
if (
|
||||||
|
modimpl.module.module_type == ModuleType.MALUS
|
||||||
|
or evaluation.evaluation_type == Evaluation.EVALUATION_BONUS
|
||||||
|
):
|
||||||
# store min/max values used by JS client-side checks:
|
# store min/max values used by JS client-side checks:
|
||||||
H.append(
|
H.append(
|
||||||
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
|
"""<span id="eval_note_min" class="sco-hidden">-20.</span>
|
||||||
<span id="eval_note_max" class="sco-hidden">20.</span>"""
|
<span id="eval_note_max" class="sco-hidden">20.</span>"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# date et absences (pas pour evals de malus)
|
# date et absences (pas pour evals bonus ni des modules de malus)
|
||||||
if evaluation.date_debut is not None:
|
if evaluation.date_debut is not None:
|
||||||
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
|
H.append(f"<p>Réalisée le <b>{evaluation.descr_date()}</b> ")
|
||||||
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
group_id = sco_groups.get_default_group(modimpl.formsemestre_id)
|
||||||
|
@ -534,7 +534,7 @@ def excel_feuille_saisie(evaluation: "Evaluation", titreannee, description, line
|
|||||||
# description evaluation
|
# description evaluation
|
||||||
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
ws.append_single_cell_row(scu.unescape_html(description), style_titres)
|
||||||
ws.append_single_cell_row(
|
ws.append_single_cell_row(
|
||||||
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient or 0.0):g})",
|
f"Evaluation {evaluation.descr_date()} (coef. {(evaluation.coefficient):g})",
|
||||||
style,
|
style,
|
||||||
)
|
)
|
||||||
# ligne blanche
|
# ligne blanche
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"""Exception handling
|
"""Exception handling
|
||||||
"""
|
"""
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
import app
|
||||||
|
|
||||||
|
|
||||||
# --- Exceptions
|
# --- Exceptions
|
||||||
@ -237,8 +238,11 @@ class ScoTemporaryError(ScoValueError):
|
|||||||
|
|
||||||
def __init__(self, msg: str = ""):
|
def __init__(self, msg: str = ""):
|
||||||
msg = """
|
msg = """
|
||||||
<p>"Erreur temporaire</p>
|
<p>Erreur temporaire</p>
|
||||||
<p>Veuillez ré-essayer. Si le problème persiste, merci de contacter l'assistance ScoDoc
|
<p>Veuillez ré-essayer. Si le problème persiste (ou s'il venait
|
||||||
|
à se produire fréquemment), merci de contacter l'assistance ScoDoc
|
||||||
|
(voir <a href="https://scodoc.org/Contact/">les informations de contact</a>).
|
||||||
</p>
|
</p>
|
||||||
"""
|
"""
|
||||||
|
app.clear_scodoc_cache()
|
||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
|
@ -31,6 +31,7 @@ import flask
|
|||||||
from flask import url_for, flash, redirect
|
from flask import url_for, flash, redirect
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
@ -63,8 +64,6 @@ from app.scodoc import html_sco_header
|
|||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_compute_moy
|
from app.scodoc import sco_compute_moy
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_edit_ue
|
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups_copy
|
from app.scodoc import sco_groups_copy
|
||||||
from app.scodoc import sco_modalites
|
from app.scodoc import sco_modalites
|
||||||
@ -1113,7 +1112,8 @@ def formsemestre_delete_moduleimpls(formsemestre_id, module_ids_to_del):
|
|||||||
f"""<b>impossible de supprimer {module.code} ({module.titre or ""})
|
f"""<b>impossible de supprimer {module.code} ({module.titre or ""})
|
||||||
car il y a {nb_evals} évaluations définies
|
car il y a {nb_evals} évaluations définies
|
||||||
(<a href="{
|
(<a href="{
|
||||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
url_for("notes.moduleimpl_status",
|
||||||
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||||
}" class="stdlink">supprimez-les d\'abord</a>)</b>"""
|
}" class="stdlink">supprimez-les d\'abord</a>)</b>"""
|
||||||
]
|
]
|
||||||
ok = False
|
ok = False
|
||||||
@ -1233,7 +1233,11 @@ def formsemestre_clone(formsemestre_id):
|
|||||||
return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
|
return "".join(H) + msg + tf[1] + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1: # cancel
|
elif tf[0] == -1: # cancel
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
url_for(
|
||||||
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
resp = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
resp = User.get_user_from_nomplogin(tf[2]["responsable_id"])
|
||||||
@ -1356,9 +1360,9 @@ def do_formsemestre_clone(
|
|||||||
return formsemestre_id
|
return formsemestre_id
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_delete(formsemestre_id):
|
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||||
"""Delete a formsemestre (affiche avertissements)"""
|
"""Delete a formsemestre (affiche avertissements)"""
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header("Suppression du semestre"),
|
html_sco_header.html_sem_header("Suppression du semestre"),
|
||||||
"""<div class="ue_warning"><span>Attention !</span>
|
"""<div class="ue_warning"><span>Attention !</span>
|
||||||
@ -1376,17 +1380,18 @@ Ceci n'est possible que si :
|
|||||||
</ol>
|
</ol>
|
||||||
</div>""",
|
</div>""",
|
||||||
]
|
]
|
||||||
|
evaluations = (
|
||||||
evals = sco_evaluation_db.do_evaluation_list_in_formsemestre(formsemestre_id)
|
Evaluation.query.join(ModuleImpl)
|
||||||
if evals:
|
.filter_by(formsemestre_id=formsemestre.id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
if evaluations:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p class="warning">Attention: il y a {len(evals)} évaluations
|
f"""<p class="warning">Attention: il y a {len(evaluations)} évaluations
|
||||||
dans ce semestre
|
dans ce semestre
|
||||||
(sa suppression entrainera l'effacement définif des notes) !</p>"""
|
(sa suppression entrainera l'effacement définif des notes) !</p>"""
|
||||||
)
|
)
|
||||||
submit_label = (
|
submit_label = f"Confirmer la suppression (du semestre et des {len(evaluations)} évaluations !)"
|
||||||
f"Confirmer la suppression (du semestre et des {len(evals)} évaluations !)"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
submit_label = "Confirmer la suppression du semestre"
|
submit_label = "Confirmer la suppression du semestre"
|
||||||
tf = TrivialFormulator(
|
tf = TrivialFormulator(
|
||||||
@ -1413,8 +1418,10 @@ Ceci n'est possible que si :
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
H.append(tf[1])
|
H.append(tf[1])
|
||||||
|
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
return "\n".join(H) + html_sco_header.sco_footer()
|
||||||
elif tf[0] == -1: # cancel
|
|
||||||
|
if tf[0] == -1: # cancel
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
@ -1422,7 +1429,6 @@ Ceci n'est possible que si :
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
"formsemestre_delete2?formsemestre_id=" + str(formsemestre_id)
|
||||||
)
|
)
|
||||||
@ -1486,106 +1492,165 @@ def formsemestre_has_decisions_or_compensations(
|
|||||||
return False, ""
|
return False, ""
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_delete(formsemestre_id):
|
def do_formsemestre_delete(formsemestre_id: int):
|
||||||
"""delete formsemestre, and all its moduleimpls.
|
"""delete formsemestre, and all its moduleimpls.
|
||||||
No checks, no warnings: erase all !
|
No checks, no warnings: erase all !
|
||||||
"""
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sco_cache.EvaluationCache.invalidate_sem(formsemestre.id)
|
||||||
|
titre_sem = formsemestre.titre_annee()
|
||||||
sco_cache.EvaluationCache.invalidate_sem(formsemestre_id)
|
|
||||||
|
|
||||||
# --- Destruction des modules de ce semestre
|
# --- Destruction des modules de ce semestre
|
||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
for modimpl in formsemestre.modimpls:
|
||||||
for mod in mods:
|
|
||||||
# evaluations
|
# evaluations
|
||||||
evals = sco_evaluation_db.get_evaluations_dict(
|
for e in modimpl.evaluations:
|
||||||
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""DELETE FROM notes_notes WHERE evaluation_id=:evaluation_id"""
|
||||||
|
),
|
||||||
|
{"evaluation_id": e.id},
|
||||||
)
|
)
|
||||||
for e in evals:
|
db.session.execute(
|
||||||
ndb.SimpleQuery(
|
sa.text(
|
||||||
"DELETE FROM notes_notes WHERE evaluation_id=%(evaluation_id)s",
|
"""DELETE FROM notes_notes_log WHERE evaluation_id=:evaluation_id"""
|
||||||
e,
|
),
|
||||||
)
|
{"evaluation_id": e.id},
|
||||||
ndb.SimpleQuery(
|
|
||||||
"DELETE FROM notes_notes_log WHERE evaluation_id=%(evaluation_id)s",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
ndb.SimpleQuery(
|
|
||||||
"DELETE FROM notes_evaluation WHERE id=%(evaluation_id)s",
|
|
||||||
e,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_moduleimpl.do_moduleimpl_delete(
|
db.session.delete(e)
|
||||||
mod["moduleimpl_id"], formsemestre_id=formsemestre_id
|
db.session.delete(modimpl)
|
||||||
)
|
|
||||||
# --- Desinscription des etudiants
|
# --- Desinscription des etudiants
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
db.session.execute(
|
||||||
req = "DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=%(formsemestre_id)s"
|
sa.text(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
"DELETE FROM notes_formsemestre_inscription WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
|
|
||||||
# --- Suppression des evenements
|
# --- Suppression des evenements
|
||||||
req = "DELETE FROM scolar_events WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text("DELETE FROM scolar_events WHERE formsemestre_id=:formsemestre_id"),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des appreciations
|
# --- Suppression des appreciations
|
||||||
req = "DELETE FROM notes_appreciations WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM notes_appreciations WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Supression des validations (!!!)
|
# --- Supression des validations (!!!)
|
||||||
req = "DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM scolar_formsemestre_validation WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Supression des references a ce semestre dans les compensations:
|
# --- Supression des references a ce semestre dans les compensations:
|
||||||
req = "UPDATE scolar_formsemestre_validation SET compense_formsemestre_id=NULL WHERE compense_formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"""UPDATE scolar_formsemestre_validation
|
||||||
|
SET compense_formsemestre_id=NULL
|
||||||
|
WHERE compense_formsemestre_id=:formsemestre_id"""
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des autorisations
|
# --- Suppression des autorisations
|
||||||
req = "DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM scolar_autorisation_inscription WHERE origin_formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des coefs d'UE capitalisées
|
# --- Suppression des coefs d'UE capitalisées
|
||||||
req = "DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM notes_formsemestre_uecoef WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des item du menu custom
|
# --- Suppression des item du menu custom
|
||||||
req = "DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM notes_formsemestre_custommenu WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des formules
|
# --- Suppression des formules
|
||||||
req = "DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text(
|
||||||
|
"DELETE FROM notes_formsemestre_ue_computation_expr WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des preferences
|
# --- Suppression des preferences
|
||||||
req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s"
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text("DELETE FROM sco_prefs WHERE formsemestre_id=:formsemestre_id"),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Suppression des groupes et partitions
|
# --- Suppression des groupes et partitions
|
||||||
req = """DELETE FROM group_membership
|
db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
DELETE FROM group_membership
|
||||||
WHERE group_id IN
|
WHERE group_id IN
|
||||||
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
(SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd
|
||||||
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
WHERE gm.group_id = gd.id AND gd.partition_id = p.id
|
||||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
AND p.formsemestre_id=:formsemestre_id)
|
||||||
"""
|
"""
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
),
|
||||||
req = """DELETE FROM group_descr
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"""
|
||||||
|
DELETE FROM group_descr
|
||||||
WHERE id IN
|
WHERE id IN
|
||||||
(SELECT gd.id FROM group_descr gd, partition p
|
(SELECT gd.id FROM group_descr gd, partition p
|
||||||
WHERE gd.partition_id = p.id
|
WHERE gd.partition_id = p.id
|
||||||
AND p.formsemestre_id=%(formsemestre_id)s)
|
AND p.formsemestre_id=:formsemestre_id)
|
||||||
"""
|
"""
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
),
|
||||||
req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s"
|
{"formsemestre_id": formsemestre_id},
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
)
|
||||||
|
db.session.execute(
|
||||||
|
sa.text("DELETE FROM partition WHERE formsemestre_id=:formsemestre_id"),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Responsables
|
# --- Responsables
|
||||||
req = """DELETE FROM notes_formsemestre_responsables
|
db.session.execute(
|
||||||
WHERE formsemestre_id=%(formsemestre_id)s"""
|
sa.text(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
"DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Etapes
|
# --- Etapes
|
||||||
req = """DELETE FROM notes_formsemestre_etapes
|
db.session.execute(
|
||||||
WHERE formsemestre_id=%(formsemestre_id)s"""
|
sa.text(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
"DELETE FROM notes_formsemestre_etapes WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
|
# --- SemSets
|
||||||
|
db.session.execute(
|
||||||
|
sa.text(
|
||||||
|
"DELETE FROM notes_semset_formsemestre WHERE formsemestre_id=:formsemestre_id"
|
||||||
|
),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Dispenses d'UE
|
# --- Dispenses d'UE
|
||||||
req = """DELETE FROM "dispenseUE" WHERE formsemestre_id=%(formsemestre_id)s"""
|
db.session.execute(
|
||||||
cursor.execute(req, {"formsemestre_id": formsemestre_id})
|
sa.text("""DELETE FROM "dispenseUE" WHERE formsemestre_id=:formsemestre_id"""),
|
||||||
|
{"formsemestre_id": formsemestre_id},
|
||||||
|
)
|
||||||
# --- Destruction du semestre
|
# --- Destruction du semestre
|
||||||
sco_formsemestre._formsemestreEditor.delete(cnx, formsemestre_id)
|
db.session.delete(formsemestre)
|
||||||
|
|
||||||
# news
|
# news
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_SEM,
|
typ=ScolarNews.NEWS_SEM,
|
||||||
obj=formsemestre_id,
|
obj=formsemestre_id,
|
||||||
text="Suppression du semestre %(titre)s" % sem,
|
text=f"Suppression du semestre {titre_sem}",
|
||||||
max_frequency=0,
|
max_frequency=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1678,7 +1743,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
|
|||||||
sum_coefs_by_ue_id = {}
|
sum_coefs_by_ue_id = {}
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
sum_coefs_by_ue_id[ue.id] = sum(
|
sum_coefs_by_ue_id[ue.id] = sum(
|
||||||
modimpl.module.coefficient
|
modimpl.module.coefficient or 0.0
|
||||||
for modimpl in modimpls
|
for modimpl in modimpls
|
||||||
if modimpl.module.ue_id == ue.id
|
if modimpl.module.ue_id == ue.id
|
||||||
)
|
)
|
||||||
|
@ -191,12 +191,26 @@ def do_formsemestre_inscription_edit(args=None, formsemestre_id=None):
|
|||||||
) # > modif inscription semestre
|
) # > modif inscription semestre
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_desinscription(etudid, formsemestre_id):
|
def check_if_has_decision_jury(
|
||||||
|
formsemestre: FormSemestre, etudids: list[int] | set[int]
|
||||||
|
):
|
||||||
|
"raise exception if one of the etuds has a decision in formsemestre"
|
||||||
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
for etudid in etudids:
|
||||||
|
if nt.etud_has_decision(etudid):
|
||||||
|
etud = Identite.query.get(etudid)
|
||||||
|
raise ScoValueError(
|
||||||
|
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
||||||
|
une décision de jury (la supprimer avant si nécessaire)"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def do_formsemestre_desinscription(
|
||||||
|
etudid, formsemestre_id: int, check_has_dec_jury=True
|
||||||
|
):
|
||||||
"""Désinscription d'un étudiant.
|
"""Désinscription d'un étudiant.
|
||||||
Si semestre extérieur et dernier inscrit, suppression de ce semestre.
|
Si semestre extérieur et dernier inscrit, suppression de ce semestre.
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formsemestre_edit
|
|
||||||
|
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
# -- check lock
|
# -- check lock
|
||||||
@ -204,13 +218,8 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
|||||||
raise ScoValueError("désinscription impossible: semestre verrouille")
|
raise ScoValueError("désinscription impossible: semestre verrouille")
|
||||||
|
|
||||||
# -- Si decisions de jury, désinscription interdite
|
# -- Si decisions de jury, désinscription interdite
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
if check_has_dec_jury:
|
||||||
|
check_if_has_decision_jury(formsemestre, [etudid])
|
||||||
if nt.etud_has_decision(etudid):
|
|
||||||
raise ScoValueError(
|
|
||||||
f"""désinscription impossible: l'étudiant {etud.nomprenom} a
|
|
||||||
une décision de jury (la supprimer avant si nécessaire)"""
|
|
||||||
)
|
|
||||||
|
|
||||||
insem = do_formsemestre_inscription_list(
|
insem = do_formsemestre_inscription_list(
|
||||||
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||||
@ -247,17 +256,14 @@ def do_formsemestre_desinscription(etudid, formsemestre_id):
|
|||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
||||||
# --- Semestre extérieur
|
# --- Semestre extérieur
|
||||||
if formsemestre.modalite == "EXT":
|
if formsemestre.modalite == "EXT":
|
||||||
inscrits = do_formsemestre_inscription_list(
|
if 0 == len(formsemestre.inscriptions):
|
||||||
args={"formsemestre_id": formsemestre_id}
|
|
||||||
)
|
|
||||||
nbinscrits = len(inscrits)
|
|
||||||
if nbinscrits == 0:
|
|
||||||
log(
|
log(
|
||||||
f"""do_formsemestre_desinscription:
|
f"""do_formsemestre_desinscription:
|
||||||
suppression du semestre extérieur {formsemestre}"""
|
suppression du semestre extérieur {formsemestre}"""
|
||||||
)
|
)
|
||||||
flash("Semestre exterieur supprimé")
|
db.session.delete(formsemestre)
|
||||||
sco_formsemestre_edit.do_formsemestre_delete(formsemestre_id)
|
db.session.commit()
|
||||||
|
flash(f"Semestre extérieur supprimé: {formsemestre.titre_annee()}")
|
||||||
|
|
||||||
logdb(
|
logdb(
|
||||||
cnx,
|
cnx,
|
||||||
@ -576,26 +582,29 @@ def formsemestre_inscription_option(etudid, formsemestre_id):
|
|||||||
ue_id = ue.id
|
ue_id = ue.id
|
||||||
ue_descr = ue.acronyme
|
ue_descr = ue.acronyme
|
||||||
if ue.type != UE_STANDARD:
|
if ue.type != UE_STANDARD:
|
||||||
ue_descr += " <em>%s</em>" % UE_TYPE_NAME[ue.type]
|
ue_descr += f" <em>{UE_TYPE_NAME[ue.type]}</em>"
|
||||||
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
if ue_status and ue_status["is_capitalized"]:
|
if ue_status and ue_status["is_capitalized"]:
|
||||||
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
sem_origin = sco_formsemestre.get_formsemestre(ue_status["formsemestre_id"])
|
||||||
ue_descr += (
|
ue_descr += f"""
|
||||||
' <a class="discretelink" href="formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" title="%s">(capitalisée le %s)'
|
<a class="discretelink" href="{ url_for(
|
||||||
% (
|
'notes.formsemestre_bulletinetud', scodoc_dept=g.scodoc_dept,
|
||||||
sem_origin["formsemestre_id"],
|
formsemestre_id=sem_origin["formsemestre_id"],
|
||||||
etudid,
|
etudid = etudid
|
||||||
sem_origin["titreannee"],
|
)}" title="{sem_origin['titreannee']}">(capitalisée le {
|
||||||
ndb.DateISOtoDMY(ue_status["event_date"]),
|
ndb.DateISOtoDMY(ue_status["event_date"])
|
||||||
)
|
})
|
||||||
)
|
"""
|
||||||
descr.append(
|
descr.append(
|
||||||
(
|
(
|
||||||
"sec_%s" % ue_id,
|
f"sec_{ue_id}",
|
||||||
{
|
{
|
||||||
"input_type": "separator",
|
"input_type": "separator",
|
||||||
"title": """<b>%s :</b> <a href="#" onclick="chkbx_select('%s', true);">inscrire</a> | <a href="#" onclick="chkbx_select('%s', false);">désinscrire</a> à tous les modules"""
|
"title": f"""<b>{ue_descr} :</b>
|
||||||
% (ue_descr, ue_id, ue_id),
|
<a href="#" onclick="chkbx_select('{ue_id}', true);">inscrire</a> | <a
|
||||||
|
href="#" onclick="chkbx_select('{ue_id}', false);">désinscrire</a>
|
||||||
|
à tous les modules
|
||||||
|
""",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -765,9 +774,7 @@ def do_moduleimpl_incription_options(
|
|||||||
# verifie que ce module existe bien
|
# verifie que ce module existe bien
|
||||||
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if len(mods) != 1:
|
if len(mods) != 1:
|
||||||
raise ScoValueError(
|
raise ScoValueError(f"inscription: invalid moduleimpl_id: {moduleimpl_id}")
|
||||||
"inscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
|
||||||
)
|
|
||||||
mod = mods[0]
|
mod = mods[0]
|
||||||
sco_moduleimpl.do_moduleimpl_inscription_create(
|
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||||
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
||||||
@ -779,7 +786,7 @@ def do_moduleimpl_incription_options(
|
|||||||
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)
|
||||||
if len(mods) != 1:
|
if len(mods) != 1:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"desinscription: invalid moduleimpl_id: %s" % moduleimpl_id
|
f"desinscription: invalid moduleimpl_id: {moduleimpl_id}"
|
||||||
)
|
)
|
||||||
mod = mods[0]
|
mod = mods[0]
|
||||||
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
|
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||||
@ -787,8 +794,7 @@ def do_moduleimpl_incription_options(
|
|||||||
)
|
)
|
||||||
if not inscr:
|
if not inscr:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"pas inscrit a ce module ! (etudid=%s, moduleimpl_id=%s)"
|
f"pas inscrit a ce module ! (etudid={etudid}, moduleimpl_id={moduleimpl_id})"
|
||||||
% (etudid, moduleimpl_id)
|
|
||||||
)
|
)
|
||||||
oid = inscr[0]["moduleimpl_inscription_id"]
|
oid = inscr[0]["moduleimpl_inscription_id"]
|
||||||
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
||||||
@ -797,11 +803,13 @@ def do_moduleimpl_incription_options(
|
|||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(),
|
html_sco_header.sco_header(),
|
||||||
"""<h3>Modifications effectuées</h3>
|
f"""<h3>Modifications effectuées</h3>
|
||||||
<p><a class="stdlink" href="%s">
|
<p><a class="stdlink" href="{
|
||||||
Retour à la fiche étudiant</a></p>
|
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
"""
|
}">
|
||||||
% url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
Retour à la fiche étudiant</a>
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
return "\n".join(H)
|
return "\n".join(H)
|
||||||
@ -845,49 +853,59 @@ def formsemestre_inscrits_ailleurs(formsemestre_id):
|
|||||||
"""Page listant les étudiants inscrits dans un autre semestre
|
"""Page listant les étudiants inscrits dans un autre semestre
|
||||||
dont les dates recouvrent le semestre indiqué.
|
dont les dates recouvrent le semestre indiqué.
|
||||||
"""
|
"""
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.html_sem_header(
|
html_sco_header.html_sem_header(
|
||||||
"Inscriptions multiples parmi les étudiants du semestre ",
|
"Inscriptions multiples parmi les étudiants du semestre ",
|
||||||
|
init_qtip=True,
|
||||||
|
javascripts=["js/etud_info.js"],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
insd = list_inscrits_ailleurs(formsemestre_id)
|
insd = list_inscrits_ailleurs(formsemestre_id)
|
||||||
# liste ordonnée par nom
|
# liste ordonnée par nom
|
||||||
etudlist = [
|
etudlist = [Identite.get_etud(etudid) for etudid, sems in insd.items() if sems]
|
||||||
sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etudlist.sort(key=lambda x: x.sort_key)
|
||||||
for etudid in insd.keys()
|
|
||||||
if insd[etudid]
|
|
||||||
]
|
|
||||||
etudlist.sort(key=lambda x: x["nom"])
|
|
||||||
if etudlist:
|
if etudlist:
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
for etud in etudlist:
|
for etud in etudlist:
|
||||||
H.append(
|
H.append(
|
||||||
'<li><a href="%s" class="discretelink">%s</a> : '
|
f"""<li><a id="{etud.id}" class="discretelink etudinfo"
|
||||||
% (
|
href={
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.fiche_etud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud.id,
|
||||||
),
|
|
||||||
etud["nomprenom"],
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
>{etud.nomprenom}</a> :
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
l = []
|
l = []
|
||||||
for s in insd[etud["etudid"]]:
|
for s in insd[etud.id]:
|
||||||
l.append(
|
l.append(
|
||||||
'<a class="discretelink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titremois)s</a>'
|
f"""<a class="discretelink" href="{
|
||||||
% s
|
url_for('notes.formsemestre_status',
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||||
|
)}">{s['titremois']}</a>"""
|
||||||
)
|
)
|
||||||
H.append(", ".join(l))
|
H.append(", ".join(l))
|
||||||
H.append("</li>")
|
H.append("</li>")
|
||||||
H.append("</ul>")
|
|
||||||
H.append("<p>Total: %d étudiants concernés.</p>" % len(etudlist))
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi dans d'autres semestres qui se déroulent en même temps ! <br>Sauf exception, cette situation est anormale:</p>
|
f"""
|
||||||
|
</ul>
|
||||||
|
<p><b>Total: {len(etudlist)} étudiants concernés.</b></p>
|
||||||
|
|
||||||
|
<p class="help">Ces étudiants sont inscrits dans le semestre sélectionné et aussi
|
||||||
|
dans d'autres semestres qui se déroulent en même temps !
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Sauf exception, cette situation est anormale:</b>
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>vérifier que les dates des semestres se suivent sans se chevaucher</li>
|
<li>vérifier que les dates des semestres se suivent <em>sans se chevaucher</em>
|
||||||
<li>ou si besoin désinscrire le(s) étudiant(s) de l'un des semestres (via leurs fiches individuelles).</li>
|
</li>
|
||||||
|
<li>ou bien si besoin désinscrire le(s) étudiant(s) de l'un des semestres
|
||||||
|
(via leurs fiches individuelles).
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -1173,7 +1173,8 @@ def formsemestre_tableau_modules(
|
|||||||
moduleimpl_id=modimpl.id,
|
moduleimpl_id=modimpl.id,
|
||||||
)
|
)
|
||||||
mod_descr = "Module " + (mod.titre or "")
|
mod_descr = "Module " + (mod.titre or "")
|
||||||
if mod.is_apc():
|
is_apc = mod.is_apc() # SAE ou ressource
|
||||||
|
if is_apc:
|
||||||
coef_descr = ", ".join(
|
coef_descr = ", ".join(
|
||||||
[
|
[
|
||||||
f"{ue.acronyme}: {co}"
|
f"{ue.acronyme}: {co}"
|
||||||
@ -1193,6 +1194,7 @@ def formsemestre_tableau_modules(
|
|||||||
[u.get_nomcomplet() for u in modimpl.enseignants]
|
[u.get_nomcomplet() for u in modimpl.enseignants]
|
||||||
)
|
)
|
||||||
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
mod_nb_inscrits = nt.modimpls_results[modimpl.id].nb_inscrits_module
|
||||||
|
mod_is_conforme = modimpl.check_apc_conformity(nt)
|
||||||
ue = modimpl.module.ue
|
ue = modimpl.module.ue
|
||||||
if show_ues and (prev_ue_id != ue.id):
|
if show_ues and (prev_ue_id != ue.id):
|
||||||
prev_ue_id = ue.id
|
prev_ue_id = ue.id
|
||||||
@ -1200,10 +1202,12 @@ def formsemestre_tableau_modules(
|
|||||||
if use_ue_coefs:
|
if use_ue_coefs:
|
||||||
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
titre += f""" <b>(coef. {ue.coefficient or 0.0})</b>"""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<tr class="formsemestre_status_ue"><td colspan="4">
|
f"""<tr class="formsemestre_status_ue">
|
||||||
|
<td colspan="4">
|
||||||
<span class="status_ue_acro">{ue.acronyme}</span>
|
<span class="status_ue_acro">{ue.acronyme}</span>
|
||||||
<span class="status_ue_title">{titre}</span>
|
<span class="status_ue_title">{titre}</span>
|
||||||
</td><td colspan="2">"""
|
</td>
|
||||||
|
<td colspan="2">"""
|
||||||
)
|
)
|
||||||
|
|
||||||
expr = sco_compute_moy.get_ue_expression(
|
expr = sco_compute_moy.get_ue_expression(
|
||||||
@ -1226,21 +1230,26 @@ def formsemestre_tableau_modules(
|
|||||||
fontorange = ""
|
fontorange = ""
|
||||||
|
|
||||||
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
etat = sco_evaluations.do_evaluation_etat_in_mod(nt, modimpl)
|
||||||
# if nt.parcours.APC_SAE:
|
|
||||||
# tbd style si module non conforme
|
|
||||||
if (
|
if (
|
||||||
etat["nb_evals_completes"] > 0
|
etat["nb_evals_completes"] > 0
|
||||||
and etat["nb_evals_en_cours"] == 0
|
and etat["nb_evals_en_cours"] == 0
|
||||||
and etat["nb_evals_vides"] == 0
|
and etat["nb_evals_vides"] == 0
|
||||||
and not etat["attente"]
|
and not etat["attente"]
|
||||||
|
and not etat["nb_evals_blocked"] > 0
|
||||||
):
|
):
|
||||||
H.append(f'<tr class="formsemestre_status_green{fontorange}">')
|
tr_classes = f"formsemestre_status_green{fontorange}"
|
||||||
else:
|
else:
|
||||||
H.append(f'<tr class="formsemestre_status{fontorange}">')
|
tr_classes = f"formsemestre_status{fontorange}"
|
||||||
|
if etat["attente"]:
|
||||||
|
tr_classes += " modimpl_attente"
|
||||||
|
if not mod_is_conforme:
|
||||||
|
tr_classes += " modimpl_non_conforme"
|
||||||
|
if etat["nb_evals_blocked"] > 0:
|
||||||
|
tr_classes += " modimpl_has_blocked"
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<td class="formsemestre_status_code""><a
|
<tr class="{tr_classes}">
|
||||||
|
<td class="formsemestre_status_code"><a
|
||||||
href="{moduleimpl_status_url}"
|
href="{moduleimpl_status_url}"
|
||||||
title="{mod_descr}" class="stdlink">{mod.code}</a></td>
|
title="{mod_descr}" class="stdlink">{mod.code}</a></td>
|
||||||
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
<td class="scotext"><a href="{moduleimpl_status_url}" title="{mod_descr}"
|
||||||
@ -1278,17 +1287,20 @@ def formsemestre_tableau_modules(
|
|||||||
ModuleType.SAE,
|
ModuleType.SAE,
|
||||||
):
|
):
|
||||||
H.append('<td class="evals">')
|
H.append('<td class="evals">')
|
||||||
nb_evals = (
|
nb_evals = etat["nb_evals"]
|
||||||
etat["nb_evals_completes"]
|
|
||||||
+ etat["nb_evals_en_cours"]
|
|
||||||
+ etat["nb_evals_vides"]
|
|
||||||
)
|
|
||||||
if nb_evals != 0:
|
if nb_evals != 0:
|
||||||
|
if etat["nb_evals_blocked"] > 0:
|
||||||
|
blocked_txt = f"""<span class="nb_evals_blocked">{
|
||||||
|
etat["nb_evals_blocked"]} bloquée{'s'
|
||||||
|
if etat["nb_evals_blocked"] > 1 else ''}</span>"""
|
||||||
|
else:
|
||||||
|
blocked_txt = ""
|
||||||
H.append(
|
H.append(
|
||||||
f"""<a href="{moduleimpl_status_url}"
|
f"""<a href="{moduleimpl_status_url}"
|
||||||
title="les évaluations 'ok' sont celles prises en compte dans les calculs"
|
title="les évaluations 'ok' sont celles prises en compte dans les calculs"
|
||||||
class="formsemestre_status_link">{nb_evals} prévues,
|
class="formsemestre_status_link">{nb_evals} prévues,
|
||||||
{etat["nb_evals_completes"]} ok</a>"""
|
{etat["nb_evals_completes"]} ok {blocked_txt}
|
||||||
|
</a>"""
|
||||||
)
|
)
|
||||||
if etat["nb_evals_en_cours"] > 0:
|
if etat["nb_evals_en_cours"] > 0:
|
||||||
H.append(
|
H.append(
|
||||||
@ -1300,7 +1312,12 @@ def formsemestre_tableau_modules(
|
|||||||
if etat["attente"]:
|
if etat["attente"]:
|
||||||
H.append(
|
H.append(
|
||||||
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
||||||
title="Il y a des notes en attente">[en attente]</a></span>"""
|
title="Il y a des notes en attente"><span class="evals_attente">en attente</span></a></span>"""
|
||||||
|
)
|
||||||
|
if not mod_is_conforme:
|
||||||
|
H.append(
|
||||||
|
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
||||||
|
title="évaluations non conformes">[non conforme]</a></span>"""
|
||||||
)
|
)
|
||||||
elif mod.module_type == ModuleType.MALUS:
|
elif mod.module_type == ModuleType.MALUS:
|
||||||
nb_malus_notes = sum(
|
nb_malus_notes = sum(
|
||||||
|
@ -34,7 +34,7 @@ from flask import url_for, flash, g, request
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app.models.etudiants import Identite
|
from app.models import Identite, Evaluation
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db, log
|
from app import db, log
|
||||||
@ -232,7 +232,9 @@ def formsemestre_validation_etud_form(
|
|||||||
H.append(
|
H.append(
|
||||||
tf_error_message(
|
tf_error_message(
|
||||||
f"""Impossible de statuer sur cet étudiant: il a des notes en
|
f"""Impossible de statuer sur cet étudiant: il a des notes en
|
||||||
attente dans des évaluations de ce semestre (voir <a href="{
|
attente dans des évaluations de ce semestre (voir
|
||||||
|
<a class="stdlink"
|
||||||
|
href="{
|
||||||
url_for( "notes.formsemestre_status",
|
url_for( "notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
||||||
}">tableau de bord</a>)
|
}">tableau de bord</a>)
|
||||||
@ -241,6 +243,26 @@ def formsemestre_validation_etud_form(
|
|||||||
)
|
)
|
||||||
return "\n".join(H + footer)
|
return "\n".join(H + footer)
|
||||||
|
|
||||||
|
evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud(
|
||||||
|
formsemestre, etud
|
||||||
|
)
|
||||||
|
if evaluations_a_debloquer:
|
||||||
|
links_evals = [
|
||||||
|
f"""<a class="stdlink" href="{url_for(
|
||||||
|
'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id
|
||||||
|
)}">{e.description} en {e.moduleimpl.module.code}</a>"""
|
||||||
|
for e in evaluations_a_debloquer
|
||||||
|
]
|
||||||
|
H.append(
|
||||||
|
tf_error_message(
|
||||||
|
f"""Impossible de statuer sur cet étudiant:
|
||||||
|
il a des notes dans des évaluations qui seront débloquées plus tard:
|
||||||
|
voir {", ".join(links_evals)}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "\n".join(H + footer)
|
||||||
|
|
||||||
# Infos si pas de semestre précédent
|
# Infos si pas de semestre précédent
|
||||||
if not Se.prev:
|
if not Se.prev:
|
||||||
if Se.sem["semestre_id"] == 1:
|
if Se.sem["semestre_id"] == 1:
|
||||||
@ -1399,7 +1421,7 @@ def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[Unite
|
|||||||
if (
|
if (
|
||||||
len(semestre_ids) > 1
|
len(semestre_ids) > 1
|
||||||
): # plusieurs semestres d'indices differents dans le cursus
|
): # plusieurs semestres d'indices differents dans le cursus
|
||||||
ue_multiples[ue["ue_id"]] = sems
|
ue_multiples[ue.id] = sems
|
||||||
|
|
||||||
if not ue_multiples:
|
if not ue_multiples:
|
||||||
return "", {}
|
return "", {}
|
||||||
@ -1423,12 +1445,12 @@ def check_formation_ues(formation: Formation) -> tuple[str, dict[int, list[Unite
|
|||||||
]
|
]
|
||||||
slist = ", ".join(
|
slist = ", ".join(
|
||||||
[
|
[
|
||||||
"""%(titreannee)s (<em>semestre <b class="fontred">%(semestre_id)s</b></em>)"""
|
f"""{s['titreannee']
|
||||||
% s
|
} (<em>semestre <b class="fontred">{s['semestre_id']}</b></em>)"""
|
||||||
for s in sems
|
for s in sems
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
H.append("<li><b>%s</b> : %s</li>" % (ue.acronyme, slist))
|
H.append(f"<li><b>{ue.acronyme}</b> : {slist}</li>")
|
||||||
H.append("</ul></div>")
|
H.append("</ul></div>")
|
||||||
|
|
||||||
return "\n".join(H), ue_multiples
|
return "\n".join(H), ue_multiples
|
||||||
|
@ -331,6 +331,7 @@ class DisplayedGroupsInfos:
|
|||||||
empty_list_select_all=True,
|
empty_list_select_all=True,
|
||||||
moduleimpl_id=None, # used to find formsemestre when unspecified
|
moduleimpl_id=None, # used to find formsemestre when unspecified
|
||||||
):
|
):
|
||||||
|
group_ids = [] if group_ids is None else group_ids
|
||||||
if isinstance(group_ids, int):
|
if isinstance(group_ids, int):
|
||||||
if group_ids:
|
if group_ids:
|
||||||
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
|
group_ids = [group_ids] # cas ou un seul parametre, pas de liste
|
||||||
@ -466,6 +467,10 @@ class DisplayedGroupsInfos:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_groups_key(self) -> str:
|
||||||
|
"clé identifiant les groupes sélectionnés, utile pour cache"
|
||||||
|
return "-".join(str(x) for x in sorted(self.group_ids))
|
||||||
|
|
||||||
|
|
||||||
# Ancien ZScolar.group_list renommé ici en group_table
|
# Ancien ZScolar.group_list renommé ici en group_table
|
||||||
def groups_table(
|
def groups_table(
|
||||||
@ -514,10 +519,11 @@ def groups_table(
|
|||||||
"paiementinscription_str": "Paiement",
|
"paiementinscription_str": "Paiement",
|
||||||
"etudarchive": "Fichiers",
|
"etudarchive": "Fichiers",
|
||||||
"annotations_str": "Annotations",
|
"annotations_str": "Annotations",
|
||||||
"bourse_str": "Boursier",
|
"bourse_str": "Boursier", # requière ViewEtudData
|
||||||
"etape": "Etape",
|
"etape": "Etape",
|
||||||
"semestre_groupe": "Semestre-Groupe", # pour Moodle
|
"semestre_groupe": "Semestre-Groupe", # pour Moodle
|
||||||
"annee": "annee_admission",
|
"annee": "annee_admission",
|
||||||
|
"nationalite": "nationalite", # requière ViewEtudData
|
||||||
}
|
}
|
||||||
|
|
||||||
# ajoute colonnes pour groupes
|
# ajoute colonnes pour groupes
|
||||||
@ -559,53 +565,61 @@ def groups_table(
|
|||||||
moodle_sem_name = groups_infos.formsemestre["session_id"]
|
moodle_sem_name = groups_infos.formsemestre["session_id"]
|
||||||
moodle_groupenames = set()
|
moodle_groupenames = set()
|
||||||
# ajoute liens
|
# ajoute liens
|
||||||
for etud in groups_infos.members:
|
for etud_info in groups_infos.members:
|
||||||
if etud["email"]:
|
if etud_info["email"]:
|
||||||
etud["_email_target"] = "mailto:" + etud["email"]
|
etud_info["_email_target"] = "mailto:" + etud_info["email"]
|
||||||
else:
|
else:
|
||||||
etud["_email_target"] = ""
|
etud_info["_email_target"] = ""
|
||||||
if etud["emailperso"]:
|
if etud_info["emailperso"]:
|
||||||
etud["_emailperso_target"] = "mailto:" + etud["emailperso"]
|
etud_info["_emailperso_target"] = "mailto:" + etud_info["emailperso"]
|
||||||
else:
|
else:
|
||||||
etud["_emailperso_target"] = ""
|
etud_info["_emailperso_target"] = ""
|
||||||
fiche_url = url_for(
|
fiche_url = url_for(
|
||||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud_info["etudid"]
|
||||||
)
|
)
|
||||||
etud["_nom_disp_target"] = fiche_url
|
etud_info["_nom_disp_target"] = fiche_url
|
||||||
etud["_nom_disp_order"] = etud_sort_key(etud)
|
etud_info["_nom_disp_order"] = etud_sort_key(etud_info)
|
||||||
etud["_prenom_target"] = fiche_url
|
etud_info["_prenom_target"] = fiche_url
|
||||||
|
|
||||||
etud["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])
|
etud_info["_nom_disp_td_attrs"] = 'id="%s" class="etudinfo"' % (
|
||||||
etud["bourse_str"] = "oui" if etud["boursier"] else "non"
|
etud_info["etudid"]
|
||||||
if etud["etat"] == "D":
|
)
|
||||||
etud["_css_row_class"] = "etuddem"
|
etud_info["bourse_str"] = "oui" if etud_info["boursier"] else "non"
|
||||||
|
if etud_info["etat"] == "D":
|
||||||
|
etud_info["_css_row_class"] = "etuddem"
|
||||||
# et groupes:
|
# et groupes:
|
||||||
for partition_id in etud["partitions"]:
|
for partition_id in etud_info["partitions"]:
|
||||||
etud[partition_id] = etud["partitions"][partition_id]["group_name"]
|
etud_info[partition_id] = etud_info["partitions"][partition_id][
|
||||||
|
"group_name"
|
||||||
|
]
|
||||||
# Ajoute colonne pour moodle: semestre_groupe, de la forme
|
# Ajoute colonne pour moodle: semestre_groupe, de la forme
|
||||||
# RT-DUT-FI-S3-2021-PARTITION-GROUPE
|
# RT-DUT-FI-S3-2021-PARTITION-GROUPE
|
||||||
moodle_groupename = []
|
moodle_groupename = []
|
||||||
if groups_infos.selected_partitions:
|
if groups_infos.selected_partitions:
|
||||||
# il y a des groupes selectionnes, utilise leurs partitions
|
# il y a des groupes selectionnes, utilise leurs partitions
|
||||||
for partition_id in groups_infos.selected_partitions:
|
for partition_id in groups_infos.selected_partitions:
|
||||||
if partition_id in etud["partitions"]:
|
if partition_id in etud_info["partitions"]:
|
||||||
moodle_groupename.append(
|
moodle_groupename.append(
|
||||||
partitions_name[partition_id]
|
partitions_name[partition_id]
|
||||||
+ "-"
|
+ "-"
|
||||||
+ etud["partitions"][partition_id]["group_name"]
|
+ etud_info["partitions"][partition_id]["group_name"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
# pas de groupes sélectionnés: prend le premier s'il y en a un
|
||||||
moodle_groupename = ["tous"]
|
moodle_groupename = ["tous"]
|
||||||
if etud["partitions"]:
|
if etud_info["partitions"]:
|
||||||
for p in etud["partitions"].items(): # partitions is an OrderedDict
|
for p in etud_info[
|
||||||
|
"partitions"
|
||||||
|
].items(): # partitions is an OrderedDict
|
||||||
moodle_groupename = [
|
moodle_groupename = [
|
||||||
partitions_name[p[0]] + "-" + p[1]["group_name"]
|
partitions_name[p[0]] + "-" + p[1]["group_name"]
|
||||||
]
|
]
|
||||||
break
|
break
|
||||||
|
|
||||||
moodle_groupenames |= set(moodle_groupename)
|
moodle_groupenames |= set(moodle_groupename)
|
||||||
etud["semestre_groupe"] = moodle_sem_name + "-" + "+".join(moodle_groupename)
|
etud_info["semestre_groupe"] = (
|
||||||
|
moodle_sem_name + "-" + "+".join(moodle_groupename)
|
||||||
|
)
|
||||||
|
|
||||||
if groups_infos.nbdem > 1:
|
if groups_infos.nbdem > 1:
|
||||||
s = "s"
|
s = "s"
|
||||||
@ -714,9 +728,11 @@ def groups_table(
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
""",
|
""",
|
||||||
|
(
|
||||||
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||||
if not can_view_etud_data
|
if not can_view_etud_data
|
||||||
else "",
|
else ""
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
H.append("</div></form>")
|
H.append("</div></form>")
|
||||||
@ -768,13 +784,7 @@ def groups_table(
|
|||||||
|
|
||||||
return "".join(H)
|
return "".join(H)
|
||||||
|
|
||||||
elif (
|
elif fmt in {"pdf", "xml", "json", "xls", "moodlecsv"}:
|
||||||
fmt == "pdf"
|
|
||||||
or fmt == "xml"
|
|
||||||
or fmt == "json"
|
|
||||||
or fmt == "xls"
|
|
||||||
or fmt == "moodlecsv"
|
|
||||||
):
|
|
||||||
if fmt == "moodlecsv":
|
if fmt == "moodlecsv":
|
||||||
fmt = "csv"
|
fmt = "csv"
|
||||||
return tab.make_page(fmt=fmt)
|
return tab.make_page(fmt=fmt)
|
||||||
@ -789,7 +799,7 @@ def groups_table(
|
|||||||
with_paiement=with_paiement,
|
with_paiement=with_paiement,
|
||||||
server_name=request.url_root,
|
server_name=request.url_root,
|
||||||
)
|
)
|
||||||
filename = "liste_%s" % groups_infos.groups_filename
|
filename = f"liste_{groups_infos.groups_filename}"
|
||||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, scu.XLSX_MIMETYPE)
|
||||||
elif fmt == "allxls":
|
elif fmt == "allxls":
|
||||||
if not can_view_etud_data:
|
if not can_view_etud_data:
|
||||||
@ -823,6 +833,7 @@ def groups_table(
|
|||||||
"fax",
|
"fax",
|
||||||
"date_naissance",
|
"date_naissance",
|
||||||
"lieu_naissance",
|
"lieu_naissance",
|
||||||
|
"nationalite",
|
||||||
"bac",
|
"bac",
|
||||||
"specialite",
|
"specialite",
|
||||||
"annee_bac",
|
"annee_bac",
|
||||||
@ -845,16 +856,16 @@ def groups_table(
|
|||||||
# remplis infos lycee si on a que le code lycée
|
# remplis infos lycee si on a que le code lycée
|
||||||
# et ajoute infos inscription
|
# et ajoute infos inscription
|
||||||
for m in groups_infos.members:
|
for m in groups_infos.members:
|
||||||
etud = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
etud_info = sco_etud.get_etud_info(m["etudid"], filled=True)[0]
|
||||||
m.update(etud)
|
m.update(etud_info)
|
||||||
sco_etud.etud_add_lycee_infos(etud)
|
sco_etud.etud_add_lycee_infos(etud_info)
|
||||||
# et ajoute le parcours
|
# et ajoute le parcours
|
||||||
Se = sco_cursus.get_situation_etud_cursus(
|
Se = sco_cursus.get_situation_etud_cursus(
|
||||||
etud, groups_infos.formsemestre_id
|
etud_info, groups_infos.formsemestre_id
|
||||||
)
|
)
|
||||||
m["parcours"] = Se.get_cursus_descr()
|
m["parcours"] = Se.get_cursus_descr()
|
||||||
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
m["code_cursus"], _ = sco_report.get_code_cursus_etud(
|
||||||
etud["etudid"], sems=etud["sems"]
|
etud_info["etudid"], sems=etud_info["sems"]
|
||||||
)
|
)
|
||||||
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
rows = [[m.get(k, "") for k in keys] for m in groups_infos.members]
|
||||||
title = "etudiants_%s" % groups_infos.groups_filename
|
title = "etudiants_%s" % groups_infos.groups_filename
|
||||||
@ -905,9 +916,11 @@ def tab_absences_html(groups_infos, etat=None):
|
|||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
|
"""<li><a class="stdlink" href="trombino?%s&fmt=pdflist">Liste d'appel avec photos</a></li>"""
|
||||||
% groups_infos.groups_query_args,
|
% groups_infos.groups_query_args,
|
||||||
|
(
|
||||||
f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
|
f"""<li><a class="stdlink" href="groups_export_annotations?{groups_infos.groups_query_args}">Liste des annotations</a></li>"""
|
||||||
if authuser.has_permission(Permission.ViewEtudData)
|
if authuser.has_permission(Permission.ViewEtudData)
|
||||||
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>""",
|
else """<li class="unauthorized" title="non autorisé">Liste des annotations</li>"""
|
||||||
|
),
|
||||||
"</ul>",
|
"</ul>",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -36,13 +36,14 @@ from flask import url_for, g, request
|
|||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import Formation, FormSemestre, GroupDescr
|
from app.comp import res_sem
|
||||||
|
from app.comp.res_compat import NotesTableCompat
|
||||||
|
from app.models import Formation, FormSemestre, GroupDescr, Identite
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_formsemestre
|
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
@ -50,62 +51,69 @@ from app.scodoc import sco_pv_dict
|
|||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
|
def _list_authorized_etuds_by_sem(
|
||||||
|
formsemestre: FormSemestre, ignore_jury=False
|
||||||
|
) -> tuple[dict[int, dict], list[dict], dict[int, Identite]]:
|
||||||
"""Liste des etudiants autorisés à s'inscrire dans sem.
|
"""Liste des etudiants autorisés à s'inscrire dans sem.
|
||||||
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
|
delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible.
|
||||||
ignore_jury: si vrai, considère tous les étudiants comme autorisés, même
|
ignore_jury: si vrai, considère tous les étudiants comme autorisés, même
|
||||||
s'ils n'ont pas de décision de jury.
|
s'ils n'ont pas de décision de jury.
|
||||||
"""
|
"""
|
||||||
src_sems = list_source_sems(sem, delai=delai)
|
src_sems = _list_source_sems(formsemestre)
|
||||||
inscrits = list_inscrits(sem["formsemestre_id"])
|
inscrits = list_inscrits(formsemestre.id)
|
||||||
r = {}
|
r = {}
|
||||||
candidats = {} # etudid : etud (tous les etudiants candidats)
|
candidats = {} # etudid : etud (tous les etudiants candidats)
|
||||||
nb = 0 # debug
|
nb = 0 # debug
|
||||||
for src in src_sems:
|
src_formsemestre: FormSemestre
|
||||||
|
for src_formsemestre in src_sems:
|
||||||
if ignore_jury:
|
if ignore_jury:
|
||||||
# liste de tous les inscrits au semestre (sans dems)
|
# liste de tous les inscrits au semestre (sans dems)
|
||||||
liste = list_inscrits(src["formsemestre_id"]).values()
|
etud_list = list_inscrits(src_formsemestre.id).values()
|
||||||
else:
|
else:
|
||||||
# liste des étudiants autorisés par le jury à s'inscrire ici
|
# liste des étudiants autorisés par le jury à s'inscrire ici
|
||||||
liste = list_etuds_from_sem(src, sem)
|
etud_list = _list_etuds_from_sem(src_formsemestre, formsemestre)
|
||||||
liste_filtree = []
|
liste_filtree = []
|
||||||
for e in liste:
|
for e in etud_list:
|
||||||
# Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src
|
# Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src
|
||||||
auth_used = False # autorisation deja utilisée ?
|
auth_used = False # autorisation deja utilisée ?
|
||||||
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0]
|
etud = Identite.get_etud(e["etudid"])
|
||||||
for isem in etud["sems"]:
|
for inscription in etud.inscriptions():
|
||||||
if ndb.DateDMYtoISO(isem["date_debut"]) >= ndb.DateDMYtoISO(
|
if inscription.formsemestre.date_debut >= src_formsemestre.date_fin:
|
||||||
src["date_fin"]
|
|
||||||
):
|
|
||||||
auth_used = True
|
auth_used = True
|
||||||
if not auth_used:
|
if not auth_used:
|
||||||
candidats[e["etudid"]] = etud
|
candidats[e["etudid"]] = etud
|
||||||
liste_filtree.append(e)
|
liste_filtree.append(e)
|
||||||
nb += 1
|
nb += 1
|
||||||
r[src["formsemestre_id"]] = {
|
r[src_formsemestre.id] = {
|
||||||
"etuds": liste_filtree,
|
"etuds": liste_filtree,
|
||||||
"infos": {
|
"infos": {
|
||||||
"id": src["formsemestre_id"],
|
"id": src_formsemestre.id,
|
||||||
"title": src["titreannee"],
|
"title": src_formsemestre.titre_annee(),
|
||||||
"title_target": "formsemestre_status?formsemestre_id=%s"
|
"title_target": url_for(
|
||||||
% src["formsemestre_id"],
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=src_formsemestre.id,
|
||||||
|
),
|
||||||
"filename": "etud_autorises",
|
"filename": "etud_autorises",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
|
# ajoute attribut inscrit qui indique si l'étudiant est déjà inscrit dans le semestre dest.
|
||||||
for e in r[src["formsemestre_id"]]["etuds"]:
|
for e in r[src_formsemestre.id]["etuds"]:
|
||||||
e["inscrit"] = e["etudid"] in inscrits
|
e["inscrit"] = e["etudid"] in inscrits
|
||||||
|
|
||||||
# Ajoute liste des etudiants actuellement inscrits
|
# Ajoute liste des etudiants actuellement inscrits
|
||||||
for e in inscrits.values():
|
for e in inscrits.values():
|
||||||
e["inscrit"] = True
|
e["inscrit"] = True
|
||||||
r[sem["formsemestre_id"]] = {
|
r[formsemestre.id] = {
|
||||||
"etuds": list(inscrits.values()),
|
"etuds": list(inscrits.values()),
|
||||||
"infos": {
|
"infos": {
|
||||||
"id": sem["formsemestre_id"],
|
"id": formsemestre.id,
|
||||||
"title": "Semestre cible: " + sem["titreannee"],
|
"title": "Semestre cible: " + formsemestre.titre_annee(),
|
||||||
"title_target": "formsemestre_status?formsemestre_id=%s"
|
"title_target": url_for(
|
||||||
% sem["formsemestre_id"],
|
"notes.formsemestre_status",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
),
|
||||||
"comment": " actuellement inscrits dans ce semestre",
|
"comment": " actuellement inscrits dans ce semestre",
|
||||||
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
|
"help": "Ces étudiants sont actuellement inscrits dans ce semestre. Si vous les décochez, il seront désinscrits.",
|
||||||
"filename": "etud_inscrits",
|
"filename": "etud_inscrits",
|
||||||
@ -115,7 +123,7 @@ def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False):
|
|||||||
return r, inscrits, candidats
|
return r, inscrits, candidats
|
||||||
|
|
||||||
|
|
||||||
def list_inscrits(formsemestre_id, with_dems=False):
|
def list_inscrits(formsemestre_id: int, with_dems=False) -> list[dict]:
|
||||||
"""Étudiants déjà inscrits à ce semestre
|
"""Étudiants déjà inscrits à ce semestre
|
||||||
{ etudid : etud }
|
{ etudid : etud }
|
||||||
"""
|
"""
|
||||||
@ -133,28 +141,27 @@ def list_inscrits(formsemestre_id, with_dems=False):
|
|||||||
return inscr
|
return inscr
|
||||||
|
|
||||||
|
|
||||||
def list_etuds_from_sem(src, dst) -> list[dict]:
|
def _list_etuds_from_sem(src: FormSemestre, dst: FormSemestre) -> list[dict]:
|
||||||
"""Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
"""Liste des étudiants du semestre src qui sont autorisés à passer dans le semestre dst."""
|
||||||
target = dst["semestre_id"]
|
target_semestre_id = dst.semestre_id
|
||||||
dpv = sco_pv_dict.dict_pvjury(src["formsemestre_id"])
|
dpv = sco_pv_dict.dict_pvjury(src.id)
|
||||||
if not dpv:
|
if not dpv:
|
||||||
return []
|
return []
|
||||||
etuds = [
|
etuds = [
|
||||||
x["identite"]
|
x["identite"]
|
||||||
for x in dpv["decisions"]
|
for x in dpv["decisions"]
|
||||||
if target in [a["semestre_id"] for a in x["autorisations"]]
|
if target_semestre_id in [a["semestre_id"] for a in x["autorisations"]]
|
||||||
]
|
]
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
|
|
||||||
def list_inscrits_date(sem):
|
def list_inscrits_date(formsemestre: FormSemestre):
|
||||||
"""Liste les etudiants inscrits dans n'importe quel semestre
|
"""Liste les etudiants inscrits à la date de début de formsemestre
|
||||||
du même département
|
dans n'importe quel semestre du même département
|
||||||
SAUF sem à la date de début de sem.
|
SAUF formsemestre
|
||||||
"""
|
"""
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""SELECT ins.etudid
|
"""SELECT ins.etudid
|
||||||
FROM
|
FROM
|
||||||
@ -166,12 +173,18 @@ def list_inscrits_date(sem):
|
|||||||
AND S.date_fin >= %(date_debut_iso)s
|
AND S.date_fin >= %(date_debut_iso)s
|
||||||
AND S.dept_id = %(dept_id)s
|
AND S.dept_id = %(dept_id)s
|
||||||
""",
|
""",
|
||||||
sem,
|
{
|
||||||
|
"formsemestre_id": formsemestre.id,
|
||||||
|
"date_debut_iso": formsemestre.date_debut.isoformat(),
|
||||||
|
"dept_id": formsemestre.dept_id,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
return [x[0] for x in cursor.fetchall()]
|
return [x[0] for x in cursor.fetchall()]
|
||||||
|
|
||||||
|
|
||||||
def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
def do_inscrit(
|
||||||
|
formsemestre: FormSemestre, etudids, inscrit_groupes=False, inscrit_parcours=False
|
||||||
|
):
|
||||||
"""Inscrit ces etudiants dans ce semestre
|
"""Inscrit ces etudiants dans ce semestre
|
||||||
(la liste doit avoir été vérifiée au préalable)
|
(la liste doit avoir été vérifiée au préalable)
|
||||||
En option:
|
En option:
|
||||||
@ -181,12 +194,11 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
|||||||
(si les deux sont vrais, inscrit_parcours n'a pas d'effet)
|
(si les deux sont vrais, inscrit_parcours n'a pas d'effet)
|
||||||
"""
|
"""
|
||||||
# TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr
|
# TODO à ré-écrire pour utiliser les modèles, notamment GroupDescr
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
log(f"do_inscrit (inscrit_groupes={inscrit_groupes}): {etudids}")
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||||
sem["formsemestre_id"],
|
formsemestre.id,
|
||||||
etudid,
|
etudid,
|
||||||
etat=scu.INSCRIT,
|
etat=scu.INSCRIT,
|
||||||
method="formsemestre_inscr_passage",
|
method="formsemestre_inscr_passage",
|
||||||
@ -210,7 +222,7 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
|||||||
|
|
||||||
cursem_groups_by_name = {
|
cursem_groups_by_name = {
|
||||||
g["group_name"]: g
|
g["group_name"]: g
|
||||||
for g in sco_groups.get_sem_groups(sem["formsemestre_id"])
|
for g in sco_groups.get_sem_groups(formsemestre.id)
|
||||||
if g["group_name"]
|
if g["group_name"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,53 +246,46 @@ def do_inscrit(sem, etudids, inscrit_groupes=False, inscrit_parcours=False):
|
|||||||
sco_groups.change_etud_group_in_partition(etudid, group)
|
sco_groups.change_etud_group_in_partition(etudid, group)
|
||||||
|
|
||||||
|
|
||||||
def do_desinscrit(sem: dict, etudids: list[int]):
|
def do_desinscrit(
|
||||||
|
formsemestre: FormSemestre, etudids: list[int], check_has_dec_jury=True
|
||||||
|
):
|
||||||
"désinscrit les étudiants indiqués du formsemestre"
|
"désinscrit les étudiants indiqués du formsemestre"
|
||||||
log(f"do_desinscrit: {etudids}")
|
log(f"do_desinscrit: {etudids}")
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
sco_formsemestre_inscriptions.do_formsemestre_desinscription(
|
||||||
etudid, sem["formsemestre_id"]
|
etudid, formsemestre.id, check_has_dec_jury=check_has_dec_jury
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_source_sems(sem, delai=None) -> list[dict]:
|
def _list_source_sems(formsemestre: FormSemestre) -> list[FormSemestre]:
|
||||||
"""Liste des semestres sources
|
"""Liste des semestres sources
|
||||||
sem est le semestre destination
|
formsemestre est le semestre destination
|
||||||
"""
|
"""
|
||||||
# liste des semestres débutant a moins
|
# liste des semestres du même type de cursus terminant
|
||||||
# de delai (en jours) de la date de fin du semestre d'origine.
|
# pas trop loin de la date de début du semestre destination
|
||||||
sems = sco_formsemestre.do_formsemestre_list()
|
date_fin_min = formsemestre.date_debut - datetime.timedelta(days=275)
|
||||||
othersems = []
|
date_fin_max = formsemestre.date_debut + datetime.timedelta(days=45)
|
||||||
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
return (
|
||||||
date_debut_dst = datetime.date(y, m, d)
|
FormSemestre.query.filter(
|
||||||
|
FormSemestre.dept_id == formsemestre.dept_id,
|
||||||
delais = datetime.timedelta(delai)
|
# saute le semestre destination:
|
||||||
for s in sems:
|
FormSemestre.id != formsemestre.id,
|
||||||
if s["formsemestre_id"] == sem["formsemestre_id"]:
|
# et les semestres de formations speciales (monosemestres):
|
||||||
continue # saute le semestre destination
|
FormSemestre.semestre_id != codes_cursus.NO_SEMESTRE_ID,
|
||||||
if s["date_fin"]:
|
# semestre pas trop dans le futur
|
||||||
d, m, y = [int(x) for x in s["date_fin"].split("/")]
|
FormSemestre.date_fin <= date_fin_max,
|
||||||
date_fin = datetime.date(y, m, d)
|
# ni trop loin dans le passé
|
||||||
if date_debut_dst - date_fin > delais:
|
FormSemestre.date_fin >= date_fin_min,
|
||||||
continue # semestre trop ancien
|
)
|
||||||
if date_fin > date_debut_dst:
|
.join(Formation)
|
||||||
continue # semestre trop récent
|
.filter_by(type_parcours=formsemestre.formation.type_parcours)
|
||||||
# Elimine les semestres de formations speciales (sans parcours)
|
).all()
|
||||||
if s["semestre_id"] == codes_cursus.NO_SEMESTRE_ID:
|
|
||||||
continue
|
|
||||||
#
|
|
||||||
formation: Formation = Formation.query.get_or_404(s["formation_id"])
|
|
||||||
parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
|
|
||||||
if not parcours.ALLOW_SEM_SKIP:
|
|
||||||
if s["semestre_id"] < (sem["semestre_id"] - 1):
|
|
||||||
continue
|
|
||||||
othersems.append(s)
|
|
||||||
return othersems
|
|
||||||
|
|
||||||
|
|
||||||
|
# view, GET, POST
|
||||||
def formsemestre_inscr_passage(
|
def formsemestre_inscr_passage(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
etuds=[],
|
etuds: str | list[int] | list[str] | int | None = None,
|
||||||
inscrit_groupes=False,
|
inscrit_groupes=False,
|
||||||
inscrit_parcours=False,
|
inscrit_parcours=False,
|
||||||
submitted=False,
|
submitted=False,
|
||||||
@ -300,36 +305,42 @@ def formsemestre_inscr_passage(
|
|||||||
- Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant.
|
- Confirmation: indiquer les étudiants inscrits et ceux désinscrits, le total courant.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
inscrit_parcours = int(inscrit_parcours)
|
inscrit_parcours = int(inscrit_parcours)
|
||||||
ignore_jury = int(ignore_jury)
|
ignore_jury = int(ignore_jury)
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
||||||
# -- check lock
|
# -- check lock
|
||||||
if not sem["etat"]:
|
if not formsemestre.etat:
|
||||||
raise ScoValueError("opération impossible: semestre verrouille")
|
raise ScoValueError("opération impossible: semestre verrouille")
|
||||||
header = html_sco_header.sco_header(page_title="Passage des étudiants")
|
H = [
|
||||||
|
html_sco_header.sco_header(
|
||||||
|
page_title="Passage des étudiants",
|
||||||
|
init_qtip=True,
|
||||||
|
javascripts=["js/etud_info.js"],
|
||||||
|
)
|
||||||
|
]
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
H = [header]
|
etuds = [] if etuds is None else etuds
|
||||||
if isinstance(etuds, str):
|
if isinstance(etuds, str):
|
||||||
# list de strings, vient du form de confirmation
|
# string, vient du form de confirmation
|
||||||
etuds = [int(x) for x in etuds.split(",") if x]
|
etuds = [int(x) for x in etuds.split(",") if x]
|
||||||
elif isinstance(etuds, int):
|
elif isinstance(etuds, int):
|
||||||
etuds = [etuds]
|
etuds = [etuds]
|
||||||
elif etuds and isinstance(etuds[0], str):
|
elif etuds and isinstance(etuds[0], str):
|
||||||
etuds = [int(x) for x in etuds]
|
etuds = [int(x) for x in etuds]
|
||||||
|
|
||||||
auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(
|
auth_etuds_by_sem, inscrits, candidats = _list_authorized_etuds_by_sem(
|
||||||
sem, ignore_jury=ignore_jury
|
formsemestre, ignore_jury=ignore_jury
|
||||||
)
|
)
|
||||||
etuds_set = set(etuds)
|
etuds_set = set(etuds)
|
||||||
candidats_set = set(candidats)
|
candidats_set = set(candidats)
|
||||||
inscrits_set = set(inscrits)
|
inscrits_set = set(inscrits)
|
||||||
candidats_non_inscrits = candidats_set - inscrits_set
|
candidats_non_inscrits = candidats_set - inscrits_set
|
||||||
inscrits_ailleurs = set(list_inscrits_date(sem))
|
inscrits_ailleurs = set(list_inscrits_date(formsemestre))
|
||||||
|
|
||||||
def set_to_sorted_etud_list(etudset):
|
def set_to_sorted_etud_list(etudset) -> list[Identite]:
|
||||||
etuds = [candidats[etudid] for etudid in etudset]
|
etuds = [candidats[etudid] for etudid in etudset]
|
||||||
etuds.sort(key=itemgetter("nom"))
|
etuds.sort(key=lambda e: e.sort_key)
|
||||||
return etuds
|
return etuds
|
||||||
|
|
||||||
if submitted:
|
if submitted:
|
||||||
@ -340,7 +351,7 @@ def formsemestre_inscr_passage(
|
|||||||
|
|
||||||
if not submitted:
|
if not submitted:
|
||||||
H += _build_page(
|
H += _build_page(
|
||||||
sem,
|
formsemestre,
|
||||||
auth_etuds_by_sem,
|
auth_etuds_by_sem,
|
||||||
inscrits,
|
inscrits,
|
||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
@ -355,30 +366,31 @@ def formsemestre_inscr_passage(
|
|||||||
if a_inscrire:
|
if a_inscrire:
|
||||||
H.append("<h3>Étudiants à inscrire</h3><ol>")
|
H.append("<h3>Étudiants à inscrire</h3><ol>")
|
||||||
for etud in set_to_sorted_etud_list(a_inscrire):
|
for etud in set_to_sorted_etud_list(a_inscrire):
|
||||||
H.append("<li>%(nomprenom)s</li>" % etud)
|
H.append(f"<li>{etud.nomprenom}</li>")
|
||||||
H.append("</ol>")
|
H.append("</ol>")
|
||||||
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
||||||
if a_inscrire_en_double:
|
if a_inscrire_en_double:
|
||||||
H.append("<h3>dont étudiants déjà inscrits:</h3><ul>")
|
H.append("<h3>dont étudiants déjà inscrits:</h3><ul>")
|
||||||
for etud in set_to_sorted_etud_list(a_inscrire_en_double):
|
for etud in set_to_sorted_etud_list(a_inscrire_en_double):
|
||||||
H.append('<li class="inscrailleurs">%(nomprenom)s</li>' % etud)
|
H.append(f'<li class="inscrit-ailleurs">{etud.nomprenom}</li>')
|
||||||
H.append("</ul>")
|
H.append("</ul>")
|
||||||
if a_desinscrire:
|
if a_desinscrire:
|
||||||
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
H.append("<h3>Étudiants à désinscrire</h3><ol>")
|
||||||
for etudid in a_desinscrire:
|
a_desinscrire_ident = sorted(
|
||||||
H.append(
|
(Identite.query.get(eid) for eid in a_desinscrire),
|
||||||
'<li class="desinscription">%(nomprenom)s</li>'
|
key=lambda x: x.sort_key,
|
||||||
% inscrits[etudid]
|
|
||||||
)
|
)
|
||||||
|
for etud in a_desinscrire_ident:
|
||||||
|
H.append(f'<li class="desinscription">{etud.nomprenom}</li>')
|
||||||
H.append("</ol>")
|
H.append("</ol>")
|
||||||
todo = a_inscrire or a_desinscrire
|
todo = a_inscrire or a_desinscrire
|
||||||
if not todo:
|
if not todo:
|
||||||
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
|
H.append("""<h3>Il n'y a rien à modifier !</h3>""")
|
||||||
H.append(
|
H.append(
|
||||||
scu.confirm_dialog(
|
scu.confirm_dialog(
|
||||||
dest_url="formsemestre_inscr_passage"
|
dest_url=(
|
||||||
if todo
|
"formsemestre_inscr_passage" if todo else "formsemestre_status"
|
||||||
else "formsemestre_status",
|
),
|
||||||
message="<p>Confirmer ?</p>" if todo else "",
|
message="<p>Confirmer ?</p>" if todo else "",
|
||||||
add_headers=False,
|
add_headers=False,
|
||||||
cancel_url="formsemestre_inscr_passage?formsemestre_id="
|
cancel_url="formsemestre_inscr_passage?formsemestre_id="
|
||||||
@ -395,16 +407,26 @@ def formsemestre_inscr_passage(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# check decisions jury ici pour éviter de recontruire le cache
|
||||||
|
# après chaque desinscription
|
||||||
|
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||||
|
formsemestre, a_desinscrire
|
||||||
|
)
|
||||||
|
# check decisions jury ici pour éviter de recontruire le cache
|
||||||
|
# après chaque desinscription
|
||||||
|
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||||
|
formsemestre, a_desinscrire
|
||||||
|
)
|
||||||
with sco_cache.DeferredSemCacheManager():
|
with sco_cache.DeferredSemCacheManager():
|
||||||
# Inscription des étudiants au nouveau semestre:
|
# Inscription des étudiants au nouveau semestre:
|
||||||
do_inscrit(
|
do_inscrit(
|
||||||
sem,
|
formsemestre,
|
||||||
a_inscrire,
|
a_inscrire,
|
||||||
inscrit_groupes=inscrit_groupes,
|
inscrit_groupes=inscrit_groupes,
|
||||||
inscrit_parcours=inscrit_parcours,
|
inscrit_parcours=inscrit_parcours,
|
||||||
)
|
)
|
||||||
# Désinscriptions:
|
# Désinscriptions:
|
||||||
do_desinscrit(sem, a_desinscrire)
|
do_desinscrit(formsemestre, a_desinscrire, check_has_dec_jury=False)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""<h3>Opération effectuée</h3>
|
f"""<h3>Opération effectuée</h3>
|
||||||
@ -441,7 +463,7 @@ def formsemestre_inscr_passage(
|
|||||||
|
|
||||||
|
|
||||||
def _build_page(
|
def _build_page(
|
||||||
sem,
|
formsemestre: FormSemestre,
|
||||||
auth_etuds_by_sem,
|
auth_etuds_by_sem,
|
||||||
inscrits,
|
inscrits,
|
||||||
candidats_non_inscrits,
|
candidats_non_inscrits,
|
||||||
@ -450,7 +472,6 @@ def _build_page(
|
|||||||
inscrit_parcours=False,
|
inscrit_parcours=False,
|
||||||
ignore_jury=False,
|
ignore_jury=False,
|
||||||
):
|
):
|
||||||
formsemestre: FormSemestre = db.session.get(FormSemestre, sem["formsemestre_id"])
|
|
||||||
inscrit_groupes = int(inscrit_groupes)
|
inscrit_groupes = int(inscrit_groupes)
|
||||||
inscrit_parcours = int(inscrit_parcours)
|
inscrit_parcours = int(inscrit_parcours)
|
||||||
ignore_jury = int(ignore_jury)
|
ignore_jury = int(ignore_jury)
|
||||||
@ -472,7 +493,7 @@ def _build_page(
|
|||||||
),
|
),
|
||||||
f"""<form name="f" method="post" action="{request.base_url}">
|
f"""<form name="f" method="post" action="{request.base_url}">
|
||||||
|
|
||||||
<input type="hidden" name="formsemestre_id" value="{sem['formsemestre_id']}"/>
|
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"/>
|
||||||
|
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
<a href="#help">aide</a>
|
<a href="#help">aide</a>
|
||||||
@ -491,7 +512,7 @@ def _build_page(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>{scu.EMO_WARNING}
|
<div>{scu.EMO_WARNING}
|
||||||
<em>Seuls les semestres dont la date de fin est antérieure à la date de début
|
<em>Seuls les semestres dont la date de fin est proche de la date de début
|
||||||
de ce semestre ({formsemestre.date_debut.strftime("%d/%m/%Y")}) sont pris en
|
de ce semestre ({formsemestre.date_debut.strftime("%d/%m/%Y")}) sont pris en
|
||||||
compte.</em>
|
compte.</em>
|
||||||
</div>
|
</div>
|
||||||
@ -499,7 +520,7 @@ def _build_page(
|
|||||||
|
|
||||||
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
||||||
|
|
||||||
{formsemestre_inscr_passage_help(sem)}
|
{formsemestre_inscr_passage_help(formsemestre)}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
""",
|
""",
|
||||||
@ -524,19 +545,20 @@ def _build_page(
|
|||||||
return H
|
return H
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_inscr_passage_help(sem: dict):
|
def formsemestre_inscr_passage_help(formsemestre: FormSemestre):
|
||||||
"texte d'aide en bas de la page passage des étudiants"
|
"texte d'aide en bas de la page passage des étudiants"
|
||||||
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
return f"""<div class="pas_help"><h3><a name="help">Explications</a></h3>
|
||||||
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
<p>Cette page permet d'inscrire des étudiants dans le semestre destination
|
||||||
<a class="stdlink"
|
<a class="stdlink"
|
||||||
href="{
|
href="{
|
||||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=sem["formsemestre_id"] )
|
url_for("notes.formsemestre_status",
|
||||||
}">{sem['titreannee']}</a>,
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
|
||||||
|
}">{formsemestre.titre_annee()}</a>,
|
||||||
et d'en désincrire si besoin.
|
et d'en désincrire si besoin.
|
||||||
</p>
|
</p>
|
||||||
<p>Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères
|
<p>Les étudiants sont groupés par semestre d'origine. Ceux qui sont en caractères
|
||||||
<span class="inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
<span class="deja-inscrit">gras</span> sont déjà inscrits dans le semestre destination.
|
||||||
Ceux qui sont en <span class"inscrailleurs">gras et en rouge</span> sont inscrits
|
Ceux qui sont en <span class="inscrit-ailleurs">gras et en rouge</span> sont inscrits
|
||||||
dans un <em>autre</em> semestre.
|
dans un <em>autre</em> semestre.
|
||||||
</p>
|
</p>
|
||||||
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter
|
<p>Au départ, les étudiants déjà inscrits sont sélectionnés; vous pouvez ajouter
|
||||||
@ -555,7 +577,7 @@ def formsemestre_inscr_passage_help(sem: dict):
|
|||||||
conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans
|
conserve les groupes, on conserve les parcours (là aussi, pensez à les cocher dans
|
||||||
<a class="stdlink" href="{
|
<a class="stdlink" href="{
|
||||||
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
url_for("notes.formsemestre_editwithmodules", scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=sem["formsemestre_id"] )
|
formsemestre_id=formsemestre.id )
|
||||||
}">modifier le semestre</a> avant de faire passer les étudiants).
|
}">modifier le semestre</a> avant de faire passer les étudiants).
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -656,25 +678,24 @@ def etuds_select_boxes(
|
|||||||
H.append("</div>")
|
H.append("</div>")
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
if etud.get("inscrit", False):
|
if etud.get("inscrit", False):
|
||||||
c = " inscrit"
|
c = " deja-inscrit"
|
||||||
checked = 'checked="checked"'
|
checked = 'checked="checked"'
|
||||||
else:
|
else:
|
||||||
checked = ""
|
checked = ""
|
||||||
if etud["etudid"] in inscrits_ailleurs:
|
if etud["etudid"] in inscrits_ailleurs:
|
||||||
c = " inscrailleurs"
|
c = " inscrit-ailleurs"
|
||||||
else:
|
else:
|
||||||
c = ""
|
c = ""
|
||||||
sco_etud.format_etud_ident(etud)
|
sco_etud.format_etud_ident(etud)
|
||||||
if etud["etudid"]:
|
if etud["etudid"]:
|
||||||
elink = """<a class="discretelink %s" href="%s">%s</a>""" % (
|
elink = f"""<a id="{etud['etudid']}" class="discretelink etudinfo {c}"
|
||||||
c,
|
href="{ url_for(
|
||||||
url_for(
|
'scolar.fiche_etud',
|
||||||
"scolar.fiche_etud",
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
etudid=etud["etudid"],
|
etudid=etud['etudid'],
|
||||||
),
|
|
||||||
etud["nomprenom"],
|
|
||||||
)
|
)
|
||||||
|
}">{etud['nomprenom']}</a>
|
||||||
|
"""
|
||||||
else:
|
else:
|
||||||
# ce n'est pas un etudiant ScoDoc
|
# ce n'est pas un etudiant ScoDoc
|
||||||
elink = etud["nomprenom"]
|
elink = etud["nomprenom"]
|
||||||
|
@ -490,9 +490,9 @@ def _make_table_notes(
|
|||||||
rlinks = {"_table_part": "head"}
|
rlinks = {"_table_part": "head"}
|
||||||
for e in evaluations:
|
for e in evaluations:
|
||||||
rlinks[e.id] = "afficher"
|
rlinks[e.id] = "afficher"
|
||||||
rlinks[
|
rlinks["_" + str(e.id) + "_help"] = (
|
||||||
"_" + str(e.id) + "_help"
|
"afficher seulement les notes de cette évaluation"
|
||||||
] = "afficher seulement les notes de cette évaluation"
|
)
|
||||||
rlinks["_" + str(e.id) + "_target"] = url_for(
|
rlinks["_" + str(e.id) + "_target"] = url_for(
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -709,9 +709,9 @@ def _add_eval_columns(
|
|||||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||||
|
|
||||||
if evaluation.date_debut:
|
if evaluation.date_debut:
|
||||||
titles[
|
titles[evaluation.id] = (
|
||||||
evaluation.id
|
f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
||||||
] = f"{evaluation.description} ({evaluation.date_debut.strftime('%d/%m/%Y')})"
|
)
|
||||||
else:
|
else:
|
||||||
titles[evaluation.id] = f"{evaluation.description} "
|
titles[evaluation.id] = f"{evaluation.description} "
|
||||||
|
|
||||||
@ -820,14 +820,17 @@ def _add_eval_columns(
|
|||||||
row_moys[evaluation.id] = scu.fmt_note(
|
row_moys[evaluation.id] = scu.fmt_note(
|
||||||
sum_notes / nb_notes, keep_numeric=keep_numeric
|
sum_notes / nb_notes, keep_numeric=keep_numeric
|
||||||
)
|
)
|
||||||
row_moys[
|
row_moys["_" + str(evaluation.id) + "_help"] = (
|
||||||
"_" + str(evaluation.id) + "_help"
|
"moyenne sur %d notes (%s le %s)"
|
||||||
] = "moyenne sur %d notes (%s le %s)" % (
|
% (
|
||||||
nb_notes,
|
nb_notes,
|
||||||
evaluation.description,
|
evaluation.description,
|
||||||
|
(
|
||||||
evaluation.date_debut.strftime("%d/%m/%Y")
|
evaluation.date_debut.strftime("%d/%m/%Y")
|
||||||
if evaluation.date_debut
|
if evaluation.date_debut
|
||||||
else "",
|
else ""
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
row_moys[evaluation.id] = ""
|
row_moys[evaluation.id] = ""
|
||||||
@ -884,6 +887,7 @@ def _add_moymod_column(
|
|||||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||||
if etudid in inscrits and not isinstance(val, str):
|
if etudid in inscrits and not isinstance(val, str):
|
||||||
notes.append(val)
|
notes.append(val)
|
||||||
|
if not np.isnan(val):
|
||||||
nb_notes = nb_notes + 1
|
nb_notes = nb_notes + 1
|
||||||
sum_notes += val
|
sum_notes += val
|
||||||
row_coefs[col_id] = "(avec abs)"
|
row_coefs[col_id] = "(avec abs)"
|
||||||
|
@ -91,7 +91,9 @@ def do_moduleimpl_delete(oid, formsemestre_id=None):
|
|||||||
) # > moduleimpl_delete
|
) # > moduleimpl_delete
|
||||||
|
|
||||||
|
|
||||||
def moduleimpl_list(moduleimpl_id=None, formsemestre_id=None, module_id=None):
|
def moduleimpl_list(
|
||||||
|
moduleimpl_id=None, formsemestre_id=None, module_id=None
|
||||||
|
) -> list[dict]:
|
||||||
"list moduleimpls"
|
"list moduleimpls"
|
||||||
args = locals()
|
args = locals()
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
|
@ -617,7 +617,7 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
|
|||||||
<p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
|
<p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
|
||||||
mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
|
mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
|
||||||
</p>
|
</p>
|
||||||
<p>Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
|
<p>Il peut s'agir d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
|
||||||
présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres
|
présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres
|
||||||
cas particuliers.
|
cas particuliers.
|
||||||
</p>
|
</p>
|
||||||
|
@ -519,16 +519,22 @@ def _ligne_evaluation(
|
|||||||
partition_id=partition_id,
|
partition_id=partition_id,
|
||||||
select_first_partition=True,
|
select_first_partition=True,
|
||||||
)
|
)
|
||||||
if evaluation.evaluation_type in (
|
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||||
scu.EVALUATION_RATTRAPAGE,
|
|
||||||
scu.EVALUATION_SESSION2,
|
|
||||||
):
|
|
||||||
tr_class = "mievr mievr_rattr"
|
tr_class = "mievr mievr_rattr"
|
||||||
|
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||||
|
tr_class = "mievr mievr_session2"
|
||||||
|
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||||
|
tr_class = "mievr mievr_bonus"
|
||||||
else:
|
else:
|
||||||
tr_class = "mievr"
|
tr_class = "mievr"
|
||||||
|
|
||||||
if not evaluation.visibulletin:
|
if not evaluation.visibulletin:
|
||||||
tr_class += " non_visible_inter"
|
tr_class += " non_visible_inter"
|
||||||
tr_class_1 = "mievr"
|
tr_class_1 = "mievr"
|
||||||
|
if evaluation.is_blocked():
|
||||||
|
tr_class += " evaluation_blocked"
|
||||||
|
tr_class_1 += " evaluation_blocked"
|
||||||
|
|
||||||
if not first_eval:
|
if not first_eval:
|
||||||
H.append("""<tr><td colspan="8"> </td></tr>""")
|
H.append("""<tr><td colspan="8"> </td></tr>""")
|
||||||
tr_class_1 += " mievr_spaced"
|
tr_class_1 += " mievr_spaced"
|
||||||
@ -562,14 +568,18 @@ def _ligne_evaluation(
|
|||||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
}" class="mievr_evalnodate">Évaluation sans date</a>"""
|
}" class="mievr_evalnodate">Évaluation sans date</a>"""
|
||||||
)
|
)
|
||||||
H.append(f" <em>{evaluation.description or ''}</em>")
|
H.append(f" <em>{evaluation.description}</em>")
|
||||||
if evaluation.evaluation_type == scu.EVALUATION_RATTRAPAGE:
|
if evaluation.evaluation_type == Evaluation.EVALUATION_RATTRAPAGE:
|
||||||
H.append(
|
H.append(
|
||||||
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
|
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
|
||||||
)
|
)
|
||||||
elif evaluation.evaluation_type == scu.EVALUATION_SESSION2:
|
elif evaluation.evaluation_type == Evaluation.EVALUATION_SESSION2:
|
||||||
H.append(
|
H.append(
|
||||||
"""<span class="mievr_rattr" title="remplace autres notes">session 2</span>"""
|
"""<span class="mievr_session2" title="remplace autres notes">session 2</span>"""
|
||||||
|
)
|
||||||
|
elif evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||||
|
H.append(
|
||||||
|
"""<span class="mievr_bonus" title="s'ajoute aux moyennes de ce module">bonus</span>"""
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
if etat["last_modif"]:
|
if etat["last_modif"]:
|
||||||
@ -605,8 +615,15 @@ def _ligne_evaluation(
|
|||||||
else:
|
else:
|
||||||
H.append(arrow_none)
|
H.append(arrow_none)
|
||||||
|
|
||||||
if etat["evalcomplete"]:
|
if evaluation.is_blocked():
|
||||||
etat_txt = f"""(prise en compte{
|
etat_txt = f"""évaluation bloquée {
|
||||||
|
"jusqu'au " + evaluation.blocked_until.strftime("%d/%m/%Y")
|
||||||
|
if evaluation.blocked_until < Evaluation.BLOCKED_FOREVER
|
||||||
|
else "" }
|
||||||
|
"""
|
||||||
|
etat_descr = """prise en compte bloquée"""
|
||||||
|
elif etat["evalcomplete"]:
|
||||||
|
etat_txt = f"""Moyenne (prise en compte{
|
||||||
""
|
""
|
||||||
if evaluation.visibulletin
|
if evaluation.visibulletin
|
||||||
else ", cachée en intermédiaire"})
|
else ", cachée en intermédiaire"})
|
||||||
@ -615,7 +632,7 @@ def _ligne_evaluation(
|
|||||||
", évaluation cachée sur les bulletins en version intermédiaire et sur la passerelle"
|
", évaluation cachée sur les bulletins en version intermédiaire et sur la passerelle"
|
||||||
}"""
|
}"""
|
||||||
elif etat["evalattente"] and not evaluation.publish_incomplete:
|
elif etat["evalattente"] and not evaluation.publish_incomplete:
|
||||||
etat_txt = "(prise en compte, mais <b>notes en attente</b>)"
|
etat_txt = "Moyenne (prise en compte, mais <b>notes en attente</b>)"
|
||||||
etat_descr = "il y a des notes en attente"
|
etat_descr = "il y a des notes en attente"
|
||||||
elif evaluation.publish_incomplete:
|
elif evaluation.publish_incomplete:
|
||||||
etat_txt = """(prise en compte <b>immédiate</b>)"""
|
etat_txt = """(prise en compte <b>immédiate</b>)"""
|
||||||
@ -623,11 +640,12 @@ def _ligne_evaluation(
|
|||||||
"il manque des notes, mais la prise en compte immédiate a été demandée"
|
"il manque des notes, mais la prise en compte immédiate a été demandée"
|
||||||
)
|
)
|
||||||
elif etat["nb_notes"] != 0:
|
elif etat["nb_notes"] != 0:
|
||||||
etat_txt = "(<b>non</b> prise en compte)"
|
etat_txt = "Moyenne (<b>non</b> prise en compte)"
|
||||||
etat_descr = "il manque des notes"
|
etat_descr = "il manque des notes"
|
||||||
else:
|
else:
|
||||||
etat_txt = ""
|
etat_txt = ""
|
||||||
if can_edit_evals and etat_txt:
|
if etat_txt:
|
||||||
|
if can_edit_evals:
|
||||||
etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
|
etat_txt = f"""<a href="{ url_for("notes.evaluation_edit",
|
||||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
}" title="{etat_descr}">{etat_txt}</a>"""
|
}" title="{etat_descr}">{etat_txt}</a>"""
|
||||||
@ -635,16 +653,16 @@ def _ligne_evaluation(
|
|||||||
H.append(
|
H.append(
|
||||||
f"""</span></span></td>
|
f"""</span></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="{tr_class}">
|
<tr class="{tr_class} mievr_in">
|
||||||
<th class="moduleimpl_evaluations" colspan="2"> </th>
|
<th class="moduleimpl_evaluations" colspan="2"> </th>
|
||||||
<th class="moduleimpl_evaluations">Durée</th>
|
<th class="moduleimpl_evaluations">Durée</th>
|
||||||
<th class="moduleimpl_evaluations">Coef.</th>
|
<th class="moduleimpl_evaluations">Coef.</th>
|
||||||
<th class="moduleimpl_evaluations">Notes</th>
|
<th class="moduleimpl_evaluations">Notes</th>
|
||||||
<th class="moduleimpl_evaluations">Abs</th>
|
<th class="moduleimpl_evaluations">Abs</th>
|
||||||
<th class="moduleimpl_evaluations">N</th>
|
<th class="moduleimpl_evaluations">N</th>
|
||||||
<th class="moduleimpl_evaluations" colspan="2">Moyenne {etat_txt}</th>
|
<th class="moduleimpl_evaluations moduleimpl_evaluation_moy" colspan="2"><span>{etat_txt}</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="{tr_class}">
|
<tr class="{tr_class} mievr_in">
|
||||||
<td class="mievr">"""
|
<td class="mievr">"""
|
||||||
)
|
)
|
||||||
if can_edit_evals:
|
if can_edit_evals:
|
||||||
@ -826,7 +844,7 @@ def _evaluation_poids_html(evaluation: Evaluation, max_poids: float = 0.0) -> st
|
|||||||
+ "\n".join(
|
+ "\n".join(
|
||||||
[
|
[
|
||||||
f"""<div title="poids vers {ue.acronyme}: {poids:g}">
|
f"""<div title="poids vers {ue.acronyme}: {poids:g}">
|
||||||
<div style="--size:{math.sqrt(poids*(evaluation.coefficient or 0.)/max_poids*144)}px;
|
<div style="--size:{math.sqrt(poids*(evaluation.coefficient)/max_poids*144)}px;
|
||||||
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
{'background-color: ' + ue.color + ';' if ue.color else ''}
|
||||||
"></div>
|
"></div>
|
||||||
</div>"""
|
</div>"""
|
||||||
|
@ -36,7 +36,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.but import cursus_but
|
from app.but import cursus_but, validations_view
|
||||||
from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig
|
from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig
|
||||||
from app.scodoc import (
|
from app.scodoc import (
|
||||||
codes_cursus,
|
codes_cursus,
|
||||||
@ -445,6 +445,14 @@ def fiche_etud(etudid=None):
|
|||||||
# Liens vers compétences BUT
|
# Liens vers compétences BUT
|
||||||
if last_formsemestre and last_formsemestre.formation.is_apc():
|
if last_formsemestre and last_formsemestre.formation.is_apc():
|
||||||
but_cursus = cursus_but.EtudCursusBUT(etud, last_formsemestre.formation)
|
but_cursus = cursus_but.EtudCursusBUT(etud, last_formsemestre.formation)
|
||||||
|
refcomp = last_formsemestre.formation.referentiel_competence
|
||||||
|
if refcomp:
|
||||||
|
ue_validation_by_niveau = validations_view.get_ue_validation_by_niveau(
|
||||||
|
refcomp, etud
|
||||||
|
)
|
||||||
|
ects_total = sum((v.ects() for v in ue_validation_by_niveau.values()))
|
||||||
|
else:
|
||||||
|
ects_total = ""
|
||||||
info[
|
info[
|
||||||
"but_cursus_mkup"
|
"but_cursus_mkup"
|
||||||
] = f"""
|
] = f"""
|
||||||
@ -454,6 +462,7 @@ def fiche_etud(etudid=None):
|
|||||||
cursus=but_cursus,
|
cursus=but_cursus,
|
||||||
scu=scu,
|
scu=scu,
|
||||||
)}
|
)}
|
||||||
|
<div class="fiche_but_col2">
|
||||||
<div class="link_validation_rcues">
|
<div class="link_validation_rcues">
|
||||||
<a class="stdlink" href="{url_for("notes.validation_rcues",
|
<a class="stdlink" href="{url_for("notes.validation_rcues",
|
||||||
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||||
@ -461,9 +470,13 @@ def fiche_etud(etudid=None):
|
|||||||
title="Visualiser les compétences BUT"
|
title="Visualiser les compétences BUT"
|
||||||
>
|
>
|
||||||
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/>
|
||||||
<div>Compétences BUT</div>
|
<div style="text-align: center;">Compétences BUT</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="fiche_total_etcs">
|
||||||
|
Total ECTS BUT: {ects_total:g}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
|
@ -48,20 +48,17 @@ from wtforms import (
|
|||||||
HiddenField,
|
HiddenField,
|
||||||
SelectMultipleField,
|
SelectMultipleField,
|
||||||
)
|
)
|
||||||
from app.models import ModuleImpl
|
from app.models import Evaluation, ModuleImpl
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app import ScoValueError
|
|
||||||
from app.scodoc import html_sco_header, sco_preferences
|
from app.scodoc import html_sco_header, sco_preferences
|
||||||
from app.scodoc import sco_edit_module
|
from app.scodoc import sco_edit_module
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_excel
|
from app.scodoc import sco_excel
|
||||||
from app.scodoc.sco_excel import ScoExcelBook, COLORS
|
from app.scodoc.sco_excel import ScoExcelBook, COLORS
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_permissions_check
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
import sco_version
|
import sco_version
|
||||||
@ -138,11 +135,7 @@ class PlacementForm(FlaskForm):
|
|||||||
|
|
||||||
def set_evaluation_infos(self, evaluation_id):
|
def set_evaluation_infos(self, evaluation_id):
|
||||||
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
||||||
eval_data = sco_evaluation_db.get_evaluations_dict(
|
_ = Evaluation.get_evaluation(evaluation_id) # check exist ?
|
||||||
{"evaluation_id": evaluation_id}
|
|
||||||
)
|
|
||||||
if not eval_data:
|
|
||||||
raise ScoValueError("invalid evaluation_id")
|
|
||||||
self.groups_tree, self.has_groups, self.nb_groups = _get_group_info(
|
self.groups_tree, self.has_groups, self.nb_groups = _get_group_info(
|
||||||
evaluation_id
|
evaluation_id
|
||||||
)
|
)
|
||||||
@ -239,14 +232,12 @@ class PlacementRunner:
|
|||||||
self.groups_ids = [
|
self.groups_ids = [
|
||||||
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
||||||
]
|
]
|
||||||
self.eval_data = sco_evaluation_db.get_evaluations_dict(
|
self.evaluation = Evaluation.get_evaluation(self.evaluation_id)
|
||||||
{"evaluation_id": self.evaluation_id}
|
|
||||||
)[0]
|
|
||||||
self.groups = sco_groups.listgroups(self.groups_ids)
|
self.groups = sco_groups.listgroups(self.groups_ids)
|
||||||
self.gr_title_filename = sco_groups.listgroups_filename(self.groups)
|
self.gr_title_filename = sco_groups.listgroups_filename(self.groups)
|
||||||
# gr_title = sco_groups.listgroups_abbrev(d['groups'])
|
# gr_title = sco_groups.listgroups_abbrev(d['groups'])
|
||||||
self.current_user = current_user
|
self.current_user = current_user
|
||||||
self.moduleimpl_id = self.eval_data["moduleimpl_id"]
|
self.moduleimpl_id = self.evaluation.moduleimpl_id
|
||||||
self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id)
|
self.moduleimpl: ModuleImpl = ModuleImpl.query.get_or_404(self.moduleimpl_id)
|
||||||
# TODO: à revoir pour utiliser modèle ModuleImpl
|
# TODO: à revoir pour utiliser modèle ModuleImpl
|
||||||
self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
|
self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
|
||||||
@ -260,20 +251,25 @@ class PlacementRunner:
|
|||||||
)
|
)
|
||||||
self.evalname = "%s-%s" % (
|
self.evalname = "%s-%s" % (
|
||||||
self.module_data["code"] or "?",
|
self.module_data["code"] or "?",
|
||||||
ndb.DateDMYtoISO(self.eval_data["jour"]),
|
(
|
||||||
|
self.evaluation.date_debut.strftime("%Y-%m-%d_%Hh%M")
|
||||||
|
if self.evaluation.date_debut
|
||||||
|
else ""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if self.eval_data["description"]:
|
if self.evaluation.description:
|
||||||
self.evaltitre = self.eval_data["description"]
|
self.evaltitre = self.evaluation.description
|
||||||
else:
|
else:
|
||||||
self.evaltitre = "évaluation du %s" % self.eval_data["jour"]
|
self.evaltitre = f"""évaluation{
|
||||||
|
self.evaluation.date_debut.strftime(' du %d/%m/%Y à %Hh%M')
|
||||||
|
if self.evaluation.date_debut else ''}"""
|
||||||
self.desceval = [ # une liste de chaines: description de l'evaluation
|
self.desceval = [ # une liste de chaines: description de l'evaluation
|
||||||
"%s" % self.sem["titreannee"],
|
self.sem["titreannee"],
|
||||||
"Module : %s - %s"
|
"Module : %s - %s"
|
||||||
% (self.module_data["code"] or "?", self.module_data["abbrev"] or ""),
|
% (self.module_data["code"] or "?", self.module_data["abbrev"] or ""),
|
||||||
"Surveillants : %s" % self.surveillants,
|
"Surveillants : %s" % self.surveillants,
|
||||||
"Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
|
"Batiment : %(batiment)s - Salle : %(salle)s" % self.__dict__,
|
||||||
"Controle : %s (coef. %g)"
|
"Controle : %s (coef. %g)" % (self.evaltitre, self.evaluation.coefficient),
|
||||||
% (self.evaltitre, self.eval_data["coefficient"]),
|
|
||||||
]
|
]
|
||||||
self.styles = None
|
self.styles = None
|
||||||
self.plan = None
|
self.plan = None
|
||||||
@ -339,10 +335,10 @@ class PlacementRunner:
|
|||||||
|
|
||||||
def _production_pdf(self):
|
def _production_pdf(self):
|
||||||
pdf_title = "<br>".join(self.desceval)
|
pdf_title = "<br>".join(self.desceval)
|
||||||
pdf_title += (
|
pdf_title += f"""\nDate : {self.evaluation.date_debut.strftime("%d/%m/%Y")
|
||||||
"\nDate : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
|
if self.evaluation.date_debut else '-'
|
||||||
% self.eval_data
|
} - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin()
|
||||||
)
|
}"""
|
||||||
filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__
|
filename = "placement_%(evalname)s_%(gr_title_filename)s" % self.__dict__
|
||||||
titles = {
|
titles = {
|
||||||
"nom": "Nom",
|
"nom": "Nom",
|
||||||
@ -489,8 +485,10 @@ class PlacementRunner:
|
|||||||
worksheet.append_blank_row()
|
worksheet.append_blank_row()
|
||||||
worksheet.append_single_cell_row(desceval, self.styles["titres"])
|
worksheet.append_single_cell_row(desceval, self.styles["titres"])
|
||||||
worksheet.append_single_cell_row(
|
worksheet.append_single_cell_row(
|
||||||
"Date : %(jour)s - Horaire : %(heure_debut)s à %(heure_fin)s"
|
f"""Date : {self.evaluation.date_debut.strftime("%d/%m/%Y")
|
||||||
% self.eval_data,
|
if self.evaluation.date_debut else '-'
|
||||||
|
} - Horaire : {self.evaluation.heure_debut()} à {self.evaluation.heure_fin()
|
||||||
|
}""",
|
||||||
self.styles["titres"],
|
self.styles["titres"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,9 +72,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
|||||||
moy_ues.append(
|
moy_ues.append(
|
||||||
(
|
(
|
||||||
ue["acronyme"],
|
ue["acronyme"],
|
||||||
scu.fmt_note(
|
scu.fmt_note(ue_status["moy"]),
|
||||||
nt.get_etud_ue_status(etudid, ue["ue_id"])["moy"]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -134,12 +134,12 @@ def _displayNote(val):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
def _check_notes(notes: list[(int, float | str)], evaluation: Evaluation):
|
||||||
# XXX typehint : float or str
|
|
||||||
"""notes is a list of tuples (etudid, value)
|
"""notes is a list of tuples (etudid, value)
|
||||||
mod is the module (used to ckeck type, for malus)
|
mod is the module (used to ckeck type, for malus)
|
||||||
returns list of valid notes (etudid, float value)
|
returns list of valid notes (etudid, float value)
|
||||||
and 4 lists of etudid: etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
and 4 lists of etudid:
|
||||||
|
etudids_invalids, etudids_without_notes, etudids_absents, etudid_to_suppress
|
||||||
"""
|
"""
|
||||||
note_max = evaluation.note_max or 0.0
|
note_max = evaluation.note_max or 0.0
|
||||||
module: Module = evaluation.moduleimpl.module
|
module: Module = evaluation.moduleimpl.module
|
||||||
@ -148,6 +148,9 @@ def _check_notes(notes: list[(int, float)], evaluation: Evaluation):
|
|||||||
scu.ModuleType.RESSOURCE,
|
scu.ModuleType.RESSOURCE,
|
||||||
scu.ModuleType.SAE,
|
scu.ModuleType.SAE,
|
||||||
):
|
):
|
||||||
|
if evaluation.evaluation_type == Evaluation.EVALUATION_BONUS:
|
||||||
|
note_min, note_max = -20, 20
|
||||||
|
else:
|
||||||
note_min = scu.NOTES_MIN
|
note_min = scu.NOTES_MIN
|
||||||
elif module.module_type == ModuleType.MALUS:
|
elif module.module_type == ModuleType.MALUS:
|
||||||
note_min = -20.0
|
note_min = -20.0
|
||||||
@ -881,7 +884,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
if evaluation.date_debut:
|
if evaluation.date_debut:
|
||||||
indication_date = evaluation.date_debut.date().isoformat()
|
indication_date = evaluation.date_debut.date().isoformat()
|
||||||
else:
|
else:
|
||||||
indication_date = scu.sanitize_filename(evaluation.description or "")[:12]
|
indication_date = scu.sanitize_filename(evaluation.description)[:12]
|
||||||
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
eval_name = f"{evaluation.moduleimpl.module.code}-{indication_date}"
|
||||||
|
|
||||||
date_str = (
|
date_str = (
|
||||||
|
@ -35,7 +35,7 @@ from flask import g, url_for
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import Admission, Adresse, Identite, ScolarNews
|
from app.models import Admission, Adresse, FormSemestre, Identite, ScolarNews
|
||||||
|
|
||||||
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
|
||||||
@ -94,6 +94,7 @@ def formsemestre_synchro_etuds(
|
|||||||
que l'on va importer/inscrire
|
que l'on va importer/inscrire
|
||||||
"""
|
"""
|
||||||
etuds = etuds or []
|
etuds = etuds or []
|
||||||
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
inscrits_without_key = inscrits_without_key or []
|
inscrits_without_key = inscrits_without_key or []
|
||||||
log(f"formsemestre_synchro_etuds: formsemestre_id={formsemestre_id}")
|
log(f"formsemestre_synchro_etuds: formsemestre_id={formsemestre_id}")
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
@ -109,12 +110,13 @@ def formsemestre_synchro_etuds(
|
|||||||
raise ScoValueError("opération impossible: semestre verrouille")
|
raise ScoValueError("opération impossible: semestre verrouille")
|
||||||
if not sem["etapes"]:
|
if not sem["etapes"]:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""opération impossible: ce semestre n'a pas de code étape
|
f"""opération impossible: ce semestre n'a pas de code étape
|
||||||
(voir "<a href="formsemestre_editwithmodules?formsemestre_id=%(formsemestre_id)s">Modifier ce semestre</a>")
|
(voir <a class="stdlink" href="{
|
||||||
|
url_for('notes.formsemestre_editwithmodules',
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||||
|
}">Modifier ce semestre</a>)
|
||||||
"""
|
"""
|
||||||
% sem
|
|
||||||
)
|
)
|
||||||
header = html_sco_header.sco_header(page_title="Synchronisation étudiants")
|
|
||||||
footer = html_sco_header.sco_footer()
|
footer = html_sco_header.sco_footer()
|
||||||
base_url = url_for(
|
base_url = url_for(
|
||||||
"notes.formsemestre_synchro_etuds",
|
"notes.formsemestre_synchro_etuds",
|
||||||
@ -165,7 +167,13 @@ def formsemestre_synchro_etuds(
|
|||||||
suffix=scu.XLSX_SUFFIX,
|
suffix=scu.XLSX_SUFFIX,
|
||||||
)
|
)
|
||||||
|
|
||||||
H = [header]
|
H = [
|
||||||
|
html_sco_header.sco_header(
|
||||||
|
page_title="Synchronisation étudiants",
|
||||||
|
init_qtip=True,
|
||||||
|
javascripts=["js/etud_info.js"],
|
||||||
|
)
|
||||||
|
]
|
||||||
if not submitted:
|
if not submitted:
|
||||||
H += _build_page(
|
H += _build_page(
|
||||||
sem,
|
sem,
|
||||||
@ -184,7 +192,7 @@ def formsemestre_synchro_etuds(
|
|||||||
inscrits_without_key
|
inscrits_without_key
|
||||||
)
|
)
|
||||||
log("a_desinscrire_without_key=%s" % a_desinscrire_without_key)
|
log("a_desinscrire_without_key=%s" % a_desinscrire_without_key)
|
||||||
inscrits_ailleurs = set(sco_inscr_passage.list_inscrits_date(sem))
|
inscrits_ailleurs = set(sco_inscr_passage.list_inscrits_date(formsemestre))
|
||||||
a_inscrire = a_inscrire.intersection(etuds_set)
|
a_inscrire = a_inscrire.intersection(etuds_set)
|
||||||
|
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
@ -205,10 +213,12 @@ def formsemestre_synchro_etuds(
|
|||||||
|
|
||||||
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
a_inscrire_en_double = inscrits_ailleurs.intersection(a_inscrire)
|
||||||
if a_inscrire_en_double:
|
if a_inscrire_en_double:
|
||||||
H.append("<h3>dont étudiants déjà inscrits:</h3><ol>")
|
H.append(
|
||||||
|
"<h3>dont étudiants déjà inscrits dans un autre semestre:</h3><ol>"
|
||||||
|
)
|
||||||
for key in a_inscrire_en_double:
|
for key in a_inscrire_en_double:
|
||||||
nom = f"""{etudsapo_ident[key]['nom']} {etudsapo_ident[key].get("prenom", "")}"""
|
nom = f"""{etudsapo_ident[key]['nom']} {etudsapo_ident[key].get("prenom", "")}"""
|
||||||
H.append(f'<li class="inscrailleurs">{nom}</li>')
|
H.append(f'<li class="inscrit-ailleurs">{nom}</li>')
|
||||||
H.append("</ol>")
|
H.append("</ol>")
|
||||||
|
|
||||||
if a_desinscrire:
|
if a_desinscrire:
|
||||||
@ -260,16 +270,26 @@ def formsemestre_synchro_etuds(
|
|||||||
etudids_a_desinscrire = [nip2etudid(x) for x in a_desinscrire]
|
etudids_a_desinscrire = [nip2etudid(x) for x in a_desinscrire]
|
||||||
etudids_a_desinscrire += a_desinscrire_without_key
|
etudids_a_desinscrire += a_desinscrire_without_key
|
||||||
#
|
#
|
||||||
|
# check decisions jury ici pour éviter de recontruire le cache
|
||||||
|
# après chaque desinscription
|
||||||
|
sco_formsemestre_inscriptions.check_if_has_decision_jury(
|
||||||
|
formsemestre, a_desinscrire
|
||||||
|
)
|
||||||
with sco_cache.DeferredSemCacheManager():
|
with sco_cache.DeferredSemCacheManager():
|
||||||
do_import_etuds_from_portal(sem, a_importer, etudsapo_ident)
|
do_import_etuds_from_portal(formsemestre, a_importer, etudsapo_ident)
|
||||||
sco_inscr_passage.do_inscrit(sem, etudids_a_inscrire)
|
sco_inscr_passage.do_inscrit(formsemestre, etudids_a_inscrire)
|
||||||
sco_inscr_passage.do_desinscrit(sem, etudids_a_desinscrire)
|
sco_inscr_passage.do_desinscrit(
|
||||||
|
formsemestre, etudids_a_desinscrire, check_has_dec_jury=False
|
||||||
|
)
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
"""<h3>Opération effectuée</h3>
|
f"""<h3>Opération effectuée</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a class="stdlink" href="formsemestre_synchro_etuds?formsemestre_id=%s">Continuer la synchronisation</a></li>"""
|
<li><a class="stdlink" href="{
|
||||||
% formsemestre_id
|
url_for('notes.formsemestre_synchro_etuds',
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||||
|
)}">Continuer la synchronisation</a>
|
||||||
|
</li>"""
|
||||||
)
|
)
|
||||||
#
|
#
|
||||||
partitions = sco_groups.get_partitions_list(
|
partitions = sco_groups.get_partitions_list(
|
||||||
@ -279,8 +299,9 @@ def formsemestre_synchro_etuds(
|
|||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="stdlink" href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for("scolar.partition_editor",
|
url_for("scolar.partition_editor",
|
||||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
|
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id
|
||||||
}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
|
)}">Répartir les groupes de {partitions[0]["partition_name"]}</a>
|
||||||
|
</li>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -618,7 +639,7 @@ def get_annee_naissance(ddmmyyyyy: str) -> int:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
def do_import_etuds_from_portal(formsemestre: FormSemestre, a_importer, etudsapo_ident):
|
||||||
"""Inscrit les etudiants Apogee dans ce semestre."""
|
"""Inscrit les etudiants Apogee dans ce semestre."""
|
||||||
log(f"do_import_etuds_from_portal: a_importer={a_importer}")
|
log(f"do_import_etuds_from_portal: a_importer={a_importer}")
|
||||||
if not a_importer:
|
if not a_importer:
|
||||||
@ -672,7 +693,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
|||||||
|
|
||||||
# Inscription au semestre
|
# Inscription au semestre
|
||||||
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||||
sem["formsemestre_id"],
|
formsemestre.id,
|
||||||
etud.id,
|
etud.id,
|
||||||
etat=scu.INSCRIT,
|
etat=scu.INSCRIT,
|
||||||
etape=args["etape"],
|
etape=args["etape"],
|
||||||
@ -716,7 +737,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
|
|||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_INSCR,
|
typ=ScolarNews.NEWS_INSCR,
|
||||||
text=f"Import Apogée de {len(created_etudids)} étudiants en ",
|
text=f"Import Apogée de {len(created_etudids)} étudiants en ",
|
||||||
obj=sem["formsemestre_id"],
|
obj=formsemestre.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ def external_ue_inscrit_et_note(
|
|||||||
note_max=20.0,
|
note_max=20.0,
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
publish_incomplete=True,
|
publish_incomplete=True,
|
||||||
evaluation_type=scu.EVALUATION_NORMALE,
|
evaluation_type=Evaluation.EVALUATION_NORMALE,
|
||||||
visibulletin=False,
|
visibulletin=False,
|
||||||
description="note externe",
|
description="note externe",
|
||||||
)
|
)
|
||||||
|
@ -48,16 +48,15 @@ Opérations:
|
|||||||
import datetime
|
import datetime
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from app.models import FormSemestre
|
from app.models import Evaluation, FormSemestre
|
||||||
from app.scodoc.intervals import intervalmap
|
from app.scodoc.intervals import intervalmap
|
||||||
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 import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_moduleimpl
|
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_users
|
from app.scodoc import sco_users
|
||||||
import sco_version
|
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
|
import sco_version
|
||||||
|
|
||||||
# deux notes (de même uid) sont considérées comme de la même opération si
|
# deux notes (de même uid) sont considérées comme de la même opération si
|
||||||
# elles sont séparées de moins de 2*tolerance:
|
# elles sont séparées de moins de 2*tolerance:
|
||||||
@ -149,10 +148,8 @@ def list_operations(evaluation_id):
|
|||||||
|
|
||||||
def evaluation_list_operations(evaluation_id):
|
def evaluation_list_operations(evaluation_id):
|
||||||
"""Page listing operations on evaluation"""
|
"""Page listing operations on evaluation"""
|
||||||
E = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id})[0]
|
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
operations = list_operations(evaluation_id)
|
||||||
|
|
||||||
Ops = list_operations(evaluation_id)
|
|
||||||
|
|
||||||
columns_ids = ("datestr", "user_name", "nb_notes", "comment")
|
columns_ids = ("datestr", "user_name", "nb_notes", "comment")
|
||||||
titles = {
|
titles = {
|
||||||
@ -164,11 +161,14 @@ def evaluation_list_operations(evaluation_id):
|
|||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
titles=titles,
|
titles=titles,
|
||||||
columns_ids=columns_ids,
|
columns_ids=columns_ids,
|
||||||
rows=Ops,
|
rows=operations,
|
||||||
html_sortable=False,
|
html_sortable=False,
|
||||||
html_title="<h2>Opérations sur l'évaluation %s du %s</h2>"
|
html_title=f"""<h2>Opérations sur l'évaluation {evaluation.description} {
|
||||||
% (E["description"], E["jour"]),
|
evaluation.date_debut.strftime("du %d/%m/%Y") if evaluation.date_debut else "(sans date)"
|
||||||
preferences=sco_preferences.SemPreferences(M["formsemestre_id"]),
|
}</h2>""",
|
||||||
|
preferences=sco_preferences.SemPreferences(
|
||||||
|
evaluation.moduleimpl.formsemestre_id
|
||||||
|
),
|
||||||
)
|
)
|
||||||
return tab.make_page()
|
return tab.make_page()
|
||||||
|
|
||||||
|
@ -454,10 +454,6 @@ NOTES_MENTIONS_LABS = (
|
|||||||
"Excellent",
|
"Excellent",
|
||||||
)
|
)
|
||||||
|
|
||||||
EVALUATION_NORMALE = 0
|
|
||||||
EVALUATION_RATTRAPAGE = 1
|
|
||||||
EVALUATION_SESSION2 = 2
|
|
||||||
|
|
||||||
# Dates et années scolaires
|
# Dates et années scolaires
|
||||||
# Ces dates "pivot" sont paramétrables dans les préférences générales
|
# Ces dates "pivot" sont paramétrables dans les préférences générales
|
||||||
# on donne ici les valeurs par défaut.
|
# on donne ici les valeurs par défaut.
|
||||||
|
@ -25,12 +25,14 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)
|
"""Apogée: gestion du VDI avec le code étape (noms de fichiers maquettes et code semestres)
|
||||||
"""
|
"""
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class ApoEtapeVDI(object):
|
class ApoEtapeVDI(object):
|
||||||
|
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)"""
|
||||||
|
|
||||||
_ETAPE_VDI_SEP = "!"
|
_ETAPE_VDI_SEP = "!"
|
||||||
|
|
||||||
def __init__(self, etape_vdi: str = None, etape: str = "", vdi: str = ""):
|
def __init__(self, etape_vdi: str = None, etape: str = "", vdi: str = ""):
|
||||||
@ -110,7 +112,8 @@ class ApoEtapeVDI(object):
|
|||||||
elif len(t) == 2:
|
elif len(t) == 2:
|
||||||
etape, vdi = t
|
etape, vdi = t
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid code etape")
|
# code étape invalide
|
||||||
|
etape, vdi = "", ""
|
||||||
return etape, vdi
|
return etape, vdi
|
||||||
else:
|
else:
|
||||||
return etape_vdi, ""
|
return etape_vdi, ""
|
||||||
|
@ -35,6 +35,11 @@
|
|||||||
min-width: var(--sco-content-min-width);
|
min-width: var(--sco-content-min-width);
|
||||||
max-width: var(--sco-content-max-width);
|
max-width: var(--sco-content-max-width);
|
||||||
}
|
}
|
||||||
|
div.jury_but_warning {
|
||||||
|
background-color: yellow;
|
||||||
|
border-color: red;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
div.jury_but_box_title {
|
div.jury_but_box_title {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -273,6 +273,10 @@ section>div:nth-child(1) {
|
|||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
div.eval-bonus {
|
||||||
|
color: #197614;
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
|
||||||
.ueBonus,
|
.ueBonus,
|
||||||
.ueBonus h3 {
|
.ueBonus h3 {
|
||||||
|
@ -962,10 +962,18 @@ td.fichetitre2 .fl {
|
|||||||
div.section_but {
|
div.section_but {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
}
|
}
|
||||||
|
div.fiche_but_col2 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
div.fiche_total_etcs {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
div.section_but > div.link_validation_rcues {
|
div.section_but > div.link_validation_rcues {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -1461,6 +1469,9 @@ span.eval_title {
|
|||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#evaluation-edit-blocked td, #evaluation-edit-coef td {
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
/* #saisie_notes span.eval_title {
|
/* #saisie_notes span.eval_title {
|
||||||
border-bottom: 1px solid rgb(100,100,100);
|
border-bottom: 1px solid rgb(100,100,100);
|
||||||
}
|
}
|
||||||
@ -1793,11 +1804,42 @@ table.formsemestre_status {
|
|||||||
tr.formsemestre_status {
|
tr.formsemestre_status {
|
||||||
background-color: rgb(90%, 90%, 90%);
|
background-color: rgb(90%, 90%, 90%);
|
||||||
}
|
}
|
||||||
|
table.formsemestre_status tr td:first-child {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
table.formsemestre_status tr td:last-child {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
tr.formsemestre_status_green {
|
tr.formsemestre_status_green {
|
||||||
background-color: #eff7f2;
|
background-color: #eff7f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr.modimpl_non_conforme td {
|
||||||
|
background-color: #ffc458;
|
||||||
|
}
|
||||||
|
tr.modimpl_non_conforme td, tr.modimpl_attente td {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
tr.modimpl_has_blocked span.nb_evals_blocked, tr span.evals_attente {
|
||||||
|
background-color: yellow;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 8px;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
tr.modimpl_has_blocked span.nb_evals_blocked {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
tr span.evals_attente {
|
||||||
|
background-color: orange;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
table.formsemestre_status a.redlink {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
tr.formsemestre_status_ue {
|
tr.formsemestre_status_ue {
|
||||||
background-color: rgb(90%, 90%, 90%);
|
background-color: rgb(90%, 90%, 90%);
|
||||||
}
|
}
|
||||||
@ -2075,15 +2117,23 @@ th.moduleimpl_evaluations a:hover {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr.mievr_in.evaluation_blocked th.moduleimpl_evaluation_moy span, tr.evaluation_blocked th.moduleimpl_evaluation_moy a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
background-color: yellow;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
tr.mievr {
|
tr.mievr {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.mievr_rattr {
|
tr.mievr_rattr, tr.mievr_session2, tr.mievr_bonus {
|
||||||
background-color: #dddddd;
|
background-color: #dddddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.mievr_rattr {
|
span.mievr_rattr, span.mievr_session2, span.mievr_bonus {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
@ -2129,6 +2179,16 @@ tr.mievr.non_visible_inter th {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr.mievr_tit.evaluation_blocked td,tr.mievr_tit.evaluation_blocked th {
|
||||||
|
background-image: radial-gradient(#bd7777 1px, transparent 1px);
|
||||||
|
background-size: 10px 10px;
|
||||||
|
}
|
||||||
|
tr.mievr_in.evaluation_blocked td, tr.mievr_in.evaluation_blocked th {
|
||||||
|
background-color: rgb(195, 235, 255);
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
tr.mievr th {
|
tr.mievr th {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
@ -2139,6 +2199,7 @@ tr.mievr td.mievr {
|
|||||||
|
|
||||||
tr.mievr td.mievr_menu {
|
tr.mievr td.mievr_menu {
|
||||||
width: 110px;
|
width: 110px;
|
||||||
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.mievr td.mievr_dur {
|
tr.mievr td.mievr_dur {
|
||||||
@ -2411,6 +2472,29 @@ div.formation_list_ues_titre {
|
|||||||
color: #eee;
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.formation_semestre_niveaux_warning {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
padding: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 24px;
|
||||||
|
margin-right: 24px;
|
||||||
|
background-color: yellow;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
div.formation_semestre_niveaux_warning div {
|
||||||
|
color: black;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
div.formation_semestre_niveaux_warning ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
div.formation_semestre_niveaux_warning ul li:before {
|
||||||
|
content: '⚠️';
|
||||||
|
margin-right: 10px; /* Adjust space between emoji and text */
|
||||||
|
}
|
||||||
|
|
||||||
div.formation_list_modules,
|
div.formation_list_modules,
|
||||||
div.formation_list_ues {
|
div.formation_list_ues {
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
@ -2426,6 +2510,7 @@ div.formation_list_ues {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.formation_list_ues_content {
|
div.formation_list_ues_content {
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.formation_list_modules {
|
div.formation_list_modules {
|
||||||
@ -2508,7 +2593,13 @@ div.formation_parcs > div {
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4px 8px;
|
padding: 2px 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
div.formation_parcs > div.ue_tc {
|
||||||
|
color: black;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.formation_parcs > div.focus {
|
div.formation_parcs > div.focus {
|
||||||
@ -3316,14 +3407,24 @@ li.tf-msg {
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning, .warning-bloquant {
|
||||||
font-weight: bold;
|
|
||||||
color: red;
|
color: red;
|
||||||
|
margin-left: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
min-width: var(--sco-content-min-width);
|
||||||
|
max-width: var(--sco-content-max-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning::before {
|
.warning::before {
|
||||||
content: url(/ScoDoc/static/icons/warning_img.png);
|
content:"";
|
||||||
vertical-align: -80%;
|
margin-right: 8px;
|
||||||
|
height:32px;
|
||||||
|
width: 32px;
|
||||||
|
background-size: 32px 32px;
|
||||||
|
background-image: url(/ScoDoc/static/icons/warning_std.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-light {
|
.warning-light {
|
||||||
@ -3336,6 +3437,19 @@ li.tf-msg {
|
|||||||
/* EMO_WARNING, "⚠️" */
|
/* EMO_WARNING, "⚠️" */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning-bloquant::before {
|
||||||
|
content:"";
|
||||||
|
margin-right: 8px;
|
||||||
|
height:32px;
|
||||||
|
width: 32px;
|
||||||
|
background-size: 32px 32px;
|
||||||
|
background-image: url(/ScoDoc/static/icons/warning_bloquant.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
p.error {
|
p.error {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: red;
|
color: red;
|
||||||
@ -3714,10 +3828,17 @@ span.sp_etape {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inscrailleurs {
|
.deja-inscrit {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(1, 76, 1) !important;
|
||||||
|
}
|
||||||
|
.inscrit-ailleurs {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: red !important;
|
color: red !important;
|
||||||
}
|
}
|
||||||
|
div.etuds_select_boxes {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
span.paspaye,
|
span.paspaye,
|
||||||
span.paspaye a {
|
span.paspaye a {
|
||||||
@ -4682,6 +4803,10 @@ table.table_recap th.col_malus {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: rgb(165, 0, 0);
|
color: rgb(165, 0, 0);
|
||||||
}
|
}
|
||||||
|
table.table_recap td.col_eval_bonus,
|
||||||
|
table.table_recap th.col_eval_bonus {
|
||||||
|
color: #90c;
|
||||||
|
}
|
||||||
|
|
||||||
table.table_recap tr.ects td {
|
table.table_recap tr.ects td {
|
||||||
color: rgb(160, 86, 3);
|
color: rgb(160, 86, 3);
|
||||||
|
@ -491,14 +491,15 @@ class releveBUT extends HTMLElement {
|
|||||||
let output = "";
|
let output = "";
|
||||||
evaluations.forEach((evaluation) => {
|
evaluations.forEach((evaluation) => {
|
||||||
output += `
|
output += `
|
||||||
<div class=eval>
|
<div class="eval ${evaluation.evaluation_type == 3 ? "eval-bonus" : ""}">
|
||||||
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
|
||||||
<div>
|
<div>
|
||||||
${evaluation.note.value}
|
${evaluation.note.value}
|
||||||
<em>Coef. ${evaluation.coef ?? "*"}</em>
|
<em>${evaluation.evaluation_type == 0 ? "Coef." : evaluation.evaluation_type == 3 ? "Bonus" : ""
|
||||||
|
} ${evaluation.coef ?? ""}</em>
|
||||||
</div>
|
</div>
|
||||||
<div class=complement>
|
<div class=complement>
|
||||||
<div>Coef</div><div>${evaluation.coef}</div>
|
<div>${evaluation.evaluation_type == 0 ? "Coef." : ""}</div><div>${evaluation.coef ?? ""}</div>
|
||||||
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
<div>Max. promo.</div><div>${evaluation.note.max}</div>
|
||||||
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
<div>Moy. promo.</div><div>${evaluation.note.moy}</div>
|
||||||
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
<div>Min. promo.</div><div>${evaluation.note.min}</div>
|
||||||
|
@ -13,7 +13,7 @@ import numpy as np
|
|||||||
from app import db
|
from app import db
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.comp.res_common import ResultatsSemestre
|
from app.comp.res_common import ResultatsSemestre
|
||||||
from app.models import Identite, FormSemestre, UniteEns
|
from app.models import Identite, Evaluation, FormSemestre, UniteEns
|
||||||
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
from app.scodoc.codes_cursus import UE_SPORT, DEF
|
||||||
from app.scodoc import sco_evaluation_db
|
from app.scodoc import sco_evaluation_db
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
@ -405,8 +405,13 @@ class TableRecap(tb.Table):
|
|||||||
val = notes_db[etudid]["value"]
|
val = notes_db[etudid]["value"]
|
||||||
else:
|
else:
|
||||||
# Note manquante mais prise en compte immédiate: affiche ATT
|
# Note manquante mais prise en compte immédiate: affiche ATT
|
||||||
val = scu.NOTES_ATTENTE
|
val = (
|
||||||
|
scu.NOTES_ATTENTE
|
||||||
|
if e.evaluation_type != Evaluation.EVALUATION_BONUS
|
||||||
|
else ""
|
||||||
|
)
|
||||||
content = self.fmt_note(val)
|
content = self.fmt_note(val)
|
||||||
|
if e.evaluation_type != Evaluation.EVALUATION_BONUS:
|
||||||
classes = col_classes + [
|
classes = col_classes + [
|
||||||
{
|
{
|
||||||
"ABS": "abs",
|
"ABS": "abs",
|
||||||
@ -414,6 +419,8 @@ class TableRecap(tb.Table):
|
|||||||
"EXC": "exc",
|
"EXC": "exc",
|
||||||
}.get(content, "")
|
}.get(content, "")
|
||||||
]
|
]
|
||||||
|
else:
|
||||||
|
classes = col_classes + ["col_eval_bonus"]
|
||||||
row.add_cell(
|
row.add_cell(
|
||||||
col_id, title, content, group="eval", classes=classes
|
col_id, title, content, group="eval", classes=classes
|
||||||
)
|
)
|
||||||
@ -450,7 +457,7 @@ class TableRecap(tb.Table):
|
|||||||
row_descr_eval.add_cell(
|
row_descr_eval.add_cell(
|
||||||
col_id,
|
col_id,
|
||||||
None,
|
None,
|
||||||
e.description or "",
|
e.description,
|
||||||
target=url_for(
|
target=url_for(
|
||||||
"notes.evaluation_listenotes",
|
"notes.evaluation_listenotes",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
@ -20,7 +20,7 @@ Assiduité lors de l'évaluation
|
|||||||
<a class="stdlink" href="{{
|
<a class="stdlink" href="{{
|
||||||
url_for('notes.evaluation_listenotes',
|
url_for('notes.evaluation_listenotes',
|
||||||
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
scodoc_dept=g.scodoc_dept, evaluation_id=evaluation.id)
|
||||||
}}"><em>{{evaluation.description or ''}}</em></a>
|
}}"><em>{{evaluation.description}}</em></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
{%- block styles %}
|
{%- block styles %}
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
<link href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.css" rel="stylesheet">
|
<link href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||||
|
<link type="text/css" rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
||||||
{%- endblock styles %}
|
{%- endblock styles %}
|
||||||
{%- endblock head %}
|
{%- endblock head %}
|
||||||
</head>
|
</head>
|
||||||
@ -26,7 +27,14 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap/js/bootstrap.min.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<div class="formation_list_ues_titre">Unités d'Enseignement
|
<div class="formation_list_ues_titre">Unités d'Enseignement
|
||||||
semestre {{semestre_idx}} - {{ects_by_sem[semestre_idx] | safe}} ECTS
|
semestre {{semestre_idx}} - {{ects_by_sem[semestre_idx] | safe}} ECTS
|
||||||
</div>
|
</div>
|
||||||
|
{{ html_ue_warning[semestre_idx] | safe }}
|
||||||
<div class="formation_list_ues_content">
|
<div class="formation_list_ues_content">
|
||||||
<ul class="apc_ue_list">
|
<ul class="apc_ue_list">
|
||||||
{% for ue in ues_by_sem[semestre_idx] %}
|
{% for ue in ues_by_sem[semestre_idx] %}
|
||||||
@ -62,6 +63,8 @@
|
|||||||
<div class="formation_parcs">
|
<div class="formation_parcs">
|
||||||
{% for parc in ue.parcours %}
|
{% for parc in ue.parcours %}
|
||||||
<div>{{ parc.code }}</div>
|
<div>{{ parc.code }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="ue_tc" title="aucun parcours">Tronc Commun</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -43,13 +43,6 @@
|
|||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/menu.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/bubble.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery.field.min.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
|
||||||
|
|
||||||
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
|
<script src="{{scu.STATIC_DIR}}/DataTables/datatables.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -8,13 +8,15 @@
|
|||||||
</p>
|
</p>
|
||||||
{%if is_apc%}
|
{%if is_apc%}
|
||||||
<p class="help help_but">
|
<p class="help help_but">
|
||||||
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques... (à compléter)
|
Dans le BUT, une évaluation peut évaluer différents apprentissages critiques,
|
||||||
Le coefficient est multiplié par les poids vers chaque UE.
|
et les poids permettent de moduler l'importance de l'évaluation pour
|
||||||
|
chaque compétence (UE).
|
||||||
|
Le coefficient de l'évaluation est multiplié par les poids vers chaque UE.
|
||||||
</p>
|
</p>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
Ne pas confondre ce coefficient avec le coefficient du module, qui est
|
Ne pas confondre ce coefficient avec le coefficient du module, qui est
|
||||||
lui fixé par le programme pédagogique (le PPN pour les DUT) et pondère
|
lui fixé par le programme pédagogique (le PN pour les BUT) et pondère
|
||||||
les moyennes de chaque module pour obtenir les moyennes d'UE et la
|
les moyennes de chaque module pour obtenir les moyennes d'UE et la
|
||||||
moyenne générale.
|
moyenne générale.
|
||||||
</p>
|
</p>
|
||||||
@ -22,17 +24,31 @@
|
|||||||
L'option <em>Visible sur bulletins</em> indique que la note sera
|
L'option <em>Visible sur bulletins</em> indique que la note sera
|
||||||
reportée sur les bulletins en version dite "intermédiaire" (dans cette
|
reportée sur les bulletins en version dite "intermédiaire" (dans cette
|
||||||
version, on peut ne faire apparaitre que certaines notes, en sus des
|
version, on peut ne faire apparaitre que certaines notes, en sus des
|
||||||
moyennes de modules. Attention, cette option n'empêche pas la
|
moyennes de modules). Attention, cette option n'empêche pas la
|
||||||
publication sur les bulletins en version "longue" (la note est donc
|
publication sur les bulletins en version "longue" (la note est donc
|
||||||
visible par les étudiants sur le portail).
|
visible par les étudiants sur le portail).
|
||||||
</p>
|
</p>
|
||||||
|
<p class="help">
|
||||||
|
Les évaluations bonus sont particulières:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>la valeur est ajoutée à la moyenne du module;</li>
|
||||||
|
<li>le bonus peut être négatif (malus);
|
||||||
|
</li>
|
||||||
|
<li>le bonus ne s'applique pas aux notes de rattrapage et deuxième session;
|
||||||
|
</li>
|
||||||
|
<li>le coefficient est ignoré, mais en BUT le bonus vers une UE est multiplié
|
||||||
|
par le poids correspondant (par défaut égal à 1);
|
||||||
|
</li>
|
||||||
|
<li>les notes de bonus sont prises en compte même si incomplètes.</li>
|
||||||
|
</ul>
|
||||||
<p class="help">
|
<p class="help">
|
||||||
Les modalités "rattrapage" et "deuxième session" définissent des
|
Les modalités "rattrapage" et "deuxième session" définissent des
|
||||||
évaluations prises en compte de façon spéciale:
|
évaluations prises en compte de façon spéciale:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes
|
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes
|
||||||
du module <em>si elles sont meilleures que celles calculées</em>.
|
du module <em>si elles sont meilleures que celles calculées;</em>.
|
||||||
</li>
|
</li>
|
||||||
<li>les notes de "deuxième session" remplacent, lorsqu'elles sont
|
<li>les notes de "deuxième session" remplacent, lorsqu'elles sont
|
||||||
saisies, la moyenne de l'étudiant à ce module, même si la note de
|
saisies, la moyenne de l'étudiant à ce module, même si la note de
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
{% 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'] }}">({{sco.prefs["assi_metrique"]}})
|
au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
|
||||||
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
|
<br />{{'%1.0f'|format(sco.nbabsjust)}} J., {{'%1.0f'|format(sco.nbabsnj)}} N.J.</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
{% if current_user.has_permission(sco.Permission.AbsChange) %}
|
||||||
|
@ -279,6 +279,7 @@ def ajout_assiduite_etud() -> str | Response:
|
|||||||
|
|
||||||
def _get_dates_from_assi_form(
|
def _get_dates_from_assi_form(
|
||||||
form: AjoutAssiOrJustForm,
|
form: AjoutAssiOrJustForm,
|
||||||
|
all_day: bool = False,
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
||||||
]:
|
]:
|
||||||
@ -308,13 +309,23 @@ def _get_dates_from_assi_form(
|
|||||||
|
|
||||||
if date_fin:
|
if date_fin:
|
||||||
# ignore les heures si plusieurs jours
|
# ignore les heures si plusieurs jours
|
||||||
heure_debut = datetime.time.fromisoformat(debut_jour) # 0h
|
|
||||||
heure_fin = datetime.time.fromisoformat(fin_jour) # minuit
|
# Assiduité : garde les heures inscritent dans le formulaire
|
||||||
|
# Justificatif : ignore les heures inscrites dans le formulaire (0h -> 23h59)
|
||||||
|
|
||||||
|
heure_debut = (
|
||||||
|
datetime.time.fromisoformat(debut_jour)
|
||||||
|
if not all_day
|
||||||
|
else datetime.time(0, 0, 0)
|
||||||
|
) # 0h ou ConfigAssiduite.MorningTime
|
||||||
|
heure_fin = (
|
||||||
|
datetime.time.fromisoformat(fin_jour)
|
||||||
|
if not all_day
|
||||||
|
else datetime.time(23, 59, 59)
|
||||||
|
) # 23h59 ou ConfigAssiduite.AfternoonTime
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
heure_debut = datetime.time.fromisoformat(
|
heure_debut = datetime.time.fromisoformat(form.heure_debut.data or "00:00")
|
||||||
form.heure_debut.data or debut_jour
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
form.set_error("heure début invalide", form.heure_debut)
|
form.set_error("heure début invalide", form.heure_debut)
|
||||||
if bool(form.heure_debut.data) != bool(form.heure_fin.data):
|
if bool(form.heure_debut.data) != bool(form.heure_fin.data):
|
||||||
@ -322,7 +333,7 @@ def _get_dates_from_assi_form(
|
|||||||
"Les deux heures début et fin doivent être spécifiées, ou aucune"
|
"Les deux heures début et fin doivent être spécifiées, ou aucune"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or "23:59")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
form.set_error("heure fin invalide", form.heure_fin)
|
form.set_error("heure fin invalide", form.heure_fin)
|
||||||
|
|
||||||
@ -694,7 +705,7 @@ def _record_justificatif_etud(
|
|||||||
dt_debut_tz_server,
|
dt_debut_tz_server,
|
||||||
dt_fin_tz_server,
|
dt_fin_tz_server,
|
||||||
dt_entry_date_tz_server,
|
dt_entry_date_tz_server,
|
||||||
) = _get_dates_from_assi_form(form)
|
) = _get_dates_from_assi_form(form, all_day=True)
|
||||||
|
|
||||||
if not ok:
|
if not ok:
|
||||||
log("_record_justificatif_etud: dates invalides")
|
log("_record_justificatif_etud: dates invalides")
|
||||||
|
@ -90,7 +90,6 @@ from app.decorators import (
|
|||||||
# ---------------
|
# ---------------
|
||||||
from app.pe import pe_view # ne pas enlever, ajoute des vues
|
from app.pe import pe_view # ne pas enlever, ajoute des vues
|
||||||
from app.scodoc import sco_bulletins_json, sco_utils as scu
|
from app.scodoc import sco_bulletins_json, sco_utils as scu
|
||||||
from app.scodoc import notesdb as ndb
|
|
||||||
from app import log, send_scodoc_alarm
|
from app import log, send_scodoc_alarm
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
@ -98,59 +97,62 @@ from app.scodoc.sco_exceptions import (
|
|||||||
ScoValueError,
|
ScoValueError,
|
||||||
ScoInvalidIdType,
|
ScoInvalidIdType,
|
||||||
)
|
)
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import (
|
||||||
from app.scodoc import sco_apogee_compare
|
html_sco_header,
|
||||||
from app.scodoc import sco_archives_formsemestre
|
sco_apogee_compare,
|
||||||
from app.scodoc import sco_assiduites
|
sco_archives_formsemestre,
|
||||||
from app.scodoc import sco_bulletins
|
sco_assiduites,
|
||||||
from app.scodoc import sco_bulletins_pdf
|
sco_bulletins,
|
||||||
from app.scodoc import sco_cache
|
sco_bulletins_pdf,
|
||||||
from app.scodoc import sco_cost_formation
|
sco_cache,
|
||||||
from app.scodoc import sco_debouche
|
sco_cost_formation,
|
||||||
from app.scodoc import sco_edit_apc
|
sco_debouche,
|
||||||
from app.scodoc import sco_edit_formation
|
sco_edit_apc,
|
||||||
from app.scodoc import sco_edit_matiere
|
sco_edit_formation,
|
||||||
from app.scodoc import sco_edit_module
|
sco_edit_matiere,
|
||||||
from app.scodoc import sco_edit_ue
|
sco_edit_module,
|
||||||
from app.scodoc import sco_etape_apogee_view
|
sco_edit_ue,
|
||||||
from app.scodoc import sco_etud
|
sco_etape_apogee_view,
|
||||||
from app.scodoc import sco_evaluations
|
sco_etud,
|
||||||
from app.scodoc import sco_evaluation_check_abs
|
sco_evaluations,
|
||||||
from app.scodoc import sco_evaluation_db
|
sco_evaluation_check_abs,
|
||||||
from app.scodoc import sco_evaluation_edit
|
sco_evaluation_db,
|
||||||
from app.scodoc import sco_evaluation_recap
|
sco_evaluation_edit,
|
||||||
from app.scodoc import sco_export_results
|
sco_evaluation_recap,
|
||||||
from app.scodoc import sco_formations
|
sco_export_results,
|
||||||
from app.scodoc import sco_formation_recap
|
sco_formations,
|
||||||
from app.scodoc import sco_formation_versions
|
sco_formation_recap,
|
||||||
from app.scodoc import sco_formsemestre
|
sco_formation_versions,
|
||||||
from app.scodoc import sco_formsemestre_custommenu
|
sco_formsemestre,
|
||||||
from app.scodoc import sco_formsemestre_edit
|
sco_formsemestre_custommenu,
|
||||||
from app.scodoc import sco_formsemestre_exterieurs
|
sco_formsemestre_edit,
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
sco_formsemestre_exterieurs,
|
||||||
from app.scodoc import sco_formsemestre_status
|
sco_formsemestre_inscriptions,
|
||||||
from app.scodoc import sco_formsemestre_validation
|
sco_formsemestre_status,
|
||||||
from app.scodoc import sco_inscr_passage
|
sco_formsemestre_validation,
|
||||||
from app.scodoc import sco_liste_notes
|
sco_groups_view,
|
||||||
from app.scodoc import sco_lycee
|
sco_inscr_passage,
|
||||||
from app.scodoc import sco_moduleimpl
|
sco_liste_notes,
|
||||||
from app.scodoc import sco_moduleimpl_inscriptions
|
sco_lycee,
|
||||||
from app.scodoc import sco_moduleimpl_status
|
sco_moduleimpl,
|
||||||
from app.scodoc import sco_placement
|
sco_moduleimpl_inscriptions,
|
||||||
from app.scodoc import sco_poursuite_dut
|
sco_moduleimpl_status,
|
||||||
from app.scodoc import sco_preferences
|
sco_placement,
|
||||||
from app.scodoc import sco_prepajury
|
sco_poursuite_dut,
|
||||||
from app.scodoc import sco_pv_forms
|
sco_preferences,
|
||||||
from app.scodoc import sco_recapcomplet
|
sco_prepajury,
|
||||||
from app.scodoc import sco_report
|
sco_pv_forms,
|
||||||
from app.scodoc import sco_report_but
|
sco_recapcomplet,
|
||||||
from app.scodoc import sco_saisie_notes
|
sco_report,
|
||||||
from app.scodoc import sco_semset
|
sco_report_but,
|
||||||
from app.scodoc import sco_synchro_etuds
|
sco_saisie_notes,
|
||||||
from app.scodoc import sco_tag_module
|
sco_semset,
|
||||||
from app.scodoc import sco_ue_external
|
sco_synchro_etuds,
|
||||||
from app.scodoc import sco_undo_notes
|
sco_tag_module,
|
||||||
from app.scodoc import sco_users
|
sco_ue_external,
|
||||||
|
sco_undo_notes,
|
||||||
|
sco_users,
|
||||||
|
)
|
||||||
from app.scodoc.gen_tables import GenTable
|
from app.scodoc.gen_tables import GenTable
|
||||||
from app.scodoc.sco_pv_dict import descr_autorisations
|
from app.scodoc.sco_pv_dict import descr_autorisations
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
@ -1640,7 +1642,7 @@ def evaluation_delete(evaluation_id):
|
|||||||
.first_or_404()
|
.first_or_404()
|
||||||
)
|
)
|
||||||
|
|
||||||
tit = f"""Suppression de l'évaluation {evaluation.description or ""} ({evaluation.descr_date()})"""
|
tit = f"""Suppression de l'évaluation {evaluation.description} ({evaluation.descr_date()})"""
|
||||||
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
|
etat = sco_evaluations.do_evaluation_etat(evaluation.id)
|
||||||
H = [
|
H = [
|
||||||
f"""
|
f"""
|
||||||
@ -1844,10 +1846,20 @@ sco_publish(
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
def formsemestre_bulletins_pdf(
|
||||||
|
formsemestre_id,
|
||||||
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||||
|
version="selectedevals",
|
||||||
|
):
|
||||||
"Publie les bulletins dans un classeur PDF"
|
"Publie les bulletins dans un classeur PDF"
|
||||||
|
# Informations sur les groupes à utiliser:
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
select_all_when_unspecified=True,
|
||||||
|
)
|
||||||
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||||
formsemestre_id, version=version
|
formsemestre_id, groups_infos=groups_infos, version=version
|
||||||
)
|
)
|
||||||
return scu.sendPDFFile(pdfdoc, filename)
|
return scu.sendPDFFile(pdfdoc, filename)
|
||||||
|
|
||||||
@ -1864,18 +1876,29 @@ _EXPL_BULL = """Versions des bulletins:
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
def formsemestre_bulletins_pdf_choice(formsemestre_id, version=None):
|
def formsemestre_bulletins_pdf_choice(
|
||||||
|
formsemestre_id,
|
||||||
|
version=None,
|
||||||
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||||
|
):
|
||||||
"""Choix version puis envoi classeur bulletins pdf"""
|
"""Choix version puis envoi classeur bulletins pdf"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
# Informations sur les groupes à utiliser:
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
select_all_when_unspecified=True,
|
||||||
|
)
|
||||||
if version:
|
if version:
|
||||||
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf(
|
||||||
formsemestre_id, version=version
|
formsemestre_id, groups_infos=groups_infos, version=version
|
||||||
)
|
)
|
||||||
return scu.sendPDFFile(pdfdoc, filename)
|
return scu.sendPDFFile(pdfdoc, filename)
|
||||||
return _formsemestre_bulletins_choice(
|
return _formsemestre_bulletins_choice(
|
||||||
formsemestre,
|
formsemestre,
|
||||||
title="Choisir la version des bulletins à générer",
|
|
||||||
explanation=_EXPL_BULL,
|
explanation=_EXPL_BULL,
|
||||||
|
groups_infos=groups_infos,
|
||||||
|
title="Choisir la version des bulletins à générer",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1900,8 +1923,15 @@ def formsemestre_bulletins_mailetuds_choice(
|
|||||||
version=None,
|
version=None,
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
prefer_mail_perso=0,
|
prefer_mail_perso=0,
|
||||||
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||||
):
|
):
|
||||||
"""Choix version puis envoi classeur bulletins pdf"""
|
"""Choix version puis envoi classeur bulletins pdf"""
|
||||||
|
# Informations sur les groupes à utiliser:
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
select_all_when_unspecified=True,
|
||||||
|
)
|
||||||
if version:
|
if version:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -1909,8 +1939,9 @@ def formsemestre_bulletins_mailetuds_choice(
|
|||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
version=version,
|
version=version,
|
||||||
dialog_confirmed=dialog_confirmed,
|
dialog_confirmed=int(dialog_confirmed),
|
||||||
prefer_mail_perso=prefer_mail_perso,
|
prefer_mail_perso=prefer_mail_perso,
|
||||||
|
group_ids=groups_infos.group_ids,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
@ -1934,45 +1965,41 @@ def formsemestre_bulletins_mailetuds_choice(
|
|||||||
</p><p>"""
|
</p><p>"""
|
||||||
+ expl_bull,
|
+ expl_bull,
|
||||||
choose_mail=True,
|
choose_mail=True,
|
||||||
|
groups_infos=groups_infos,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# not published
|
# not published
|
||||||
def _formsemestre_bulletins_choice(
|
def _formsemestre_bulletins_choice(
|
||||||
formsemestre: FormSemestre, title="", explanation="", choose_mail=False
|
formsemestre: FormSemestre,
|
||||||
|
title="",
|
||||||
|
explanation="",
|
||||||
|
choose_mail=False,
|
||||||
|
groups_infos=None,
|
||||||
):
|
):
|
||||||
"""Choix d'une version de bulletin"""
|
"""Choix d'une version de bulletin
|
||||||
versions = (
|
(pour envois mail ou génération classeur pdf)
|
||||||
|
"""
|
||||||
|
versions_bulletins = (
|
||||||
scu.BULLETINS_VERSIONS_BUT
|
scu.BULLETINS_VERSIONS_BUT
|
||||||
if formsemestre.formation.is_apc()
|
if formsemestre.formation.is_apc()
|
||||||
else scu.BULLETINS_VERSIONS
|
else scu.BULLETINS_VERSIONS
|
||||||
)
|
)
|
||||||
H = [
|
|
||||||
html_sco_header.html_sem_header(title),
|
|
||||||
f"""
|
|
||||||
<form name="f" method="GET" action="{request.base_url}">
|
|
||||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
|
|
||||||
""",
|
|
||||||
]
|
|
||||||
H.append("""<select name="version" class="noprint">""")
|
|
||||||
for version, description in versions.items():
|
|
||||||
H.append(f"""<option value="{version}">{description}</option>""")
|
|
||||||
|
|
||||||
H.append("""</select> <input type="submit" value="Générer"/>""")
|
return render_template(
|
||||||
if choose_mail:
|
"formsemestre/bulletins_choice.j2",
|
||||||
H.append(
|
explanation=explanation,
|
||||||
"""<div>
|
choose_mail=choose_mail,
|
||||||
<input type="checkbox" name="prefer_mail_perso" value="1"
|
formsemestre=formsemestre,
|
||||||
/>Utiliser si possible les adresses personnelles
|
menu_groups_choice=sco_groups_view.menu_groups_choice(groups_infos),
|
||||||
</div>"""
|
sco=ScoData(formsemestre=formsemestre),
|
||||||
|
sco_groups_view=sco_groups_view,
|
||||||
|
title=title,
|
||||||
|
versions_bulletins=versions_bulletins,
|
||||||
)
|
)
|
||||||
|
|
||||||
H.append(f"""<p class="help">{explanation}</p>""")
|
|
||||||
|
|
||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
@bp.route("/formsemestre_bulletins_mailetuds", methods=["GET", "POST"])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/formsemestre_bulletins_mailetuds")
|
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
@ -1981,16 +2008,23 @@ def formsemestre_bulletins_mailetuds(
|
|||||||
version="long",
|
version="long",
|
||||||
dialog_confirmed=False,
|
dialog_confirmed=False,
|
||||||
prefer_mail_perso=0,
|
prefer_mail_perso=0,
|
||||||
|
group_ids: list[int] = None, # si indiqué, ne prend que ces groupes
|
||||||
):
|
):
|
||||||
"""Envoie à chaque etudiant son bulletin
|
"""Envoie à chaque etudiant son bulletin
|
||||||
(inscrit non démissionnaire ni défaillant et ayant un mail renseigné dans ScoDoc)
|
(inscrit non démissionnaire ni défaillant et ayant un mail renseigné dans ScoDoc)
|
||||||
"""
|
"""
|
||||||
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||||
|
group_ids,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
select_all_when_unspecified=True,
|
||||||
|
)
|
||||||
|
etudids = {m["etudid"] for m in groups_infos.members}
|
||||||
prefer_mail_perso = int(prefer_mail_perso)
|
prefer_mail_perso = int(prefer_mail_perso)
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
inscriptions = [
|
inscriptions = [
|
||||||
inscription
|
inscription
|
||||||
for inscription in formsemestre.inscriptions
|
for inscription in formsemestre.inscriptions
|
||||||
if inscription.etat == scu.INSCRIT
|
if inscription.etat == scu.INSCRIT and inscription.etudid in etudids
|
||||||
]
|
]
|
||||||
#
|
#
|
||||||
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
|
if not sco_bulletins.can_send_bulletin_by_mail(formsemestre_id):
|
||||||
@ -1998,7 +2032,7 @@ def formsemestre_bulletins_mailetuds(
|
|||||||
# Confirmation dialog
|
# Confirmation dialog
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
f"<h2>Envoyer les {len(inscriptions)} bulletins par e-mail aux étudiants inscrits ?",
|
f"<h2>Envoyer les {len(inscriptions)} bulletins par e-mail aux étudiants inscrits sélectionnés ?",
|
||||||
dest_url="",
|
dest_url="",
|
||||||
cancel_url=url_for(
|
cancel_url=url_for(
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
@ -2374,10 +2408,12 @@ def formsemestre_validation_but(
|
|||||||
)
|
)
|
||||||
|
|
||||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||||
if len(deca.get_decisions_rcues_annee()) == 0:
|
has_notes_en_attente = deca.has_notes_en_attente()
|
||||||
return jury_but_view.jury_but_semestriel(
|
evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud(
|
||||||
formsemestre, etud, read_only, navigation_div=navigation_div
|
formsemestre, etud
|
||||||
)
|
)
|
||||||
|
if has_notes_en_attente or evaluations_a_debloquer:
|
||||||
|
read_only = True
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if not read_only:
|
if not read_only:
|
||||||
deca.record_form(request.form)
|
deca.record_form(request.form)
|
||||||
@ -2422,9 +2458,21 @@ def formsemestre_validation_but(
|
|||||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||||
|
|
||||||
if deca.has_notes_en_attente():
|
if has_notes_en_attente:
|
||||||
warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente.
|
warning += f"""<div class="warning-bloquant">{etud.nomprenom} a des notes en ATTente.
|
||||||
Vous devriez régler cela avant de statuer en jury !</div>"""
|
Vous devez régler cela avant de statuer en jury !</div>"""
|
||||||
|
if evaluations_a_debloquer:
|
||||||
|
links_evals = [
|
||||||
|
f"""<a class="stdlink" href="{url_for(
|
||||||
|
'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id
|
||||||
|
)}">{e.description} en {e.moduleimpl.module.code}</a>"""
|
||||||
|
for e in evaluations_a_debloquer
|
||||||
|
]
|
||||||
|
warning += f"""<div class="warning-bloquant">Impossible de statuer sur cet étudiant:
|
||||||
|
il a des notes dans des évaluations qui seront débloquées plus tard:
|
||||||
|
voir {", ".join(links_evals)}
|
||||||
|
"""
|
||||||
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<div>
|
<div>
|
||||||
@ -2440,8 +2488,10 @@ def formsemestre_validation_but(
|
|||||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="jury_but_warning jury_but_box">
|
||||||
{warning}
|
{warning}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post" class="jury_but_box" id="jury_but">
|
<form method="post" class="jury_but_box" id="jury_but">
|
||||||
"""
|
"""
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.940"
|
SCOVERSION = "9.6.946"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
SCONEWS = """
|
SCONEWS = """
|
||||||
<h4>Année 2023</h4>
|
<h4>Année 2023-2024</h4>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
<li>ScoDoc 9.6 (juillet 2023)</li>
|
<li>ScoDoc 9.6 (2023-2024)</li>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Nouveaux bulletins BUT compacts</li>
|
<li>Nouveaux bulletins BUT compacts</li>
|
||||||
<li>Nouvelle gestion des absences et assiduité</li>
|
<li>Nouvelle gestion des absences et assiduité</li>
|
||||||
<li>Mise à jour logiciels: Debian 12, Python 3.11, ...</li>
|
<li>Mise à jour logiciels: Debian 12, Python 3.11, ...</li>
|
||||||
|
<li>Evaluations bonus</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<li>ScoDoc 9.5 (juillet 2023)</li>
|
<li>ScoDoc 9.5 (juillet 2023)</li>
|
||||||
|
@ -59,6 +59,7 @@ cli.register(app)
|
|||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_sco_utils():
|
def inject_sco_utils():
|
||||||
"Make scu available in all Jinja templates"
|
"Make scu available in all Jinja templates"
|
||||||
|
# if modified, put the same in conftest.py#27
|
||||||
return dict(scu=scu)
|
return dict(scu=scu)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user
|
||||||
|
|
||||||
from config import TestConfig
|
from config import TestConfig
|
||||||
import app
|
import app
|
||||||
from app import db, create_app
|
from app import db, create_app
|
||||||
from app import initialize_scodoc_database, clear_scodoc_cache
|
from app import initialize_scodoc_database, clear_scodoc_cache
|
||||||
from app import models
|
from app import models
|
||||||
from app.auth.models import User, Role, UserRole, Permission
|
from app.auth.models import User, Role
|
||||||
from app.auth.models import get_super_admin
|
from app.auth.models import get_super_admin
|
||||||
from app.scodoc import sco_bulletins_standard
|
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
RESOURCES_DIR = "/opt/scodoc/tests/ressources"
|
RESOURCES_DIR = "/opt/scodoc/tests/ressources"
|
||||||
|
|
||||||
@ -23,6 +23,12 @@ def test_client():
|
|||||||
# Run tests:
|
# Run tests:
|
||||||
with apptest.test_client() as client:
|
with apptest.test_client() as client:
|
||||||
with apptest.app_context():
|
with apptest.app_context():
|
||||||
|
|
||||||
|
@apptest.context_processor
|
||||||
|
def inject_sco_utils():
|
||||||
|
"Make scu available in all Jinja templates"
|
||||||
|
return dict(scu=scu)
|
||||||
|
|
||||||
with apptest.test_request_context():
|
with apptest.test_request_context():
|
||||||
# initialize scodoc "g":
|
# initialize scodoc "g":
|
||||||
g.stored_get_formsemestre = {}
|
g.stored_get_formsemestre = {}
|
||||||
|
@ -1148,13 +1148,8 @@ def _setup_fake_db(
|
|||||||
|
|
||||||
moduleimpls.append(ModuleImpl.query.filter_by(id=moduleimpl_id).first())
|
moduleimpls.append(ModuleImpl.query.filter_by(id=moduleimpl_id).first())
|
||||||
|
|
||||||
# Création de 3 étudiants
|
# Création de x étudiants
|
||||||
etud_0 = g_fake.create_etud(prenom="etud0")
|
etuds_dict: list[dict] = [
|
||||||
etud_1 = g_fake.create_etud(prenom="etud1")
|
|
||||||
etud_2 = g_fake.create_etud(prenom="etud2")
|
|
||||||
etuds_dict = [etud_0, etud_1, etud_2]
|
|
||||||
|
|
||||||
etud_dicts: list[dict] = [
|
|
||||||
g_fake.create_etud(prenom=f"etud{i}") for i in range(nb_etuds)
|
g_fake.create_etud(prenom=f"etud{i}") for i in range(nb_etuds)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1406,13 +1401,106 @@ def test_calcul_assiduites(test_client):
|
|||||||
"total": {"journee": 11, "demi": 20, "heure": 81.0, "compte": 26},
|
"total": {"journee": 11, "demi": 20, "heure": 81.0, "compte": 26},
|
||||||
}
|
}
|
||||||
|
|
||||||
for key in resultat_attendu:
|
for key, value in resultat_attendu.items():
|
||||||
assert (
|
assert value["journee"] * 2 >= value["demi"], f"Trop de demi-journées [{key}]"
|
||||||
resultat_attendu[key]["journee"] * 2 >= resultat_attendu[key]["demi"]
|
|
||||||
), f"Trop de demi-journées [{key}]"
|
|
||||||
|
|
||||||
for key in resultat_attendu:
|
for key, value in resultat_attendu.items():
|
||||||
for key2 in resultat_attendu[key]:
|
for key2, value2 in value.items():
|
||||||
assert (
|
assert (
|
||||||
result[key][key2] == resultat_attendu[key][key2]
|
result[key][key2] == value2
|
||||||
), f"Le calcul [{key}][{key2}] est faux (attendu > {resultat_attendu[key][key2]} ≠ {result[key][key2]} < obtenu)"
|
), f"Le calcul [{key}][{key2}] est faux (attendu > {value2} ≠ {result[key][key2]} < obtenu)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cas_justificatifs(test_client):
|
||||||
|
"""
|
||||||
|
Tests de certains cas particuliers des justificatifs
|
||||||
|
- Création du justificatif avant ou après assiduité
|
||||||
|
- Assiduité complétement couverte ou non
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = _setup_fake_db(
|
||||||
|
[("2024-01-01", "2024-06-30")],
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# <- Vérification justification si justif créé avant assi ->
|
||||||
|
# Période : 8h -> 10h le 01/01/2024
|
||||||
|
|
||||||
|
etud_1: Identite = data["etuds"][0]
|
||||||
|
justif_1: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
assi_1: Assiduite = Assiduite.create_assiduite(
|
||||||
|
etud=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert assi_1.est_just is True, "Justification non prise en compte (a1)"
|
||||||
|
assert len(scass.justifies(justif_1)) == 1, "Justification non prise en compte (a2)"
|
||||||
|
|
||||||
|
# <- Vérification justification si justif créé après assi ->
|
||||||
|
# Période : 8h -> 10h le 02/01/2024
|
||||||
|
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etud=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
justif_2: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
compute_assiduites_justified(etud_1.etudid, [justif_2])
|
||||||
|
|
||||||
|
assert len(scass.justifies(justif_2)) == 1, "Justification non prise en compte (b1)"
|
||||||
|
|
||||||
|
# Ne fonctionne pas ⬇️
|
||||||
|
# assert assi_2.est_just is True, "Justification non prise en compte (b2)"
|
||||||
|
|
||||||
|
# <- Vérification assiduité complétement couverte ->
|
||||||
|
# Période : 12h -> 19h le 03/01/2024
|
||||||
|
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etud=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-03T12:00:00+01:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-03T19:00:00+01:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Justification complète
|
||||||
|
justif_3: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-03T00:00:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-03T23:59:59", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Justification incomplète
|
||||||
|
justif_4: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-03T08:00:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-03T18:00:00", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mise à jour de l'assiduité
|
||||||
|
compute_assiduites_justified(etud_1.etudid, [justif_3, justif_4])
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(scass.justifies(justif_3)) == 1
|
||||||
|
), "Justification complète non prise en compte (c1)"
|
||||||
|
assert (
|
||||||
|
len(scass.justifies(justif_4)) == 0
|
||||||
|
), "Justification complète non prise en compte (c2)"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Test calculs rattrapages
|
"""Test calculs rattrapages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import app
|
import app
|
||||||
@ -68,7 +69,7 @@ def test_notes_rattrapage(test_client):
|
|||||||
date_debut=datetime.datetime(2020, 1, 2),
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
description="evaluation rattrapage",
|
description="evaluation rattrapage",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
evaluation_type=Evaluation.EVALUATION_RATTRAPAGE,
|
||||||
)
|
)
|
||||||
etud = etuds[0]
|
etud = etuds[0]
|
||||||
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
_, _, _ = G.create_note(evaluation_id=e["id"], etudid=etud["etudid"], note=12.0)
|
||||||
@ -144,7 +145,7 @@ def test_notes_rattrapage(test_client):
|
|||||||
date_debut=datetime.datetime(2020, 1, 2),
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
description="evaluation session 2",
|
description="evaluation session 2",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
evaluation_type=scu.EVALUATION_SESSION2,
|
evaluation_type=Evaluation.EVALUATION_SESSION2,
|
||||||
)
|
)
|
||||||
|
|
||||||
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
||||||
@ -182,3 +183,43 @@ def test_notes_rattrapage(test_client):
|
|||||||
)
|
)
|
||||||
# Note moyenne: revient à note normale
|
# Note moyenne: revient à note normale
|
||||||
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||||
|
# Supprime évaluation session 2
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e_session2["id"], etudid=etud["etudid"], note=scu.NOTES_SUPPRESS
|
||||||
|
)
|
||||||
|
evaluation = db.session.get(Evaluation, e_session2["id"])
|
||||||
|
assert evaluation
|
||||||
|
evaluation.delete()
|
||||||
|
#
|
||||||
|
# --- Evaluation bonus ---
|
||||||
|
#
|
||||||
|
# --- Création d'une évaluation "bonus"
|
||||||
|
e_bonus = G.create_evaluation(
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
|
description="evaluation bonus",
|
||||||
|
coefficient=1.0,
|
||||||
|
evaluation_type=Evaluation.EVALUATION_BONUS,
|
||||||
|
)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
# Note moyenne sans bonus
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
||||||
|
# Saisie note bonus
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=1.0
|
||||||
|
)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
# Note moyenne sans bonus
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(11.0)
|
||||||
|
# Négatif, avec clip à zéro
|
||||||
|
_, _, _ = G.create_note(
|
||||||
|
evaluation_id=e_bonus["id"], etudid=etud["etudid"], note=-20.0
|
||||||
|
)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
|
||||||
|
@ -13,7 +13,7 @@ Au besoin, créer un base de test neuve:
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from app.models import FormSemestreInscription, Identite
|
from app.models import Evaluation, FormSemestreInscription, Identite, ModuleImpl
|
||||||
|
|
||||||
from config import TestConfig
|
from config import TestConfig
|
||||||
from tests.unit import sco_fake_gen
|
from tests.unit import sco_fake_gen
|
||||||
@ -29,7 +29,6 @@ from app.scodoc import sco_bulletins
|
|||||||
from app.scodoc import codes_cursus
|
from app.scodoc import codes_cursus
|
||||||
from app.scodoc import sco_assiduites as scass
|
from app.scodoc import sco_assiduites as scass
|
||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_evaluation_db
|
|
||||||
from app.scodoc import sco_formsemestre_validation
|
from app.scodoc import sco_formsemestre_validation
|
||||||
from app.scodoc import sco_cursus_dut
|
from app.scodoc import sco_cursus_dut
|
||||||
from app.scodoc import sco_saisie_notes
|
from app.scodoc import sco_saisie_notes
|
||||||
@ -81,7 +80,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
module_id=module_id,
|
module_id=module_id,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
|
moduleimpl: ModuleImpl = db.session.get(ModuleImpl, moduleimpl_id)
|
||||||
# --- Inscription des étudiants
|
# --- Inscription des étudiants
|
||||||
for etud in etuds:
|
for etud in etuds:
|
||||||
G.inscrit_etudiant(formsemestre_id, etud)
|
G.inscrit_etudiant(formsemestre_id, etud)
|
||||||
@ -97,17 +96,18 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
assert ins.parcour is None
|
assert ins.parcour is None
|
||||||
|
|
||||||
# --- Création évaluation
|
# --- Création évaluation
|
||||||
e = G.create_evaluation(
|
e1 = Evaluation.create(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl=moduleimpl,
|
||||||
date_debut=datetime.datetime(2020, 1, 1),
|
date_debut=datetime.datetime(2020, 1, 1),
|
||||||
description="evaluation test",
|
description="evaluation test",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
)
|
)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# --- Saisie toutes les notes de l'évaluation
|
# --- Saisie toutes les notes de l'évaluation
|
||||||
for idx, etud in enumerate(etuds):
|
for idx, etud in enumerate(etuds):
|
||||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||||
evaluation_id=e["evaluation_id"],
|
evaluation_id=e1.id,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
note=NOTES_T[idx % len(NOTES_T)],
|
note=NOTES_T[idx % len(NOTES_T)],
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
# --- Vérifie que les notes sont prises en compte:
|
# --- Vérifie que les notes sont prises en compte:
|
||||||
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
|
b = sco_bulletins.formsemestre_bulletinetud_dict(formsemestre_id, etud["etudid"])
|
||||||
# Toute les notes sont saisies, donc eval complète
|
# Toute les notes sont saisies, donc eval complète
|
||||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||||
assert etat["evalcomplete"]
|
assert etat["evalcomplete"]
|
||||||
assert etat["nb_inscrits"] == len(etuds)
|
assert etat["nb_inscrits"] == len(etuds)
|
||||||
assert etat["nb_notes"] == len(etuds)
|
assert etat["nb_notes"] == len(etuds)
|
||||||
@ -131,30 +131,32 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# --- Une autre évaluation
|
# --- Une autre évaluation
|
||||||
e2 = G.create_evaluation(
|
e2 = Evaluation.create(
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl=moduleimpl,
|
||||||
date_debut=datetime.datetime(2020, 1, 2),
|
date_debut=datetime.datetime(2020, 1, 2),
|
||||||
description="evaluation test 2",
|
description="evaluation test 2",
|
||||||
coefficient=1.0,
|
coefficient=1.0,
|
||||||
)
|
)
|
||||||
|
db.session.commit()
|
||||||
# Saisie les notes des 5 premiers étudiants:
|
# Saisie les notes des 5 premiers étudiants:
|
||||||
for idx, etud in enumerate(etuds[:5]):
|
for idx, etud in enumerate(etuds[:5]):
|
||||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||||
evaluation_id=e2["evaluation_id"],
|
evaluation_id=e2.id,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
note=NOTES_T[idx % len(NOTES_T)],
|
note=NOTES_T[idx % len(NOTES_T)],
|
||||||
)
|
)
|
||||||
# Cette éval n'est pas complète
|
# Cette éval n'est pas complète
|
||||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||||
assert etat["evalcomplete"] is False
|
assert etat["evalcomplete"] is False
|
||||||
# la première éval est toujours complète:
|
# la première éval est toujours complète:
|
||||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||||
assert etat["evalcomplete"]
|
assert etat["evalcomplete"]
|
||||||
|
|
||||||
# Modifie l'évaluation 2 pour "prise en compte immédiate"
|
# Modifie l'évaluation 2 pour "prise en compte immédiate"
|
||||||
e2["publish_incomplete"] = True
|
e2.publish_incomplete = True
|
||||||
sco_evaluation_db.do_evaluation_edit(e2)
|
db.session.add(e2)
|
||||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
db.session.flush()
|
||||||
|
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||||
assert etat["evalcomplete"] is False
|
assert etat["evalcomplete"] is False
|
||||||
assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente
|
assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente
|
||||||
assert etat["evalattente"] # mais l'eval est en attente (prise en compte immédiate)
|
assert etat["evalattente"] # mais l'eval est en attente (prise en compte immédiate)
|
||||||
@ -162,26 +164,26 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
# Saisie des notes qui manquent:
|
# Saisie des notes qui manquent:
|
||||||
for idx, etud in enumerate(etuds[5:]):
|
for idx, etud in enumerate(etuds[5:]):
|
||||||
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
etudids_changed, nb_suppress, existing_decisions = G.create_note(
|
||||||
evaluation_id=e2["evaluation_id"],
|
evaluation_id=e2.id,
|
||||||
etudid=etud["etudid"],
|
etudid=etud["etudid"],
|
||||||
note=NOTES_T[idx % len(NOTES_T)],
|
note=NOTES_T[idx % len(NOTES_T)],
|
||||||
)
|
)
|
||||||
etat = sco_evaluations.do_evaluation_etat(e2["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e2.id)
|
||||||
assert etat["evalcomplete"]
|
assert etat["evalcomplete"]
|
||||||
assert etat["nb_att"] == 0
|
assert etat["nb_att"] == 0
|
||||||
assert not etat["evalattente"] # toutes les notes sont présentes
|
assert not etat["evalattente"] # toutes les notes sont présentes
|
||||||
|
|
||||||
# --- Suppression des notes
|
# --- Suppression des notes
|
||||||
sco_saisie_notes.evaluation_suppress_alln(e["evaluation_id"], dialog_confirmed=True)
|
sco_saisie_notes.evaluation_suppress_alln(e1.id, dialog_confirmed=True)
|
||||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||||
assert etat["nb_notes"] == 0
|
assert etat["nb_notes"] == 0
|
||||||
assert not etat["evalcomplete"]
|
assert not etat["evalcomplete"]
|
||||||
# --- Saisie des notes manquantes
|
# --- Saisie des notes manquantes
|
||||||
ans = sco_saisie_notes.do_evaluation_set_missing(
|
ans = sco_saisie_notes.do_evaluation_set_missing(
|
||||||
e["evaluation_id"], 12.34, dialog_confirmed=True
|
e1.id, 12.34, dialog_confirmed=True
|
||||||
)
|
)
|
||||||
assert f'{etat["nb_inscrits"]} notes changées' in ans
|
assert f'{etat["nb_inscrits"]} notes changées' in ans
|
||||||
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
|
etat = sco_evaluations.do_evaluation_etat(e1.id)
|
||||||
assert etat["evalcomplete"]
|
assert etat["evalcomplete"]
|
||||||
|
|
||||||
# -----------------------
|
# -----------------------
|
||||||
|
@ -78,6 +78,13 @@ def import_formation(dept_id: int) -> Formation:
|
|||||||
)
|
)
|
||||||
formation.referentiel_competence_id = ref_comp.id
|
formation.referentiel_competence_id = ref_comp.id
|
||||||
db.session.add(formation)
|
db.session.add(formation)
|
||||||
|
# --- Association niveaux de compétences aux UE de S1:
|
||||||
|
niveaux = ref_comp.get_niveaux_by_parcours(1)[1]["TC"]
|
||||||
|
ues = formation.ues.filter_by(semestre_idx=1).all()
|
||||||
|
assert len(niveaux) == len(ues) # le ref comp et les formation doivent correspondre
|
||||||
|
for ue, niveau in zip(ues, niveaux):
|
||||||
|
ue.niveau_competence = niveau
|
||||||
|
db.session.add(ue)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return formation
|
return formation
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user