form inscription/desinscription à toutes les UEs du BUT

This commit is contained in:
Emmanuel Viennet 2023-01-12 13:08:39 -03:00
parent 79bdf03caa
commit 9a4b78be16
8 changed files with 206 additions and 13 deletions

View File

@ -122,6 +122,10 @@ def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame
event_date : event_date :
} ] } ]
""" """
# Note: pour récupérer aussi les UE validées en CMp ou ADJ, changer une ligne
# and ( SFV.code = 'ADM' or SFV.code = 'ADJ' or SFV.code = 'CMP' )
query = """ query = """
SELECT DISTINCT SFV.*, ue.ue_code SELECT DISTINCT SFV.*, ue.ue_code
FROM FROM

View File

@ -35,7 +35,7 @@ from flask_login import current_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 FormSemestre from app.models import FormSemestre, Identite, UniteEns
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
@ -297,7 +297,13 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
options.append(modimpl) options.append(modimpl)
# Page HTML: # Page HTML:
H = [html_sco_header.html_sem_header("Inscriptions aux modules du semestre")] H = [
html_sco_header.html_sem_header(
"Inscriptions aux modules et UE du semestre",
javascripts=["js/etud_info.js", "js/moduleimpl_inscriptions_stats.js"],
init_qtip=True,
)
]
H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>") H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>")
@ -393,7 +399,9 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
# Etudiants "dispensés" d'une UE (capitalisée) # Etudiants "dispensés" d'une UE (capitalisée)
ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id) ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id)
if ues_cap_info: if ues_cap_info:
H.append('<h3>Étudiants avec UEs capitalisées:</h3><ul class="ue_inscr_list">') H.append(
'<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">'
)
ues = [ ues = [
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys() sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
] ]
@ -461,14 +469,18 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
if can_change: if can_change:
H.append( H.append(
f"""<div><a class="stdlink" href="{ f"""<div><a class="stdlink" href="{
url_for("notes.etud_inscrit_ue", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"], url_for("notes.etud_inscrit_ue",
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"]) scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
}">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div> }">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div>
""" """
) )
H.append("</li>") H.append("</li>")
H.append("</ul></li>") H.append("</ul></li>")
H.append("</ul>") H.append("</ul>")
# BUT: propose dispense de toutes UEs
if is_apc:
H.append(_list_but_ue_inscriptions(res, read_only=not can_change))
H.append( H.append(
"""<hr/><p class="help">Cette page décrit les inscriptions actuelles. """<hr/><p class="help">Cette page décrit les inscriptions actuelles.
@ -483,6 +495,93 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
return "\n".join(H) return "\n".join(H)
def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> str:
"""HTML pour dispenser/reinscrire chaque étudiant à chaque UE du BUT"""
H = [
"""
<div class="list_but_ue_inscriptions">
<h3>Inscriptions/déinscription aux UEs du BUT</h3>
<form class="list_but_ue_inscriptions">
"""
]
table_inscr = _table_but_ue_inscriptions(res)
ue_ids = set.union(*(set(x.keys()) for x in table_inscr.values()))
ues = sorted(
(UniteEns.query.get(ue_id) for ue_id in ue_ids),
key=lambda u: (u.numero or 0, u.acronyme),
)
H.append("""<table><tr><th></th>""")
for ue in ues:
H.append(f"""<th title="{ue.titre or ''}">{ue.acronyme}</th>""")
H.append("""</tr>""")
for etudid, ues_etud in table_inscr.items():
etud: Identite = Identite.query.get(etudid)
H.append(
f"""<tr><td><a class="discretelink etudinfo" id={etudid}
href="{url_for(
"scolar.ficheEtud",
scodoc_dept=g.scodoc_dept,
etudid=etudid,
)}"
>{etud.nomprenom}</a></td>"""
)
for ue in ues:
est_inscr = ues_etud.get(ue.id) # None si pas concerné
if est_inscr is None:
content = ""
else:
content = f"""<input type="checkbox"
{'checked' if est_inscr else ''}
{'disabled' if read_only else ''}
title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}",
onchange="change_ue_inscr(this);"
data-url_inscr={
url_for("notes.etud_inscrit_ue",
scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
}
data-url_desinscr={
url_for("notes.etud_desinscrit_ue",
scodoc_dept=g.scodoc_dept, etudid=etudid,
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
}
/>
"""
H.append(f"""<td>{content}</td>""")
H.append(
"""</table>
</form>
<div class="help">
L'inscription ou désinscription aux UE du BUT n'affecte pas les inscriptions aux modules
mais permet de "dispenser" un étudiant de suivre certaines UE de son parcours.
Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'autres cas particuliers.
La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre)
et n'affecte pas les notes saisies.
</div>
</div>
"""
)
return "\n".join(H)
def _table_but_ue_inscriptions(res: NotesTableCompat) -> dict[int, dict]:
""" "table" avec les inscriptions aux UEs de chaque étudiant
{
etudid : { ue_id : True | False }
}
"""
return {
etudid: {
ue_id: (etudid, ue_id) not in res.dispense_ues
for ue_id in res.etud_ues_ids(etudid)
}
for etudid, inscr in res.formsemestre.etuds_inscriptions.items()
if inscr.etat == scu.INSCRIT
}
def descr_inscrs_module(moduleimpl_id, set_all, partitions): def descr_inscrs_module(moduleimpl_id, set_all, partitions):
"""returns tous_inscrits, nb_inscrits, descr""" """returns tous_inscrits, nb_inscrits, descr"""
ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id) ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)

View File

@ -1955,6 +1955,56 @@ span.eval_coef_ue {
span.eval_coef_ue_titre {} span.eval_coef_ue_titre {}
/* Inscriptions modules/UE */
div.list_but_ue_inscriptions {
margin-top: 16px;
margin-bottom: 16px;
padding-left: 8px;
padding-bottom: 8px;
border-radius: 16px;
border: 2px solid black;
background-color: #eafffa;
}
div.list_but_ue_inscriptions h3 {
margin-top: 8px;
}
div.list_but_ue_inscriptions table th:first-child {
border-left: 0px;
border-top: 0px;
}
div.list_but_ue_inscriptions table th:last-child,
div.list_but_ue_inscriptions table td:last-child {
border-right: 1px solid salmon;
}
div.list_but_ue_inscriptions table th {
border-top: 1px solid salmon;
}
div.list_but_ue_inscriptions table th,
div.list_but_ue_inscriptions table td {
padding-left: 8px;
padding-right: 8px;
border-left: 1px solid salmon;
border-bottom: 1px solid salmon;
}
form.list_but_ue_inscriptions {
margin-bottom: 16px;
}
form.list_but_ue_inscriptions td:first-child {
text-align: left;
}
form.list_but_ue_inscriptions td {
text-align: center;
}
/* Formulaire edition des partitions */ /* Formulaire edition des partitions */
form#editpart table { form#editpart table {
border: 1px solid gray; border: 1px solid gray;

View File

@ -0,0 +1,17 @@
/* Page Inscriptions aux modules et UE du semestre
*/
function change_ue_inscr(elt) {
let url = "";
if (elt.checked) {
url = elt.dataset.url_inscr;
} else {
url = elt.dataset.url_desinscr;
}
$.post(url,
{},
function (result) {
sco_message("changement inscription UE enregistré");
}
);
}

View File

@ -50,7 +50,7 @@ from app.but import jury_but_view
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 ScolarNews from app.models import ScolarNews, Scolog
from app.models.but_refcomp import ApcNiveau, ApcParcours from app.models.but_refcomp import ApcNiveau, ApcParcours
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite from app.models.etudiants import Identite
@ -1600,10 +1600,12 @@ sco_publish(
) )
@bp.route("/etud_desinscrit_ue") @bp.route(
"/etud_desinscrit_ue/<int:etudid>/<int:formsemestre_id>/<int:ue_id>",
methods=["GET", "POST"],
)
@scodoc @scodoc
@permission_required(Permission.ScoEtudInscrit) @permission_required(Permission.ScoEtudInscrit)
@scodoc7func
def etud_desinscrit_ue(etudid, formsemestre_id, ue_id): def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
""" """
- En classique: désinscrit l'etudiant de tous les modules de cette UE dans ce semestre. - En classique: désinscrit l'etudiant de tous les modules de cette UE dans ce semestre.
@ -1617,6 +1619,13 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
disp = DispenseUE(ue_id=ue_id, etudid=etudid) disp = DispenseUE(ue_id=ue_id, etudid=etudid)
db.session.add(disp) db.session.add(disp)
db.session.commit() db.session.commit()
log(f"etud_desinscrit_ue {etud} {ue}")
Scolog.logdb(
method="etud_desinscrit_ue",
etudid=etud.id,
msg=f"Désinscription de l'UE {ue.acronyme}",
commit=True,
)
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
else: else:
sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic( sco_moduleimpl_inscriptions.do_etud_desinscrit_ue_classic(
@ -1632,10 +1641,12 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
) )
@bp.route("/etud_inscrit_ue") @bp.route(
"/etud_inscrit_ue/<int:etudid>/<int:formsemestre_id>/<int:ue_id>",
methods=["GET", "POST"],
)
@scodoc @scodoc
@permission_required(Permission.ScoEtudInscrit) @permission_required(Permission.ScoEtudInscrit)
@scodoc7func
def etud_inscrit_ue(etudid, formsemestre_id, ue_id): def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
""" """
En classic: inscrit l'étudiant à tous les modules de cette UE dans ce semestre. En classic: inscrit l'étudiant à tous les modules de cette UE dans ce semestre.
@ -1649,6 +1660,13 @@ def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
if ue.formation.is_apc(): if ue.formation.is_apc():
for disp in DispenseUE.query.filter_by(etudid=etud.id, ue_id=ue_id): for disp in DispenseUE.query.filter_by(etudid=etud.id, ue_id=ue_id):
db.session.delete(disp) db.session.delete(disp)
log(f"etud_inscrit_ue {etud} {ue}")
Scolog.logdb(
method="etud_inscrit_ue",
etudid=etud.id,
msg=f"Inscription à l'UE {ue.acronyme}",
commit=True,
)
db.session.commit() db.session.commit()
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id) sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
else: else:

View File

@ -34,6 +34,7 @@ from app.views import ScoData
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def refcomp(refcomp_id): def refcomp(refcomp_id):
"""Le référentiel de compétences, en JSON."""
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id) ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
return jsonify(ref.to_dict()) return jsonify(ref.to_dict())
@ -42,6 +43,7 @@ def refcomp(refcomp_id):
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
def refcomp_show(refcomp_id): def refcomp_show(refcomp_id):
"""Affichage du référentiel de compétences."""
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id) ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
return render_template( return render_template(
"but/refcomp_show.html", "but/refcomp_show.html",
@ -60,6 +62,7 @@ def refcomp_show(refcomp_id):
@scodoc @scodoc
@permission_required(Permission.ScoChangeFormation) @permission_required(Permission.ScoChangeFormation)
def refcomp_delete(refcomp_id): def refcomp_delete(refcomp_id):
"""Suppression du référentiel de la base. Le fichier source n'est pas affecté."""
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id) ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
db.session.delete(ref) db.session.delete(ref)
db.session.commit() db.session.commit()

View File

@ -1,6 +1,7 @@
[pytest] [pytest]
markers = markers =
slow: marks tests as slow (deselect with '-m "not slow"') slow: marks tests as slow (deselect with '-m "not slow"')
but_gb
lemans lemans
lyon lyon

View File

@ -30,6 +30,7 @@ DEPT = TestConfig.DEPT_TEST
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.but_gb
def test_but_jury_GB(test_client): def test_but_jury_GB(test_client):
"""Tests sur un cursus GB """Tests sur un cursus GB
- construction des semestres et de leurs étudianst à partir du yaml - construction des semestres et de leurs étudianst à partir du yaml