forked from ScoDoc/ScoDoc
BUT: associe UE aux parcours. Modification pour #487.
This commit is contained in:
parent
59fdc80d60
commit
a3593d5a74
@ -8,14 +8,14 @@
|
||||
Edition associations UE <-> Ref. Compétence
|
||||
"""
|
||||
from flask import g, url_for
|
||||
from app import db, log
|
||||
from app.models import Formation, UniteEns
|
||||
from app.models.but_refcomp import ApcNiveau
|
||||
from app.models import ApcReferentielCompetences, Formation, UniteEns
|
||||
from app.scodoc import sco_codes_parcours
|
||||
|
||||
|
||||
def form_ue_choix_niveau(formation: Formation, ue: UniteEns) -> str:
|
||||
"""Form. HTML pour associer une UE à un niveau de compétence"""
|
||||
def form_ue_choix_niveau(ue: UniteEns) -> str:
|
||||
"""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:
|
||||
return ""
|
||||
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>
|
||||
</div>
|
||||
</div>"""
|
||||
annee = 1 if ue.semestre_idx is None else (ue.semestre_idx + 1) // 2 # 1, 2, 3
|
||||
niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
|
||||
# Les parcours:
|
||||
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
|
||||
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 = {
|
||||
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:
|
||||
disabled = ""
|
||||
options.append(
|
||||
f"""<option value="{n.id}" {'selected'
|
||||
if ue.niveau_competence == n else ''}
|
||||
f"""<option value="{n.id}" {
|
||||
'selected' if ue.niveau_competence == n else ''}
|
||||
{disabled}>{n.annee} {n.competence.titre_long}
|
||||
niveau {n.ordre}</option>"""
|
||||
)
|
||||
options.append("""</optgroup>""")
|
||||
for parcour in ref_comp.parcours:
|
||||
for parcour in parcours:
|
||||
if len(niveaux_by_parcours[parcour.id]):
|
||||
options.append(f"""<optgroup label="Parcours {parcour.libelle}">""")
|
||||
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>"""
|
||||
)
|
||||
options.append("""</optgroup>""")
|
||||
options_str = "\n".join(options)
|
||||
return f"""
|
||||
<div class="ue_choix_niveau">
|
||||
<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
|
||||
return (
|
||||
f"""<option value="" {'selected' if ue.niveau_competence is None else ''}>aucun</option>"""
|
||||
+ "\n".join(options)
|
||||
)
|
||||
|
@ -774,7 +774,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
||||
def list_ue_parcour_etud(
|
||||
formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT
|
||||
) -> 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:
|
||||
parcour = None
|
||||
# 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},
|
||||
}
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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).
|
||||
@ -122,10 +126,14 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
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 = {
|
||||
parcour.id: ApcNiveau.niveaux_annee_de_parcours(parcour, annee, self)
|
||||
for parcour in parcours
|
||||
for parcour in parcours_ref
|
||||
}
|
||||
# Cherche tronc commun
|
||||
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
|
||||
]
|
||||
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):
|
||||
@ -436,6 +444,7 @@ class ApcParcours(db.Model, XMLModel):
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
ues = db.relationship("UniteEns", back_populates="parcour")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
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)`
|
||||
"""
|
||||
return UniteEns.query.filter_by(formation=self).filter(
|
||||
UniteEns.niveau_competence_id == ApcNiveau.id,
|
||||
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.annee_parcours_id == ApcAnneeParcours.id,
|
||||
ApcAnneeParcours.parcours_id == parcour.id,
|
||||
|
@ -1,9 +1,11 @@
|
||||
"""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 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
|
||||
|
||||
|
||||
@ -49,6 +51,9 @@ class UniteEns(db.Model):
|
||||
niveau_competence_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"))
|
||||
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
|
||||
matieres = db.relationship("Matiere", 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)
|
||||
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):
|
||||
"""True if UE should not be modified
|
||||
(contains modules used in a locked formsemestre)
|
||||
@ -135,3 +146,72 @@ class UniteEns(db.Model):
|
||||
if self.code_apogee:
|
||||
return {x.strip() for x in self.code_apogee.split(",") if x}
|
||||
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:
|
||||
niveau_competence_div = ""
|
||||
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:
|
||||
modules_div = f"""<div id="ue_list_modules">
|
||||
<div><b>{ue.modules.count()} modules sont rattachés
|
||||
|
@ -172,8 +172,9 @@ class ScoInvalidDateError(ScoValueError):
|
||||
pass
|
||||
|
||||
|
||||
# Pour les API JSON
|
||||
class APIInvalidParams(Exception):
|
||||
"""Exception pour les API JSON"""
|
||||
|
||||
status_code = 400
|
||||
|
||||
def __init__(self, message, status_code=None, payload=None):
|
||||
@ -184,6 +185,11 @@ class APIInvalidParams(Exception):
|
||||
self.payload = payload
|
||||
|
||||
def to_dict(self):
|
||||
"dict"
|
||||
rv = dict(self.payload or ())
|
||||
rv["message"] = self.message
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
background-color: rgb(251, 225, 165);
|
||||
border: 1px solid blue;
|
||||
|
@ -11,6 +11,7 @@ $().ready(function () {
|
||||
});
|
||||
update_bonus_description();
|
||||
}
|
||||
update_menus_niveau_competence();
|
||||
});
|
||||
|
||||
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) {
|
||||
let ue_id = elem.dataset.ue_id;
|
||||
let niveau_id = elem.value;
|
||||
@ -46,7 +63,6 @@ function set_ue_niveau_competence(elem) {
|
||||
niveau_id: niveau_id,
|
||||
},
|
||||
function (result) {
|
||||
// alert("niveau de compétence enregistré"); // XXX #frontend à améliorer
|
||||
sco_message("niveau de compétence enregistré");
|
||||
|
||||
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
|
||||
// dans les menus d'association UE <-> niveau
|
||||
function update_menus_niveau_competence() {
|
||||
let selected_niveaux = [];
|
||||
document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||
elem => { selected_niveaux.push(elem.value); }
|
||||
);
|
||||
// let selected_niveaux = [];
|
||||
// document.querySelectorAll("form.form_ue_choix_niveau select").forEach(
|
||||
// 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 => {
|
||||
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 != "");
|
||||
}
|
||||
let ue_id = elem.dataset.ue_id;
|
||||
$.get("get_ue_niveaux_options_html",
|
||||
{
|
||||
ue_id: ue_id,
|
||||
},
|
||||
function (result) {
|
||||
elem.innerHTML = result;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
@ -3,88 +3,86 @@
|
||||
<div class="formation_list_ues">
|
||||
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
||||
{% for semestre_idx in semestre_ids %}
|
||||
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}} (ECTS: {{ects_by_sem[semestre_idx] | safe}})</div>
|
||||
<ul class="apc_ue_list">
|
||||
{% for ue in ues_by_sem[semestre_idx] %}
|
||||
<li class="notes_ue_list">
|
||||
{% if editable and not loop.first %}
|
||||
<a href="{{ url_for('notes.ue_move',
|
||||
<div class="formation_list_ues_sem">Semestre S{{semestre_idx}} (ECTS: {{ects_by_sem[semestre_idx] | safe}})</div>
|
||||
<ul class="apc_ue_list">
|
||||
{% for ue in ues_by_sem[semestre_idx] %}
|
||||
<li class="notes_ue_list">
|
||||
{% if editable and not loop.first %}
|
||||
<a href="{{ url_for('notes.ue_move',
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id, after=0 )
|
||||
}}" class="aud">{{icons.arrow_up|safe}}</a>
|
||||
{% else %}
|
||||
{{icons.arrow_none|safe}}
|
||||
{% endif %}
|
||||
{% if editable and not loop.last %}
|
||||
<a href="{{ url_for('notes.ue_move',
|
||||
{% else %}
|
||||
{{icons.arrow_none|safe}}
|
||||
{% endif %}
|
||||
{% if editable and not loop.last %}
|
||||
<a href="{{ url_for('notes.ue_move',
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id, after=1 )
|
||||
}}" class="aud">{{icons.arrow_down|safe}}</a>
|
||||
{% else %}
|
||||
{{icons.arrow_none|safe}}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
||||
{% else %}
|
||||
{{icons.arrow_none|safe}}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
||||
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>
|
||||
|
||||
<span class="ue_type_{{ue.type}}">
|
||||
}}">{% 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_color_indicator" style="background:{{
|
||||
ue.color if ue.color is not none else 'blue'}}"></span>
|
||||
<b>{{ue.acronyme}} <a class="discretelink" href="{{
|
||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}"
|
||||
title="{{ue.acronyme}}: {{
|
||||
url_for('notes.ue_infos', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}" title="{{ue.acronyme}}: {{
|
||||
('pas de compétence associée'
|
||||
if ue.niveau_competence is none
|
||||
else 'compétence ' + ue.niveau_competence.annee + ' ' + ue.niveau_competence.competence.titre_long)
|
||||
if ue.type == 0
|
||||
else ''
|
||||
}}"
|
||||
>{{ue.titre}}</a>
|
||||
}}">{{ue.titre}}</a>
|
||||
</b>
|
||||
{% set virg = joiner(", ") %}
|
||||
<span class="ue_code">(
|
||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||
{{ virg() }}
|
||||
{%- if ue.type == 0 -%}
|
||||
{{ue.ects
|
||||
if ue.ects is not none
|
||||
else '<span class="missing_ue_ects">aucun</span>'|safe
|
||||
}} ECTS
|
||||
{%- endif -%}
|
||||
{%- if ue.code_apogee -%}
|
||||
{{ virg() }} Apo {{ue.code_apogee}}
|
||||
{%- endif -%}
|
||||
{% set virg = joiner(", ") %}
|
||||
<span class="ue_code">(
|
||||
{%- if ue.ue_code -%}{{ virg() }}code {{ue.ue_code}} {%- endif -%}
|
||||
{{ virg() }}
|
||||
{%- if ue.type == 0 -%}
|
||||
{{ue.ects
|
||||
if ue.ects is not none
|
||||
else '<span class="missing_ue_ects">aucun</span>'|safe
|
||||
}} ECTS
|
||||
{%- endif -%}
|
||||
{%- if ue.code_apogee -%}
|
||||
{{ virg() }} Apo {{ue.code_apogee}}
|
||||
{%- endif -%}
|
||||
)
|
||||
</span>
|
||||
|
||||
</span>
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
<a class="stdlink" href="{{ url_for('notes.ue_edit',
|
||||
|
||||
</span>
|
||||
|
||||
{% if editable and not ue.is_locked() %}
|
||||
<a class="stdlink" href="{{ url_for('notes.ue_edit',
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
}}">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 %}
|
||||
<span class="warning" title="pas de module, donc pas de bonus calculé">aucun module rattaché !</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if editable %}
|
||||
<ul>
|
||||
<li class="notes_ue_list notes_ue_list_add"><a class="stdlink" href="{{
|
||||
{% if ue.type == 1 and ue.modules.count() == 0 %}
|
||||
<span class="warning" title="pas de module, donc pas de bonus calculé">aucun module rattaché !</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if editable %}
|
||||
<ul>
|
||||
<li class="notes_ue_list notes_ue_list_add"><a class="stdlink" href="{{
|
||||
url_for('notes.ue_create',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation.id,
|
||||
default_semestre_idx=semestre_idx,
|
||||
)}}"
|
||||
>ajouter une UE</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
)}}">ajouter une UE</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
@ -33,11 +33,10 @@ Emmanuel Viennet, 2021
|
||||
|
||||
from operator import itemgetter
|
||||
import time
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import flask
|
||||
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 app import db
|
||||
@ -52,6 +51,7 @@ from app.but import jury_but_view
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_compat import NotesTableCompat
|
||||
from app.models import ScolarNews
|
||||
from app.models.but_refcomp import ApcNiveau, ApcParcours
|
||||
from app.models.config import ScoDocSiteConfig
|
||||
from app.models.etudiants import Identite
|
||||
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.modules import Module
|
||||
from app.models.ues import UniteEns
|
||||
from app.scodoc.sco_exceptions import ScoFormationConflict
|
||||
from app.views import notes_bp as bp
|
||||
|
||||
from app.decorators import (
|
||||
@ -403,10 +404,50 @@ sco_publish(
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
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")
|
||||
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
|
||||
|
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 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.3.60"
|
||||
SCOVERSION = "9.3.60-dev"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user