forked from ScoDoc/ScoDoc
BUT: association UE <-> niveau competence
This commit is contained in:
parent
0709b53bbe
commit
1a18fef3e0
68
app/but/apc_edit_ue.py
Normal file
68
app/but/apc_edit_ue.py
Normal file
@ -0,0 +1,68 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Edition associations UE <-> Ref. Compétence
|
||||
"""
|
||||
from flask import g, url_for
|
||||
from app import db, log
|
||||
from app.models import UniteEns
|
||||
from app.models.but_refcomp import ApcNiveau
|
||||
|
||||
|
||||
def form_ue_choix_niveau(ue: UniteEns) -> str:
|
||||
"""Form. HTML pour associer une UE à un niveau de compétence"""
|
||||
ref_comp = ue.formation.referentiel_competence
|
||||
if ref_comp is None:
|
||||
return """<div class="ue_choix_niveau">pas de référentiel de compétence</div>"""
|
||||
annee = (ue.semestre_idx + 1) // 2 # 1, 2, 3
|
||||
niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
|
||||
|
||||
options = []
|
||||
if niveaux_by_parcours["TC"]:
|
||||
options.append("""<optgroup label="Tronc commun">""")
|
||||
for n in niveaux_by_parcours["TC"]:
|
||||
options.append(
|
||||
f"""<option value="{n.id}" {'selected' if ue.niveau_competence == n else ''}>{n.annee} {n.competence.titre_long} niveau {n.ordre}</option>"""
|
||||
)
|
||||
options.append("""</optgroup>""")
|
||||
for parcour in ref_comp.parcours:
|
||||
if len(niveaux_by_parcours[parcour.id]):
|
||||
options.append(f"""<optgroup label="Parcours {parcour.libelle}">""")
|
||||
for n in niveaux_by_parcours[parcour.id]:
|
||||
options.append(
|
||||
f"""<option value="{n.id}" {'selected' if ue.niveau_competence == n else ''}>{n.annee} {n.competence.titre_long} niveau {n.ordre}</option>"""
|
||||
)
|
||||
options.append("""</optgroup>""")
|
||||
options_str = "\n".join(options)
|
||||
return f"""
|
||||
<div id="ue_choix_niveau">
|
||||
<form id="form_ue_choix_niveau">
|
||||
<b>Niveau de compétence associé:</b>
|
||||
<select onchange="set_ue_niveau_competence();" data-setter="{
|
||||
url_for( "notes.set_ue_niveau_competence", scodoc_dept=g.scodoc_dept)
|
||||
}">
|
||||
<option value="" {'selected' if ue.niveau_competence is None else ''}>aucun</option>
|
||||
{options_str}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def set_ue_niveau_competence(ue_id: int, niveau_id: int):
|
||||
"""Associe le niveau et l'UE"""
|
||||
log(f"set_ue_niveau_competence( {ue_id}, {niveau_id} )")
|
||||
ue = UniteEns.query.get_or_404(ue_id)
|
||||
if niveau_id == "":
|
||||
# suppression de l'association
|
||||
ue.niveau_competence = None
|
||||
else:
|
||||
niveau = ApcNiveau.query.get_or_404(niveau_id)
|
||||
ue.niveau_competence = niveau
|
||||
db.session.add(ue)
|
||||
db.session.commit()
|
||||
return "", 204
|
@ -7,6 +7,7 @@
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
import flask_sqlalchemy
|
||||
from sqlalchemy.orm import class_mapper
|
||||
import sqlalchemy
|
||||
|
||||
@ -105,6 +106,52 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
||||
}
|
||||
|
||||
def get_niveaux_by_parcours(self, annee) -> dict:
|
||||
"""
|
||||
Construit la liste des niveaux de compétences pour chaque parcours
|
||||
de ce référentiel.
|
||||
Les niveaux sont groupés par parcours, en isolant les niveaux de tronc commun.
|
||||
Le tronc commun n'est pas identifié comme tel dans les référentiels Orébut:
|
||||
on cherche les niveaux qui sont présents dans tous les parcours et les range sous
|
||||
la clé "TC" (toujours présente mais éventuellement liste vide si pas de tronc commun).
|
||||
|
||||
résultat:
|
||||
{
|
||||
"TC" : [ ApcNiveau ],
|
||||
parcour.id : [ ApcNiveau ]
|
||||
}
|
||||
"""
|
||||
parcours = self.parcours.order_by(ApcParcours.numero).all()
|
||||
niveaux_by_parcours = {
|
||||
parcour.id: ApcNiveau.niveaux_annee_de_parcours(parcour, annee)
|
||||
for parcour in parcours
|
||||
}
|
||||
# Cherche tronc commun
|
||||
niveaux_ids_tc = set.intersection(
|
||||
*[
|
||||
{n.id for n in niveaux_by_parcours[parcour_id]}
|
||||
for parcour_id in niveaux_by_parcours
|
||||
]
|
||||
)
|
||||
# Enleve les niveaux du tronc commun
|
||||
niveaux_by_parcours_no_tc = {
|
||||
parcour.id: [
|
||||
niveau
|
||||
for niveau in niveaux_by_parcours[parcour.id]
|
||||
if niveau.id not in niveaux_ids_tc
|
||||
]
|
||||
for parcour in parcours
|
||||
}
|
||||
# Niveaux du TC
|
||||
niveaux_tc = []
|
||||
if len(parcours):
|
||||
niveaux_parcours_1 = niveaux_by_parcours[parcours[0].id]
|
||||
niveaux_tc = [
|
||||
niveau for niveau in niveaux_parcours_1 if niveau.id in niveaux_ids_tc
|
||||
]
|
||||
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
||||
return niveaux_by_parcours_no_tc
|
||||
|
||||
|
||||
class ApcCompetence(db.Model, XMLModel):
|
||||
"Compétence"
|
||||
@ -186,13 +233,20 @@ class ApcComposanteEssentielle(db.Model, XMLModel):
|
||||
|
||||
|
||||
class ApcNiveau(db.Model, XMLModel):
|
||||
"""Niveau de compétence
|
||||
Chaque niveau peut être associé à deux UE,
|
||||
des semestres impair et pair de la même année.
|
||||
"""
|
||||
|
||||
__tablename__ = "apc_niveau"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
competence_id = db.Column(
|
||||
db.Integer, db.ForeignKey("apc_competence.id"), nullable=False
|
||||
)
|
||||
libelle = db.Column(db.Text(), nullable=False)
|
||||
annee = db.Column(db.Text(), nullable=False) # "BUT2"
|
||||
# L'ordre est l'année d'apparition de ce niveau
|
||||
annee = db.Column(db.Text(), nullable=False) # "BUT1", "BUT2", "BUT3"
|
||||
# L'ordre est le niveau (1,2,3) ou (1,2) suivant la competence
|
||||
ordre = db.Column(db.Integer, nullable=False) # 1, 2, 3
|
||||
app_critiques = db.relationship(
|
||||
"ApcAppCritique",
|
||||
@ -200,9 +254,10 @@ class ApcNiveau(db.Model, XMLModel):
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
ues = db.relationship("UniteEns", back_populates="niveau_competence")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} ordre={self.ordre}>"
|
||||
return f"<{self.__class__.__name__} ordre={self.ordre} annee={self.annee} {self.competence}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
@ -212,6 +267,24 @@ class ApcNiveau(db.Model, XMLModel):
|
||||
"app_critiques": {x.code: x.to_dict() for x in self.app_critiques},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def niveaux_annee_de_parcours(
|
||||
cls, parcour: "ApcParcours", annee: int
|
||||
) -> flask_sqlalchemy.BaseQuery:
|
||||
"""Les niveaux de l'année du parcours"""
|
||||
if annee not in {1, 2, 3}:
|
||||
raise ValueError("annee invalide pour un parcours BUT")
|
||||
annee_formation = f"BUT{annee}"
|
||||
return ApcNiveau.query.filter(
|
||||
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
|
||||
ApcParcours.id == ApcAnneeParcours.parcours_id,
|
||||
ApcParcours.referentiel == parcour.referentiel,
|
||||
ApcParcoursNiveauCompetence.competence_id == ApcCompetence.id,
|
||||
ApcCompetence.id == ApcNiveau.competence_id,
|
||||
ApcAnneeParcours.parcours == parcour,
|
||||
ApcNiveau.annee == annee_formation,
|
||||
)
|
||||
|
||||
|
||||
class ApcAppCritique(db.Model, XMLModel):
|
||||
"Apprentissage Critique BUT"
|
||||
@ -281,9 +354,10 @@ class ApcAnneeParcours(db.Model, XMLModel):
|
||||
db.Integer, db.ForeignKey("apc_parcours.id"), nullable=False
|
||||
)
|
||||
ordre = db.Column(db.Integer)
|
||||
"numéro de l'année: 1, 2, 3"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} ordre={self.ordre}>"
|
||||
return f"<{self.__class__.__name__} ordre={self.ordre} parcours={self.parcours.code}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
@ -321,6 +395,7 @@ class ApcParcoursNiveauCompetence(db.Model):
|
||||
"annee_parcours",
|
||||
passive_deletes=True,
|
||||
cascade="save-update, merge, delete, delete-orphan",
|
||||
lazy="dynamic",
|
||||
),
|
||||
)
|
||||
annee_parcours = db.relationship(
|
||||
@ -333,4 +408,4 @@ class ApcParcoursNiveauCompetence(db.Model):
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.competence} {self.annee_parcours}>"
|
||||
return f"<{self.__class__.__name__} {self.competence}<->{self.annee_parcours} niveau={self.niveau}>"
|
||||
|
@ -42,6 +42,10 @@ class UniteEns(db.Model):
|
||||
|
||||
color = db.Column(db.Text())
|
||||
|
||||
# BUT
|
||||
niveau_competence_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"))
|
||||
niveau_competence = db.relationship("ApcNiveau", back_populates="ues")
|
||||
|
||||
# relations
|
||||
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
||||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||
|
@ -35,6 +35,7 @@ from flask_login import current_user
|
||||
|
||||
from app import db
|
||||
from app import log
|
||||
from app.but import apc_edit_ue
|
||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||
from app.models import Formation, UniteEns, ModuleImpl, Module
|
||||
from app.models import ScolarNews
|
||||
@ -283,6 +284,11 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
<p class="help">Note: sauf exception, l'UE n'a pas de coefficient associé.
|
||||
Seuls les <em>modules</em> ont des coefficients.
|
||||
</p>""",
|
||||
f"""
|
||||
<h4>UE du semestre S{ue.semestre_idx}</h4>
|
||||
"""
|
||||
if is_apc
|
||||
else "",
|
||||
]
|
||||
|
||||
ue_types = parcours.ALLOWED_UE_TYPES
|
||||
@ -416,8 +422,12 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
form_descr,
|
||||
initvalues=initvalues,
|
||||
submitlabel=submitlabel,
|
||||
cancelbutton="Revenir à la formation",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
niveau_competence_div = ""
|
||||
if ue and is_apc:
|
||||
niveau_competence_div = apc_edit_ue.form_ue_choix_niveau(ue)
|
||||
if ue and ue.modules.count() and ue.semestre_idx is not None:
|
||||
modules_div = f"""<div id="ue_list_modules">
|
||||
<div><b>{ue.modules.count()} modules sont rattachés
|
||||
@ -435,6 +445,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ tf[1]
|
||||
+ niveau_competence_div
|
||||
+ modules_div
|
||||
+ bonus_div
|
||||
+ ue_div
|
||||
@ -1004,6 +1015,9 @@ def _ue_table_ues(
|
||||
}">transformer en UE ordinaire</a> """
|
||||
)
|
||||
H.append("</span>")
|
||||
breakpoint()
|
||||
if ue.niveau_competence is None:
|
||||
H.append(" pas de compétence associée ")
|
||||
ue_editable = editable and not ue_is_locked(ue["ue_id"])
|
||||
if ue_editable:
|
||||
H.append(
|
||||
|
@ -194,8 +194,8 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None):
|
||||
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
|
||||
modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
|
||||
M = modimpl.to_dict()
|
||||
formsemestre_id = M["formsemestre_id"]
|
||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||
formsemestre_id = modimpl.formsemestre_id
|
||||
Mod = sco_edit_module.module_list(args={"module_id": modimpl.module_id})[0]
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
F = sco_formations.formation_list(args={"formation_id": sem["formation_id"]})[0]
|
||||
mod_inscrits = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||
|
@ -2206,7 +2206,7 @@ ul.notes_module_list {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div#ue_list_modules {
|
||||
div#ue_choix_niveau {
|
||||
background-color: rgb(191, 242, 255);
|
||||
border: 1px solid blue;
|
||||
border-radius: 10px;
|
||||
@ -2215,6 +2215,15 @@ div#ue_list_modules {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
div#ue_list_modules {
|
||||
background-color: rgb(251, 225, 165);
|
||||
border: 1px solid blue;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
div#ue_list_etud_validations {
|
||||
background-color: rgb(220, 250, 220);
|
||||
padding-left: 4px;
|
||||
|
@ -25,10 +25,27 @@ function update_bonus_description() {
|
||||
}
|
||||
|
||||
function update_ue_list() {
|
||||
var ue_id = $("#tf_ue_id")[0].value;
|
||||
var ue_code = $("#tf_ue_code")[0].value;
|
||||
var query = SCO_URL + "/Notes/ue_sharing_code?ue_code=" + ue_code + "&hide_ue_id=" + ue_id + "&ue_id=" + ue_id;
|
||||
let ue_id = $("#tf_ue_id")[0].value;
|
||||
let ue_code = $("#tf_ue_code")[0].value;
|
||||
let query = SCO_URL + "/Notes/ue_sharing_code?ue_code=" + ue_code + "&hide_ue_id=" + ue_id + "&ue_id=" + ue_id;
|
||||
$.get(query, '', function (data) {
|
||||
$("#ue_list_code").html(data);
|
||||
});
|
||||
}
|
||||
|
||||
function set_ue_niveau_competence() {
|
||||
let ue_id = document.querySelector("#tf_ue_id").value;
|
||||
let select = document.querySelector("#form_ue_choix_niveau select");
|
||||
let niveau_id = select.value;
|
||||
let set_ue_niveau_competence_url = select.dataset.setter;
|
||||
$.post(set_ue_niveau_competence_url,
|
||||
{
|
||||
ue_id: ue_id,
|
||||
niveau_id: niveau_id,
|
||||
},
|
||||
function (result) {
|
||||
// obj.classList.remove("sco_wait");
|
||||
// obj.classList.add("sco_modified");
|
||||
}
|
||||
);
|
||||
}
|
@ -40,6 +40,9 @@
|
||||
else '<span class="missing_ue_ects">aucun</span>'|safe}} ECTS)
|
||||
</span>
|
||||
</span>
|
||||
{% if ue.niveau_competence is none %}
|
||||
<span class="fontred">pas de compétence associée</span>
|
||||
{% endif %}
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
<a class="stdlink" href="{{ url_for('notes.ue_edit',
|
||||
|
@ -53,7 +53,7 @@ from app import db
|
||||
from app import models
|
||||
from app.models import ScolarNews
|
||||
from app.auth.models import User
|
||||
from app.but import bulletin_but
|
||||
from app.but import apc_edit_ue, bulletin_but
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
scodoc7func,
|
||||
@ -416,6 +416,15 @@ sco_publish(
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/set_ue_niveau_competence", methods=["POST"])
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
def set_ue_niveau_competence(scodoc_dept=""):
|
||||
"associe UE et niveau"
|
||||
ue_id = request.form.get("ue_id")
|
||||
niveau_id = request.form.get("niveau_id")
|
||||
return apc_edit_ue.set_ue_niveau_competence(ue_id, niveau_id)
|
||||
|
||||
|
||||
@bp.route("/ue_list") # backward compat
|
||||
@bp.route("/ue_table")
|
||||
@scodoc
|
||||
|
34
migrations/versions/6002d7d366e5_assoc_ue_niveau.py
Normal file
34
migrations/versions/6002d7d366e5_assoc_ue_niveau.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""assoc UE - Niveau
|
||||
|
||||
Revision ID: 6002d7d366e5
|
||||
Revises: af77ca6a89d0
|
||||
Create Date: 2022-04-26 12:58:32.929910
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "6002d7d366e5"
|
||||
down_revision = "af77ca6a89d0"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"notes_ue", sa.Column("niveau_competence_id", sa.Integer(), nullable=True)
|
||||
)
|
||||
op.create_foreign_key(
|
||||
None, "notes_ue", "apc_niveau", ["niveau_competence_id"], ["id"]
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, "notes_ue", type_="foreignkey")
|
||||
op.drop_column("notes_ue", "niveau_competence_id")
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue
Block a user