1
0
forked from ScoDoc/ScoDoc
ScoDoc/app/views/but_formation.py

211 lines
7.2 KiB
Python

##############################################################################
#
# ScoDoc
#
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""
Vues sur les formations BUT
Emmanuel Viennet, 2023
"""
from flask import flash, g, redirect, render_template, request, url_for
from app import db, log
from app.decorators import (
scodoc,
permission_required,
)
from app.forms.formation.ue_parcours_ects import UEParcoursECTSForm
from app.models import (
ApcCompetence,
ApcNiveau,
ApcParcours,
ApcReferentielCompetences,
Formation,
UniteEns,
)
from app.scodoc.codes_cursus import UE_STANDARD
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ScoValueError
from app.views import notes_bp as bp
from app.views import ScoData
@bp.route("/parcour_formation/<int:formation_id>/<int:parcour_id>")
@bp.route("/parcour_formation/<int:formation_id>")
@scodoc
@permission_required(Permission.ScoView)
def parcour_formation(formation_id: int, parcour_id: int = None) -> str:
"""visu HTML d'un parcours dans une formation,
avec les compétences, niveaux et UEs associées."""
formation: Formation = Formation.query.filter_by(
id=formation_id, dept_id=g.scodoc_dept_id
).first_or_404()
ref_comp: ApcReferentielCompetences = formation.referentiel_competence
if ref_comp is None:
return "pas de référentiel de compétences"
if parcour_id is None:
parcour = None
else:
parcour: ApcParcours = ref_comp.parcours.filter_by(id=parcour_id).first()
if parcour is None:
raise ScoValueError("parcours invalide ou hors référentiel de formation")
competences_parcour = (
parcour_formation_competences(parcour, formation) if parcour else None
)
return render_template(
"but/parcour_formation.j2",
formation=formation,
parcour=parcour,
competences_parcour=competences_parcour,
sco=ScoData(),
title=f"{formation.acronyme} - Niveaux et UEs",
)
def parcour_formation_competences(parcour: ApcParcours, formation: Formation) -> list:
"""
[
{
'competence' : ApcCompetence,
'niveaux' : {
1 : { ... },
2 : { ... },
3 : {
'niveau' : ApcNiveau,
'ue_impair' : UniteEns, # actuellement associée
'ues_impair' : list[UniteEns], # choix possibles
'ue_pair' : UniteEns,
'ues_pair' : list[UniteEns],
}
}
}
]
"""
def _niveau_ues(competence: ApcCompetence, annee: int) -> dict:
"niveau et ues pour l'année du parcours"
niveaux = ApcNiveau.niveaux_annee_de_parcours(
parcour, annee, competence=competence
)
if len(niveaux) > 0:
if len(niveaux) > 1:
log(f"_niveau_ues: plus d'un niveau pour {competence} annee {annee}")
niveau = niveaux[0]
elif len(niveaux) == 0:
return {"niveau": None, "ue_pair": None, "ue_impair": None}
# toutes les UEs de la formation associées à ce niveau
ues = [
ue
for ue in niveau.ues
if ue.formation.id == formation.id
# and parcour.id in (p.id for p in ue.parcours)
]
ues_pair = [ue for ue in ues if ue.semestre_idx == 2 * annee]
if len(ues_pair) > 0:
ue_pair = ues_pair[0]
if len(ues_pair) > 1:
log(
f"_niveau_ues: {len(ues)} associées au niveau {niveau} / S{2*annee}"
)
else:
ue_pair = None
ues_pair_possibles = [
ue
for ue in formation.ues.filter_by(semestre_idx=2 * annee, type=UE_STANDARD)
if (ue.niveau_competence is None) or (ue.niveau_competence_id == niveau.id)
]
ues_impair = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)]
if len(ues_impair) > 0:
ue_impair = ues_impair[0]
if len(ues_impair) > 1:
log(
f"_niveau_ues: {len(ues)} associées au niveau {niveau} / S{2*annee-1}"
)
else:
ue_impair = None
ues_impair_possibles = [
ue
for ue in formation.ues.filter_by(
semestre_idx=2 * annee - 1, type=UE_STANDARD
)
if (ue.niveau_competence is None) or (ue.niveau_competence_id == niveau.id)
]
return {
"niveau": niveau,
"ue_pair": ue_pair,
"ues_pair": ues_pair_possibles,
"ue_impair": ue_impair,
"ues_impair": ues_impair_possibles,
}
competences = [
{
"competence": competence,
"niveaux": {annee: _niveau_ues(competence, annee) for annee in (1, 2, 3)},
}
for competence in parcour.query_competences()
]
return competences
@bp.route("/ue_parcours_ects/<int:ue_id>", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def ue_parcours_ects(ue_id: int):
"""formulaire (div) pour associer des ECTS par parcours d'une UE"""
ue: UniteEns = (
UniteEns.query.filter_by(id=ue_id)
.join(Formation)
.filter_by(dept_id=g.scodoc_dept_id)
.first_or_404()
)
if ue.type != UE_STANDARD:
raise ScoValueError("Pas d'ECTS / Parcours pour ce type d'UE")
ref_comp = ue.formation.referentiel_competence
if ref_comp is None:
raise ScoValueError("Pas référentiel de compétence pour cette UE !")
form = UEParcoursECTSForm(ue)
edit_url = url_for("notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
if request.method == "POST":
if request.form.get("submit"):
if form.validate():
for parcour in ue.formation.referentiel_competence.parcours:
field = getattr(form, f"ects_parcour_{parcour.id}")
if field:
ue.set_ects(field.data, parcour=parcour)
db.session.commit()
flash("ECTS enregistrés")
return redirect(edit_url)
elif request.form.get("cancel"):
return redirect(edit_url)
return render_template(
"formation/ue_assoc_parcours_ects.j2", form=form, sco=ScoData(), ue=ue
)