BUT: associe UE aux parcours. Modification pour #487.
This commit is contained in:
parent
93e54982b6
commit
b9d6688250
@ -8,14 +8,14 @@
|
|||||||
Edition associations UE <-> Ref. Compétence
|
Edition associations UE <-> Ref. Compétence
|
||||||
"""
|
"""
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
from app import db, log
|
from app.models import ApcReferentielCompetences, Formation, UniteEns
|
||||||
from app.models import Formation, UniteEns
|
|
||||||
from app.models.but_refcomp import ApcNiveau
|
|
||||||
from app.scodoc import sco_codes_parcours
|
from app.scodoc import sco_codes_parcours
|
||||||
|
|
||||||
|
|
||||||
def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
def form_ue_choix_niveau(ue: UniteEns) -> str:
|
||||||
"""Form. HTML pour associer une UE à un niveau de compétence"""
|
"""Form. HTML pour associer une UE à un niveau de compétence.
|
||||||
|
Le menu select lui meême est vide et rempli en JS par appel à get_ue_niveaux_options_html
|
||||||
|
"""
|
||||||
if ue.type != sco_codes_parcours.UE_STANDARD:
|
if ue.type != sco_codes_parcours.UE_STANDARD:
|
||||||
return ""
|
return ""
|
||||||
ref_comp = ue.formation.referentiel_competence
|
ref_comp = ue.formation.referentiel_competence
|
||||||
@ -27,11 +27,70 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
|||||||
}">associer un référentiel de compétence</a>
|
}">associer un référentiel de compétence</a>
|
||||||
</div>
|
</div>
|
||||||
</div>"""
|
</div>"""
|
||||||
annee = 1 if ue.semestre_idx is None else (ue.semestre_idx + 1) // 2 # 1, 2, 3
|
# Les parcours:
|
||||||
niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
|
parcours_options = []
|
||||||
|
for parcour in ref_comp.parcours:
|
||||||
|
parcours_options.append(
|
||||||
|
f"""<option value="{parcour.id}" {
|
||||||
|
'selected' if ue.parcour == parcour else ''}
|
||||||
|
>{parcour.libelle}
|
||||||
|
</option>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
newline = "\n"
|
||||||
|
return f"""
|
||||||
|
<div class="ue_choix_niveau">
|
||||||
|
<form class="form_ue_choix_niveau">
|
||||||
|
<div class="cont_ue_choix_niveau">
|
||||||
|
<div>
|
||||||
|
<b>Parcours :</b>
|
||||||
|
<select class="select_parcour"
|
||||||
|
onchange="set_ue_parcour(this);"
|
||||||
|
data-ue_id="{ue.id}"
|
||||||
|
data-setter="{
|
||||||
|
url_for( "notes.set_ue_parcours", scodoc_dept=g.scodoc_dept)
|
||||||
|
}">
|
||||||
|
<option value="" {
|
||||||
|
'selected' if ue.parcour is None else ''
|
||||||
|
}>Tous</option>
|
||||||
|
{newline.join(parcours_options)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>Niveau de compétence :</b>
|
||||||
|
<select class="select_niveau_ue"
|
||||||
|
onchange="set_ue_niveau_competence(this);"
|
||||||
|
data-ue_id="{ue.id}"
|
||||||
|
data-setter="{
|
||||||
|
url_for( "notes.set_ue_niveau_competence", scodoc_dept=g.scodoc_dept)
|
||||||
|
}">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_ue_niveaux_options_html(ue: UniteEns) -> str:
|
||||||
|
"""fragment html avec les options du menu de sélection du
|
||||||
|
niveau de compétences associé à une UE.
|
||||||
|
|
||||||
|
Si l'UE n'a pas de parcours associé: présente les niveaux
|
||||||
|
de tous les parcours.
|
||||||
|
Si l'UE a un parcours: seulement les niveaux de ce parcours.
|
||||||
|
"""
|
||||||
|
ref_comp: ApcReferentielCompetences = ue.formation.referentiel_competence
|
||||||
|
if ref_comp is None:
|
||||||
|
return ""
|
||||||
|
# Les niveaux:
|
||||||
|
annee = ue.annee() # 1, 2, 3
|
||||||
|
parcours, niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(
|
||||||
|
annee, parcour=ue.parcour
|
||||||
|
)
|
||||||
|
|
||||||
# Les niveaux déjà associés à d'autres UE du même semestre
|
# Les niveaux déjà associés à d'autres UE du même semestre
|
||||||
autres_ues = formation.ues.filter_by(semestre_idx=ue.semestre_idx)
|
autres_ues = ue.formation.ues.filter_by(semestre_idx=ue.semestre_idx)
|
||||||
niveaux_autres_ues = {
|
niveaux_autres_ues = {
|
||||||
oue.niveau_competence_id for oue in autres_ues if oue.id != ue.id
|
oue.niveau_competence_id for oue in autres_ues if oue.id != ue.id
|
||||||
}
|
}
|
||||||
@ -44,13 +103,13 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
|||||||
else:
|
else:
|
||||||
disabled = ""
|
disabled = ""
|
||||||
options.append(
|
options.append(
|
||||||
f"""<option value="{n.id}" {'selected'
|
f"""<option value="{n.id}" {
|
||||||
if ue.niveau_competence == n else ''}
|
'selected' if ue.niveau_competence == n else ''}
|
||||||
{disabled}>{n.annee} {n.competence.titre_long}
|
{disabled}>{n.annee} {n.competence.titre_long}
|
||||||
niveau {n.ordre}</option>"""
|
niveau {n.ordre}</option>"""
|
||||||
)
|
)
|
||||||
options.append("""</optgroup>""")
|
options.append("""</optgroup>""")
|
||||||
for parcour in ref_comp.parcours:
|
for parcour in parcours:
|
||||||
if len(niveaux_by_parcours[parcour.id]):
|
if len(niveaux_by_parcours[parcour.id]):
|
||||||
options.append(f"""<optgroup label="Parcours {parcour.libelle}">""")
|
options.append(f"""<optgroup label="Parcours {parcour.libelle}">""")
|
||||||
for n in niveaux_by_parcours[parcour.id]:
|
for n in niveaux_by_parcours[parcour.id]:
|
||||||
@ -65,46 +124,7 @@ def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
|||||||
niveau {n.ordre}</option>"""
|
niveau {n.ordre}</option>"""
|
||||||
)
|
)
|
||||||
options.append("""</optgroup>""")
|
options.append("""</optgroup>""")
|
||||||
options_str = "\n".join(options)
|
return (
|
||||||
return f"""
|
f"""<option value="" {'selected' if ue.niveau_competence is None else ''}>aucun</option>"""
|
||||||
<div class="ue_choix_niveau">
|
+ "\n".join(options)
|
||||||
<form class="form_ue_choix_niveau">
|
|
||||||
<b>Niveau de compétence associé:</b>
|
|
||||||
<select onchange="set_ue_niveau_competence(this);"
|
|
||||||
data-ue_id="{ue.id}"
|
|
||||||
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"""
|
|
||||||
ue = UniteEns.query.get_or_404(ue_id)
|
|
||||||
|
|
||||||
autres_ues = ue.formation.ues.filter_by(semestre_idx=ue.semestre_idx)
|
|
||||||
niveaux_autres_ues = {
|
|
||||||
oue.niveau_competence_id for oue in autres_ues if oue.id != ue.id
|
|
||||||
}
|
|
||||||
if niveau_id in niveaux_autres_ues:
|
|
||||||
log(
|
|
||||||
f"set_ue_niveau_competence: denying association of {ue} to already associated {niveau_id}"
|
|
||||||
)
|
)
|
||||||
return "", 409 # conflict
|
|
||||||
if niveau_id == "":
|
|
||||||
niveau = ""
|
|
||||||
# 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()
|
|
||||||
log(f"set_ue_niveau_competence( {ue}, {niveau} )")
|
|
||||||
|
|
||||||
return "", 204
|
|
||||||
|
@ -774,7 +774,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||||||
def list_ue_parcour_etud(
|
def list_ue_parcour_etud(
|
||||||
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
|
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
|
||||||
) -> tuple[ApcParcours, list[UniteEns]]:
|
) -> tuple[ApcParcours, list[UniteEns]]:
|
||||||
"""Parcour dans lequel l'étudiant est inscrit, et liste des UEs pour ce semestre"""
|
"""Parcour dans lequel l'étudiant est inscrit,
|
||||||
|
et liste des UEs à valider pour ce semestre
|
||||||
|
"""
|
||||||
if res.etuds_parcour_id[etud.id] is None:
|
if res.etuds_parcour_id[etud.id] is None:
|
||||||
parcour = None
|
parcour = None
|
||||||
# pas de parcour: prend toutes les UEs (non bonus)
|
# pas de parcour: prend toutes les UEs (non bonus)
|
||||||
|
@ -107,11 +107,15 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_niveaux_by_parcours(self, annee) -> dict:
|
def get_niveaux_by_parcours(
|
||||||
|
self, annee, parcour: "ApcParcours" = None
|
||||||
|
) -> tuple[list["ApcParcours"], dict]:
|
||||||
"""
|
"""
|
||||||
Construit la liste des niveaux de compétences pour chaque parcours
|
Construit la liste des niveaux de compétences pour chaque parcours
|
||||||
de ce référentiel.
|
de ce référentiel, ou seulement pour le parcours donné.
|
||||||
|
|
||||||
Les niveaux sont groupés par parcours, en isolant les niveaux de tronc commun.
|
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:
|
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
|
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).
|
la clé "TC" (toujours présente mais éventuellement liste vide si pas de tronc commun).
|
||||||
@ -122,10 +126,14 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
parcour.id : [ ApcNiveau ]
|
parcour.id : [ ApcNiveau ]
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
parcours = self.parcours.order_by(ApcParcours.numero).all()
|
parcours_ref = self.parcours.order_by(ApcParcours.numero).all()
|
||||||
|
if parcour is None:
|
||||||
|
parcours = parcours_ref
|
||||||
|
else:
|
||||||
|
parcours = [parcour]
|
||||||
niveaux_by_parcours = {
|
niveaux_by_parcours = {
|
||||||
parcour.id: ApcNiveau.niveaux_annee_de_parcours(parcour, annee, self)
|
parcour.id: ApcNiveau.niveaux_annee_de_parcours(parcour, annee, self)
|
||||||
for parcour in parcours
|
for parcour in parcours_ref
|
||||||
}
|
}
|
||||||
# Cherche tronc commun
|
# Cherche tronc commun
|
||||||
if niveaux_by_parcours:
|
if niveaux_by_parcours:
|
||||||
@ -154,7 +162,7 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
|||||||
niveau for niveau in niveaux_parcours_1 if niveau.id in niveaux_ids_tc
|
niveau for niveau in niveaux_parcours_1 if niveau.id in niveaux_ids_tc
|
||||||
]
|
]
|
||||||
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
niveaux_by_parcours_no_tc["TC"] = niveaux_tc
|
||||||
return niveaux_by_parcours_no_tc
|
return parcours, niveaux_by_parcours_no_tc
|
||||||
|
|
||||||
|
|
||||||
class ApcCompetence(db.Model, XMLModel):
|
class ApcCompetence(db.Model, XMLModel):
|
||||||
@ -436,6 +444,7 @@ class ApcParcours(db.Model, XMLModel):
|
|||||||
lazy="dynamic",
|
lazy="dynamic",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
ues = db.relationship("UniteEns", back_populates="parcour")
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.__class__.__name__} {self.id} {self.code!r} ref={self.referentiel}>"
|
return f"<{self.__class__.__name__} {self.id} {self.code!r} ref={self.referentiel}>"
|
||||||
|
@ -205,8 +205,9 @@ class Formation(db.Model):
|
|||||||
`formation.query_ues_parcour(parcour).filter_by(semestre_idx=3)`
|
`formation.query_ues_parcour(parcour).filter_by(semestre_idx=3)`
|
||||||
"""
|
"""
|
||||||
return UniteEns.query.filter_by(formation=self).filter(
|
return UniteEns.query.filter_by(formation=self).filter(
|
||||||
UniteEns.niveau_competence_id == ApcNiveau.id,
|
|
||||||
UniteEns.type == UE_STANDARD,
|
UniteEns.type == UE_STANDARD,
|
||||||
|
UniteEns.niveau_competence_id == ApcNiveau.id,
|
||||||
|
(UniteEns.parcour_id == parcour.id) | (UniteEns.parcour_id == None),
|
||||||
ApcParcoursNiveauCompetence.competence_id == ApcNiveau.competence_id,
|
ApcParcoursNiveauCompetence.competence_id == ApcNiveau.competence_id,
|
||||||
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
|
ApcParcoursNiveauCompetence.annee_parcours_id == ApcAnneeParcours.id,
|
||||||
ApcAnneeParcours.parcours_id == parcour.id,
|
ApcAnneeParcours.parcours_id == parcour.id,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
"""ScoDoc 9 models : Unités d'Enseignement (UE)
|
"""ScoDoc 9 models : Unités d'Enseignement (UE)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from app import db
|
from app import db, log
|
||||||
from app.models import APO_CODE_STR_LEN
|
from app.models import APO_CODE_STR_LEN
|
||||||
from app.models import SHORT_STR_LEN
|
from app.models import SHORT_STR_LEN
|
||||||
|
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||||
|
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -49,6 +51,9 @@ class UniteEns(db.Model):
|
|||||||
niveau_competence_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"))
|
niveau_competence_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"))
|
||||||
niveau_competence = db.relationship("ApcNiveau", back_populates="ues")
|
niveau_competence = db.relationship("ApcNiveau", back_populates="ues")
|
||||||
|
|
||||||
|
parcour_id = db.Column(db.Integer, db.ForeignKey("apc_parcours.id"), index=True)
|
||||||
|
parcour = db.relationship("ApcParcours", back_populates="ues")
|
||||||
|
|
||||||
# relations
|
# relations
|
||||||
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
matieres = db.relationship("Matiere", lazy="dynamic", backref="ue")
|
||||||
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
modules = db.relationship("Module", lazy="dynamic", backref="ue")
|
||||||
@ -83,6 +88,12 @@ class UniteEns(db.Model):
|
|||||||
e.pop("module_ue_coefs", None)
|
e.pop("module_ue_coefs", None)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
def annee(self) -> int:
|
||||||
|
"""L'année dans la formation (commence à 1).
|
||||||
|
En APC seulement, en classic renvoie toujours 1.
|
||||||
|
"""
|
||||||
|
return 1 if self.semestre_idx is None else (self.semestre_idx - 1) // 2 + 1
|
||||||
|
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
"""True if UE should not be modified
|
"""True if UE should not be modified
|
||||||
(contains modules used in a locked formsemestre)
|
(contains modules used in a locked formsemestre)
|
||||||
@ -135,3 +146,72 @@ class UniteEns(db.Model):
|
|||||||
if self.code_apogee:
|
if self.code_apogee:
|
||||||
return {x.strip() for x in self.code_apogee.split(",") if x}
|
return {x.strip() for x in self.code_apogee.split(",") if x}
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
def _check_apc_conflict(self, new_niveau_id: int, new_parcour_id: int):
|
||||||
|
"raises ScoFormationConflict si (niveau, parcours) pas unique dans ce semestre"
|
||||||
|
# Les UE du même semestre que nous:
|
||||||
|
ues_sem = self.formation.ues.filter_by(semestre_idx=self.semestre_idx)
|
||||||
|
if (new_niveau_id, new_parcour_id) in (
|
||||||
|
(oue.niveau_competence_id, oue.parcour_id)
|
||||||
|
for oue in ues_sem
|
||||||
|
if oue.id != self.id
|
||||||
|
):
|
||||||
|
log(
|
||||||
|
f"set_ue_niveau_competence: {self}: ({new_niveau_id}, {new_parcour_id}) déjà associé"
|
||||||
|
)
|
||||||
|
raise ScoFormationConflict()
|
||||||
|
|
||||||
|
def set_niveau_competence(self, niveau: ApcNiveau):
|
||||||
|
"""Associe cette UE au niveau de compétence indiqué.
|
||||||
|
Le niveau doit être dans le parcours de l'UE, s'il y en a un.
|
||||||
|
Assure que ce soit la seule dans son parcours.
|
||||||
|
Sinon, raises ScoFormationConflict.
|
||||||
|
|
||||||
|
Si niveau est None, désassocie.
|
||||||
|
"""
|
||||||
|
if niveau is not None:
|
||||||
|
self._check_apc_conflict(niveau.id, self.parcour_id)
|
||||||
|
# Le niveau est-il dans le parcours ? Sinon, erreur
|
||||||
|
if self.parcour and niveau.id not in (
|
||||||
|
n.id
|
||||||
|
for n in niveau.niveaux_annee_de_parcours(
|
||||||
|
self.parcour, self.annee(), self.formation.referentiel_competence
|
||||||
|
)
|
||||||
|
):
|
||||||
|
log(
|
||||||
|
f"set_niveau_competence: niveau {niveau} hors parcours {self.parcour}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.niveau_competence = niveau
|
||||||
|
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
log(f"ue.set_niveau_competence( {self}, {niveau} )")
|
||||||
|
|
||||||
|
def set_parcour(self, parcour: ApcParcours):
|
||||||
|
"""Associe cette UE au parcours indiqué.
|
||||||
|
Assure que ce soit la seule dans son parcours.
|
||||||
|
Sinon, raises ScoFormationConflict.
|
||||||
|
|
||||||
|
Si niveau est None, désassocie.
|
||||||
|
"""
|
||||||
|
if (parcour is not None) and self.niveau_competence is not None:
|
||||||
|
self._check_apc_conflict(self.niveau_competence.id, parcour.id)
|
||||||
|
self.parcour = parcour
|
||||||
|
# Le niveau est-il dans ce parcours ? Sinon, l'enlève
|
||||||
|
if (
|
||||||
|
parcour
|
||||||
|
and self.niveau_competence
|
||||||
|
and self.niveau_competence.id
|
||||||
|
not in (
|
||||||
|
n.id
|
||||||
|
for n in self.niveau_competence.niveaux_annee_de_parcours(
|
||||||
|
parcour, self.annee(), self.formation.referentiel_competence
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self.niveau_competence = None
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
log(f"ue.set_parcour( {self}, {parcour} )")
|
||||||
|
@ -458,7 +458,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
|||||||
if tf[0] == 0:
|
if tf[0] == 0:
|
||||||
niveau_competence_div = ""
|
niveau_competence_div = ""
|
||||||
if ue and is_apc:
|
if ue and is_apc:
|
||||||
niveau_competence_div = apc_edit_ue.form_ue_choix_niveau(formation, ue)
|
niveau_competence_div = apc_edit_ue.form_ue_choix_niveau(ue)
|
||||||
if ue and ue.modules.count() and ue.semestre_idx is not None:
|
if ue and ue.modules.count() and ue.semestre_idx is not None:
|
||||||
modules_div = f"""<div id="ue_list_modules">
|
modules_div = f"""<div id="ue_list_modules">
|
||||||
<div><b>{ue.modules.count()} modules sont rattachés
|
<div><b>{ue.modules.count()} modules sont rattachés
|
||||||
|
@ -172,8 +172,9 @@ class ScoInvalidDateError(ScoValueError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Pour les API JSON
|
|
||||||
class APIInvalidParams(Exception):
|
class APIInvalidParams(Exception):
|
||||||
|
"""Exception pour les API JSON"""
|
||||||
|
|
||||||
status_code = 400
|
status_code = 400
|
||||||
|
|
||||||
def __init__(self, message, status_code=None, payload=None):
|
def __init__(self, message, status_code=None, payload=None):
|
||||||
@ -184,6 +185,11 @@ class APIInvalidParams(Exception):
|
|||||||
self.payload = payload
|
self.payload = payload
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
"dict"
|
||||||
rv = dict(self.payload or ())
|
rv = dict(self.payload or ())
|
||||||
rv["message"] = self.message
|
rv["message"] = self.message
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class ScoFormationConflict(Exception):
|
||||||
|
"""Conflit cohérence formation (APC)"""
|
||||||
|
@ -2282,6 +2282,25 @@ div.formation_list_ues div.ue_choix_niveau b {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.cont_ue_choix_niveau {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cont_ue_choix_niveau>div {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-left: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cont_ue_choix_niveau select {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cont_ue_choix_niveau select.select_niveau_ue {
|
||||||
|
width: 490px;
|
||||||
|
}
|
||||||
|
|
||||||
div#ue_list_modules {
|
div#ue_list_modules {
|
||||||
background-color: rgb(251, 225, 165);
|
background-color: rgb(251, 225, 165);
|
||||||
border: 1px solid blue;
|
border: 1px solid blue;
|
||||||
|
@ -11,6 +11,7 @@ $().ready(function () {
|
|||||||
});
|
});
|
||||||
update_bonus_description();
|
update_bonus_description();
|
||||||
}
|
}
|
||||||
|
update_menus_niveau_competence();
|
||||||
});
|
});
|
||||||
|
|
||||||
function update_bonus_description() {
|
function update_bonus_description() {
|
||||||
@ -36,6 +37,22 @@ function update_ue_list() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_ue_parcour(elem) {
|
||||||
|
let ue_id = elem.dataset.ue_id;
|
||||||
|
let parcour_id = elem.value;
|
||||||
|
let set_ue_parcour_url = elem.dataset.setter;
|
||||||
|
$.post(set_ue_parcour_url,
|
||||||
|
{
|
||||||
|
ue_id: ue_id,
|
||||||
|
parcour_id: parcour_id,
|
||||||
|
},
|
||||||
|
function (result) {
|
||||||
|
sco_message("UE associée au parcours");
|
||||||
|
update_menus_niveau_competence();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function set_ue_niveau_competence(elem) {
|
function set_ue_niveau_competence(elem) {
|
||||||
let ue_id = elem.dataset.ue_id;
|
let ue_id = elem.dataset.ue_id;
|
||||||
let niveau_id = elem.value;
|
let niveau_id = elem.value;
|
||||||
@ -46,7 +63,6 @@ function set_ue_niveau_competence(elem) {
|
|||||||
niveau_id: niveau_id,
|
niveau_id: niveau_id,
|
||||||
},
|
},
|
||||||
function (result) {
|
function (result) {
|
||||||
// alert("niveau de compétence enregistré"); // XXX #frontend à améliorer
|
|
||||||
sco_message("niveau de compétence enregistré");
|
sco_message("niveau de compétence enregistré");
|
||||||
|
|
||||||
update_menus_niveau_competence();
|
update_menus_niveau_competence();
|
||||||
@ -57,18 +73,33 @@ function set_ue_niveau_competence(elem) {
|
|||||||
// Met à jour les niveaux utilisés (disabled) ou non affectés
|
// Met à jour les niveaux utilisés (disabled) ou non affectés
|
||||||
// dans les menus d'association UE <-> niveau
|
// dans les menus d'association UE <-> niveau
|
||||||
function update_menus_niveau_competence() {
|
function update_menus_niveau_competence() {
|
||||||
let selected_niveaux = [];
|
// let selected_niveaux = [];
|
||||||
document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
// document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||||
elem => { selected_niveaux.push(elem.value); }
|
// elem => { selected_niveaux.push(elem.value); }
|
||||||
);
|
// );
|
||||||
|
|
||||||
document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
// document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||||
|
// elem => {
|
||||||
|
// for (let i = 0; i < elem.options.length; i++) {
|
||||||
|
// elem.options[i].disabled = (i != elem.options.selectedIndex)
|
||||||
|
// && (selected_niveaux.indexOf(elem.options[i].value) != -1)
|
||||||
|
// && (elem.options[i].value != "");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// nouveau:
|
||||||
|
document.querySelectorAll("select.select_niveau_ue").forEach(
|
||||||
elem => {
|
elem => {
|
||||||
for (let i = 0; i < elem.options.length; i++) {
|
let ue_id = elem.dataset.ue_id;
|
||||||
elem.options[i].disabled = (i != elem.options.selectedIndex)
|
$.get("get_ue_niveaux_options_html",
|
||||||
&& (selected_niveaux.indexOf(elem.options[i].value) != -1)
|
{
|
||||||
&& (elem.options[i].value != "");
|
ue_id: ue_id,
|
||||||
|
},
|
||||||
|
function (result) {
|
||||||
|
elem.innerHTML = result;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -25,21 +25,20 @@
|
|||||||
|
|
||||||
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
||||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||||
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else %}{{icons.delete_disabled|safe}}{% endif %}</a>
|
}}">{% if editable and not ue.modules.count() %}{{icons.delete|safe}}{% else
|
||||||
|
%}{{icons.delete_disabled|safe}}{% endif %}</a>
|
||||||
|
|
||||||
<span class="ue_type_{{ue.type}}">
|
<span class="ue_type_{{ue.type}}">
|
||||||
<span class="ue_color_indicator" style="background:{{
|
<span class="ue_color_indicator" style="background:{{
|
||||||
ue.color if ue.color is not none else 'blue'}}"></span>
|
ue.color if ue.color is not none else 'blue'}}"></span>
|
||||||
<b>{{ue.acronyme}} <a class="discretelink" href="{{
|
<b>{{ue.acronyme}} <a class="discretelink" href="{{
|
||||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}" title="{{ue.acronyme}}: {{
|
||||||
title="{{ue.acronyme}}: {{
|
|
||||||
('pas de compétence associée'
|
('pas de compétence associée'
|
||||||
if ue.niveau_competence is none
|
if ue.niveau_competence is none
|
||||||
else 'compétence ' + ue.niveau_competence.annee + ' ' + ue.niveau_competence.competence.titre_long)
|
else 'compétence ' + ue.niveau_competence.annee + ' ' + ue.niveau_competence.competence.titre_long)
|
||||||
if ue.type == 0
|
if ue.type == 0
|
||||||
else ''
|
else ''
|
||||||
}}"
|
}}">{{ue.titre}}</a>
|
||||||
>{{ue.titre}}</a>
|
|
||||||
</b>
|
</b>
|
||||||
{% set virg = joiner(", ") %}
|
{% set virg = joiner(", ") %}
|
||||||
<span class="ue_code">(
|
<span class="ue_code">(
|
||||||
@ -65,7 +64,7 @@
|
|||||||
}}">modifier</a>
|
}}">modifier</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ form_ue_choix_niveau(formation, ue)|safe }}
|
{{ form_ue_choix_niveau(ue)|safe }}
|
||||||
|
|
||||||
|
|
||||||
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
||||||
@ -81,8 +80,7 @@
|
|||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formation_id=formation.id,
|
formation_id=formation.id,
|
||||||
default_semestre_idx=semestre_idx,
|
default_semestre_idx=semestre_idx,
|
||||||
)}}"
|
)}}">ajouter une UE</a>
|
||||||
>ajouter une UE</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -33,11 +33,10 @@ Emmanuel Viennet, 2021
|
|||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
import time
|
import time
|
||||||
from xml.etree import ElementTree
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import abort, flash, redirect, render_template, url_for
|
from flask import abort, flash, redirect, render_template, url_for
|
||||||
from flask import current_app, g, request
|
from flask import g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -52,6 +51,7 @@ from app.but import jury_but_view
|
|||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.models import ScolarNews
|
from app.models import ScolarNews
|
||||||
|
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.formsemestre import FormSemestre
|
from app.models.formsemestre import FormSemestre
|
||||||
@ -59,6 +59,7 @@ from app.models.formsemestre import FormSemestreUEComputationExpr
|
|||||||
from app.models.moduleimpls import ModuleImpl
|
from app.models.moduleimpls import ModuleImpl
|
||||||
from app.models.modules import Module
|
from app.models.modules import Module
|
||||||
from app.models.ues import UniteEns
|
from app.models.ues import UniteEns
|
||||||
|
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
|
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
@ -403,10 +404,50 @@ sco_publish(
|
|||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoChangeFormation)
|
@permission_required(Permission.ScoChangeFormation)
|
||||||
def set_ue_niveau_competence():
|
def set_ue_niveau_competence():
|
||||||
"associe UE et niveau"
|
"""Associe UE et niveau.
|
||||||
|
Si le niveau_id est "", désassocie."""
|
||||||
ue_id = request.form.get("ue_id")
|
ue_id = request.form.get("ue_id")
|
||||||
niveau_id = request.form.get("niveau_id")
|
niveau_id = request.form.get("niveau_id")
|
||||||
return apc_edit_ue.set_ue_niveau_competence(ue_id, niveau_id)
|
if niveau_id == "":
|
||||||
|
niveau_id = None
|
||||||
|
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
|
||||||
|
niveau = None if niveau_id is None else ApcNiveau.query.get_or_404(niveau_id)
|
||||||
|
try:
|
||||||
|
ue.set_niveau_competence(niveau)
|
||||||
|
except ScoFormationConflict:
|
||||||
|
return "", 409 # conflict
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/set_ue_parcours", methods=["POST"])
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoChangeFormation)
|
||||||
|
def set_ue_parcours():
|
||||||
|
"""Associe UE et parcours BUT.
|
||||||
|
Si le parcour_id est "", désassocie."""
|
||||||
|
ue_id = request.form.get("ue_id")
|
||||||
|
parcour_id = request.form.get("parcour_id")
|
||||||
|
if parcour_id == "":
|
||||||
|
parcour_id = None
|
||||||
|
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
|
||||||
|
parcour = None if parcour_id is None else ApcParcours.query.get_or_404(parcour_id)
|
||||||
|
try:
|
||||||
|
ue.set_parcour(parcour)
|
||||||
|
except ScoFormationConflict:
|
||||||
|
return "", 409 # conflict
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/get_ue_niveaux_options_html")
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def get_ue_niveaux_options_html():
|
||||||
|
"""fragment html avec les options du menu de sélection du
|
||||||
|
niveau de compétences associé à une UE
|
||||||
|
"""
|
||||||
|
ue_id = request.args.get("ue_id")
|
||||||
|
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
|
||||||
|
return apc_edit_ue.get_ue_niveaux_options_html(ue)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ue_list") # backward compat
|
@bp.route("/ue_list") # backward compat
|
||||||
|
34
migrations/versions/dbb4a0b19dbb_association_ue_parcours.py
Normal file
34
migrations/versions/dbb4a0b19dbb_association_ue_parcours.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""Association UE/Parcours
|
||||||
|
|
||||||
|
Revision ID: dbb4a0b19dbb
|
||||||
|
Revises: 6bc3f51154b4
|
||||||
|
Create Date: 2022-10-29 19:06:12.897905
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "dbb4a0b19dbb"
|
||||||
|
down_revision = "6bc3f51154b4"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("notes_ue", sa.Column("parcour_id", sa.Integer(), nullable=True))
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_notes_ue_parcour_id"), "notes_ue", ["parcour_id"], unique=False
|
||||||
|
)
|
||||||
|
op.create_foreign_key(None, "notes_ue", "apc_parcours", ["parcour_id"], ["id"])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, "notes_ue", type_="foreignkey")
|
||||||
|
op.drop_index(op.f("ix_notes_ue_parcour_id"), table_name="notes_ue")
|
||||||
|
op.drop_column("notes_ue", "parcour_id")
|
||||||
|
# ### end Alembic commands ###
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.3.60"
|
SCOVERSION = "9.3.60-dev"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user