diff --git a/app/api/jury.py b/app/api/jury.py index 7de6cdef08fd42f240125b43eabc85804ecf86a1..6103d11649a7130373c4a11ea207eea842aaa57e 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 7686d78976a85f3d7b197f5a42ae396fb89b3019..10791758a0d68e525f7694e29537abdf7fea8f22 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 c827207b5518e780b8d5824ebd57a5c0f42aac5e..d8f62b983e7a1c5c7746d8ad2ab71f09f19786c1 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 3cf2897fa3bd0d5f4f3f5e3de1d1fddf9ba7b781..9eab3da6b76b5b58150ca953f6c622d89414373b 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 fbe8e2be9611c664182b8f0dcf8d072444d39e06..90f01f04eeadfab7df9431e67286a5204089c685 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 67f05d625a6fb95a7c452912711bdaf039715fa2..de32912f3e2ca505011958cbc6a50b6646ba8577 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 c087ff2b79667a6ff13431fb6de1aa0145d921c9..f00542f7bfdfb6e9f1587513e3688c381e869f22 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 6fa14f9ea009b0ea8e46fe11c13c091c4cdcfc3b..d41e4f802b16655c24fcf231c7c6ebc5ffbde8ac 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 ca1b1f12907004167d1f78b0a2d8a22c235c1277..443f47bc8a2ac7741896e4b5969dda4f16f29f50 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 a741380b31d3041fcbe3efd4b2efb0371e4597de..22655f498e23016a74dc2ec745e0359b145e731c 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 e423501fbfcfa760a0a4f5fc93be54ee8225666d..f1b750a885d598c10d9833054f576f0418ce37b4 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 abdfdfaf1d824e252d867f443c0fc636f04981fd..3b1772982de5b56e833ad63d0ed3fce058aac10e 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)