diff --git a/app/comp/jury.py b/app/comp/jury.py
index efee1445f8..e41af51078 100644
--- a/app/comp/jury.py
+++ b/app/comp/jury.py
@@ -122,6 +122,10 @@ def formsemestre_get_ue_capitalisees(formsemestre: FormSemestre) -> pd.DataFrame
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 = """
SELECT DISTINCT SFV.*, ue.ue_code
FROM
diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py
index 78cac0435b..27d7ff3e66 100644
--- a/app/scodoc/sco_moduleimpl_inscriptions.py
+++ b/app/scodoc/sco_moduleimpl_inscriptions.py
@@ -35,7 +35,7 @@ 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, Identite, UniteEns
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@@ -297,7 +297,13 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
options.append(modimpl)
# 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"
Inscrits au semestre: {len(inscrits)} étudiants
")
@@ -393,7 +399,9 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
# Etudiants "dispensés" d'une UE (capitalisée)
ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id)
if ues_cap_info:
- H.append('Étudiants avec UEs capitalisées:
')
+ H.append(
+ 'Étudiants avec UEs capitalisées (ADM):
'
+ )
ues = [
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
]
@@ -461,20 +469,24 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
if can_change:
H.append(
f"""
"""
)
H.append("")
H.append("
")
H.append("
")
+ # BUT: propose dispense de toutes UEs
+ if is_apc:
+ H.append(_list_but_ue_inscriptions(res, read_only=not can_change))
H.append(
- """
Cette page décrit les inscriptions actuelles.
- Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
+ """
Cette page décrit les inscriptions actuelles.
+ Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
cliquant sur la ligne du module.
- Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
+
Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.
"""
)
@@ -483,6 +495,93 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
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 = [
+ """
+
+
Inscriptions/déinscription aux UEs du BUT
+
+
+ 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.
+
+
+ """
+ )
+ 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):
"""returns tous_inscrits, nb_inscrits, descr"""
ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 15f5f73858..01b6ed0332 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1955,6 +1955,56 @@ span.eval_coef_ue {
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 */
form#editpart table {
border: 1px solid gray;
diff --git a/app/static/js/moduleimpl_inscriptions_stats.js b/app/static/js/moduleimpl_inscriptions_stats.js
new file mode 100644
index 0000000000..4686bd8523
--- /dev/null
+++ b/app/static/js/moduleimpl_inscriptions_stats.js
@@ -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é");
+ }
+ );
+}
\ No newline at end of file
diff --git a/app/views/notes.py b/app/views/notes.py
index 06d62445d4..08f139a8dc 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -50,7 +50,7 @@ from app.but import jury_but_view
from app.comp import res_sem
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.config import ScoDocSiteConfig
from app.models.etudiants import Identite
@@ -1600,10 +1600,12 @@ sco_publish(
)
-@bp.route("/etud_desinscrit_ue")
+@bp.route(
+ "/etud_desinscrit_ue///",
+ methods=["GET", "POST"],
+)
@scodoc
@permission_required(Permission.ScoEtudInscrit)
-@scodoc7func
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.
@@ -1617,6 +1619,13 @@ def etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
disp = DispenseUE(ue_id=ue_id, etudid=etudid)
db.session.add(disp)
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)
else:
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///",
+ methods=["GET", "POST"],
+)
@scodoc
@permission_required(Permission.ScoEtudInscrit)
-@scodoc7func
def etud_inscrit_ue(etudid, formsemestre_id, ue_id):
"""
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():
for disp in DispenseUE.query.filter_by(etudid=etud.id, ue_id=ue_id):
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()
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre.id)
else:
diff --git a/app/views/refcomp.py b/app/views/refcomp.py
index 4aad122f9b..86606de850 100644
--- a/app/views/refcomp.py
+++ b/app/views/refcomp.py
@@ -34,6 +34,7 @@ from app.views import ScoData
@scodoc
@permission_required(Permission.ScoView)
def refcomp(refcomp_id):
+ """Le référentiel de compétences, en JSON."""
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
return jsonify(ref.to_dict())
@@ -42,6 +43,7 @@ def refcomp(refcomp_id):
@scodoc
@permission_required(Permission.ScoView)
def refcomp_show(refcomp_id):
+ """Affichage du référentiel de compétences."""
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
return render_template(
"but/refcomp_show.html",
@@ -60,6 +62,7 @@ def refcomp_show(refcomp_id):
@scodoc
@permission_required(Permission.ScoChangeFormation)
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)
db.session.delete(ref)
db.session.commit()
diff --git a/pytest.ini b/pytest.ini
index e6563b88a4..67d286601e 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,7 @@
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
+ but_gb
lemans
lyon
diff --git a/tests/unit/test_but_jury.py b/tests/unit/test_but_jury.py
index 29cf718265..5ee80a684e 100644
--- a/tests/unit/test_but_jury.py
+++ b/tests/unit/test_but_jury.py
@@ -30,6 +30,7 @@ DEPT = TestConfig.DEPT_TEST
@pytest.mark.slow
+@pytest.mark.but_gb
def test_but_jury_GB(test_client):
"""Tests sur un cursus GB
- construction des semestres et de leurs étudianst à partir du yaml