Associations UE / Parcours: UI

This commit is contained in:
Emmanuel Viennet 2023-04-11 23:56:50 +02:00
parent 5f719442f0
commit e5cdb2ef69
11 changed files with 84 additions and 159 deletions

View File

@ -13,15 +13,15 @@ from app.models import ApcReferentielCompetences, UniteEns
from app.scodoc import codes_cursus
def form_ue_choix_niveau(ue: UniteEns) -> str:
"""Form. HTML pour associer une UE à un niveau de compétence.
def form_ue_choix_parcours(ue: UniteEns) -> str:
"""Form. HTML pour associer une UE à ses parcours.
Le menu select lui même est vide et rempli en JS par appel à get_ue_niveaux_options_html
"""
if ue.type != codes_cursus.UE_STANDARD:
return ""
ref_comp = ue.formation.referentiel_competence
if ref_comp is None:
return f"""<div class="ue_choix_niveau">
return f"""<div class="ue_advanced">
<div class="warning">Pas de référentiel de compétence associé à cette formation !</div>
<div><a class="stdlink" href="{ url_for('notes.refcomp_assoc_formation',
scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)
@ -29,8 +29,27 @@ def form_ue_choix_niveau(ue: UniteEns) -> str:
</div>
</div>"""
return f"""
H = [
"""
<div class="ue_advanced">
<h3>Parcours du BUT</h3>
"""
]
# Choix des parcours
ue_pids = [p.id for p in ue.parcours]
H.append("""<form id="choix_parcours">""")
for parcour in ref_comp.parcours:
H.append(
f"""<label><input type="checkbox" name="{parcour.id}" value="{parcour.id}"
{'checked' if parcour.id in ue_pids else ""}
onclick="set_ue_parcour(this);"
data-setter="{url_for("apiweb.set_ue_parcours", scodoc_dept=g.scodoc_dept, ue_id=ue.id)}"
>{parcour.code}</label>"""
)
H.append("""</form>""")
#
H.append(
f"""
<ul>
<li>
<a class="stdlink" href="{
@ -41,6 +60,8 @@ def form_ue_choix_niveau(ue: UniteEns) -> str:
</ul>
</div>
"""
)
return "\n".join(H)
def get_ue_niveaux_options_html(ue: UniteEns) -> str:

View File

@ -371,7 +371,7 @@ def formsemestre_warning_apc_setup(
return ""
if formsemestre.formation.referentiel_competence is None:
return f"""<div class="formsemestre_status_warning">
La <a class=stdlink" href="{
La <a class="stdlink" href="{
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)
}">formation n'est pas associée à un référentiel de compétence.</a>
</div>
@ -404,12 +404,12 @@ def formsemestre_warning_apc_setup(
return f"""<div class="formsemestre_status_warning">
Problème dans la configuration de la formation:
<ul>
<li>{ '<li></li>'.join(H) }</li>
<li>{ '</li><li>'.join(H) }</li>
</ul>
<p class="help">Vérifiez les parcours cochés pour ce semestre,
et les associations entre UE et niveaux <a class=stdlink" href="{
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)
}">dans la formation.
et les associations entre UE et niveaux <a class="stdlink" href="{
url_for("notes.parcour_formation", scodoc_dept=g.scodoc_dept, formation_id=formsemestre.formation.id)
}">dans la formation.</a>
</p>
</div>
"""

View File

@ -151,6 +151,7 @@ class FormSemestre(db.Model):
secondary=parcours_formsemestre,
lazy="subquery",
backref=db.backref("formsemestres", lazy=True),
order_by=(ApcParcours.numero, ApcParcours.code),
)
def __init__(self, **kwargs):

View File

@ -317,7 +317,7 @@ class UniteEns(db.Model):
if niveau.competence.referentiel.id != self.formation.referentiel_competence.id:
return False, "Le niveau n'appartient pas au référentiel de la formation"
if niveau.id == self.niveau_competence_id:
return True # nothing to do
return True, "" # nothing to do
if (niveau is not None) and (self.niveau_competence_id is not None):
ok, error_message = self.check_niveau_unique_dans_parcours(
niveau, self.parcours

View File

@ -476,9 +476,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
cancelbutton="Revenir à la formation",
)
if tf[0] == 0:
niveau_competence_div = ""
ue_parcours_div = ""
if ue and is_apc:
niveau_competence_div = apc_edit_ue.form_ue_choix_niveau(ue)
ue_parcours_div = apc_edit_ue.form_ue_choix_parcours(ue)
if ue and ue.modules.count() and ue.semestre_idx is not None:
modules_div = f"""<div id="ue_list_modules">
<div><b>{ue.modules.count()} modules sont rattachés
@ -508,7 +508,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"\n".join(H)
+ tf[1]
+ clone_form
+ niveau_competence_div
+ ue_parcours_div
+ modules_div
+ bonus_div
+ ue_div

View File

@ -19,12 +19,18 @@ div.les_parcours>div.focus {
opacity: 1;
}
div.les_parcours>div>a:hover {
div.les_parcours>div.link {
background-color: var(--sco-color-background);
color: navy;
}
div.les_parcours>div.parc>a:hover {
color: #ccc;
}
div.les_parcours>div>a,
div.les_parcours>div>a:visited {
div.les_parcours>div.parc>a,
div.les_parcours>div.parc>a:visited {
color: white;
}

View File

@ -22,8 +22,8 @@
div.table_niveaux_parcours {
margin-left: 12px;
margin-top: 12px;
background: rgb(14, 5, 73);
color: #eee;
background: rgb(210, 210, 210);
color: #111;
border-radius: 8px;
width: fit-content;
padding: 8px;
@ -47,7 +47,7 @@ table.table_niveaux_parcours th {
}
table.table_niveaux_parcours tr.parcours_but {
color: white;
color: #111;
}
table.table_niveaux_parcours tr.parcours_but td {
@ -70,7 +70,7 @@ table.table_niveaux_parcours tr td:not(:first-child) {
table.table_niveaux_parcours tr.annee_but td:first-child {
width: 92px;
font-weight: bold;
color: #ddd;
color: #111;
}
table.table_niveaux_parcours tr.annee_but td.empty {

View File

@ -5,6 +5,7 @@
--sco-content-min-width: 600px;
--sco-content-max-width: 1024px;
--sco-color-explication: rgb(10, 58, 140);
--sco-color-background: rgb(242, 242, 238);
}
html,
@ -12,7 +13,7 @@ body {
margin: 0;
padding: 0;
width: 100%;
background-color: rgb(242, 242, 238);
background-color: var(--sco-color-background);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12pt;
}
@ -937,7 +938,7 @@ span.linktitresem a:visited {
a.stdlink,
a.stdlink:visited {
color: blue;
color: #0e0e9d;
text-decoration: underline;
}
@ -2539,6 +2540,10 @@ div.ue_advanced {
margin-right: 15px;
}
div.ue_advanced h3 {
margin-top: 2px;
}
div#ue_list_modules {
background-color: rgb(251, 225, 165);
border: 1px solid blue;

View File

@ -11,7 +11,6 @@ $().ready(function () {
});
update_bonus_description();
}
update_menus_niveau_competence();
});
function update_bonus_description() {
@ -37,131 +36,26 @@ function update_ue_list() {
});
}
function set_ue_parcour(elem) {
let ue_id = elem.dataset.ue_id;
let parcour_id = elem.value;
let set_ue_parcour_url = elem.dataset.setter;
$.post(set_ue_parcour_url,
{
ue_id: ue_id,
parcour_id: parcour_id,
},
function (result) {
sco_message("UE associée au parcours");
update_menus_niveau_competence();
}
);
}
function set_ue_parcour(checkbox) {
let url = checkbox.dataset.setter;
const checkboxes = document.querySelectorAll('#choix_parcours input[type="checkbox"]:checked');
const parcours_ids = [];
checkboxes.forEach(function (checkbox) {
parcours_ids.push(checkbox.value);
});
function set_ue_niveau_competence(elem) {
let ue_id = elem.dataset.ue_id;
let niveau_id = elem.value;
let set_ue_niveau_competence_url = elem.dataset.setter;
$.post(set_ue_niveau_competence_url,
{
ue_id: ue_id,
niveau_id: niveau_id,
},
function (result) {
sco_message("niveau de compétence enregistré");
update_menus_niveau_competence();
}
);
}
// Met à jour les niveaux utilisés (disabled) ou non affectés
// dans les menus d'association UE <-> niveau
function update_menus_niveau_competence() {
// let selected_niveaux = [];
// document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
// elem => { selected_niveaux.push(elem.value); }
// );
// document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
// elem => {
// for (let i = 0; i < elem.options.length; i++) {
// elem.options[i].disabled = (i != elem.options.selectedIndex)
// && (selected_niveaux.indexOf(elem.options[i].value) != -1)
// && (elem.options[i].value != "");
// }
// }
// );
// nouveau:
document.querySelectorAll("select.niveau_select").forEach(
elem => {
let ue_id = elem.dataset.ue_id;
$.get("get_ue_niveaux_options_html",
{
ue_id: ue_id,
},
function (result) {
elem.innerHTML = result;
}
);
}
);
}
// ---- Nouveau formulaire choix parcours et niveau -----
//document.querySelectorAll("select.select_ue_parcours").forEach(
// elem => { elem.addEventListener('change', change_ue_parcours); }
//);
$().ready(function () {
$('select.select_ue_parcours').multiselect(
{
includeSelectAllOption: false,
nonSelectedText: 'choisir...',
// buttonContainer: '<div id="group_ids_sel_container"/>',
onChange: function (element, checked) {
var parent = element.parent();
var selectedOptions = parent.getValue().split(",");
let set_ue_parcours = element.context.dataset.set_ue_parcours;
fetch(set_ue_parcours, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(selectedOptions)
})
.then(response => response.json())
.then(data => {
if (!data.status) {
sco_message(data.message);
// get the option element corresponding to the selected value
var option = parent.find('option[value="' + element.val() + '"]');
// uncheck the option
option.prop('selected', false);
// refresh the multiselect to reflect the change
parent.multiselect('refresh');
}
})
.catch(error => console.error('Error: ' + error));
// // referme le menu apres chaque choix:
// $("#group_selector .btn-group").removeClass('open');
// if ($("#group_ids_sel").hasClass("submit_on_change")) {
// submit_group_selector();
// }
}
}
);
});
function change_ue_parcours(event) {
const multiselect = event.target;
const selectedOptions = Array.from(this.selectedOptions).map(option => option.value);
fetch('/set_option/', { // XXX TODO
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(selectedOptions)
body: JSON.stringify(parcours_ids)
})
.then(response => response.json())
.then(data => console.log('Success!'))
.catch(error => console.error('Error: ' + error));
};
.then(data => {
if (data.status) {
sco_message(data.message);
}
});
}

View File

@ -49,13 +49,24 @@
{# Liens vers les différents parcours #}
<div class="les_parcours">
{% for parc in formation.referentiel_competence.parcours %}
<div class="{{'focus' if parcour and parc.id == parcour.id else ''}}">
<div class="parc {{'focus' if parcour and parc.id == parcour.id else ''}}">
<a href="{{
url_for('notes.parcour_formation', scodoc_dept=g.scodoc_dept,
parcour_id=parc.id, formation_id=formation.id )
}}">{{parc.code}}</a>
</div>
{% endfor %}
<div class="link">
<a class="stdlink" target="_blank" href="{{
url_for('notes.refcomp_show',
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
}}">référentiel de compétences</a>
</div>
<div class="link"><a class="stdlink" href="{{
url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=formation.id )
}}">formation</a>
</div>
</div>
{# Description d'un parcours #}
@ -107,19 +118,6 @@ Choisissez un parcours...
</div>
{% endif %}
{# Liens bas de page #}
<div class="links">
<div><a class="stdlink" href="{{
url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=formation.id )
}}">Voir la formation</a>
</div>
<div><a class="stdlink" href="{{
url_for('notes.refcomp_show',
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
}}">Référentiel de compétences</a>
</div>
</div>
{% if parcour %}
<div class="help">

View File

@ -724,4 +724,4 @@ def _compare_formsemestre_resultat(res: list[dict], ref: list[dict]):
for res_d, ref_d in zip(res, ref):
assert sorted(res_d.keys()) == sorted(ref_d.keys())
for k in res_d:
assert res_d[k] == ref_d[k]
assert res_d[k] == ref_d[k], f"values for key {k} differ."