Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
9 changed files with 386 additions and 10 deletions
Showing only changes of commit 07318b5d77 - Show all commits

View File

@ -12,11 +12,20 @@ from flask_json import as_json
from flask_login import login_required
import app
from app.api import api_bp as bp, api_web_bp
from app import db, log
from app.api import api_bp as bp, api_web_bp, tools
from app.decorators import scodoc, permission_required
from app.scodoc.sco_exceptions import ScoException
from app.but import jury_but_results
from app.models import FormSemestre
from app.models import (
ApcValidationAnnee,
ApcValidationRCUE,
FormSemestre,
Identite,
ScolarAutorisationInscription,
ScolarFormSemestreValidation,
)
from app.scodoc import sco_cache
from app.scodoc.sco_permissions import Permission
@ -36,3 +45,134 @@ def decisions_jury(formsemestre_id: int):
return rows
else:
raise ScoException("non implemente")
@bp.route(
"/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/validation_ue/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_ue_delete(etudid: int, validation_id: int):
"Efface cette validation"
return _validation_ue_delete(etudid, validation_id)
@bp.route(
"/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/validation_formsemestre/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_formsemestre_delete(etudid: int, validation_id: int):
"Efface cette validation"
# c'est la même chose (formations classiques)
return _validation_ue_delete(etudid, validation_id)
def _validation_ue_delete(etudid: int, validation_id: int):
"Efface cette validation (semestres classiques ou UEs)"
etud = tools.get_etud(etudid)
if etud is None:
return "étudiant inconnu", 404
validation = ScolarFormSemestreValidation.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
log(f"validation_ue_delete: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit()
return "ok"
@bp.route(
"/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/autorisation_inscription/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def autorisation_inscription_delete(etudid: int, validation_id: int):
"Efface cette validation"
etud = tools.get_etud(etudid)
if etud is None:
return "étudiant inconnu", 404
validation = ScolarAutorisationInscription.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
log(f"autorisation_inscription_delete: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit()
return "ok"
@bp.route(
"/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/validation_rcue/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_rcue_delete(etudid: int, validation_id: int):
"Efface cette validation"
etud = tools.get_etud(etudid)
if etud is None:
return "étudiant inconnu", 404
validation = ApcValidationRCUE.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
log(f"validation_ue_delete: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit()
return "ok"
@bp.route(
"/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/validation_annee_but/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.ScoEtudInscrit)
@as_json
def validation_annee_but_delete(etudid: int, validation_id: int):
"Efface cette validation"
etud = tools.get_etud(etudid)
if etud is None:
return "étudiant inconnu", 404
validation = ApcValidationAnnee.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
log(f"validation_annee_but: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit()
return "ok"

View File

@ -0,0 +1,66 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Jury édition manuelle des décisions (correction d'erreurs, parcours hors normes)
Non spécifique au BUT.
"""
import flask
from flask import flash, render_template, url_for
from flask import g, request
from app import db
from app.models import (
ApcValidationAnnee,
ApcValidationRCUE,
FormSemestre,
Identite,
UniteEns,
ScolarAutorisationInscription,
ScolarFormSemestreValidation,
)
from app.views import ScoData
def jury_delete_manual(etud: Identite):
"""Vue (réservée au chef de dept.)
présentant *toutes* les décisions de jury concernant cet étudiant
et permettant de les supprimer une à une.
"""
sem_vals = ScolarFormSemestreValidation.query.filter_by(
etudid=etud.id, ue_id=None
).order_by(ScolarFormSemestreValidation.event_date)
ue_vals = (
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
.join(UniteEns)
.order_by(ScolarFormSemestreValidation.event_date, UniteEns.numero)
)
autorisations = ScolarAutorisationInscription.query.filter_by(
etudid=etud.id
).order_by(
ScolarAutorisationInscription.semestre_id, ScolarAutorisationInscription.date
)
rcue_vals = (
ApcValidationRCUE.query.filter_by(etudid=etud.id)
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
.order_by(UniteEns.semestre_idx, UniteEns.numero, ApcValidationRCUE.date)
)
annee_but_vals = ApcValidationAnnee.query.filter_by(etudid=etud.id).order_by(
ApcValidationAnnee.ordre, ApcValidationAnnee.date
)
return render_template(
"jury/jury_delete_manual.j2",
etud=etud,
sem_vals=sem_vals,
ue_vals=ue_vals,
autorisations=autorisations,
rcue_vals=rcue_vals,
annee_but_vals=annee_but_vals,
sco=ScoData(),
title=f"Toutes les décisions de jury enregistrées pour {etud.html_link_fiche()}",
)

View File

@ -82,6 +82,12 @@ class Identite(db.Model):
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
)
def html_link_fiche(self) -> str:
"lien vers la fiche"
return f"""<a class="stdlink" href="{
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=self.id)
}">{self.nomprenom}</a>"""
@classmethod
def from_request(cls, etudid=None, code_nip=None) -> "Identite":
"""Étudiant à partir de l'etudid ou du code_nip, soit

View File

@ -76,12 +76,14 @@ class ScolarFormSemestreValidation(db.Model):
d.pop("_sa_instance_state", None)
return d
def html(self) -> str:
def html(self, detail=False) -> str:
"Affichage html"
if self.ue_id is not None:
return f"""Validation de l'UE {self.ue.acronyme}
(<b>{self.code}</b>
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")})
return f"""Validation de l'UE {self.ue.acronyme} de {self.ue.formation.acronyme}
{("émise par " + self.formsemestre.html_link_status())
if self.formsemestre else ""}
:<b>{self.code}</b>
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")}
"""
else:
return f"""Validation du semestre S{

View File

@ -312,7 +312,13 @@ def ficheEtud(etudid=None):
] = f"""<span class="link_bul_pdf"><a class="stdlink" href="{
url_for("notes.formsemestre_inscription_with_modules_form",
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">inscrire à un autre semestre</a></span>"""
}">inscrire à un autre semestre</a></span>
<span class="link_bul_pdf"><a class="stdlink" href="{
url_for("notes.jury_delete_manual",
scodoc_dept=g.scodoc_dept, etudid=etudid)
}">éditer toutes décisions de jury</a></span>
"""
else:
info["link_inscrire_ailleurs"] = ""
else:

View File

@ -0,0 +1,9 @@
div.jury_decisions_list div {
font-size: 120%;
font-weight: bold;
}
div.jury_decisions_list form {
display: inline-block;
}

View File

@ -3,7 +3,7 @@
{% block app_content %}
{% if not validations %}
<p>Aucune validation de jury enregistrée pour <b>{{etud.nom_disp()}}</b> sur
<p>Aucune validation de jury enregistrée pour <b>{{etud.html_link_fiche()}}</b> sur
<b>l'année {{annee}}</b>
de la formation <em>{{ formation.html() }}</em>
</p>
@ -13,7 +13,7 @@ de la formation <em>{{ formation.html() }}</em>
</div>
{% else %}
<h2>Effacer les décisions de jury pour l'année {{annee}} de {{etud.nom_disp()}} ?</h2>
<h2>Effacer les décisions de jury pour l'année {{annee}} de {{etud.html_link_fiche()}} ?</h2>
<p class="help">Affectera toutes les décisions concernant l'année {{annee}} de la formation,
quelle que soit leur origine.</p>

View File

@ -0,0 +1,134 @@
{% extends 'base.j2' %}
{% block styles %}
{{super()}}
<link href="{{scu.STATIC_DIR}}/css/jury_delete_manual.css" rel="stylesheet" type="text/css" />
{% endblock %}
{% block app_content %}
<h2>Décisions de jury enregistrées pour {{etud.html_link_fiche()|safe}}</h2>
<p class="help">
Cette page liste toutes les décisions de jury connus de ScoDoc concernant cet étudiant
et permet de les effacer une par une.
</p>
<p class="help">
<b>Attention</b>, il vous appartient de vérifier la cohérence du résultat !
En principe, <b>l'usage de cette page devrait rester exceptionnel</b>.
Aucune annulation n'est ici possible (vous devrez re-saisir les décisions via les
pages de saisie de jury habituelles).
</p>
{% if sem_vals.first() %}
<div class="jury_decisions_list jury_decisions_sems">
<div>Décisions de semestres</div>
<ul>
{% for v in sem_vals %}
<li>{{v.html()|safe}}
<form><button data-v_id="{{v.id}}" data-type="validation_formsemestre">effacer</button></form>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if ue_vals.first() %}
<div class="jury_decisions_list jury_decisions_ues">
<div>Décisions d'UEs</div>
<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>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if rcue_vals.first() %}
<div class="jury_decisions_list jury_decisions_rcues">
<div>Décisions de RCUE (niveaux de compétences)</div>
<ul>
{% for v in rcue_vals %}
<li>{{v.html()|safe}}
<form><button data-v_id="{{v.id}}" data-type="validation_rcue">effacer</button></form>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if annee_but_vals.first() %}
<div class="jury_decisions_list jury_decisions_annees_but">
<div>Décisions d'années BUT</div>
<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>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if autorisations.first() %}
<div class="jury_decisions_list jury_decisions_autorisation_inscription">
<div>Autorisations d'inscriptions (passages)</div>
<ul>
{% for v in autorisations %}
<li>{{v.html()|safe}}
<form><button data-v_id="{{v.id}}" data-type="autorisation_inscription">effacer</button></form>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if not(
sem_vals.first() or sem_ues.first() or sem_rcues.first()
or annee_but_vals.first() or autorisations.first())
%}
<div>
<p class="fontred">aucune décision enregistrée</p>
</div>
{% endif %}
<div>
<a class="stdlink" href="{{etud.html_link_fiche()}}">retour à sa fiche</a>
</div>
{% endblock %}
{% block scripts %}
{{super()}}
<script>
document.addEventListener('DOMContentLoaded', () => {
const buttons = document.querySelectorAll('.jury_decisions_list button');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
// Handle button click event here
event.preventDefault();
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`,
{
method: "POST",
}).then(response => {
// Handle the response
if (response.ok) {
location.reload();
} else {
throw new Error('Request failed');
}
});
}
});
});
});
</script>
{% endblock %}

View File

@ -47,11 +47,12 @@ from app.but import jury_but, jury_but_validation_auto
from app.but.forms import jury_but_forms
from app.but import jury_but_pv
from app.but import jury_but_view
from app.but import jury_edit_manual
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.but_refcomp import ApcNiveau, ApcParcours
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
@ -2940,6 +2941,18 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
)
@bp.route(
"/jury_delete_manual/<int:etudid>",
methods=["GET", "POST"],
)
@scodoc
@permission_required(Permission.ScoEtudInscrit)
def jury_delete_manual(etudid: int):
"""Efface toute les décisions d'une année pour cet étudiant"""
etud: Identite = Identite.query.get_or_404(etudid)
return jury_edit_manual.jury_delete_manual(etud)
sco_publish(
"/formsemestre_lettres_individuelles",
sco_pv_forms.formsemestre_lettres_individuelles,