WIP: nouvelles gestion jury BUT.
This commit is contained in:
parent
4b49fd5ed9
commit
5d30b9233b
@ -36,7 +36,6 @@ from app.models import Scolog, ScolarAutorisationInscription
|
||||
from app.models.but_validations import (
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
RegroupementCoherentUE,
|
||||
)
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -153,7 +153,7 @@ def pvjury_table_but(
|
||||
etudid=etud.id,
|
||||
),
|
||||
"cursus": _descr_cursus_but(etud),
|
||||
"ects": f"{deca.formsemestre_ects():g}",
|
||||
"ects": f"{deca.ects_annee():g}",
|
||||
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
|
||||
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
|
||||
if deca
|
||||
|
@ -48,9 +48,9 @@ def _get_jury_but_etud_result(
|
||||
# --- Les RCUEs
|
||||
rcue_list = []
|
||||
if deca:
|
||||
for rcue in deca.rcues_annee:
|
||||
dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
|
||||
if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
|
||||
for dec_rcue in deca.get_decisions_rcues_annee():
|
||||
rcue = dec_rcue.rcue
|
||||
if rcue.complete: # n'exporte que les RCUEs complets
|
||||
dec_ue1 = deca.decisions_ues[rcue.ue_1.id]
|
||||
dec_ue2 = deca.decisions_ues[rcue.ue_2.id]
|
||||
rcue_dict = {
|
||||
|
@ -93,35 +93,25 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
||||
<div class="titre">RCUE</div>
|
||||
"""
|
||||
)
|
||||
for niveau in deca.niveaux_competences:
|
||||
for dec_rcue in deca.get_decisions_rcues_annee():
|
||||
rcue = dec_rcue.rcue
|
||||
niveau = rcue.niveau
|
||||
H.append(
|
||||
f"""<div class="but_niveau_titre">
|
||||
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
||||
</div>"""
|
||||
)
|
||||
dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id) # peut être None
|
||||
ues = [
|
||||
ue
|
||||
for ue in deca.ues_impair
|
||||
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
||||
]
|
||||
ue_impair = ues[0] if ues else None
|
||||
ues = [
|
||||
ue
|
||||
for ue in deca.ues_pair
|
||||
if ue.niveau_competence and ue.niveau_competence.id == niveau.id
|
||||
]
|
||||
ue_pair = ues[0] if ues else None
|
||||
ue_impair, ue_pair = rcue.ue_1, rcue.ue_2
|
||||
# Les UEs à afficher,
|
||||
# qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant
|
||||
# qui
|
||||
ues_ro = [
|
||||
(
|
||||
ue_impair,
|
||||
(deca.a_cheval and deca.formsemestre_id != deca.formsemestre_impair.id),
|
||||
rcue.ue_cur_impair is None,
|
||||
),
|
||||
(
|
||||
ue_pair,
|
||||
deca.a_cheval and deca.formsemestre_id != deca.formsemestre_pair.id,
|
||||
rcue.ue_cur_pair is None,
|
||||
),
|
||||
]
|
||||
# Ordonne selon les dates des 2 semestres considérés:
|
||||
@ -155,12 +145,13 @@ def _gen_but_select(
|
||||
code_valide: str,
|
||||
disabled: bool = False,
|
||||
klass: str = "",
|
||||
data: dict = {},
|
||||
data: dict = None,
|
||||
code_valide_label: str = "",
|
||||
) -> str:
|
||||
"Le menu html select avec les codes"
|
||||
# if disabled: # mauvaise idée car le disabled est traité en JS
|
||||
# return f"""<div class="but_code {klass}">{code_valide}</div>"""
|
||||
data = data or {}
|
||||
options_htm = "\n".join(
|
||||
[
|
||||
f"""<option value="{code}"
|
||||
@ -220,8 +211,14 @@ def _gen_but_niveau_ue(
|
||||
else:
|
||||
scoplement = ""
|
||||
|
||||
return f"""<div class="but_niveau_ue {
|
||||
'recorded' if dec_ue.code_valide is not None else ''}
|
||||
ue_class = "" # 'recorded' if dec_ue.code_valide is not None else ''
|
||||
if dec_ue.code_valide is not None and dec_ue.codes:
|
||||
if dec_ue.code_valide == dec_ue.codes[0]:
|
||||
ue_class = "recorded"
|
||||
else:
|
||||
ue_class = "recorded_different"
|
||||
|
||||
return f"""<div class="but_niveau_ue {ue_class}
|
||||
{'annee_prec' if annee_prec else ''}
|
||||
">
|
||||
<div title="{ue.titre}">{ue.acronyme}</div>
|
||||
@ -242,7 +239,7 @@ def _gen_but_niveau_ue(
|
||||
|
||||
|
||||
def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
|
||||
if dec_rcue is None:
|
||||
if dec_rcue is None or not dec_rcue.rcue.complete:
|
||||
return """
|
||||
<div class="but_niveau_rcue niveau_vide with_scoplement">
|
||||
<div></div>
|
||||
|
@ -4,7 +4,6 @@
|
||||
"""
|
||||
from typing import Union
|
||||
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
from app import db
|
||||
from app.models import CODE_STR_LEN
|
||||
@ -13,8 +12,6 @@ from app.models.etudiants import Identite
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc import codes_cursus as sco_codes
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
||||
|
||||
class ApcValidationRCUE(db.Model):
|
||||
@ -22,7 +19,7 @@ class ApcValidationRCUE(db.Model):
|
||||
|
||||
aka "regroupements cohérents d'UE" dans le jargon BUT.
|
||||
|
||||
Le formsemestre est celui du semestre PAIR du niveau de compétence
|
||||
Le formsemestre est l'origine, utilisé pour effacer
|
||||
"""
|
||||
|
||||
__tablename__ = "apc_validation_rcue"
|
||||
@ -109,139 +106,6 @@ class ApcValidationRCUE(db.Model):
|
||||
}
|
||||
|
||||
|
||||
# Attention: ce n'est pas un modèle mais une classe ordinaire:
|
||||
class RegroupementCoherentUE:
|
||||
"""Le regroupement cohérent d'UE, dans la terminologie du BUT, est le couple d'UEs
|
||||
de la même année (BUT1,2,3) liées au *même niveau de compétence*.
|
||||
|
||||
La moyenne (10/20) au RCUE déclenche la compensation des UE.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
etud: Identite,
|
||||
formsemestre_1: FormSemestre,
|
||||
dec_ue_1: "DecisionsProposeesUE",
|
||||
formsemestre_2: FormSemestre,
|
||||
dec_ue_2: "DecisionsProposeesUE",
|
||||
inscription_etat: str,
|
||||
):
|
||||
ue_1 = dec_ue_1.ue
|
||||
ue_2 = dec_ue_2.ue
|
||||
# Ordonne les UE dans le sens croissant (S1,S2) ou (S3,S4)...
|
||||
if formsemestre_1.semestre_id > formsemestre_2.semestre_id:
|
||||
(ue_1, formsemestre_1), (ue_2, formsemestre_2) = (
|
||||
(ue_2, formsemestre_2),
|
||||
(ue_1, formsemestre_1),
|
||||
)
|
||||
assert formsemestre_1.semestre_id % 2 == 1
|
||||
assert formsemestre_2.semestre_id % 2 == 0
|
||||
assert abs(formsemestre_1.semestre_id - formsemestre_2.semestre_id) == 1
|
||||
assert ue_1.niveau_competence_id == ue_2.niveau_competence_id
|
||||
self.etud = etud
|
||||
self.formsemestre_1 = formsemestre_1
|
||||
"semestre impair"
|
||||
self.ue_1 = ue_1
|
||||
self.formsemestre_2 = formsemestre_2
|
||||
"semestre pair"
|
||||
self.ue_2 = ue_2
|
||||
# Stocke les moyennes d'UE
|
||||
if inscription_etat != scu.INSCRIT:
|
||||
self.moy_rcue = None
|
||||
self.moy_ue_1 = self.moy_ue_2 = "-"
|
||||
self.moy_ue_1_val = self.moy_ue_2_val = 0.0
|
||||
return
|
||||
self.moy_ue_1 = dec_ue_1.moy_ue_with_cap
|
||||
self.moy_ue_1_val = self.moy_ue_1 if self.moy_ue_1 is not None else 0.0
|
||||
self.moy_ue_2 = dec_ue_2.moy_ue_with_cap
|
||||
self.moy_ue_2_val = self.moy_ue_2 if self.moy_ue_2 is not None else 0.0
|
||||
|
||||
# Calcul de la moyenne au RCUE (utilise les moy d'UE capitalisées)
|
||||
if (self.moy_ue_1 is not None) and (self.moy_ue_2 is not None):
|
||||
# Moyenne RCUE (les pondérations par défaut sont 1.)
|
||||
self.moy_rcue = (
|
||||
self.moy_ue_1 * ue_1.coef_rcue + self.moy_ue_2 * ue_2.coef_rcue
|
||||
) / (ue_1.coef_rcue + ue_2.coef_rcue)
|
||||
else:
|
||||
self.moy_rcue = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"""<{self.__class__.__name__} {
|
||||
self.ue_1.acronyme}({self.moy_ue_1}) {
|
||||
self.ue_2.acronyme}({self.moy_ue_2})>"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"""RCUE {
|
||||
self.ue_1.acronyme}({self.moy_ue_1}) + {
|
||||
self.ue_2.acronyme}({self.moy_ue_2})"""
|
||||
|
||||
def query_validations(
|
||||
self,
|
||||
) -> Query: # list[ApcValidationRCUE]
|
||||
"""Les validations de jury enregistrées pour ce RCUE"""
|
||||
niveau = self.ue_2.niveau_competence
|
||||
|
||||
return (
|
||||
ApcValidationRCUE.query.filter_by(
|
||||
etudid=self.etud.id,
|
||||
)
|
||||
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue2_id)
|
||||
.join(ApcNiveau, UniteEns.niveau_competence_id == ApcNiveau.id)
|
||||
.filter(ApcNiveau.id == niveau.id)
|
||||
)
|
||||
|
||||
def other_ue(self, ue: UniteEns) -> UniteEns:
|
||||
"""L'autre UE du regroupement. Si ue ne fait pas partie du regroupement, ValueError"""
|
||||
if ue.id == self.ue_1.id:
|
||||
return self.ue_2
|
||||
elif ue.id == self.ue_2.id:
|
||||
return self.ue_1
|
||||
raise ValueError(f"ue {ue} hors RCUE {self}")
|
||||
|
||||
def est_enregistre(self) -> bool:
|
||||
"""Vrai si ce RCUE, donc le niveau de compétences correspondant
|
||||
a une décision jury enregistrée
|
||||
"""
|
||||
return self.query_validations().count() > 0
|
||||
|
||||
def est_compensable(self):
|
||||
"""Vrai si ce RCUE est validable (uniquement) par compensation
|
||||
c'est à dire que sa moyenne est > 10 avec une UE < 10.
|
||||
Note: si ADM, est_compensable est faux.
|
||||
"""
|
||||
return (
|
||||
(self.moy_rcue is not None)
|
||||
and (self.moy_rcue > sco_codes.BUT_BARRE_RCUE)
|
||||
and (
|
||||
(self.moy_ue_1_val < sco_codes.NOTES_BARRE_GEN)
|
||||
or (self.moy_ue_2_val < sco_codes.NOTES_BARRE_GEN)
|
||||
)
|
||||
)
|
||||
|
||||
def est_suffisant(self) -> bool:
|
||||
"""Vrai si ce RCUE est > 8"""
|
||||
return (self.moy_rcue is not None) and (
|
||||
self.moy_rcue > sco_codes.BUT_RCUE_SUFFISANT
|
||||
)
|
||||
|
||||
def est_validable(self) -> bool:
|
||||
"""Vrai si ce RCUE satisfait les conditions pour être validé,
|
||||
c'est à dire que la moyenne des UE qui le constituent soit > 10
|
||||
"""
|
||||
return (self.moy_rcue is not None) and (
|
||||
self.moy_rcue > sco_codes.BUT_BARRE_RCUE
|
||||
)
|
||||
|
||||
def code_valide(self) -> Union[ApcValidationRCUE, None]:
|
||||
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
|
||||
validation = self.query_validations().first()
|
||||
if (validation is not None) and (
|
||||
validation.code in sco_codes.CODES_RCUE_VALIDES
|
||||
):
|
||||
return validation
|
||||
return None
|
||||
|
||||
|
||||
# unused
|
||||
# def find_rcues(
|
||||
# formsemestre: FormSemestre, ue: UniteEns, etud: Identite, inscription_etat: str
|
||||
@ -319,7 +183,7 @@ class ApcValidationAnnee(db.Model):
|
||||
formsemestre_id = db.Column(
|
||||
db.Integer, db.ForeignKey("notes_formsemestre.id"), nullable=True
|
||||
)
|
||||
"le semestre IMPAIR (le 1er) de l'année"
|
||||
"le semestre origine, normalement l'IMPAIR (le 1er) de l'année"
|
||||
formation_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_formations.id"),
|
||||
|
@ -265,11 +265,8 @@ class ScolarNews(db.Model):
|
||||
|
||||
# Informations générales
|
||||
H.append(
|
||||
f"""<div>
|
||||
Pour être informé des évolutions de ScoDoc,
|
||||
vous pouvez vous
|
||||
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">
|
||||
abonner à la liste de diffusion</a>.
|
||||
f"""<div><a class="discretelink" href="{scu.SCO_ANNONCES_WEBSITE}">
|
||||
Pour en savoir plus sur ScoDoc voir le site scodoc.org</a>.
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
@ -68,7 +68,7 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
if self.ue_id:
|
||||
# Note: si l'objet vient d'être créé, ue_id peut exister mais pas ue !
|
||||
return f"""décision sur UE {self.ue.acronyme if self.ue else self.ue_id
|
||||
}: {self.code}"""
|
||||
} ({self.ue_id}): {self.code}"""
|
||||
return f"""décision sur semestre {self.formsemestre.titre_mois()} du {
|
||||
self.event_date.strftime("%d/%m/%Y")}"""
|
||||
|
||||
|
@ -793,7 +793,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
{tf[1]}
|
||||
"""
|
||||
elif tf[0] == -1:
|
||||
return "<h4>annulation</h4>"
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
if tf[2]["gestion_compensation_lst"]:
|
||||
tf[2]["gestion_compensation"] = True
|
||||
|
@ -349,7 +349,7 @@ SCO_DEFAULT_SQL_USERS_CNX = "dbname=SCOUSERS port=%s" % SCO_DEFAULT_SQL_PORT
|
||||
# Valeurs utilisées pour affichage seulement, pas de requetes ni de mails envoyés:
|
||||
SCO_WEBSITE = "https://scodoc.org"
|
||||
SCO_USER_MANUAL = "https://scodoc.org/GuideUtilisateur"
|
||||
SCO_ANNONCES_WEBSITE = "https://listes.univ-paris13.fr/mailman/listinfo/scodoc-annonces"
|
||||
SCO_ANNONCES_WEBSITE = "https://scodoc.org/Contact"
|
||||
SCO_DEVEL_LIST = "scodoc-devel@listes.univ-paris13.fr"
|
||||
SCO_USERS_LIST = "notes@listes.univ-paris13.fr"
|
||||
SCO_LISTS_URL = "https://scodoc.org/Contact"
|
||||
|
@ -1,291 +1,316 @@
|
||||
// JS for all ScoDoc pages (using jQuery UI)
|
||||
|
||||
|
||||
$(function () {
|
||||
// Autocomplete recherche etudiants par nom
|
||||
$(".in-expnom").autocomplete(
|
||||
{
|
||||
delay: 300, // wait 300ms before suggestions
|
||||
minLength: 2, // min nb of chars before suggest
|
||||
position: { collision: 'flip' }, // automatic menu position up/down
|
||||
source: SCO_URL + "/search_etud_by_name",
|
||||
select: function (event, ui) {
|
||||
$(".in-expnom").val(ui.item.value);
|
||||
$("#form-chercheetud").submit();
|
||||
}
|
||||
});
|
||||
// Autocomplete recherche etudiants par nom
|
||||
$(".in-expnom").autocomplete({
|
||||
delay: 300, // wait 300ms before suggestions
|
||||
minLength: 2, // min nb of chars before suggest
|
||||
position: { collision: "flip" }, // automatic menu position up/down
|
||||
source: SCO_URL + "/search_etud_by_name",
|
||||
select: function (event, ui) {
|
||||
$(".in-expnom").val(ui.item.value);
|
||||
$("#form-chercheetud").submit();
|
||||
},
|
||||
});
|
||||
|
||||
// Date picker
|
||||
$(".datepicker").datepicker({
|
||||
showOn: 'button',
|
||||
buttonImage: '/ScoDoc/static/icons/calendar_img.png',
|
||||
buttonImageOnly: true,
|
||||
dateFormat: 'dd/mm/yy',
|
||||
duration: 'fast',
|
||||
});
|
||||
$('.datepicker').datepicker('option', $.extend({ showMonthAfterYear: false },
|
||||
$.datepicker.regional['fr']));
|
||||
// Date picker
|
||||
$(".datepicker").datepicker({
|
||||
showOn: "button",
|
||||
buttonImage: "/ScoDoc/static/icons/calendar_img.png",
|
||||
buttonImageOnly: true,
|
||||
dateFormat: "dd/mm/yy",
|
||||
duration: "fast",
|
||||
});
|
||||
$(".datepicker").datepicker(
|
||||
"option",
|
||||
$.extend({ showMonthAfterYear: false }, $.datepicker.regional["fr"])
|
||||
);
|
||||
|
||||
/* Barre menu */
|
||||
var sco_menu_position = { my: "left top", at: "left bottom" };
|
||||
$("#sco_menu").menu({
|
||||
position: sco_menu_position,
|
||||
blur: function () {
|
||||
$(this).menu("option", "position", sco_menu_position);
|
||||
},
|
||||
focus: function (e, ui) {
|
||||
if ($("#sco_menu").get(0) !== $(ui).get(0).item.parent().get(0)) {
|
||||
$(this).menu("option", "position", { my: "left top", at: "right top" });
|
||||
}
|
||||
/* Barre menu */
|
||||
var sco_menu_position = { my: "left top", at: "left bottom" };
|
||||
$("#sco_menu")
|
||||
.menu({
|
||||
position: sco_menu_position,
|
||||
blur: function () {
|
||||
$(this).menu("option", "position", sco_menu_position);
|
||||
},
|
||||
focus: function (e, ui) {
|
||||
if ($("#sco_menu").get(0) !== $(ui).get(0).item.parent().get(0)) {
|
||||
$(this).menu("option", "position", {
|
||||
my: "left top",
|
||||
at: "right top",
|
||||
});
|
||||
}
|
||||
}).mouseleave(function (x, y) {
|
||||
$("#sco_menu").menu('collapseAll');
|
||||
},
|
||||
})
|
||||
.mouseleave(function (x, y) {
|
||||
$("#sco_menu").menu("collapseAll");
|
||||
});
|
||||
|
||||
$("#sco_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s");
|
||||
$("#sco_menu > li > a > span").switchClass(
|
||||
"ui-icon-carat-1-e",
|
||||
"ui-icon-carat-1-s"
|
||||
);
|
||||
|
||||
/* Les menus isoles dropdown */
|
||||
$(".sco_dropdown_menu").menu({
|
||||
position: sco_menu_position
|
||||
}).mouseleave(function (x, y) {
|
||||
$(".sco_dropdown_menu").menu('collapseAll');
|
||||
}
|
||||
);
|
||||
$(".sco_dropdown_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s");
|
||||
/* Les menus isoles dropdown */
|
||||
$(".sco_dropdown_menu")
|
||||
.menu({
|
||||
position: sco_menu_position,
|
||||
})
|
||||
.mouseleave(function (x, y) {
|
||||
$(".sco_dropdown_menu").menu("collapseAll");
|
||||
});
|
||||
$(".sco_dropdown_menu > li > a > span").switchClass(
|
||||
"ui-icon-carat-1-e",
|
||||
"ui-icon-carat-1-s"
|
||||
);
|
||||
|
||||
/* up-to-date status */
|
||||
var update_div = document.getElementById("update_warning");
|
||||
if (update_div) {
|
||||
fetch('install_info').then(
|
||||
response => response.text()
|
||||
).then(text => {
|
||||
update_div.innerHTML = text;
|
||||
if (text) {
|
||||
update_div.style.display = "block";
|
||||
}
|
||||
});
|
||||
}
|
||||
/* up-to-date status */
|
||||
var update_div = document.getElementById("update_warning");
|
||||
if (update_div) {
|
||||
fetch("install_info")
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
update_div.innerHTML = text;
|
||||
if (text) {
|
||||
update_div.style.display = "block";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function sco_capitalize(string) {
|
||||
return string[0].toUpperCase() + string.slice(1).toLowerCase();
|
||||
return string[0].toUpperCase() + string.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
// Affiche un message transitoire (duration milliseconds, 0 means infinity)
|
||||
function sco_message(msg, className = "message_custom", duration = 0) {
|
||||
var div = document.createElement("div");
|
||||
div.className = className;
|
||||
div.innerHTML = msg;
|
||||
document.querySelector("body").appendChild(div);
|
||||
if (duration) {
|
||||
setTimeout(() => {
|
||||
div.remove();
|
||||
}, 3000);
|
||||
}
|
||||
var div = document.createElement("div");
|
||||
div.className = className;
|
||||
div.innerHTML = msg;
|
||||
document.querySelector("body").appendChild(div);
|
||||
if (duration) {
|
||||
setTimeout(() => {
|
||||
div.remove();
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function sco_error_message(msg) {
|
||||
sco_message(msg, className = "message_error", duration = 0);
|
||||
sco_message(msg, (className = "message_error"), (duration = 0));
|
||||
}
|
||||
|
||||
|
||||
function get_query_args() {
|
||||
var s = window.location.search; // eg "?x=1&y=2"
|
||||
var vars = {};
|
||||
s.replace(
|
||||
/[?&]+([^=&]+)=?([^&]*)?/gi, // regexp
|
||||
function (m, key, value) { // callback
|
||||
vars[key] = value !== undefined ? value : '';
|
||||
}
|
||||
);
|
||||
return vars;
|
||||
var s = window.location.search; // eg "?x=1&y=2"
|
||||
var vars = {};
|
||||
s.replace(
|
||||
/[?&]+([^=&]+)=?([^&]*)?/gi, // regexp
|
||||
function (m, key, value) {
|
||||
// callback
|
||||
vars[key] = value !== undefined ? value : "";
|
||||
}
|
||||
);
|
||||
return vars;
|
||||
}
|
||||
|
||||
|
||||
// Tables (gen_tables)
|
||||
$(function () {
|
||||
if ($('table.gt_table').length > 0) {
|
||||
var table_options = {
|
||||
"paging": false,
|
||||
"searching": false,
|
||||
"info": false,
|
||||
/* "autoWidth" : false, */
|
||||
"fixedHeader": {
|
||||
"header": true,
|
||||
"footer": true
|
||||
},
|
||||
"orderCellsTop": true, // cellules ligne 1 pour tri
|
||||
"aaSorting": [], // Prevent initial sorting
|
||||
};
|
||||
$('table.gt_table').DataTable(table_options);
|
||||
table_options["searching"] = true;
|
||||
$('table.gt_table_searchable').DataTable(table_options);
|
||||
}
|
||||
if ($("table.gt_table").length > 0) {
|
||||
var table_options = {
|
||||
paging: false,
|
||||
searching: false,
|
||||
info: false,
|
||||
/* "autoWidth" : false, */
|
||||
fixedHeader: {
|
||||
header: true,
|
||||
footer: true,
|
||||
},
|
||||
orderCellsTop: true, // cellules ligne 1 pour tri
|
||||
aaSorting: [], // Prevent initial sorting
|
||||
};
|
||||
$("table.gt_table").DataTable(table_options);
|
||||
table_options["searching"] = true;
|
||||
$("table.gt_table_searchable").DataTable(table_options);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Show tags (readonly)
|
||||
function readOnlyTags(nodes) {
|
||||
// nodes are textareas, hide them and create a span showing tags
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = $(nodes[i]);
|
||||
node.hide();
|
||||
var tags = nodes[i].value.split(',');
|
||||
node.after('<span class="ro_tags"><span class="ro_tag">' + tags.join('</span><span class="ro_tag">') + '</span></span>');
|
||||
}
|
||||
// nodes are textareas, hide them and create a span showing tags
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = $(nodes[i]);
|
||||
node.hide();
|
||||
var tags = nodes[i].value.split(",");
|
||||
node.after(
|
||||
'<span class="ro_tags"><span class="ro_tag">' +
|
||||
tags.join('</span><span class="ro_tag">') +
|
||||
"</span></span>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Editeur pour champs
|
||||
* Usage: créer un élément avec data-oid (object id)
|
||||
* La méthode d'URL save sera appelée en POST avec deux arguments: oid et value,
|
||||
* La méthode d'URL save sera appelée en POST avec deux arguments: oid et value,
|
||||
* value contenant la valeur du champs.
|
||||
* Inspiré par les codes et conseils de Seb. L.
|
||||
*/
|
||||
class ScoFieldEditor {
|
||||
constructor(selector, save_url, read_only) {
|
||||
this.save_url = save_url;
|
||||
this.read_only = read_only;
|
||||
this.selector = selector;
|
||||
this.installListeners();
|
||||
constructor(selector, save_url, read_only) {
|
||||
this.save_url = save_url;
|
||||
this.read_only = read_only;
|
||||
this.selector = selector;
|
||||
this.installListeners();
|
||||
}
|
||||
// Enregistre l'élément obj
|
||||
save(obj) {
|
||||
var value = obj.innerText.trim();
|
||||
if (value.length == 0) {
|
||||
value = "";
|
||||
}
|
||||
// Enregistre l'élément obj
|
||||
save(obj) {
|
||||
var value = obj.innerText.trim();
|
||||
if (value.length == 0) {
|
||||
value = "";
|
||||
}
|
||||
if (value == obj.dataset.value) {
|
||||
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
|
||||
}
|
||||
obj.classList.add("sco_wait");
|
||||
// DEBUG
|
||||
// console.log(`
|
||||
// data : ${value},
|
||||
// id: ${obj.dataset.oid}
|
||||
// `);
|
||||
if (value == obj.dataset.value) {
|
||||
return true; // Aucune modification, pas d'enregistrement mais on continue normalement
|
||||
}
|
||||
obj.classList.add("sco_wait");
|
||||
// DEBUG
|
||||
// console.log(`
|
||||
// data : ${value},
|
||||
// id: ${obj.dataset.oid}
|
||||
// `);
|
||||
|
||||
$.post(this.save_url,
|
||||
{
|
||||
oid: obj.dataset.oid,
|
||||
value: value,
|
||||
},
|
||||
function (result) {
|
||||
obj.classList.remove("sco_wait");
|
||||
obj.classList.add("sco_modified");
|
||||
}
|
||||
);
|
||||
return true;
|
||||
$.post(
|
||||
this.save_url,
|
||||
{
|
||||
oid: obj.dataset.oid,
|
||||
value: value,
|
||||
},
|
||||
function (result) {
|
||||
obj.classList.remove("sco_wait");
|
||||
obj.classList.add("sco_modified");
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
/*****************************/
|
||||
/* Gestion des évènements */
|
||||
/*****************************/
|
||||
installListeners() {
|
||||
if (this.read_only) {
|
||||
return;
|
||||
}
|
||||
/*****************************/
|
||||
/* Gestion des évènements */
|
||||
/*****************************/
|
||||
installListeners() {
|
||||
if (this.read_only) {
|
||||
return;
|
||||
document.body.addEventListener("keydown", this.key);
|
||||
let editor = this;
|
||||
this.handleSelectCell = (event) => {
|
||||
editor.selectCell(event);
|
||||
};
|
||||
this.handleModifCell = (event) => {
|
||||
editor.modifCell(event);
|
||||
};
|
||||
this.handleBlur = (event) => {
|
||||
editor.blurCell(event);
|
||||
};
|
||||
this.handleKeyCell = (event) => {
|
||||
editor.keyCell(event);
|
||||
};
|
||||
document.querySelectorAll(this.selector).forEach((cellule) => {
|
||||
cellule.addEventListener("click", this.handleSelectCell);
|
||||
cellule.addEventListener("dblclick", this.handleModifCell);
|
||||
cellule.addEventListener("blur", this.handleBlur);
|
||||
});
|
||||
}
|
||||
/*********************************/
|
||||
/* Interaction avec les cellules */
|
||||
/*********************************/
|
||||
blurCell(event) {
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (obj.classList.contains("sco_modifying")) {
|
||||
return; // Cellule en cours de modification, ne pas sélectionner.
|
||||
}
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
document.body.addEventListener("keydown", this.key);
|
||||
let editor = this;
|
||||
this.handleSelectCell = (event) => { editor.selectCell(event) };
|
||||
this.handleModifCell = (event) => { editor.modifCell(event) };
|
||||
this.handleBlur = (event) => { editor.blurCell(event) };
|
||||
this.handleKeyCell = (event) => { editor.keyCell(event) };
|
||||
document.querySelectorAll(this.selector).forEach(cellule => {
|
||||
cellule.addEventListener("click", this.handleSelectCell);
|
||||
cellule.addEventListener("dblclick", this.handleModifCell);
|
||||
cellule.addEventListener("blur", this.handleBlur);
|
||||
});
|
||||
}
|
||||
/*********************************/
|
||||
/* Interaction avec les cellules */
|
||||
/*********************************/
|
||||
blurCell(event) {
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (obj.classList.contains("sco_modifying")) {
|
||||
return; // Cellule en cours de modification, ne pas sélectionner.
|
||||
}
|
||||
let currentModif = document.querySelector(".sco_modifying");
|
||||
if (currentModif) {
|
||||
if (!this.save(currentModif)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.unselectCell();
|
||||
obj.classList.add("sco_selected");
|
||||
}
|
||||
}
|
||||
unselectCell() {
|
||||
document.querySelectorAll(".sco_selected, .sco_modifying").forEach(cellule => {
|
||||
cellule.classList.remove("sco_selected", "sco_modifying");
|
||||
cellule.removeAttribute("contentEditable");
|
||||
cellule.removeEventListener("keydown", this.handleKeyCell);
|
||||
});
|
||||
}
|
||||
modifCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
obj.classList.add("sco_modifying");
|
||||
obj.contentEditable = true;
|
||||
obj.addEventListener("keydown", this.handleKeyCell);
|
||||
obj.focus();
|
||||
}
|
||||
}
|
||||
key(event) {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
this.modifCell(document.querySelector(".sco_selected"));
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
keyCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (event.key == "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!this.save(obj)) {
|
||||
return
|
||||
}
|
||||
obj.classList.remove("sco_modifying");
|
||||
// ArrowMove(0, 1);
|
||||
// modifCell(document.querySelector(".sco_selected"));
|
||||
this.unselectCell();
|
||||
}
|
||||
this.unselectCell();
|
||||
obj.classList.add("sco_selected");
|
||||
}
|
||||
}
|
||||
unselectCell() {
|
||||
document
|
||||
.querySelectorAll(".sco_selected, .sco_modifying")
|
||||
.forEach((cellule) => {
|
||||
cellule.classList.remove("sco_selected", "sco_modifying");
|
||||
cellule.removeAttribute("contentEditable");
|
||||
cellule.removeEventListener("keydown", this.handleKeyCell);
|
||||
});
|
||||
}
|
||||
modifCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
obj.classList.add("sco_modifying");
|
||||
obj.contentEditable = true;
|
||||
obj.addEventListener("keydown", this.handleKeyCell);
|
||||
obj.focus();
|
||||
}
|
||||
}
|
||||
key(event) {
|
||||
switch (event.key) {
|
||||
case "Enter":
|
||||
this.modifCell(document.querySelector(".sco_selected"));
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
keyCell(event) {
|
||||
let obj = event.currentTarget;
|
||||
if (obj) {
|
||||
if (event.key == "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (!this.save(obj)) {
|
||||
return;
|
||||
}
|
||||
obj.classList.remove("sco_modifying");
|
||||
// ArrowMove(0, 1);
|
||||
// modifCell(document.querySelector(".sco_selected"));
|
||||
this.unselectCell();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentScriptPath() {
|
||||
// Get all the script elements on the page
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
// Get all the script elements on the page
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
|
||||
// Find the last script element (which is the currently executing script)
|
||||
var currentScript = scripts[scripts.length - 1];
|
||||
// Find the last script element (which is the currently executing script)
|
||||
var currentScript = scripts[scripts.length - 1];
|
||||
|
||||
// Retrieve the src attribute of the script element
|
||||
var scriptPath = currentScript.src;
|
||||
// Retrieve the src attribute of the script element
|
||||
var scriptPath = currentScript.src;
|
||||
|
||||
return scriptPath;
|
||||
return scriptPath;
|
||||
}
|
||||
function removeLastTwoComponents(path) {
|
||||
// Split the path into individual components
|
||||
var components = path.split('/');
|
||||
// Split the path into individual components
|
||||
var components = path.split("/");
|
||||
|
||||
// Remove the last two components (filename and enclosing directory)
|
||||
components.splice(-2);
|
||||
// Remove the last two components (filename and enclosing directory)
|
||||
components.splice(-2);
|
||||
|
||||
// Join the remaining components back into a path
|
||||
var newPath = components.join('/');
|
||||
// Join the remaining components back into a path
|
||||
var newPath = components.join("/");
|
||||
|
||||
return newPath;
|
||||
return newPath;
|
||||
}
|
||||
|
@ -74,9 +74,8 @@ class TableJury(TableRecap):
|
||||
self.freq_codes_annuels[deca.code_valide] += 1
|
||||
row.add_nb_rcues_cell()
|
||||
# --- Les RCUEs
|
||||
for rcue in deca.rcues_annee:
|
||||
dec_rcue = deca.dec_rcue_by_ue.get(rcue.ue_1.id)
|
||||
if dec_rcue is not None: # None si l'UE n'est pas associée à un niveau
|
||||
for dec_rcue in deca.get_decisions_rcues_annee():
|
||||
if dec_rcue.rcue.complete:
|
||||
row.add_rcue_cols(dec_rcue)
|
||||
self.freq_codes_annuels["total"] = len(self.rows)
|
||||
|
||||
@ -205,7 +204,7 @@ class RowJury(RowRecap):
|
||||
else:
|
||||
classes.append("moy_ue_valid")
|
||||
|
||||
if len(deca.rcues_annee) > 0:
|
||||
if len(deca.get_decisions_rcues_annee()) > 0:
|
||||
# permet un tri par nb de niveaux validables + moyenne gen indicative S_pair
|
||||
if deca.res_pair and deca.etud.id in deca.res_pair.etud_moy_gen:
|
||||
moy = deca.res_pair.etud_moy_gen[deca.etud.id]
|
||||
@ -260,9 +259,11 @@ class RowJury(RowRecap):
|
||||
|
||||
def add_rcue_cols(self, dec_rcue: DecisionsProposeesRCUE):
|
||||
"2 cells: moyenne du RCUE, code enregistré"
|
||||
self.table.group_titles["rcue"] = "RCUEs en cours"
|
||||
rcue = dec_rcue.rcue
|
||||
col_id = f"moy_rcue_{rcue.ue_1.niveau_competence_id}" # le niveau_id
|
||||
if not rcue.complete:
|
||||
return
|
||||
col_id = f"moy_rcue_{rcue.niveau.id}" # le niveau_id
|
||||
self.table.group_titles["rcue"] = "RCUEs en cours"
|
||||
note_class = ""
|
||||
val = rcue.moy_rcue
|
||||
if isinstance(val, float):
|
||||
|
@ -130,6 +130,12 @@
|
||||
<td class="amue">CODJ</td>
|
||||
<td>Acquis par décision du jury</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ADSUP</td>
|
||||
<td>{{codes["ADSUP"]}}</td>
|
||||
<td class="amue"></td>
|
||||
<td>Acquis parce que le niveau de compétence supérieur est acquis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AJ</td>
|
||||
<td>{{codes["AJ"]}}</td>
|
||||
@ -200,6 +206,12 @@
|
||||
<td class="amue"></td>
|
||||
<td>Acquis par décision de jury sur le RCUE (ECTS acquis)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ADSUP</td>
|
||||
<td>{{codes["ADSUP"]}}</td>
|
||||
<td class="amue"></td>
|
||||
<td>Acquis parce que le niveau de compétence supérieur est acquis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AJ</td>
|
||||
<td>{{codes["AJ"]}}</td>
|
||||
|
@ -13,6 +13,7 @@
|
||||
<ul>
|
||||
<li>Semestre: {{ue.semestre_idx}}</li>
|
||||
<li>Code: <tt>{{ue.ue_code}}</tt></li>
|
||||
<li>ECTS: <b>{{ue.ects or 0}}</b></li>
|
||||
<li>Type: {{ue.type}}</li>
|
||||
<li>Externe: {{ "oui" if ue.is_external else "non" }}</li>
|
||||
<li>Code Apogée: {{ue.code_apogee or "aucun"}}</li>
|
||||
|
@ -2430,7 +2430,7 @@ def formsemestre_validation_but(
|
||||
)
|
||||
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
if len(deca.rcues_annee) == 0:
|
||||
if len(deca.get_decisions_rcues_annee()) == 0:
|
||||
return jury_but_view.jury_but_semestriel(
|
||||
formsemestre, etud, read_only, navigation_div=navigation_div
|
||||
)
|
||||
@ -2459,22 +2459,25 @@ def formsemestre_validation_but(
|
||||
|
||||
warning = ""
|
||||
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
|
||||
if deca.a_cheval:
|
||||
warning += """<div class="warning">Attention: regroupements RCUE
|
||||
entre années (redoublement).</div>"""
|
||||
else:
|
||||
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
|
||||
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
|
||||
niveaux mais {len(deca.decisions_rcue_by_niveau)} regroupements RCUE.</div>"""
|
||||
if (deca.parcour is None) and len(formsemestre.parcours) > 0:
|
||||
warning += (
|
||||
"""<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>"""
|
||||
)
|
||||
if deca.formsemestre_impair and deca.inscription_etat_impair != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_impair, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
|
||||
if deca.formsemestre_pair and deca.inscription_etat_pair != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(deca.inscription_etat_pair, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||
|
||||
if deca.formsemestre_impair:
|
||||
inscription = deca.formsemestre_impair.etuds_inscriptions.get(etud.id)
|
||||
if (not inscription) or inscription.etat != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
|
||||
|
||||
if deca.formsemestre_pair:
|
||||
inscription = deca.formsemestre_pair.etuds_inscriptions.get(etud.id)
|
||||
if (not inscription) or inscription.etat != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||
|
||||
if deca.has_notes_en_attente():
|
||||
warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente.
|
||||
Vous devriez régler cela avant de statuer en jury !</div>"""
|
||||
@ -2531,7 +2534,7 @@ def formsemestre_validation_but(
|
||||
else:
|
||||
erase_span = f"""<a href="{
|
||||
url_for("notes.formsemestre_jury_but_erase",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre.id,
|
||||
etudid=deca.etud.id)}" class="stdlink"
|
||||
title="efface décisions issues des jurys de cette année"
|
||||
>effacer décisions de ce jury</a>
|
||||
@ -2564,15 +2567,7 @@ def formsemestre_validation_but(
|
||||
)
|
||||
H.append(navigation_div)
|
||||
H.append("</form>")
|
||||
if deca.a_cheval:
|
||||
H.append(
|
||||
f"""<div class="but_doc_codes but_warning_rcue_cap">
|
||||
{scu.EMO_WARNING} Rappel: pour les redoublants, seules les UE <b>capitalisées</b> (note > 10)
|
||||
lors d'une année précédente peuvent être prise en compte pour former
|
||||
un RCUE (associé à un niveau de compétence du BUT).
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
# Affichage cursus BUT
|
||||
but_cursus = cursus_but.EtudCursusBUT(etud, deca.formsemestre.formation)
|
||||
H += [
|
||||
@ -2595,7 +2590,14 @@ def formsemestre_validation_but(
|
||||
codes=ScoDocSiteConfig.get_codes_apo_dict(),
|
||||
)
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<div class="but_doc_codes but_warning_rcue_cap">
|
||||
{scu.EMO_WARNING} Rappel: pour les redoublants, seules les UE <b>capitalisées</b> (note > 10)
|
||||
lors d'une année précédente peuvent être prise en compte pour former
|
||||
un RCUE (associé à un niveau de compétence du BUT).
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.4.93"
|
||||
SCOVERSION = "9.4.94"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user