Améliore saisie des UE extérieures. Pas de coefs en BUT.

This commit is contained in:
Emmanuel Viennet 2022-09-03 20:48:56 +02:00
parent c67d9a4ba5
commit ffa5190a49
4 changed files with 218 additions and 169 deletions

View File

@ -228,7 +228,7 @@ class ResultatsSemestre(ResultatsCache):
UE capitalisées.
"""
# Supposant qu'il y a peu d'UE capitalisées,
# on va soustraire la moyenne d'UE et ajouter celle de l'UE capitalisée.
# on recalcule les moyennes gen des etuds ayant des UE capitalisée.
if not self.validations:
self.validations = res_sem.load_formsemestre_validations(self.formsemestre)
ue_capitalisees = self.validations.ue_capitalisees
@ -396,10 +396,10 @@ class ResultatsSemestre(ResultatsCache):
def compute_etud_ue_coef(self, etudid: int, ue: UniteEns) -> float:
"Détermine le coefficient de l'UE pour cet étudiant."
# calcul différent en classqiue et BUT
# calcul différent en classique et BUT
raise NotImplementedError()
def get_etud_ue_cap_coef(self, etudid, ue, ue_cap):
def get_etud_ue_cap_coef(self, etudid, ue, ue_cap): # UNUSED in ScoDoc 9
"""Calcule le coefficient d'une UE capitalisée, pour cet étudiant,
injectée dans le semestre courant.

View File

@ -28,6 +28,7 @@ def TrivialFormulator(
submitlabel="OK",
name=None,
formid="tf",
form_attrs="",
cssclass="",
cancelbutton=None,
submitbutton=True,
@ -100,7 +101,8 @@ def TrivialFormulator(
submitlabel,
name,
formid,
cssclass,
form_attrs=form_attrs,
cssclass=cssclass,
cancelbutton=cancelbutton,
submitbutton=submitbutton,
submitbuttonattributes=submitbuttonattributes,
@ -132,6 +134,7 @@ class TF(object):
submitlabel="OK",
name=None,
formid="tf",
form_attrs="",
cssclass="",
cancelbutton=None,
submitbutton=True,
@ -154,6 +157,7 @@ class TF(object):
else:
self.name = formid # 'tf'
self.formid = formid
self.form_attrs = form_attrs
self.cssclass = cssclass
self.cancelbutton = cancelbutton
self.submitbutton = submitbutton
@ -408,8 +412,16 @@ class TF(object):
klass = ""
name = self.name
R.append(
'<form action="%s" method="%s" id="%s" enctype="%s" name="%s" %s>'
% (self.form_url, self.method, self.formid, enctype, name, klass)
'<form action="%s" method="%s" id="%s" enctype="%s" name="%s" %s %s>'
% (
self.form_url,
self.method,
self.formid,
enctype,
name,
klass,
self.form_attrs,
)
)
R.append('<input type="hidden" name="%s_submitted" value="1">' % self.formid)
if self.top_buttons:

View File

@ -39,20 +39,23 @@ from flask_login import current_user
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
from app.models import (
FormSemestre,
FormSemestreUECoef,
Identite,
ScolarFormSemestreValidation,
UniteEns,
)
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_cursus_dut
from app.scodoc import sco_etud
from app.scodoc.sco_codes_parcours import UE_SPORT
def formsemestre_ext_create(etudid, sem_params):
@ -84,7 +87,7 @@ def formsemestre_ext_create(etudid, sem_params):
def formsemestre_ext_create_form(etudid, formsemestre_id):
"""Formulaire création/inscription à un semestre extérieur"""
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
etud = Identite.query.get_or_404(etudid)
H = [
html_sco_header.sco_header(),
f"""<h2>Enregistrement d'une inscription antérieure dans un autre
@ -103,7 +106,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
</p>
<h3><a href="{ url_for('scolar.ficheEtud',
scodoc_dept=g.scodoc_dept, etudid=etudid)
}" class="stdlink">Étudiant {etud["nomprenom"]}</a></h3>
}" class="stdlink">Étudiant {etud.nomprenom}</a></h3>
""",
]
F = html_sco_header.sco_footer()
@ -228,66 +231,82 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid):
"""Edition des validations d'UE et de semestre (jury)
pour un semestre extérieur.
On peut saisir pour chaque UE du programme de formation
sa validation, son code jury, sa note, son coefficient.
sa validation, son code jury, sa note, son coefficient
(sauf en BUT le coef. des UE est toujours égal aux ECTS).
La moyenne générale du semestre est calculée et affichée,
La moyenne générale indicative du semestre est calculée et affichée,
mais pas enregistrée.
"""
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
ues = _list_ue_with_coef_and_validations(sem, etudid)
descr = _ue_form_description(ues, scu.get_request_args())
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
etud = Identite.query.get_or_404(etudid)
ues = formsemestre.formation.ues.filter(UniteEns.type != UE_SPORT).order_by(
UniteEns.semestre_idx, UniteEns.numero
)
descr = _ue_form_description(formsemestre, etud, ues, scu.get_request_args())
initvalues = {}
if request.method == "GET":
initvalues = {
"note_" + str(ue["ue_id"]): ue["validation"].get("moy_ue", "") for ue in ues
}
else:
initvalues = {}
for ue in ues:
validation = ScolarFormSemestreValidation.query.filter_by(
ue_id=ue.id, etudid=etud.id, formsemestre_id=formsemestre.id
).first()
initvalues[f"note_{ue.id}"] = validation.moy_ue if validation else ""
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
descr,
cssclass="tf_ext_edit_ue_validations",
submitlabel="Enregistrer ces validations",
cancelbutton="Annuler",
initvalues=initvalues,
cssclass="tf_ext_edit_ue_validations ext_apc"
if formsemestre.formation.is_apc()
else "tf_ext_edit_ue_validations",
# En APC, stocke les coefficients pour l'affichage de la moyenne en direct
form_attrs=f"""data-ue_coefs='[{', '.join(str(ue.ects or 0) for ue in ues)}]'"""
if formsemestre.formation.is_apc()
else "",
)
if tf[0] == -1:
return "<h4>annulation</h4>"
else:
H = _make_page(etud, sem, tf)
H = _make_page(etud, formsemestre, tf)
if tf[0] == 0: # premier affichage
return "\n".join(H)
else: # soumission
# simule erreur
ok, message = _check_values(ues, tf[2])
ok, message = _check_values(formsemestre, ues, tf[2])
if not ok:
H = _make_page(etud, sem, tf, message=message)
H = _make_page(etud, formsemestre, tf, message=message)
return "\n".join(H)
else:
# Submit
_record_ue_validations_and_coefs(formsemestre_id, etudid, ues, tf[2])
_record_ue_validations_and_coefs(formsemestre, etud, ues, tf[2])
return flask.redirect(
"formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s"
% (formsemestre_id, etudid)
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
etudid=etudid,
)
)
def _make_page(etud: dict, sem, tf, message="") -> list:
formsemestre = FormSemestre.query.get_or_404(sem["formsemestre_id"])
def _make_page(etud: Identite, formsemestre: FormSemestre, tf, message="") -> list[str]:
"""html formulaire saisie"""
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
moy_gen = nt.get_etud_moy_gen(etud["etudid"])
moy_gen = nt.get_etud_moy_gen(etud.id)
H = [
html_sco_header.sco_header(
page_title="Validation des UE d'un semestre extérieur",
javascripts=["js/formsemestre_ext_edit_ue_validations.js"],
),
tf_error_message(message),
"""<p><b>%(nomprenom)s</b> est inscrit%(ne)s à ce semestre extérieur.</p>
<p>Voici les UE entregistrées avec leur notes et coefficients.
f"""<p><b>{etud.nomprenom}</b> est inscrit{etud.e} à ce semestre extérieur.</p>
<p>Voici ses UE enregistrées avec leur notes
{ "et coefficients" if not formsemestre.formation.is_apc()
else " (en BUT, les coefficients sont égaux aux ECTS)"}.
</p>
"""
% etud,
""",
f"""<p>La moyenne de ce semestre serait:
<span class="ext_sem_moy"><span class="ext_sem_moy_val">{moy_gen}</span> / 20</span>
</p>
@ -298,7 +317,7 @@ def _make_page(etud: dict, sem, tf, message="") -> list:
f"""<div>
<a class="stdlink"
href="{url_for("notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, etudid=etud['etudid']
formsemestre_id=formsemestre.id, etudid=etud.id
)}">retour au bulletin de notes</a>
</div>
""",
@ -314,7 +333,9 @@ _UE_VALID_CODES = {
}
def _ue_form_description(ues, values):
def _ue_form_description(
formsemestre: FormSemestre, etud: Identite, ues: list[UniteEns], values
):
"""Description du formulaire de saisie des UE / validations
Pour chaque UE, on peut saisir: son code jury, sa note, son coefficient.
"""
@ -324,8 +345,14 @@ def _ue_form_description(ues, values):
{
"input_type": "separator",
"template": """<tr %(item_dom_attr)s><th>UE</th>
<th>Code jury</th><th>Note/20</th><th>Coefficient UE</th></tr>
""",
<th>Code jury</th><th>Note/20</th>
"""
+ (
"""<th>Coefficient UE</th>"""
if not formsemestre.formation.is_apc()
else ""
)
+ "</tr>",
},
),
("formsemestre_id", {"input_type": "hidden"}),
@ -334,59 +361,80 @@ def _ue_form_description(ues, values):
for ue in ues:
# Menu pour code validation UE:
# Ne propose que ADM, CMP et "Non inscrit"
select_name = "valid_" + str(ue["ue_id"])
menu_code_UE = """<select class="ueext_valid_select" name="%s">""" % (
select_name,
)
cur_value = values.get("valid_" + str(ue["ue_id"]), False)
for code in _UE_VALID_CODES:
if cur_value is False: # pas dans le form, cherche en base
cur_value = ue["validation"].get("code", None)
if str(cur_value) == str(code):
select_name = f"valid_{ue.id}"
menu_code_ue = f"""<select class="ueext_valid_select" name="{select_name}">"""
cur_code_value = values.get("valid_{ue.id}", False)
for (code, explanation) in _UE_VALID_CODES.items():
if cur_code_value is False: # pas dans le form, cherche en base
validation = ScolarFormSemestreValidation.query.filter_by(
ue_id=ue.id, etudid=etud.id, formsemestre_id=formsemestre.id
).first()
cur_code_value = validation.code if validation else None
if str(cur_code_value) == str(code):
selected = "selected"
else:
selected = ""
menu_code_UE += '<option value="%s" %s>%s</option>' % (
code,
selected,
_UE_VALID_CODES[code],
# code jury:
menu_code_ue += (
f"""<option value="{code}" {selected}>{explanation}</option>"""
)
if cur_value is None:
disabled = 'disabled="1"'
if cur_code_value is None:
coef_disabled = 'disabled="1"'
else:
disabled = ""
menu_code_UE += "</select>"
cur_value = values.get("coef_" + str(ue["ue_id"]), False)
if cur_value is False: # pas dans le form, cherche en base
cur_value = ue["uecoef"].get("coefficient", "")
coef_disabled = ""
menu_code_ue += "</select>"
if formsemestre.formation.is_apc():
coef_disabled = 'disabled="1"'
cur_coef_value = ue.ects or 0
coef_input_class = "ext_coef_disabled"
else:
cur_coef_value = values.get(f"coef_{ue.id}", False)
coef_input_class = ""
if cur_coef_value is False: # pas dans le form, cherche en base
ue_coef: FormSemestreUECoef = FormSemestreUECoef.query.filter_by(
formsemestre_id=formsemestre.id, ue_id=ue.id
).first()
cur_coef_value = (ue_coef.coefficient if ue_coef else "") or ""
itemtemplate = (
"""<tr><td class="tf-fieldlabel">%(label)s</td>"""
+ "<td>"
+ menu_code_UE
+ "</td>" # code jury
+ '<td class="tf-field tf_field_note">%(elem)s</td>' # note
+ """<td class="tf-field tf_field_coef">
<input type="text" size="4" name="coef_%s" value="%s" %s></input></td>
"""
% (ue["ue_id"], cur_value, disabled)
+ "</td></tr>"
f"""
<tr>
<td class="tf-fieldlabel">%(label)s</td>
<td>{ menu_code_ue }</td>
<td class="tf-field tf_field_note">%(elem)s</td>
"""
+ (
f"""<td class="tf-field tf_field_coef">
<input type="text" size="4" name="coef_{ue.id}"
class="{coef_input_class}"
value="{cur_coef_value}" {coef_disabled}></input>
</td>"""
if not formsemestre.formation.is_apc()
else ""
)
+ """</tr>"""
)
descr.append(
(
"note_" + str(ue["ue_id"]),
f"note_{ue.id}",
{
"input_type": "text",
"size": 4,
"template": itemtemplate,
"title": "<tt><b>%(acronyme)s</b></tt> %(titre)s" % ue,
"attributes": [disabled],
"title": "<tt>"
+ (f"S{ue.semestre_idx} " if ue.semestre_idx is not None else "")
+ f"<b>{ue.acronyme}</b></tt> {ue.titre}"
+ f" ({ue.ects} ECTS)"
if ue.ects is not None
else "",
"attributes": [coef_disabled],
},
)
)
return descr
def _check_values(ue_list, values):
def _check_values(formsemestre: FormSemestre, ue_list, values):
"""Check that form values are ok
for each UE:
code != None => note and coef
@ -396,107 +444,82 @@ def _check_values(ue_list, values):
coef float >= 0
"""
for ue in ue_list:
pu = " pour UE %s" % ue["acronyme"]
code = values.get("valid_" + str(ue["ue_id"]), False)
pu = f" pour UE {ue.acronyme}"
code = values.get(f"valid_{ue.id}", False)
if code == "None":
code = None
note = values.get("note_" + str(ue["ue_id"]), False)
note = values.get(f"note_{ue.id}", False)
try:
note = _convert_field_to_float(note)
except ValueError:
return False, "note invalide" + pu
coef = values.get("coef_" + str(ue["ue_id"]), False)
if code is not False:
if code not in _UE_VALID_CODES:
return False, "code invalide" + pu
if code is not None:
if note is False or note == "":
return False, "note manquante" + pu
coef = values.get(f"coef_{ue.id}", False)
try:
coef = _convert_field_to_float(coef)
except ValueError:
return False, "coefficient invalide" + pu
if code != False:
if code not in _UE_VALID_CODES:
return False, "code invalide" + pu
if code != None:
if note is False or note == "":
return False, "note manquante" + pu
if note != False and note != "":
if code == None:
if note is not False and note != "":
if code is None:
return (
False,
"code jury incohérent (code %s, note %s)" % (code, note)
+ pu
+ " (supprimer note et coef)",
f"""code jury incohérent (code {code}, note {note}) {pu}
(supprimer note)""",
)
if note < 0 or note > 20:
return False, "valeur note invalide" + pu
if not isinstance(coef, float):
return False, "coefficient manquant pour note %s" % note + pu
if coef != False and coef != "":
if coef < 0:
return False, "valeur coefficient invalide" + pu
if not isinstance(coef, float) and not formsemestre.formation.is_apc():
return False, f"coefficient manquant pour note {note} {pu}"
# Vérifie valeur coef seulement pour formations classiques:
if not formsemestre.formation.is_apc():
if coef is not False and coef != "":
if coef < 0:
return False, "valeur coefficient invalide" + pu
return True, "ok"
def _convert_field_to_float(val):
"""value may be empty, False, or a float. Raise exception"""
if val != False:
"""val may be empty, False (left unchanged), or a float. Raise exception ValueError"""
if val is not False:
val = val.strip()
if val:
val = float(val)
return val
def _list_ue_with_coef_and_validations(sem, etudid):
"""Liste des UE de la même formation que sem,
avec leurs coefs d'UE capitalisée (si déjà saisi)
et leur validation pour cet étudiant.
def _record_ue_validations_and_coefs(
formsemestre: FormSemestre, etud: Identite, ues: list[UniteEns], values
):
"""Enregistre en base les validations
En APC, le coef est toujours NULL
"""
cnx = ndb.GetDBConnexion()
formsemestre_id = sem["formsemestre_id"]
ues = sco_edit_ue.ue_list({"formation_id": sem["formation_id"]})
for ue in ues:
# add coefficient
uecoef = sco_formsemestre.formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue["ue_id"]}
)
if uecoef:
ue["uecoef"] = uecoef[0]
else:
ue["uecoef"] = {}
# add validation
validation = sco_cursus_dut.scolar_formsemestre_validation_list(
cnx,
args={
"formsemestre_id": formsemestre_id,
"etudid": etudid,
"ue_id": ue["ue_id"],
},
)
if validation:
ue["validation"] = validation[0]
else:
ue["validation"] = {}
return ues
def _record_ue_validations_and_coefs(formsemestre_id, etudid, ues, values):
for ue in ues:
code = values.get("valid_" + str(ue["ue_id"]), False)
code = values.get(f"valid_{ue.id}", False)
if code == "None":
code = None
note = values.get("note_" + str(ue["ue_id"]), False)
note = values.get(f"note_{ue.id}", False)
note = _convert_field_to_float(note)
coef = values.get("coef_" + str(ue["ue_id"]), False)
coef = values.get(f"coef_{ue.id}", False)
coef = _convert_field_to_float(coef)
if coef == "" or coef == False:
if coef == "" or coef is False:
coef = None
now_dmy = time.strftime("%d/%m/%Y")
log(
"_record_ue_validations_and_coefs: %s etudid=%s ue_id=%s moy_ue=%s ue_coef=%s"
% (formsemestre_id, etudid, ue["ue_id"], note, repr(coef))
f"_record_ue_validations_and_coefs: {formsemestre.id} etudid={etud.id} ue_id={ue.id} moy_ue={note} ue_coef={coef}"
)
assert code == None or (note) # si code validant, il faut une note
assert code is None or (note) # si code validant, il faut une note
sco_formsemestre_validation.do_formsemestre_validate_previous_ue(
formsemestre_id,
etudid,
ue["ue_id"],
formsemestre.id,
etud.id,
ue.id,
note,
now_dmy,
code=code,

View File

@ -3,21 +3,33 @@
function compute_moyenne() {
var notes = $(".tf_field_note input").map(
function() { return parseFloat($(this).val()); }
).get();
var coefs = $(".tf_field_coef input").map(
function() { return parseFloat($(this).val()); }
function () { return parseFloat($(this).val()); }
).get();
// les coefs sont donnes (ECTS en BUT)
let coefs = $("form.tf_ext_edit_ue_validations").data("ue_coefs");
// ou saisis (formations classiques)
if (coefs == 'undefined') {
coefs = $(".tf_field_coef input").map(
function () { return parseFloat($(this).val()); }
).get();
}
var N = notes.length;
var dp = 0.;
var sum_coefs = 0.;
for (var i=0; i < N; i++) {
for (var i = 0; i < N; i++) {
if (!(isNaN(notes[i]) || isNaN(coefs[i]))) {
dp += notes[i] * coefs[i];
sum_coefs += coefs[i];
}
}
return dp / sum_coefs;
let moy = dp / sum_coefs;
if (isNaN(moy)) {
moy = "-";
}
if (typeof moy == "number") {
moy = moy.toFixed(2);
}
return moy;
}
// Callback select menu (UE code)
@ -26,42 +38,44 @@ function enable_disable_fields_cb() {
}
function enable_disable_fields(select_elt) {
// input fields controled by this menu
var input_fields = $(select_elt).parent().parent().find('input');
var input_fields = $(select_elt).parent().parent().find('input:not(.ext_coef_disabled)');
var disabled = false;
if ($(select_elt).val() === "None") {
disabled = true;
}
console.log('disabled=', disabled);
input_fields.each( function () {
var old_state = this.disabled;
console.log("old_state=", old_state)
if (old_state == disabled) {
return; /* state unchanged */
input_fields.each(function () {
if (disabled) {
let cur_value = $(this).val();
$(this).data('saved-value', cur_value);
$(this).val("");
} else {
let saved_value = $(this).data('saved-value');
if (typeof saved_value == 'undefined') {
saved_value = '';
}
if (saved_value) {
$(this).val(saved_value);
}
}
var saved_value = $(this).data('saved-value');
if (typeof saved_value == 'undefined') {
saved_value = '';
}
var cur_value = $(this).val();
// swap
$(this).data('saved-value', cur_value);
$(this).val(saved_value);
});
input_fields.prop('disabled', disabled);
}
function setup_text_fields() {
$(".ueext_valid_select").each(
function() {
function () {
enable_disable_fields(this);
}
);
}
$().ready(function(){
$(".tf_ext_edit_ue_validations").change(function (){
$(".ext_sem_moy_val")[0].innerHTML=compute_moyenne();
$().ready(function () {
$(".tf_ext_edit_ue_validations").change(function () {
$(".ext_sem_moy_val")[0].innerHTML = compute_moyenne();
});
$(".ueext_valid_select").change( enable_disable_fields_cb );
$("form.tf_ext_edit_ue_validations input").blur(function () {
$(".ext_sem_moy_val")[0].innerHTML = compute_moyenne();
});
$(".ueext_valid_select").change(enable_disable_fields_cb);
setup_text_fields();
});