From fc0a1c285a9ea7911fb343d0ed999f4eac30bbc4 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Sun, 25 Jun 2023 11:49:11 +0200
Subject: [PATCH] =?UTF-8?q?Am=C3=A9liore=20UI=20gestion=20des=20UE=20ant?=
=?UTF-8?q?=C3=A9rieures?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/api/jury.py | 25 +-
app/models/validations.py | 23 +-
app/scodoc/sco_bulletins.py | 2 +-
app/scodoc/sco_cursus_dut.py | 2 +-
app/scodoc/sco_edit_ue.py | 34 ++-
app/scodoc/sco_formsemestre_exterieurs.py | 2 +-
app/scodoc/sco_formsemestre_validation.py | 336 +++++++++++-----------
app/static/css/jury_delete_manual.css | 4 -
app/static/css/scodoc.css | 56 ++--
app/static/js/validate_previous_ue.js | 62 ++--
app/templates/jury/jury_delete_manual.j2 | 28 +-
app/views/notes.py | 69 +++--
12 files changed, 364 insertions(+), 279 deletions(-)
diff --git a/app/api/jury.py b/app/api/jury.py
index 7de6cdef0..6103d1164 100644
--- a/app/api/jury.py
+++ b/app/api/jury.py
@@ -10,7 +10,7 @@
from flask import g, url_for
from flask_json import as_json
-from flask_login import login_required
+from flask_login import current_user, login_required
import app
from app import db, log
@@ -29,6 +29,7 @@ from app.models import (
)
from app.scodoc import sco_cache
from app.scodoc.sco_permissions import Permission
+from app.scodoc.sco_utils import json_error
@bp.route("/formsemestre/<int:formsemestre_id>/decisions_jury")
@@ -73,7 +74,7 @@ def _news_delete_jury_etud(etud: Identite):
)
@login_required
@scodoc
-@permission_required(Permission.ScoEtudInscrit)
+@permission_required(Permission.ScoView)
@as_json
def validation_ue_delete(etudid: int, validation_id: int):
"Efface cette validation"
@@ -90,7 +91,7 @@ def validation_ue_delete(etudid: int, validation_id: int):
)
@login_required
@scodoc
-@permission_required(Permission.ScoEtudInscrit)
+@permission_required(Permission.ScoView)
@as_json
def validation_formsemestre_delete(etudid: int, validation_id: int):
"Efface cette validation"
@@ -106,6 +107,24 @@ def _validation_ue_delete(etudid: int, validation_id: int):
validation = ScolarFormSemestreValidation.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
+ # Vérification de la permission:
+ # A le droit de supprimer cette validation: le chef de dept ou quelqu'un ayant
+ # le droit de saisir des décisions de jury dans le formsemestre concerné s'il y en a un
+ # (c'est le cas pour les validations de jury, mais pas pour les "antérieures" non
+ # rattachées à un formsemestre)
+ if not g.scodoc_dept: # accès API
+ if not current_user.has_permission(Permission.ScoEtudInscrit):
+ return json_error(403, "validation_delete: non autorise")
+ else:
+ if validation.formsemestre:
+ if (
+ validation.formsemestre.dept_id != g.scodoc_dept_id
+ ) or not validation.formsemestre.can_edit_jury():
+ return json_error(403, "validation_delete: non autorise")
+ elif not current_user.has_permission(Permission.ScoEtudInscrit):
+ # Validation non rattachée à un semestre: on doit être chef
+ return json_error(403, "validation_delete: non autorise")
+
log(f"validation_ue_delete: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
diff --git a/app/models/validations.py b/app/models/validations.py
index 7686d7897..10791758a 100644
--- a/app/models/validations.py
+++ b/app/models/validations.py
@@ -8,6 +8,8 @@ from app import log
from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN
from app.models.events import Scolog
+from app.scodoc import sco_cache
+from app.scodoc import sco_utils as scu
class ScolarFormSemestreValidation(db.Model):
@@ -70,6 +72,14 @@ class ScolarFormSemestreValidation(db.Model):
return f"""décision sur semestre {self.formsemestre.titre_mois()} du {
self.event_date.strftime("%d/%m/%Y")}"""
+ def delete(self):
+ "Efface cette validation"
+ log(f"{self.__class__.__name__}.delete({self})")
+ etud = self.etud
+ db.session.delete(self)
+ db.session.commit()
+ sco_cache.invalidate_formsemestre_etud(etud)
+
def to_dict(self) -> dict:
"as a dict"
d = dict(self.__dict__)
@@ -79,15 +89,22 @@ class ScolarFormSemestreValidation(db.Model):
def html(self, detail=False) -> str:
"Affichage html"
if self.ue_id is not None:
- return f"""Validation de l'UE <b>{self.ue.acronyme}</b>
+ moyenne = (
+ f", moyenne {scu.fmt_note(self.moy_ue)}/20 "
+ if self.moy_ue is not None
+ else ""
+ )
+ return f"""Validation
+ {'<span class="redboldtext">externe</span>' if self.is_external else ""}
+ de l'UE <b>{self.ue.acronyme}</b>
{('parcours <span class="parcours">'
+ ", ".join([p.code for p in self.ue.parcours]))
+ "</span>"
if self.ue.parcours else ""}
de {self.ue.formation.acronyme}
{("émise par " + self.formsemestre.html_link_status())
- if self.formsemestre else ""}
- : <b>{self.code}</b>
+ if self.formsemestre else "externe/antérieure"}
+ : <b>{self.code}</b>{moyenne}
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")}
"""
else:
diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index c827207b5..d8f62b983 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -1238,7 +1238,7 @@ def make_menu_autres_operations(
"enabled": current_user.has_permission(Permission.ScoImplement),
},
{
- "title": "Enregistrer une validation d'UE antérieure",
+ "title": "Gérer les validations d'UEs antérieures",
"endpoint": "notes.formsemestre_validate_previous_ue",
"args": {
"formsemestre_id": formsemestre.id,
diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py
index 3cf2897fa..9eab3da6b 100644
--- a/app/scodoc/sco_cursus_dut.py
+++ b/app/scodoc/sco_cursus_dut.py
@@ -972,7 +972,7 @@ def do_formsemestre_validate_ue(
moy_ue = ue_status["moy"] if ue_status else ""
args["moy_ue"] = moy_ue
log("formsemestre_validate_ue: create %s" % args)
- if code != None:
+ if code is not None:
scolar_formsemestre_validation_create(cnx, args)
else:
log("formsemestre_validate_ue: code is None, not recording validation")
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index fbe8e2be9..90f01f04e 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -502,7 +502,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
else:
clone_form = ""
bonus_div = """<div id="bonus_description"></div>"""
- ue_div = """<div id="ue_list_code"></div>"""
+ ue_div = """<div id="ue_list_code" class="sco_box sco_green_bg"></div>"""
return (
"\n".join(H)
+ tf[1]
@@ -1375,13 +1375,12 @@ def _ue_table_modules(
return "\n".join(H)
-def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
+def ue_sharing_code(ue_code: str = "", ue_id: int = None, hide_ue_id: int = None):
"""HTML list of UE sharing this code
Either ue_code or ue_id may be specified.
hide_ue_id spécifie un id à retirer de la liste.
"""
- ue_code = str(ue_code)
- if ue_id:
+ if ue_id is not None:
ue = UniteEns.query.get_or_404(ue_id)
if not ue_code:
ue_code = ue.ue_code
@@ -1400,29 +1399,36 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
.filter_by(dept_id=g.scodoc_dept_id)
)
- if hide_ue_id: # enlève l'ue de depart
+ if hide_ue_id is not None: # enlève l'ue de depart
q_ues = q_ues.filter(UniteEns.id != hide_ue_id)
ues = q_ues.all()
+ msg = " dans les formations du département "
if not ues:
- if ue_id:
- return (
- f"""<span class="ue_share">Seule UE avec code {ue_code or '-'}</span>"""
- )
+ if ue_id is not None:
+ return f"""<span class="ue_share">Seule UE avec code {
+ ue_code if ue_code is not None else '-'}{msg}</span>"""
else:
- return f"""<span class="ue_share">Aucune UE avec code {ue_code or '-'}</span>"""
+ return f"""<span class="ue_share">Aucune UE avec code {
+ ue_code if ue_code is not None else '-'}{msg}</span>"""
H = []
if ue_id:
H.append(
- f"""<span class="ue_share">Autres UE avec le code {ue_code or '-'}:</span>"""
+ f"""<span class="ue_share">Pour information, autres UEs avec le code {
+ ue_code if ue_code is not None else '-'}{msg}:</span>"""
)
else:
- H.append(f"""<span class="ue_share">UE avec le code {ue_code or '-'}:</span>""")
+ H.append(
+ f"""<span class="ue_share">UE avec le code {
+ ue_code if ue_code is not None else '-'}{msg}:</span>"""
+ )
H.append("<ul>")
for ue in ues:
H.append(
- f"""<li>{ue.acronyme} ({ue.titre}) dans <a class="stdlink"
- href="{url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)}"
+ f"""<li>{ue.acronyme} ({ue.titre}) dans
+ <a class="stdlink" href="{
+ url_for("notes.ue_table",
+ scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)}"
>{ue.formation.acronyme} ({ue.formation.titre})</a>, version {ue.formation.version}
</li>
"""
diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py
index 67f05d625..de32912f3 100644
--- a/app/scodoc/sco_formsemestre_exterieurs.py
+++ b/app/scodoc/sco_formsemestre_exterieurs.py
@@ -517,7 +517,7 @@ def _record_ue_validations_and_coefs(
)
assert code is None or (note) # si code validant, il faut une note
sco_formsemestre_validation.do_formsemestre_validate_previous_ue(
- formsemestre.id,
+ formsemestre,
etud.id,
ue.id,
note,
diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py
index c087ff2b7..f00542f7b 100644
--- a/app/scodoc/sco_formsemestre_validation.py
+++ b/app/scodoc/sco_formsemestre_validation.py
@@ -31,8 +31,9 @@ import time
import flask
from flask import url_for, flash, g, request
-from app.models.etudiants import Identite
+import sqlalchemy as sa
+from app.models.etudiants import Identite
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app import db, log
@@ -1081,62 +1082,44 @@ def formsemestre_validation_suppress_etud(formsemestre_id, etudid):
) # > suppr. decision jury (peut affecter de plusieurs semestres utilisant UE capitalisée)
-def formsemestre_validate_previous_ue(formsemestre_id, etudid):
+def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite):
"""Form. saisie UE validée hors ScoDoc
(pour étudiants arrivant avec un UE antérieurement validée).
"""
- etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- formation: Formation = Formation.query.get_or_404(sem["formation_id"])
- H = [
- html_sco_header.sco_header(
- page_title="Validation UE",
- javascripts=["js/validate_previous_ue.js"],
+ formation: Formation = formsemestre.formation
+
+ # Toutes les UEs non bonus de cette formation sont présentées
+ # avec indice de semestre <= semestre courant ou NULL
+ ues = formation.ues.filter(
+ UniteEns.type != UE_SPORT,
+ db.or_(
+ UniteEns.semestre_idx == None,
+ UniteEns.semestre_idx <= formsemestre.semestre_id,
),
- '<table style="width: 100%"><tr><td>',
- """<h2 class="formsemestre">%s: validation d'une UE antérieure</h2>"""
- % etud["nomprenom"],
- (
- '</td><td style="text-align: right;"><a href="%s">%s</a></td></tr></table>'
- % (
- url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
- sco_photos.etud_photo_html(etud, title="fiche de %s" % etud["nom"]),
- )
- ),
- f"""<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
- <em>dans un semestre hors ScoDoc</em>.</p>
- <p><b>Les UE validées dans ScoDoc sont déjà
- automatiquement prises en compte</b>. Cette page n'est utile que pour les étudiants ayant
- suivi un début de cursus dans <b>un autre établissement</b>, ou bien dans un semestre géré
- <b>sans ScoDoc</b> et qui <b>redouble</b> ce semestre
- (<em>ne pas utiliser pour les semestres précédents !</em>).
- </p>
- <p>Notez que l'UE est validée, avec enregistrement immédiat de la décision et
- l'attribution des ECTS.</p>
- <p>On ne peut prendre en compte ici que les UE du cursus <b>{formation.titre}</b></p>
- """,
- ]
+ ).order_by(UniteEns.semestre_idx, UniteEns.numero)
- # Toutes les UE de cette formation sont présentées (même celles des autres semestres)
- ues = formation.ues.order_by(UniteEns.numero)
- ue_names = ["Choisir..."] + [f"{ue.acronyme} {ue.titre}" for ue in ues]
+ ue_names = ["Choisir..."] + [
+ f"""{('S'+str(ue.semestre_idx)+' : ') if ue.semestre_idx is not None else ''
+ }{ue.acronyme} {ue.titre} ({ue.ue_code or ""})"""
+ for ue in ues
+ ]
ue_ids = [""] + [ue.id for ue in ues]
- tf = TrivialFormulator(
- request.base_url,
- scu.get_request_args(),
+ form_descr = [
+ ("etudid", {"input_type": "hidden"}),
+ ("formsemestre_id", {"input_type": "hidden"}),
(
- ("etudid", {"input_type": "hidden"}),
- ("formsemestre_id", {"input_type": "hidden"}),
- (
- "ue_id",
- {
- "input_type": "menu",
- "title": "Unité d'Enseignement (UE)",
- "allow_null": False,
- "allowed_values": ue_ids,
- "labels": ue_names,
- },
- ),
+ "ue_id",
+ {
+ "input_type": "menu",
+ "title": "Unité d'Enseignement (UE)",
+ "allow_null": False,
+ "allowed_values": ue_ids,
+ "labels": ue_names,
+ },
+ ),
+ ]
+ if not formation.is_apc():
+ form_descr.append(
(
"semestre_id",
{
@@ -1147,69 +1130,159 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid):
"allowed_values": [""] + [x for x in range(11)],
"labels": ["-"] + list(range(11)),
},
- ),
- (
- "date",
- {
- "input_type": "date",
- "size": 9,
- "explanation": "j/m/a",
- "default": time.strftime("%d/%m/%Y"),
- },
- ),
- (
- "moy_ue",
- {
- "type": "float",
- "allow_null": False,
- "min_value": 0,
- "max_value": 20,
- "title": "Moyenne (/20) obtenue dans cette UE:",
- },
- ),
+ )
+ )
+ form_descr += [
+ (
+ "date",
+ {
+ "input_type": "date",
+ "size": 9,
+ "explanation": "j/m/a",
+ "default": time.strftime("%d/%m/%Y"),
+ },
+ ),
+ (
+ "moy_ue",
+ {
+ "type": "float",
+ "allow_null": False,
+ "min_value": 0,
+ "max_value": 20,
+ "title": "Moyenne (/20) obtenue dans cette UE:",
+ },
),
- cancelbutton="Annuler",
+ ]
+ tf = TrivialFormulator(
+ request.base_url,
+ scu.get_request_args(),
+ form_descr,
+ cancelbutton="Revenir au bulletin",
submitlabel="Enregistrer validation d'UE",
)
if tf[0] == 0:
- X = """
- <div id="ue_list_etud_validations"><!-- filled by get_etud_ue_cap_html --></div>
- <div id="ue_list_code"><!-- filled by ue_sharing_code --></div>
- """
- warn, ue_multiples = check_formation_ues(formation.id)
- return "\n".join(H) + tf[1] + X + warn + html_sco_header.sco_footer()
- elif tf[0] == -1:
- return flask.redirect(
- scu.NotesURL()
- + "/formsemestre_status?formsemestre_id="
- + str(formsemestre_id)
- )
- else:
- if tf[2]["semestre_id"]:
- semestre_id = int(tf[2]["semestre_id"])
- else:
- semestre_id = None
- do_formsemestre_validate_previous_ue(
- formsemestre_id,
- etudid,
- tf[2]["ue_id"],
- tf[2]["moy_ue"],
- tf[2]["date"],
- semestre_id=semestre_id,
- )
- flash("Validation d'UE enregistrée")
+ return f"""
+ {html_sco_header.sco_header(
+ page_title="Validation UE antérieure",
+ javascripts=["js/validate_previous_ue.js"],
+ cssstyles=["css/jury_delete_manual.css"],
+ etudid=etud.id,
+ )}
+ <h2 class="formsemestre">Gestion des validations d'UEs antérieures
+ de {etud.html_link_fiche()}
+ </h2>
+
+ <p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement,
+ <em>dans un semestre hors ScoDoc</em>.</p>
+ <p class="expl"><b>Les UE validées dans ScoDoc sont déjà
+ automatiquement prises en compte</b>. Cette page n'est utile que pour les étudiants ayant
+ suivi un début de cursus dans <b>un autre établissement</b>, ou bien dans un semestre géré
+ <b>sans ScoDoc</b> et qui <b>redouble</b> ce semestre
+ (<em>pour les semestres précédents gérés avec ScoDoc,
+ passer par la page jury normale)</em>).
+ </p>
+ <p>Notez que l'UE est validée (ADM), avec enregistrement immédiat de la décision et
+ l'attribution des ECTS.</p>
+ <p>On ne peut valider ici que les UEs du cursus <b>{formation.titre}</b></p>
+
+ {_get_etud_ue_cap_html(etud, formsemestre)}
+
+ <div class="sco_box">
+ <div class="sco_box_title">
+ Enregistrer une UE antérieure
+ </div>
+ {tf[1]}
+ </div>
+ <div id="ue_list_code" class="sco_box sco_green_bg">
+ <!-- filled by ue_sharing_code -->
+ </div>
+ {check_formation_ues(formation.id)[0]}
+ {html_sco_header.sco_footer()}
+ """
+
+ dest_url = url_for(
+ "notes.formsemestre_validate_previous_ue",
+ scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre.id,
+ etudid=etud.id,
+ )
+ if tf[0] == -1:
return flask.redirect(
url_for(
"notes.formsemestre_bulletinetud",
scodoc_dept=g.scodoc_dept,
- formsemestre_id=formsemestre_id,
- etudid=etudid,
+ formsemestre_id=formsemestre.id,
+ etudid=etud.id,
)
)
+ if tf[2].get("semestre_id"):
+ semestre_id = int(tf[2]["semestre_id"])
+ else:
+ semestre_id = None
+
+ do_formsemestre_validate_previous_ue(
+ formsemestre,
+ etud.id,
+ tf[2]["ue_id"],
+ tf[2]["moy_ue"],
+ tf[2]["date"],
+ semestre_id=semestre_id,
+ )
+ flash("Validation d'UE enregistrée")
+ return flask.redirect(dest_url)
+
+
+def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str:
+ """HTML listant les validations d'UEs pour cet étudiant dans des formations de même
+ code que celle du formsemestre indiqué.
+ """
+ validations: list[ScolarFormSemestreValidation] = (
+ ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
+ .join(UniteEns)
+ .join(Formation)
+ .filter_by(formation_code=formsemestre.formation.formation_code)
+ .order_by(
+ sa.desc(UniteEns.semestre_idx),
+ UniteEns.acronyme,
+ sa.desc(ScolarFormSemestreValidation.event_date),
+ )
+ .all()
+ )
+
+ if not validations:
+ return ""
+ H = [
+ f"""<div class="sco_box sco_lightgreen_bg ue_list_etud_validations">
+ <div class="sco_box_title">Validations d'UEs dans cette formation</div>
+ <div class="help">Liste de toutes les UEs validées par {etud.html_link_fiche()},
+ sur des semestres ou déclarées comme "antérieures" (externes).
+ </div>
+ <ul>"""
+ ]
+ for validation in validations:
+ if validation.formsemestre_id is None:
+ origine = " enregistrée d'un parcours antérieur (hors ScoDoc)"
+ else:
+ origine = f", du semestre {formsemestre.html_link_status()}"
+ if validation.semestre_id is not None:
+ origine += f" (<b>S{validation.semestre_id}</b>)"
+ H.append(
+ f"""
+ <li>{validation.html()}
+ <form class="inline-form">
+ <button
+ data-v_id="{validation.id}" data-type="validation_ue" data-etudid="{etud.id}"
+ >effacer</button>
+ </form>
+ </li>
+ """,
+ )
+ H.append("</ul></div>")
+ return "\n".join(H)
def do_formsemestre_validate_previous_ue(
- formsemestre_id,
+ formsemestre: FormSemestre,
etudid,
ue_id,
moy_ue,
@@ -1222,21 +1295,20 @@ def do_formsemestre_validate_previous_ue(
Si le coefficient est spécifié, modifie le coefficient de
cette UE (utile seulement pour les semestres extérieurs).
"""
- formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
cnx = ndb.GetDBConnexion()
if ue_coefficient != None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
- cnx, formsemestre_id, ue_id, ue_coefficient
+ cnx, formsemestre.id, ue_id, ue_coefficient
)
else:
- sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre_id, ue_id)
+ sco_formsemestre.do_formsemestre_uecoef_delete(cnx, formsemestre.id, ue_id)
sco_cursus_dut.do_formsemestre_validate_ue(
cnx,
nt,
- formsemestre_id, # "importe" cette UE dans le semestre (new 3/2015)
+ formsemestre.id, # "importe" cette UE dans le semestre (new 3/2015)
etudid,
ue_id,
code,
@@ -1274,62 +1346,6 @@ def _invalidate_etud_formation_caches(etudid, formation_id):
) # > modif decision UE (inval tous semestres avec cet etudiant, ok mais conservatif)
-def get_etud_ue_cap_html(etudid, formsemestre_id, ue_id):
- """Ramene bout de HTML pour pouvoir supprimer une validation de cette UE"""
- valids = ndb.SimpleDictFetch(
- """SELECT SFV.*
- FROM scolar_formsemestre_validation SFV
- WHERE ue_id=%(ue_id)s
- AND etudid=%(etudid)s""",
- {"etudid": etudid, "ue_id": ue_id},
- )
- if not valids:
- return ""
- H = [
- '<div class="existing_valids"><span>Validations existantes pour cette UE:</span><ul>'
- ]
- for valid in valids:
- valid["event_date"] = ndb.DateISOtoDMY(valid["event_date"])
- if valid["moy_ue"] != None:
- valid["m"] = ", moyenne %(moy_ue)g/20" % valid
- else:
- valid["m"] = ""
- if valid["formsemestre_id"]:
- sem = sco_formsemestre.get_formsemestre(valid["formsemestre_id"])
- valid["s"] = ", du semestre %s" % sem["titreannee"]
- else:
- valid["s"] = " enregistrée d'un parcours antérieur (hors ScoDoc)"
- if valid["semestre_id"]:
- valid["s"] += " (<b>S%d</b>)" % valid["semestre_id"]
- valid["ds"] = formsemestre_id
- H.append(
- '<li>%(code)s%(m)s%(s)s, le %(event_date)s <a class="stdlink" href="etud_ue_suppress_validation?etudid=%(etudid)s&ue_id=%(ue_id)s&formsemestre_id=%(ds)s" title="supprime cette validation">effacer</a></li>'
- % valid
- )
- H.append("</ul></div>")
- return "\n".join(H)
-
-
-def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
- """Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
- log("etud_ue_suppress_validation( %s, %s, %s)" % (etudid, formsemestre_id, ue_id))
- cnx = ndb.GetDBConnexion()
- cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
- cursor.execute(
- "DELETE FROM scolar_formsemestre_validation WHERE etudid=%(etudid)s and ue_id=%(ue_id)s",
- {"etudid": etudid, "ue_id": ue_id},
- )
-
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- _invalidate_etud_formation_caches(etudid, sem["formation_id"])
-
- return flask.redirect(
- scu.NotesURL()
- + "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s"
- % (etudid, formsemestre_id)
- )
-
-
def check_formation_ues(formation_id):
"""Verifie que les UE d'une formation sont chacune utilisée dans un seul semestre_id
Si ce n'est pas le cas, c'est probablement (mais pas forcément) une erreur de
diff --git a/app/static/css/jury_delete_manual.css b/app/static/css/jury_delete_manual.css
index 6fa14f9ea..d41e4f802 100644
--- a/app/static/css/jury_delete_manual.css
+++ b/app/static/css/jury_delete_manual.css
@@ -4,10 +4,6 @@ div.jury_decisions_list div {
font-weight: bold;
}
-div.jury_decisions_list form {
- display: inline-block;
-}
-
span.parcours {
color:blueviolet;
}
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index ca1b1f129..443f47bc8 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -76,16 +76,16 @@ div.flashes {
}
div.alert {
- /*
- position: absolute;
- top: 10px;
- right: 10px; */
+ padding: 16px;
+ border-radius: 12px;
+ font-size: 200%;
+ opacity: 0.8;
}
div.alert-info {
- color: #0019d7;
- background-color: #68f36d;
- border-color: #0a8d0c;
+ color: #208d3b;
+ background-color: #fffd97;
+ border-color: #208d3b;
}
div.alert-error {
@@ -94,6 +94,9 @@ div.alert-error {
border-color: #8d0a17;
}
+form.inline-form {
+ display: inline-block;
+}
div.tab-content {
margin-top: 10px;
@@ -1112,9 +1115,11 @@ a.discretelink:hover {
text-align: center;
}
+.expl, .help {
+ max-width: var(--sco-content-max-width);
+}
.help {
font-style: italic;
- max-width: 800px;
}
.help_important {
@@ -1122,13 +1127,28 @@ a.discretelink:hover {
color: red;
}
-div.sco_help {
+div.sco_box, div.sco_help {
margin-top: 12px;
margin-bottom: 4px;
+ margin-left: 0px;
padding: 8px;
border-radius: 4px;
+ border: 1px solid grey;
+ max-width: var(--sco-content-max-width);
+}
+div.sco_help {
font-style: italic;
- max-width: 800px;
+ background-color: rgb(209, 255, 214);
+}
+div.sco_box_title {
+ font-size: 120%;
+ font-weight: bold;
+ margin-bottom: 8px;
+}
+.sco_green_bg {
+ background-color: rgb(155, 218, 155);
+}
+.sco_lightgreen_bg {
background-color: rgb(209, 255, 214);
}
@@ -2504,13 +2524,7 @@ input.sco_tag_checkbox {
}
div#ue_list_code {
- background-color: rgb(155, 218, 155);
- padding: 10px;
border: 1px solid blue;
- border-radius: 10px;
- padding: 10px;
- margin-top: 10px;
- margin-right: 15px;
}
ul.notes_module_list {
@@ -2596,16 +2610,6 @@ div#ue_list_modules {
margin-right: 15px;
}
-div#ue_list_etud_validations {
- background-color: rgb(220, 250, 220);
- padding-left: 4px;
- padding-bottom: 1px;
- margin: 3ex;
-}
-
-div#ue_list_etud_validations span {
- font-weight: bold;
-}
span.ue_share {
font-weight: bold;
diff --git a/app/static/js/validate_previous_ue.js b/app/static/js/validate_previous_ue.js
index a741380b3..22655f498 100644
--- a/app/static/js/validate_previous_ue.js
+++ b/app/static/js/validate_previous_ue.js
@@ -1,31 +1,43 @@
// Affiche et met a jour la liste des UE partageant le meme code
-$().ready(function () {
- update_ue_validations();
- update_ue_list();
- $("#tf_ue_id").bind("change", update_ue_list);
- $("#tf_ue_id").bind("change", update_ue_validations);
-});
+document.addEventListener("DOMContentLoaded", () => {
+ update_ue_list();
+ $("#tf_ue_id").bind("change", update_ue_list);
+ const buttons = document.querySelectorAll(".ue_list_etud_validations button");
-function update_ue_list() {
- var ue_id = $("#tf_ue_id")[0].value;
- if (ue_id) {
- var query = "ue_sharing_code?ue_id=" + ue_id;
- $.get(query, '', function (data) {
- $("#ue_list_code").html(data);
+ buttons.forEach((button) => {
+ button.addEventListener("click", (event) => {
+ // Handle button click event here
+ event.preventDefault();
+ const etudid = event.target.dataset.etudid;
+ const v_id = event.target.dataset.v_id;
+ const validation_type = event.target.dataset.type;
+ if (confirm("Supprimer cette validation ?")) {
+ fetch(
+ `${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
+ {
+ method: "POST",
+ }
+ ).then((response) => {
+ // Handle the response
+ if (response.ok) {
+ location.reload();
+ } else {
+ throw new Error("Request failed");
+ }
});
- }
-}
+ }
+ });
+ });
+});
-function update_ue_validations() {
- var etudid = $("#tf_etudid")[0].value;
- var ue_id = $("#tf_ue_id")[0].value;
- var formsemestre_id = $("#tf_formsemestre_id")[0].value;
- if (ue_id) {
- var query = SCO_URL + "/Notes/get_etud_ue_cap_html?ue_id=" + ue_id + "&etudid=" + etudid + "&formsemestre_id=" + formsemestre_id;
- $.get(query, '', function (data) {
- $("#ue_list_etud_validations").html(data);
- });
- }
-}
\ No newline at end of file
+function update_ue_list() {
+ var ue_id = $("#tf_ue_id")[0].value;
+ if (ue_id) {
+ var query = SCO_URL + "/Notes/ue_sharing_code?ue_id=" + ue_id;
+ $.get(query, "", function (data) {
+ $("#ue_list_code").html(data);
+ });
+ }
+}
diff --git a/app/templates/jury/jury_delete_manual.j2 b/app/templates/jury/jury_delete_manual.j2
index e423501fb..f1b750a88 100644
--- a/app/templates/jury/jury_delete_manual.j2
+++ b/app/templates/jury/jury_delete_manual.j2
@@ -26,7 +26,10 @@ pages de saisie de jury habituelles).
<ul>
{% for v in sem_vals %}
<li>{{v.html()|safe}}
- <form><button data-v_id="{{v.id}}" data-type="validation_formsemestre">effacer</button></form>
+ <form>
+ <button
+ data-v_id="{{v.id}}" data-type="validation_formsemestre" data-etudid="{{etud.id}}"
+ >effacer</button></form>
</li>
{% endfor %}
</ul>
@@ -39,7 +42,10 @@ pages de saisie de jury habituelles).
<ul>
{% for v in ue_vals %}
<li>{{v.html(detail=True)|safe}}
- <form><button data-v_id="{{v.id}}" data-type="validation_ue">effacer</button></form>
+ <form class="inline-form">
+ <button data-v_id="{{v.id}}" data-type="validation_ue" data-etudid="{{etud.id"}}
+ >effacer</button>
+ </form>
</li>
{% endfor %}
</ul>
@@ -52,7 +58,10 @@ pages de saisie de jury habituelles).
<ul>
{% for v in rcue_vals %}
<li>{{v.html()|safe}}
- <form><button data-v_id="{{v.id}}" data-type="validation_rcue">effacer</button></form>
+ <form>
+ <button data-v_id="{{v.id}}" data-type="validation_rcue" data-etudid="{{etud.id}}"
+ >effacer</button>
+ </form>
</li>
{% endfor %}
</ul>
@@ -65,7 +74,10 @@ pages de saisie de jury habituelles).
<ul>
{% for v in annee_but_vals %}
<li>{{v.html()|safe}}
- <form><button data-v_id="{{v.id}}" data-type="validation_annee_but">effacer</button></form>
+ <form>
+ <button data-v_id="{{v.id}}" data-type="validation_annee_but" data-etudid="{{etud.id}}"
+ >effacer</button>
+ </form>
</li>
{% endfor %}
</ul>
@@ -78,7 +90,10 @@ pages de saisie de jury habituelles).
<ul>
{% for v in autorisations %}
<li>{{v.html()|safe}}
- <form><button data-v_id="{{v.id}}" data-type="autorisation_inscription">effacer</button></form>
+ <form>
+ <button data-v_id="{{v.id}}" data-type="autorisation_inscription" data-etudid="{{etud.id}}"
+ >effacer</button>
+ </form>
</li>
{% endfor %}
</ul>
@@ -113,10 +128,11 @@ document.addEventListener('DOMContentLoaded', () => {
button.addEventListener('click', (event) => {
// Handle button click event here
event.preventDefault();
+ const etudid = event.target.dataset.etudid;
const v_id = event.target.dataset.v_id;
const validation_type = event.target.dataset.type;
if (confirm("Supprimer cette validation ?")) {
- fetch(`${SCO_URL}/../api/etudiant/{{etud.id}}/jury/${validation_type}/${v_id}/delete`,
+ fetch(`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
{
method: "POST",
}).then(response => {
diff --git a/app/views/notes.py b/app/views/notes.py
index abdfdfaf1..3b1772982 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -56,17 +56,22 @@ from app.but.forms import jury_but_forms
from app.comp import jury, res_sem
from app.comp.res_compat import NotesTableCompat
-from app.models import Formation, ScolarAutorisationInscription, ScolarNews, Scolog
+from app.models import (
+ Formation,
+ ScolarFormSemestreValidation,
+ ScolarAutorisationInscription,
+ ScolarNews,
+ Scolog,
+)
from app.models.but_refcomp import ApcNiveau
from app.models.config import ScoDocSiteConfig
from app.models.etudiants import Identite
-from app.models.formsemestre import FormSemestre
+from app.models.formsemestre import FormSemestre, FormSemestreInscription
from app.models.formsemestre import FormSemestreUEComputationExpr
from app.models.moduleimpls import ModuleImpl
from app.models.modules import Module
from app.models.ues import DispenseUE, UniteEns
from app.scodoc.sco_exceptions import ScoFormationConflict, ScoPermissionDenied
-from app.tables import jury_recap
from app.views import notes_bp as bp
from app.decorators import (
@@ -483,7 +488,21 @@ def ue_set_internal(ue_id):
)
-sco_publish("/ue_sharing_code", sco_edit_ue.ue_sharing_code, Permission.ScoView)
+@bp.route("/ue_sharing_code")
+@scodoc
+@permission_required(Permission.ScoView)
+@scodoc7func
+def ue_sharing_code():
+ ue_code = request.args.get("ue_code")
+ ue_id = request.args.get("ue_id")
+ hide_ue_id = request.args.get("hide_ue_id")
+ return sco_edit_ue.ue_sharing_code(
+ ue_code=ue_code,
+ ue_id=None if ue_id is None else int(ue_id),
+ hide_ue_id=None if hide_ue_id is None else int(hide_ue_id),
+ )
+
+
sco_publish(
"/edit_ue_set_code_apogee",
sco_edit_ue.edit_ue_set_code_apogee,
@@ -2621,10 +2640,12 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
)
-@bp.route("/formsemestre_validate_previous_ue", methods=["GET", "POST"])
+@bp.route(
+ "/formsemestre_validate_previous_ue/<int:formsemestre_id>/<int:etudid>",
+ methods=["GET", "POST"],
+)
@scodoc
@permission_required(Permission.ScoView)
-@scodoc7func
def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
"Form. saisie UE validée hors ScoDoc"
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
@@ -2636,9 +2657,15 @@ def formsemestre_validate_previous_ue(formsemestre_id, etudid=None):
formsemestre_id=formsemestre_id,
)
)
+ etud: Identite = (
+ Identite.query.filter_by(id=etudid)
+ .join(FormSemestreInscription)
+ .filter_by(formsemestre_id=formsemestre_id)
+ .first_or_404()
+ )
return sco_formsemestre_validation.formsemestre_validate_previous_ue(
- formsemestre_id, etudid
+ formsemestre, etud
)
@@ -2671,34 +2698,6 @@ def formsemestre_ext_edit_ue_validations(formsemestre_id, etudid=None):
)
-sco_publish(
- "/get_etud_ue_cap_html",
- sco_formsemestre_validation.get_etud_ue_cap_html,
- Permission.ScoView,
-)
-
-
-@bp.route("/etud_ue_suppress_validation")
-@scodoc
-@permission_required(Permission.ScoView)
-@scodoc7func
-def etud_ue_suppress_validation(etudid, formsemestre_id, ue_id):
- """Suppress a validation (ue_id, etudid) and redirect to formsemestre"""
- formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
- if not formsemestre.can_edit_jury():
- raise ScoPermissionDenied(
- dest_url=url_for(
- "notes.formsemestre_status",
- scodoc_dept=g.scodoc_dept,
- formsemestre_id=formsemestre_id,
- )
- )
-
- return sco_formsemestre_validation.etud_ue_suppress_validation(
- etudid, formsemestre_id, ue_id
- )
-
-
@bp.route("/formsemestre_validation_auto")
@scodoc
@permission_required(Permission.ScoView)
--
GitLab