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:

") + # 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

+
+ """ + ] + 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("""""") + for ue in ues: + H.append(f"""""") + H.append("""""") + + for etudid, ues_etud in table_inscr.items(): + etud: Identite = Identite.query.get(etudid) + H.append( + f"""""" + ) + for ue in ues: + est_inscr = ues_etud.get(ue.id) # None si pas concerné + if est_inscr is None: + content = "" + else: + content = f""" + """ + + H.append(f"""""") + H.append( + """
{ue.acronyme}
{etud.nomprenom}{content}
+
+
+ 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