############################################################################## # # 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//") @bp.route("/parcour_formation/") @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 ue_associee_au_niveau_du_parcours( ues_possibles: list[UniteEns], niveau: ApcNiveau, sem_name: str = "S" ) -> UniteEns: "L'UE associée à ce niveau, ou None" ues = [ue for ue in ues_possibles if ue.niveau_competence_id == niveau.id] if len(ues) > 1: # plusieurs UEs associées à ce niveau: élimine celles sans parcours ues_pair_avec_parcours = [ue for ue in ues if ue.parcours] if ues_pair_avec_parcours: ues = ues_pair_avec_parcours if len(ues) > 1: log(f"_niveau_ues: {len(ues)} associées au niveau {niveau} / {sem_name}") return ues[0] if ues else None 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 cette compétence de cette 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} parcours {parcour.code}""" ) niveau = niveaux[0] elif len(niveaux) == 0: return { "niveau": None, "ue_pair": None, "ue_impair": None, "ues_pair": [], "ues_impair": [], } # Toutes les UEs de la formation dans ce parcours ou tronc commun ues = [ ue for ue in formation.ues if ((not ue.parcours) or (parcour.id in (p.id for p in ue.parcours))) and ue.type == UE_STANDARD ] ues_pair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee)] ues_impair_possibles = [ue for ue in ues if ue.semestre_idx == (2 * annee - 1)] # UE associée au niveau dans ce parcours ue_pair = ue_associee_au_niveau_du_parcours( ues_pair_possibles, niveau, f"S{2*annee}" ) ue_impair = ue_associee_au_niveau_du_parcours( ues_impair_possibles, niveau, f"S{2*annee-1}" ) return { "niveau": niveau, "ue_pair": ue_pair, "ues_pair": [ ue for ue in ues_pair_possibles if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id ], "ue_impair": ue_impair, "ues_impair": [ ue for ue in ues_impair_possibles if (not ue.niveau_competence) or ue.niveau_competence.id == niveau.id ], } 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/", 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 )