1
0
forked from ScoDoc/ScoDoc

WIP: validation du DUT120 (#577). Manque PVs.

This commit is contained in:
Emmanuel Viennet 2024-07-06 23:28:20 +02:00
parent 48e1207fd8
commit b14c3938b7
16 changed files with 484 additions and 23 deletions

View File

@ -32,6 +32,7 @@ from app.models import (
ScolarNews, ScolarNews,
Scolog, Scolog,
UniteEns, UniteEns,
ValidationDUT120,
) )
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_cache from app.scodoc import sco_cache
@ -62,7 +63,7 @@ def decisions_jury(formsemestre_id: int):
raise ScoException("non implemente") raise ScoException("non implemente")
def _news_delete_jury_etud(etud: Identite): def _news_delete_jury_etud(etud: Identite, detail: str = ""):
"génère news sur effacement décision" "génère news sur effacement décision"
# n'utilise pas g.scodoc_dept, pas toujours dispo en mode API # n'utilise pas g.scodoc_dept, pas toujours dispo en mode API
url = url_for( url = url_for(
@ -71,7 +72,7 @@ def _news_delete_jury_etud(etud: Identite):
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_JURY, typ=ScolarNews.NEWS_JURY,
obj=etud.id, obj=etud.id,
text=f"""Suppression décision jury pour <a href="{url}">{etud.nomprenom}</a>""", text=f"""Suppression décision jury {detail} pour <a href="{url}">{etud.nomprenom}</a>""",
url=url, url=url,
) )
@ -320,11 +321,11 @@ def validation_rcue_delete(etudid: int, validation_id: int):
validation = ApcValidationRCUE.query.filter_by( validation = ApcValidationRCUE.query.filter_by(
id=validation_id, etudid=etudid id=validation_id, etudid=etudid
).first_or_404() ).first_or_404()
log(f"validation_ue_delete: etuid={etudid} {validation}") log(f"delete validation_ue_delete: etuid={etudid} {validation}")
db.session.delete(validation) db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud) sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit() db.session.commit()
_news_delete_jury_etud(etud) _news_delete_jury_etud(etud, detail="UE")
return "ok" return "ok"
@ -348,9 +349,38 @@ def validation_annee_but_delete(etudid: int, validation_id: int):
validation = ApcValidationAnnee.query.filter_by( validation = ApcValidationAnnee.query.filter_by(
id=validation_id, etudid=etudid id=validation_id, etudid=etudid
).first_or_404() ).first_or_404()
log(f"validation_annee_but: etuid={etudid} {validation}") ordre = validation.ordre
log(f"delete validation_annee_but: etuid={etudid} {validation}")
db.session.delete(validation) db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud) sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit() db.session.commit()
_news_delete_jury_etud(etud) _news_delete_jury_etud(etud, detail=f"année BUT{ordre}")
return "ok"
@bp.route(
"/etudiant/<int:etudid>/jury/validation_dut120/<int:validation_id>/delete",
methods=["POST"],
)
@api_web_bp.route(
"/etudiant/<int:etudid>/jury/validation_dut120/<int:validation_id>/delete",
methods=["POST"],
)
@login_required
@scodoc
@permission_required(Permission.EtudInscrit)
@as_json
def validation_dut120_delete(etudid: int, validation_id: int):
"Efface cette validation"
etud = tools.get_etud(etudid)
if etud is None:
return "étudiant inconnu", 404
validation = ValidationDUT120.query.filter_by(
id=validation_id, etudid=etudid
).first_or_404()
log(f"delete validation_dut120: etuid={etudid} {validation}")
db.session.delete(validation)
sco_cache.invalidate_formsemestre_etud(etud)
db.session.commit()
_news_delete_jury_etud(etud, detail="diplôme DUT120")
return "ok" return "ok"

View File

@ -14,6 +14,7 @@ Classe raccordant avec ScoDoc 7:
""" """
import collections import collections
from collections.abc import Iterable
from operator import attrgetter from operator import attrgetter
from flask import g, url_for from flask import g, url_for
@ -382,18 +383,28 @@ class FormSemestreCursusBUT:
# "cache { competence_id : competence }" # "cache { competence_id : competence }"
def but_ects_valides(etud: Identite, referentiel_competence_id: int) -> int: def but_ects_valides(
etud: Identite,
referentiel_competence_id: int,
annees_but: None | Iterable[str] = None,
) -> int:
"""Nombre d'ECTS validés par etud dans le BUT de référentiel indiqué. """Nombre d'ECTS validés par etud dans le BUT de référentiel indiqué.
Ne prend que les UE associées à des niveaux de compétences, Ne prend que les UE associées à des niveaux de compétences,
et ne les compte qu'une fois même en cas de redoublement avec re-validation. et ne les compte qu'une fois même en cas de redoublement avec re-validation.
Si annees_but est spécifié, un iterable "BUT1, "BUT2" par exemple, ne prend que ces années.
""" """
validations = ( validations = (
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id) ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
.filter(ScolarFormSemestreValidation.ue_id != None) .filter(ScolarFormSemestreValidation.ue_id != None)
.join(UniteEns) .join(UniteEns)
.join(ApcNiveau) .join(ApcNiveau)
.join(ApcCompetence) )
.filter_by(referentiel_id=referentiel_competence_id) # restreint à certaines années (utile pour les ECTS du DUT120)
if annees_but:
validations = validations.filter(ApcNiveau.annee.in_(annees_but))
# Et restreint au référentiel de compétence:
validations = validations.join(ApcCompetence).filter_by(
referentiel_id=referentiel_competence_id
) )
ects_dict = {} ects_dict = {}

109
app/but/jury_dut120.py Normal file
View File

@ -0,0 +1,109 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Jury DUT120: gestion et vues
Ce diplôme est attribué sur demande aux étudiants de BUT ayant acquis les 120 ECTS
de BUT 1 et BUT 2.
"""
import time
from flask import flash, g, redirect, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import SubmitField
from app import db, log
from app.but import cursus_but
from app.decorators import scodoc, permission_required
from app.models.but_validations import ValidationDUT120
from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre
from app.scodoc.sco_exceptions import ScoPermissionDenied, ScoValueError
from app.scodoc.sco_permissions import Permission
from app.views import notes_bp as bp
from app.views import ScoData
def etud_valide_dut120(etud: Identite, referentiel_competence_id: int) -> bool:
"""Vrai si l'étudiant satisfait les conditions pour valider le DUT120"""
ects_but1_but2 = cursus_but.but_ects_valides(
etud, referentiel_competence_id, annees_but=("BUT1", "BUT2")
)
return ects_but1_but2 >= 120
class ValidationDUT120Form(FlaskForm):
"Formulaire validation DUT120"
submit = SubmitField("Enregistrer le diplôme DUT 120")
@bp.route(
"/validate_dut120/etudid/<int:etudid>/formsemestre/<int:formsemestre_id>",
methods=["GET", "POST"],
)
@scodoc
@permission_required(Permission.ScoView)
def validate_dut120_etud(etudid: int, formsemestre_id: int):
"""Formulaire validation individuelle du DUT120"""
# Check arguments
etud = Identite.get_etud(etudid)
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
refcomp = formsemestre.formation.referentiel_competence
if not refcomp:
raise ScoValueError("formation non associée à un référentiel de compétences")
# Permission
if not formsemestre.can_edit_jury():
raise ScoPermissionDenied(
dest_url=url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
ects_but1_but2 = cursus_but.but_ects_valides(
etud, refcomp.id, annees_but=("BUT1", "BUT2")
)
form = ValidationDUT120Form()
# Check if ValidationDUT120 instance already exists
existing_validation = ValidationDUT120.query.filter_by(
etudid=etudid, referentiel_competence_id=refcomp.id
).first()
if existing_validation:
flash("DUT120 déjà validé", "info")
etud_can_validate_dut = False
# Check if the student meets the criteria
elif ects_but1_but2 < 120:
flash("L'étudiant ne remplit pas les conditions", "warning")
etud_can_validate_dut = False # here existing_validation is None
else:
etud_can_validate_dut = True
if etud_can_validate_dut and request.method == "POST" and form.validate_on_submit():
new_validation = ValidationDUT120(
etudid=etudid,
referentiel_competence_id=refcomp.id,
formsemestre_id=formsemestre.id, # Replace with appropriate value
)
db.session.add(new_validation)
db.session.commit()
log(f"ValidationDUT120 enregistrée pour {etud} depuis {formsemestre}")
flash("Validation DUT120 enregistrée", "success")
return redirect(etud.url_fiche())
return render_template(
"but/validate_dut120.j2",
ects_but1_but2=ects_but1_but2,
etud=etud,
etud_can_validate_dut=etud_can_validate_dut,
form=form,
formsemestre=formsemestre,
sco=ScoData(formsemestre=formsemestre, etud=etud),
time=time,
title="Délivrance du DUT",
validation=existing_validation,
)

View File

@ -16,9 +16,10 @@ from app.models import (
ApcValidationAnnee, ApcValidationAnnee,
ApcValidationRCUE, ApcValidationRCUE,
Identite, Identite,
UniteEns,
ScolarAutorisationInscription, ScolarAutorisationInscription,
ScolarFormSemestreValidation, ScolarFormSemestreValidation,
UniteEns,
ValidationDUT120,
) )
from app.views import ScoData from app.views import ScoData
@ -60,6 +61,9 @@ def jury_delete_manual(etud: Identite):
sem_vals=sem_vals, sem_vals=sem_vals,
ue_vals=ue_vals, ue_vals=ue_vals,
autorisations=autorisations, autorisations=autorisations,
dut120_vals=ValidationDUT120.query.filter_by(etudid=etud.id).order_by(
ValidationDUT120.date
),
rcue_vals=rcue_vals, rcue_vals=rcue_vals,
annee_but_vals=annee_but_vals, annee_but_vals=annee_but_vals,
sco=ScoData(), sco=ScoData(),

View File

@ -3,6 +3,7 @@
"""Modèles base de données ScoDoc """Modèles base de données ScoDoc
""" """
from flask import abort, g
import sqlalchemy import sqlalchemy
from app import db from app import db
@ -116,6 +117,31 @@ class ScoDocModel(db.Model):
args = {field.name: field.data for field in form} args = {field.name: field.data for field in form}
return self.from_dict(args) return self.from_dict(args)
@classmethod
def get_instance(cls, oid: int, accept_none=False):
"""Instance du modèle ou ou 404 (ou None si accept_none),
cherche uniquement dans le département courant.
Ne fonctionne que si le modèle a un attribut dept_id.
Si accept_none, return None si l'id est invalide ou ne correspond
pas à une validation.
"""
if not isinstance(oid, int):
try:
oid = int(oid)
except (TypeError, ValueError):
if accept_none:
return None
abort(404, "oid invalide")
query = (
cls.query.filter_by(id=oid, dept_id=g.scodoc_dept_id)
if g.scodoc_dept
else cls.query.filter_by(id=oid)
)
if accept_none:
return query.first()
return query.first_or_404()
from app.models.absences import Absence, AbsenceNotification, BilletAbsence from app.models.absences import Absence, AbsenceNotification, BilletAbsence
from app.models.departements import Departement from app.models.departements import Departement
@ -173,7 +199,11 @@ from app.models.but_refcomp import (
ApcReferentielCompetences, ApcReferentielCompetences,
ApcSituationPro, ApcSituationPro,
) )
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE from app.models.but_validations import (
ApcValidationAnnee,
ApcValidationRCUE,
ValidationDUT120,
)
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig

View File

@ -4,8 +4,10 @@
""" """
from collections import defaultdict from collections import defaultdict
import sqlalchemy as sa
from app import db from app import db
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN, ScoDocModel
from app.models.but_refcomp import ApcNiveau from app.models.but_refcomp import ApcNiveau
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
@ -14,7 +16,7 @@ from app.scodoc import sco_preferences
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
class ApcValidationRCUE(db.Model): class ApcValidationRCUE(ScoDocModel):
"""Validation des niveaux de compétences """Validation des niveaux de compétences
aka "regroupements cohérents d'UE" dans le jargon BUT. aka "regroupements cohérents d'UE" dans le jargon BUT.
@ -121,7 +123,7 @@ class ApcValidationRCUE(db.Model):
return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue() return self.ue1.get_codes_apogee_rcue() | self.ue2.get_codes_apogee_rcue()
class ApcValidationAnnee(db.Model): class ApcValidationAnnee(ScoDocModel):
"""Validation des années du BUT""" """Validation des années du BUT"""
__tablename__ = "apc_validation_annee" __tablename__ = "apc_validation_annee"
@ -277,3 +279,64 @@ def _build_decisions_rcue_list(decisions_rcue: dict) -> list[str]:
) )
) )
return titres_rcues return titres_rcues
class ValidationDUT120(ScoDocModel):
"""Validations du DUT 120
Ce diplôme est attribué sur demande aux étudiants de BUT ayant acquis les 120 ECTS
de BUT 1 et BUT 2.
"""
id = db.Column(db.Integer, primary_key=True)
etudid = db.Column(
db.Integer,
db.ForeignKey("identite.id", ondelete="CASCADE"),
index=True,
nullable=False,
)
formsemestre_id = db.Column(
db.Integer,
db.ForeignKey("notes_formsemestre.id", ondelete="CASCADE"),
nullable=False,
)
"""le semestre origine, dans la plupart des cas le S4 (le diplôme DUT120
apparaîtra sur les PV de ce formsemestre)"""
referentiel_competence_id = db.Column(
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
) # pas de cascade, on ne doit pas supprimer un référentiel utilisé
"""Identifie la spécialité de DUT décernée"""
date = db.Column(
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
)
"""Date de délivrance"""
etud = db.relationship("Identite", backref="validations_dut120")
formsemestre = db.relationship("FormSemestre", backref="validations_dut120")
def __repr__(self):
return f"""<ValidationDUT120 {self.etud}>"""
def html(self) -> str:
"Affichage html"
date_str = (
f"""le {self.date.strftime(scu.DATEATIME_FMT)}"""
if self.date
else "(sans date)"
)
link = (
self.formsemestre.html_link_status(
label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}",
title=self.formsemestre.titre_annee(),
)
if self.formsemestre
else "externe/antérieure"
)
specialite = (
self.formsemestre.formation.referentiel_competence.get_title()
if self.formsemestre.formation.referentiel_competence
else "(désassociée!)"
)
return f"""Diplôme de <b>DUT en 120 ECTS du {specialite}</b> émis par
{link}
{date_str}
"""

View File

@ -179,7 +179,9 @@ class Identite(models.ScoDocModel):
def html_link_fiche(self) -> str: def html_link_fiche(self) -> str:
"lien vers la fiche" "lien vers la fiche"
return f"""<a class="etudlink" href="{self.url_fiche()}">{self.nomprenom}</a>""" return (
f"""<a class="etudlink" href="{self.url_fiche()}">{self.nom_prenom()}</a>"""
)
def url_fiche(self) -> str: def url_fiche(self) -> str:
"url de la fiche étudiant" "url de la fiche étudiant"
@ -314,7 +316,7 @@ class Identite(models.ScoDocModel):
@property @property
def nomprenom(self, reverse=False) -> str: def nomprenom(self, reverse=False) -> str:
"""DEPRECATED """DEPRECATED: préférer nom_prenom()
Civilité/prénom/nom pour affichages: "M. Pierre Dupont" Civilité/prénom/nom pour affichages: "M. Pierre Dupont"
Si reverse, "Dupont Pierre", sans civilité. Si reverse, "Dupont Pierre", sans civilité.
Prend l'identité courante et non celle de l'état civil si elles diffèrent. Prend l'identité courante et non celle de l'état civil si elles diffèrent.

View File

@ -37,7 +37,14 @@ import sqlalchemy as sa
from app import log from app import log
from app.auth.models import User from app.auth.models import User
from app.but import cursus_but, validations_view from app.but import cursus_but, validations_view
from app.models import Adresse, EtudAnnotation, FormSemestre, Identite, ScoDocSiteConfig from app.models import (
Adresse,
EtudAnnotation,
FormSemestre,
Identite,
ScoDocSiteConfig,
ValidationDUT120,
)
from app.scodoc import ( from app.scodoc import (
codes_cursus, codes_cursus,
html_sco_header, html_sco_header,
@ -455,6 +462,20 @@ def fiche_etud(etudid=None):
ects_total = sum((v.ects() for v in ue_validation_by_niveau.values())) ects_total = sum((v.ects() for v in ue_validation_by_niveau.values()))
else: else:
ects_total = "" ects_total = ""
validation_dut120 = ValidationDUT120.query.filter_by(etudid=etudid).first()
validation_dut120_html = (
f"""Diplôme DUT décerné
en&nbsp; <a class="stdlink" href="{
url_for("notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=validation_dut120.formsemestre.id)
}">S{validation_dut120.formsemestre.semestre_id}</a>
"""
if validation_dut120
else ""
)
info[ info[
"but_cursus_mkup" "but_cursus_mkup"
] = f""" ] = f"""
@ -463,6 +484,7 @@ def fiche_etud(etudid=None):
"but/cursus_etud.j2", "but/cursus_etud.j2",
cursus=but_cursus, cursus=but_cursus,
scu=scu, scu=scu,
validation_dut120_html=validation_dut120_html,
)} )}
<div class="fiche_but_col2"> <div class="fiche_but_col2">
<div class="link_validation_rcues"> <div class="link_validation_rcues">
@ -471,7 +493,7 @@ def fiche_etud(etudid=None):
formsemestre_id=last_formsemestre.id)}" formsemestre_id=last_formsemestre.id)}"
title="Visualiser les compétences BUT" title="Visualiser les compétences BUT"
> >
<img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="100px"/> <img src="/ScoDoc/static/icons/parcours-but.png" alt="validation_rcues" height="132px"/>
<div style="text-align: center;">Compétences BUT</div> <div style="text-align: center;">Compétences BUT</div>
</a> </a>
</div> </div>

View File

@ -40,6 +40,25 @@ div.code_rcue {
position: relative; position: relative;
} }
div.cursus_but .validation_dut120 {
grid-column: span 3;
justify-self: end;
/* align on right of BUT2 */
width: auto;
/* fit its content */
text-align: center;
padding: 6px;
font-weight: bold;
color: rgb(0, 0, 192);
background-color: #eee;
border-radius: 8px;
border: 1px solid rgb(0, 0, 192);
}
div.validation_dut120 a.stdlink {
color: rgb(0, 0, 192);
}
div.no_niveau { div.no_niveau {
background-color: rgb(245, 237, 200); background-color: rgb(245, 237, 200);
} }

View File

@ -959,7 +959,7 @@ td.fichetitre2 .fl {
div.section_but { div.section_but {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-end; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
} }
@ -974,7 +974,8 @@ div.fiche_total_etcs {
margin-top: 16px; margin-top: 16px;
} }
div.section_but>div.link_validation_rcues { div.section_but div.link_validation_rcues,
div.section_but div.link_validation_rcues img {
align-self: center; align-self: center;
text-align: center; text-align: center;
} }
@ -4590,6 +4591,10 @@ table.table_recap tr td.jury_code_sem {
border-left: 1px solid blue; border-left: 1px solid blue;
} }
table.table_recap tr td.col_jury_link {
border-left: 1px solid blue;
}
table.table_recap .admission { table.table_recap .admission {
white-space: nowrap; white-space: nowrap;
color: rgb(6, 73, 6); color: rgb(6, 73, 6);

View File

@ -18,7 +18,7 @@ from app.but import jury_but
from app.but.jury_but import DecisionsProposeesRCUE from app.but.jury_but import DecisionsProposeesRCUE
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.models import ApcNiveau, UniteEns from app.models import ApcNiveau, UniteEns, ValidationDUT120
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.scodoc.codes_cursus import ( from app.scodoc.codes_cursus import (
BUT_BARRE_RCUE, BUT_BARRE_RCUE,
@ -149,7 +149,7 @@ class TableJury(TableRecap):
"ects_acquis", "ects_acquis",
"ECTS", "ECTS",
# res.get_etud_ects_valides(etud.id), # res.get_etud_ects_valides(etud.id),
# cette recherche augmente de 10% le temps de contsruction de la table # cette recherche augmente de 10% le temps de construction de la table
cursus_but.but_ects_valides( cursus_but.but_ects_valides(
etud, res.formsemestre.formation.referentiel_competence_id etud, res.formsemestre.formation.referentiel_competence_id
), ),
@ -157,6 +157,16 @@ class TableJury(TableRecap):
classes=["recorded_code"], classes=["recorded_code"],
target_attrs={"title": "crédits validés en BUT"}, target_attrs={"title": "crédits validés en BUT"},
) )
# Diplôme DUT120
validation_dut120 = ValidationDUT120.query.filter_by(etudid=etud.id).first()
if validation_dut120:
row.add_cell(
"dut120",
"DUT",
"DUT",
group="jury_code_sem",
classes=["recorded_code"],
)
# Lien saisie ou visu jury # Lien saisie ou visu jury
a_saisir = (not res.validations) or (not res.validations.has_decision(etud)) a_saisir = (not res.validations) or (not res.validations.has_decision(etud))
row.add_cell( row.add_cell(

View File

@ -30,4 +30,10 @@
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% if validation_dut120_html %}
<div class="validation_dut120"
title="DUT en 120 ECTS dans un BUT">
{{validation_dut120_html | safe }}
</div>
{% endif %}
</div> </div>

View File

@ -0,0 +1,68 @@
{% extends "sco_page.j2" %}
{% block styles %}
{{super()}}
<link href="{{scu.STATIC_DIR}}/css/jury_but.css" rel="stylesheet" type="text/css" />
{% endblock %}
{% block app_content %}
<div class="bull_head jury_but">
<div>
<div class="titre_parcours">Validation du DUT en 120 ECTS dans un parcours BUT</div>
<div class="nom_etud">{{etud.html_link_fiche()|safe}}</div>
</div>
<div class="bull_photo">
<a href="{{etud.url_fiche()|safe}}">
{{etud.photo_html(title="fiche de " + etud.nomprenom)|safe}}
</a>
</div>
</div>
<div class="help">
<p>Les étudiants de BUT peuvent demander lattribution du <em>diplôme universitaire de technologie</em>
(DUT) au terme de lacquisition des 120 premiers crédits européens du cursus.
</p>
<p>Dans ScoDoc, c'est le référentiel de compétence qui détermine la spécialité (si un étudiant redouble dans
une formation utilisant une autre version de référentiel, pensez à revalider ses UEs).
</p>
</div>
<div style="margin-top: 16px;">
{{etud.html_link_fiche()|safe}} a acquis <b>{{ects_but1_but2}} ECTS</b> en BUT1 et BUT2.
{% if not validation %}
Son DUT n'est pas encore enregistré dans cette spécialité.
{% endif %}
</div>
{% if etud_can_validate_dut %}
<form method="POST" action="">
{{ form.hidden_tag() }}
<div style="margin-top: 24px;">
{{ form.submit() }}
</div>
</form>
{% else %}
<div class="warning">
{% if validation %}
DUT déjà validé dans cette spécialité
<b>{{formsemestre.formation.referentiel_competence.get_title()}}</b>
pour l'étudiant{{etud.ne}} {{etud.html_link_fiche()|safe}}
<ul>
<li>DUT 120 spécialité {{formsemestre.formation.referentiel_competence.specialite_long}}
enregistré le {{time.strftime("%d/%m/%Y à %Hh%M")}}
</li>
</ul>
{% else %}
L'étudiant ne satisfait pas les conditions&nbsp;:
vérifiez qu'il a validé et enregistré ses UEs de BUT1 et BUT2
et ainsi acquis 120 crédits ECTS.
Voir la page <a class="stdlink" href="{{url_for('notes.validation_rcues',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, etudid=etud.id)
}}">compétences BUT</a>.
{% endif %}
{% endif %}
{% endblock %}

View File

@ -84,6 +84,22 @@ pages de saisie de jury habituelles).
</div> </div>
{% endif %} {% endif %}
{% if dut120_vals.count() %}
<div class="jury_decisions_list jury_decisions_dut120">
<div>Diplôme de DUT en 120 ECTS (dans un parcours BUT)</div>
<ul>
{% for v in dut120_vals %}
<li>{{v.html()|safe}}
<form>
<button data-v_id="{{v.id}}" data-type="validation_dut120" data-etudid="{{etud.id}}"
>effacer</button>
</form>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if autorisations.first() %} {% if autorisations.first() %}
<div class="jury_decisions_list jury_decisions_autorisation_inscription"> <div class="jury_decisions_list jury_decisions_autorisation_inscription">
<div>Autorisations d'inscriptions (passages)</div> <div>Autorisations d'inscriptions (passages)</div>
@ -140,6 +156,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (response.ok) { if (response.ok) {
location.reload(); location.reload();
} else { } else {
console.log(`Error: ${SCO_URL}../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`);
throw new Error('Request failed'); throw new Error('Request failed');
} }
}); });

View File

@ -53,7 +53,7 @@ from app.but import (
jury_but_view, jury_but_view,
) )
from app.but import bulletin_but_court # ne pas enlever: ajoute des routes ! from app.but import bulletin_but_court # ne pas enlever: ajoute des routes !
from app.but import jury_dut120 # ne pas enlever: ajoute des routes !
from app.but.forms import jury_but_forms from app.but.forms import jury_but_forms
@ -2568,6 +2568,13 @@ def formsemestre_validation_but(
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
etudid=deca.etud.id, formsemestre_id=formsemestre_id)}" etudid=deca.etud.id, formsemestre_id=formsemestre_id)}"
>enregistrer des UEs antérieures</a> >enregistrer des UEs antérieures</a>
<a style="margin-left: 16px;" class="stdlink"
href="{
url_for("notes.validate_dut120_etud",
scodoc_dept=g.scodoc_dept,
etudid=deca.etud.id, formsemestre_id=formsemestre_id)}"
>décerner le DUT "120ECTS"</a>
""" """
H.append( H.append(
f"""<div class="but_settings"> f"""<div class="but_settings">

View File

@ -0,0 +1,58 @@
"""ValidationDUT120
Revision ID: 9794534db935
Revises: 60119446aab6
Create Date: 2024-07-06 17:36:41.576748
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "9794534db935"
down_revision = "60119446aab6"
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
"validation_dut120",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("etudid", sa.Integer(), nullable=False),
sa.Column("formsemestre_id", sa.Integer(), nullable=False),
sa.Column("referentiel_competence_id", sa.Integer(), nullable=False),
sa.Column(
"date",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(["etudid"], ["identite.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(
["formsemestre_id"],
["notes_formsemestre.id"],
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["referentiel_competence_id"],
["apc_referentiel_competences.id"],
),
sa.PrimaryKeyConstraint("id"),
)
with op.batch_alter_table("validation_dut120", schema=None) as batch_op:
batch_op.create_index(
batch_op.f("ix_validation_dut120_etudid"), ["etudid"], unique=False
)
# ### end Alembic commands ###
def downgrade():
with op.batch_alter_table("validation_dut120", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_validation_dut120_etudid"))
op.drop_table("validation_dut120")
# ### end Alembic commands ###