Parcours BUT / Ref. Compétences

+ association UE -> ApcNiveau
    + choix sur la page ue_edit
 + association Module <-> ensemble de ApcParcours
    + choix sur la page module_edit
 + association Module - ApcAppCritique
    ~ choix sur la page module_edit
    TODO: revoir pour présenter les AC du semestre et parcours sélectionnés (JS)

 + association FormSemestre <-> ApcParcours
    + choix sur la page formsemestre_editwithmodules
This commit is contained in:
Emmanuel Viennet 2022-05-22 03:26:39 +02:00
parent 539041fd0d
commit fd8116a772
17 changed files with 278 additions and 132 deletions

View File

@ -17,7 +17,13 @@ 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"""
ref_comp = ue.formation.referentiel_competence ref_comp = ue.formation.referentiel_competence
if ref_comp is None: if ref_comp is None:
return """<div class="ue_choix_niveau">pas de référentiel de compétence</div>""" return f"""<div class="ue_choix_niveau">
<div class="warning">Pas de référentiel de compétence associé à cette formation !</div>
<div><a class="stdlink" href="{ url_for('notes.refcomp_assoc_formation',
scodoc_dept=g.scodoc_dept, formation_id=ue.formation.id)
}">associer un référentiel de compétence</a>
</div>
</div>"""
annee = (ue.semestre_idx + 1) // 2 # 1, 2, 3 annee = (ue.semestre_idx + 1) // 2 # 1, 2, 3
niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee) niveaux_by_parcours = ref_comp.get_niveaux_by_parcours(annee)
@ -39,7 +45,7 @@ def form_ue_choix_niveau(ue: UniteEns) -> str:
options.append("""</optgroup>""") options.append("""</optgroup>""")
options_str = "\n".join(options) options_str = "\n".join(options)
return f""" return f"""
<div id="ue_choix_niveau"> <div class="ue_choix_niveau">
<form id="form_ue_choix_niveau"> <form id="form_ue_choix_niveau">
<b>Niveau de compétence associé:</b> <b>Niveau de compétence associé:</b>
<select onchange="set_ue_niveau_competence();" data-setter="{ <select onchange="set_ue_niveau_competence();" data-setter="{

View File

@ -13,7 +13,9 @@ from wtforms import SelectField, SubmitField
class FormationRefCompForm(FlaskForm): class FormationRefCompForm(FlaskForm):
referentiel_competence = SelectField("Référentiels déjà chargés") referentiel_competence = SelectField(
"Choisir parmi les référentiels déjà chargés :"
)
submit = SubmitField("Valider") submit = SubmitField("Valider")
cancel = SubmitField("Annuler") cancel = SubmitField("Annuler")

View File

@ -1,12 +1,9 @@
"""ScoDoc 9 models : Formation BUT 2021 """ScoDoc 9 models : Formation BUT 2021
XXX inutilisé
""" """
from enum import unique
from typing import Any
from app import db from app import db
from app.scodoc.sco_utils import ModuleType
class APCFormation(db.Model): class APCFormation(db.Model):
"""Formation par compétence""" """Formation par compétence"""

View File

@ -83,7 +83,7 @@ class ApcReferentielCompetences(db.Model, XMLModel):
formations = db.relationship("Formation", backref="referentiel_competence") formations = db.relationship("Formation", backref="referentiel_competence")
def __repr__(self): def __repr__(self):
return f"<ApcReferentielCompetences {self.id} {self.specialite}>" return f"<ApcReferentielCompetences {self.id} {self.specialite!r}>"
def to_dict(self): def to_dict(self):
"""Représentation complète du ref. de comp. """Représentation complète du ref. de comp.
@ -191,7 +191,7 @@ class ApcCompetence(db.Model, XMLModel):
) )
def __repr__(self): def __repr__(self):
return f"<ApcCompetence {self.id} {self.titre}>" return f"<ApcCompetence {self.id} {self.titre!r}>"
def to_dict(self): def to_dict(self):
return { return {
@ -257,7 +257,8 @@ class ApcNiveau(db.Model, XMLModel):
ues = db.relationship("UniteEns", back_populates="niveau_competence") ues = db.relationship("UniteEns", back_populates="niveau_competence")
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} ordre={self.ordre} annee={self.annee} {self.competence}>" return f"""<{self.__class__.__name__} ordre={self.ordre!r} annee={
self.annee!r} {self.competence!r}>"""
def to_dict(self): def to_dict(self):
return { return {
@ -341,7 +342,7 @@ class ApcAppCritique(db.Model, XMLModel):
return self.code + " - " + self.titre return self.code + " - " + self.titre
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.code}>" return f"<{self.__class__.__name__} {self.code!r}>"
def get_saes(self): def get_saes(self):
"""Liste des SAE associées""" """Liste des SAE associées"""
@ -362,6 +363,20 @@ parcours_modules = db.Table(
) )
"""Association parcours <-> modules (many-to-many)""" """Association parcours <-> modules (many-to-many)"""
parcours_formsemestre = db.Table(
"parcours_formsemestre",
db.Column(
"parcours_id", db.Integer, db.ForeignKey("apc_parcours.id"), primary_key=True
),
db.Column(
"formsemestre_id",
db.Integer,
db.ForeignKey("notes_formsemestre.id", ondelete="CASCADE"),
primary_key=True,
),
)
"""Association parcours <-> formsemestre (many-to-many)"""
class ApcParcours(db.Model, XMLModel): class ApcParcours(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -379,7 +394,7 @@ class ApcParcours(db.Model, XMLModel):
) )
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.code}>" return f"<{self.__class__.__name__} {self.code!r}>"
def to_dict(self): def to_dict(self):
return { return {
@ -399,7 +414,7 @@ class ApcAnneeParcours(db.Model, XMLModel):
"numéro de l'année: 1, 2, 3" "numéro de l'année: 1, 2, 3"
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} ordre={self.ordre} parcours={self.parcours.code}>" return f"<{self.__class__.__name__} ordre={self.ordre!r} parcours={self.parcours.code!r}>"
def to_dict(self): def to_dict(self):
return { return {
@ -450,4 +465,4 @@ class ApcParcoursNiveauCompetence(db.Model):
) )
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.competence}<->{self.annee_parcours} niveau={self.niveau}>" return f"<{self.__class__.__name__} {self.competence!r}<->{self.annee_parcours!r} niveau={self.niveau!r}>"

View File

@ -515,21 +515,21 @@ class Admission(db.Model):
def to_dict(self, no_nulls=False): def to_dict(self, no_nulls=False):
"""Représentation dictionnaire,""" """Représentation dictionnaire,"""
e = dict(self.__dict__) d = dict(self.__dict__)
e.pop("_sa_instance_state", None) d.pop("_sa_instance_state", None)
if no_nulls: if no_nulls:
for k in e: for k in d.keys():
if e[k] is None: if d[k] is None:
col_type = getattr( col_type = getattr(
sqlalchemy.inspect(models.Admission).columns, "apb_groupe" sqlalchemy.inspect(models.Admission).columns, "apb_groupe"
).expression.type ).expression.type
if isinstance(col_type, sqlalchemy.Text): if isinstance(col_type, sqlalchemy.Text):
e[k] = "" d[k] = ""
elif isinstance(col_type, sqlalchemy.Integer): elif isinstance(col_type, sqlalchemy.Integer):
e[k] = 0 d[k] = 0
elif isinstance(col_type, sqlalchemy.Boolean): elif isinstance(col_type, sqlalchemy.Boolean):
e[k] = False d[k] = False
return e return d
# Suivi scolarité / débouchés # Suivi scolarité / débouchés

View File

@ -45,7 +45,7 @@ class Formation(db.Model):
modules = db.relationship("Module", lazy="dynamic", backref="formation") modules = db.relationship("Module", lazy="dynamic", backref="formation")
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>" return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme!r}')>"
def to_dict(self): def to_dict(self):
e = dict(self.__dict__) e = dict(self.__dict__)
@ -168,7 +168,7 @@ class Matiere(db.Model):
def __repr__(self): def __repr__(self):
return f"""<{self.__class__.__name__}(id={self.id}, ue_id={ return f"""<{self.__class__.__name__}(id={self.id}, ue_id={
self.ue_id}, titre='{self.titre}')>""" self.ue_id}, titre='{self.titre!r}')>"""
def to_dict(self): def to_dict(self):
"""as a dict, with the same conversions as in ScoDoc7""" """as a dict, with the same conversions as in ScoDoc7"""

View File

@ -14,10 +14,12 @@ from app.models import SHORT_STR_LEN
from app.models import CODE_STR_LEN from app.models import CODE_STR_LEN
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.models.ues import UniteEns from app.models.but_refcomp import parcours_formsemestre
from app.models.etudiants import Identite
from app.models.modules import Module from app.models.modules import Module
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.etudiants import Identite from app.models.ues import UniteEns
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
@ -113,6 +115,14 @@ class FormSemestre(db.Model):
# ne pas utiliser après migrate_scodoc7_dept_archives # ne pas utiliser après migrate_scodoc7_dept_archives
scodoc7_id = db.Column(db.Text(), nullable=True) scodoc7_id = db.Column(db.Text(), nullable=True)
# BUT
parcours = db.relationship(
"ApcParcours",
secondary=parcours_formsemestre,
lazy="subquery",
backref=db.backref("formsemestres", lazy=True),
)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(FormSemestre, self).__init__(**kwargs) super(FormSemestre, self).__init__(**kwargs)
if self.modalite is None: if self.modalite is None:

View File

@ -65,7 +65,7 @@ class Module(db.Model):
super(Module, self).__init__(**kwargs) super(Module, self).__init__(**kwargs)
def __repr__(self): def __repr__(self):
return f"<Module{ModuleType(self.module_type or ModuleType.STANDARD).name} id={self.id} code={self.code}>" return f"<Module{ModuleType(self.module_type or ModuleType.STANDARD).name} id={self.id} code={self.code!r}>"
def to_dict(self): def to_dict(self):
e = dict(self.__dict__) e = dict(self.__dict__)

View File

@ -207,12 +207,16 @@ class TF(object):
else: else:
self.values[field] = 1 self.values[field] = 1
if field not in self.values: if field not in self.values:
if "default" in descr: # first: default in form description if (descr.get("input_type", None) == "checkbox") and self.submitted():
self.values[field] = descr["default"] # aucune case cochée
else: # then: use initvalues dict self.values[field] = []
self.values[field] = self.initvalues.get(field, "") else:
if self.values[field] == None: if "default" in descr: # first: default in form description
self.values[field] = "" self.values[field] = descr["default"]
else: # then: use initvalues dict
self.values[field] = self.initvalues.get(field, "")
if self.values[field] is None:
self.values[field] = ""
# convert numbers, except ids # convert numbers, except ids
if field.endswith("id") and self.values[field]: if field.endswith("id") and self.values[field]:
@ -392,9 +396,7 @@ class TF(object):
if self.top_buttons: if self.top_buttons:
R.append(buttons_markup + "<p></p>") R.append(buttons_markup + "<p></p>")
R.append('<table class="tf">') R.append('<table class="tf">')
idx = 0 for field, descr in self.formdescription:
for idx in range(len(self.formdescription)):
(field, descr) = self.formdescription[idx]
if descr.get("readonly", False): if descr.get("readonly", False):
R.append(self._ReadOnlyElement(field, descr)) R.append(self._ReadOnlyElement(field, descr))
continue continue
@ -408,7 +410,7 @@ class TF(object):
input_type = descr.get("input_type", "text") input_type = descr.get("input_type", "text")
item_dom_id = descr.get("dom_id", "") item_dom_id = descr.get("dom_id", "")
if item_dom_id: if item_dom_id:
item_dom_attr = ' id="%s"' % item_dom_id item_dom_attr = f' id="{item_dom_id}"'
else: else:
item_dom_attr = "" item_dom_attr = ""
# choix du template # choix du template
@ -523,7 +525,6 @@ class TF(object):
else: else:
checked = "" checked = ""
else: # boolcheckbox else: # boolcheckbox
# open('/tmp/toto','a').write('GenForm: values[%s] = %s (%s)\n' % (field, values[field], type(values[field])))
if values[field] == "True": if values[field] == "True":
v = True v = True
elif values[field] == "False": elif values[field] == "False":

View File

@ -365,6 +365,7 @@ def module_edit(
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js", "libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
"libjs/jQuery-tagEditor/jquery.caret.min.js", "libjs/jQuery-tagEditor/jquery.caret.min.js",
"js/module_tag_editor.js", "js/module_tag_editor.js",
"js/module_edit.js",
], ],
), ),
f"""<h2>{title}</h2>""", f"""<h2>{title}</h2>""",
@ -605,8 +606,7 @@ def module_edit(
"input_type": "menu", "input_type": "menu",
"type": "int", "type": "int",
"title": parcours.SESSION_NAME.capitalize(), "title": parcours.SESSION_NAME.capitalize(),
"explanation": "%s de début du module dans la formation standard" "explanation": f"{parcours.SESSION_NAME} de début du module dans la formation standard",
% parcours.SESSION_NAME,
"labels": [str(x) for x in semestres_indices], "labels": [str(x) for x in semestres_indices],
"allowed_values": semestres_indices, "allowed_values": semestres_indices,
"enabled": unlocked, "enabled": unlocked,
@ -619,7 +619,7 @@ def module_edit(
{ {
"title": "Code Apogée", "title": "Code Apogée",
"size": 25, "size": 25,
"explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP "explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP
séparés par des virgules (ce code est propre à chaque établissement, se rapprocher séparés par des virgules (ce code est propre à chaque établissement, se rapprocher
du référent Apogée). du référent Apogée).
""", """,
@ -648,11 +648,15 @@ def module_edit(
"input_type": "checkbox", "input_type": "checkbox",
"vertical": True, "vertical": True,
"dom_id": "tf_module_parcours", "dom_id": "tf_module_parcours",
"labels": [parcour.libelle for parcour in ref_comp.parcours], "labels": [parcour.libelle for parcour in ref_comp.parcours]
+ ["Tous (tronc commun)"],
"allowed_values": [ "allowed_values": [
str(parcour.id) for parcour in ref_comp.parcours str(parcour.id) for parcour in ref_comp.parcours
], ]
"explanation": "parcours dans lesquels est utilisé ce module.", + ["-1"],
"explanation": """Parcours dans lesquels est utilisé ce module.<br>
Attention: si le module ne doit pas avoir les mêmes coefficients suivant le parcours,
il faut en créer plusieurs versions, car dans ScoDoc chaque module a ses coefficients.""",
}, },
) )
] ]
@ -677,12 +681,17 @@ def module_edit(
"vertical": True, "vertical": True,
"dom_id": "tf_module_app_critiques", "dom_id": "tf_module_app_critiques",
"labels": [ "labels": [
app_crit.libelle for app_crit in app_critiques f"{app_crit.code}&nbsp; {app_crit.libelle}"
for app_crit in app_critiques
], ],
"allowed_values": [ "allowed_values": [
str(app_crit.id) for app_crit in app_critiques str(app_crit.id) for app_crit in app_critiques
], ],
"explanation": "apprentissages critiques liés à ce module.", "html_data": [],
"explanation": """Apprentissages Critiques liés à ce module.
(si vous changez le semestre, revenez ensuite sur cette page
pour associer les AC.)
""",
}, },
) )
] ]
@ -693,7 +702,9 @@ def module_edit(
{ {
"input_type": "separator", "input_type": "separator",
"title": f"""<span class="fontred">{scu.EMO_WARNING } "title": f"""<span class="fontred">{scu.EMO_WARNING }
L'UE {ue.acronyme} {ue.titre} L'UE <a class="stdlink" href="{
url_for("notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
}">{ue.acronyme} {ue.titre}</a>
n'est pas associée à un niveau de compétences n'est pas associée à un niveau de compétences
</span>""", </span>""",
}, },
@ -727,9 +738,10 @@ def module_edit(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
descr, descr,
html_foot_markup="""<div class="sco_tag_module_edit"><span class="sco_tag_edit"><textarea data-module_id="{}" class="module_tag_editor">{}</textarea></span></div>""".format( html_foot_markup=f"""<div class="sco_tag_module_edit"><span
module_id, ",".join(sco_tag_module.module_tag_list(module_id)) class="sco_tag_edit"><textarea data-module_id="{module_id}" class="module_tag_editor"
) >{','.join(sco_tag_module.module_tag_list(module_id))}</textarea></span></div>
"""
if not create if not create
else "", else "",
initvalues=module_dict if module else {}, initvalues=module_dict if module else {},
@ -793,11 +805,14 @@ def module_edit(
# #
do_module_edit(tf[2]) do_module_edit(tf[2])
# Modifie les parcours # Modifie les parcours
if "parcours" in tf[2]: if ("parcours" in tf[2]) and formation.referentiel_competence:
module.parcours = [ if "-1" in tf[2]["parcours"]: # "tous"
ApcParcours.query.get(int(parcour_id_str)) module.parcours = formation.referentiel_competence.parcours.all()
for parcour_id_str in tf[2]["parcours"] else:
] module.parcours = [
ApcParcours.query.get(int(parcour_id_str))
for parcour_id_str in tf[2]["parcours"]
]
# Modifie les AC # Modifie les AC
if "app_critiques" in tf[2]: if "app_critiques" in tf[2]:
module.app_critiques = [ module.app_critiques = [
@ -811,7 +826,7 @@ def module_edit(
"notes.ue_table", "notes.ue_table",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formation_id=formation.id, formation_id=formation.id,
semestre_idx=tf[2]["semestre_id"], semestre_idx=tf[2]["semestre_id"] if is_apc else 1,
) )
) )

View File

@ -39,23 +39,21 @@ from app.models import Module, ModuleImpl, Evaluation, EvaluationUEPoids, UniteE
from app.models import ScolarNews from app.models import ScolarNews
from app.models.formations import Formation from app.models.formations import Formation
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app.models.but_refcomp import ApcParcours
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app import log from app import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_compute_moy from app.scodoc import sco_compute_moy
from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_evaluations
from app.scodoc import sco_evaluation_db from app.scodoc import sco_evaluation_db
from app.scodoc import sco_formations from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -119,12 +117,12 @@ def formsemestre_editwithmodules(formsemestre_id):
vals = scu.get_request_args() vals = scu.get_request_args()
if not vals.get("tf_submitted", False): if not vals.get("tf_submitted", False):
H.append( H.append(
"""<p class="help">Seuls les modules cochés font partie de ce semestre. """<p class="help">Seuls les modules cochés font partie de ce semestre.
Pour les retirer, les décocher et appuyer sur le bouton "modifier". Pour les retirer, les décocher et appuyer sur le bouton "modifier".
</p> </p>
<p class="help">Attention : s'il y a déjà des évaluations dans un module, <p class="help">Attention : s'il y a déjà des évaluations dans un module,
il ne peut pas être supprimé !</p> il ne peut pas être supprimé !</p>
<p class="help">Les modules ont toujours un responsable. <p class="help">Les modules ont toujours un responsable.
Par défaut, c'est le directeur des études.</p> Par défaut, c'est le directeur des études.</p>
<p class="help">Un semestre ne peut comporter qu'une seule UE "bonus <p class="help">Un semestre ne peut comporter qu'une seule UE "bonus
sport/culture"</p> sport/culture"</p>
@ -153,7 +151,7 @@ def do_formsemestre_createwithmodules(edit=False):
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if not current_user.has_permission(Permission.ScoImplement): if not current_user.has_permission(Permission.ScoImplement):
if not edit: if not edit:
# il faut ScoImplement pour creer un semestre # il faut ScoImplement pour créer un semestre
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
else: else:
if not sem["resp_can_edit"] or current_user.id not in sem["responsables"]: if not sem["resp_can_edit"] or current_user.id not in sem["responsables"]:
@ -175,6 +173,7 @@ def do_formsemestre_createwithmodules(edit=False):
formation = Formation.query.get(formation_id) formation = Formation.query.get(formation_id)
if formation is None: if formation is None:
raise ScoValueError("Formation inexistante !") raise ScoValueError("Formation inexistante !")
is_apc = formation.is_apc()
if not edit: if not edit:
initvalues = {"titre": _default_sem_title(formation)} initvalues = {"titre": _default_sem_title(formation)}
semestre_id = int(vals["semestre_id"]) semestre_id = int(vals["semestre_id"])
@ -210,12 +209,12 @@ def do_formsemestre_createwithmodules(edit=False):
if NB_SEM == 1: if NB_SEM == 1:
semestre_id_list = [-1] semestre_id_list = [-1]
else: else:
if edit and formation.is_apc(): if edit and is_apc:
# en APC, ne permet pas de changer de semestre # en APC, ne permet pas de changer de semestre
semestre_id_list = [formsemestre.semestre_id] semestre_id_list = [formsemestre.semestre_id]
else: else:
semestre_id_list = list(range(1, NB_SEM + 1)) semestre_id_list = list(range(1, NB_SEM + 1))
if not formation.is_apc(): if not is_apc:
# propose "pas de semestre" seulement en classique # propose "pas de semestre" seulement en classique
semestre_id_list.insert(0, -1) semestre_id_list.insert(0, -1)
@ -226,7 +225,7 @@ def do_formsemestre_createwithmodules(edit=False):
else: else:
semestre_id_labels.append(f"S{sid}") semestre_id_labels.append(f"S{sid}")
# Liste des modules dans cette formation # Liste des modules dans cette formation
if formation.is_apc(): if is_apc:
modules = formation.modules.order_by(Module.module_type, Module.numero) modules = formation.modules.order_by(Module.module_type, Module.numero)
else: else:
modules = ( modules = (
@ -318,10 +317,10 @@ def do_formsemestre_createwithmodules(edit=False):
{ {
"size": 40, "size": 40,
"title": "Nom de ce semestre", "title": "Nom de ce semestre",
"explanation": """n'indiquez pas les dates, ni le semestre, ni la modalité dans "explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans
le titre: ils seront automatiquement ajoutés <input type="button" le titre: ils seront automatiquement ajoutés <input type="button"
value="remettre titre par défaut" onClick="document.tf.titre.value='%s';"/>""" value="remettre titre par défaut" onClick="document.tf.titre.value='{
% _default_sem_title(formation), _default_sem_title(formation)}';"/>""",
}, },
), ),
( (
@ -343,11 +342,9 @@ def do_formsemestre_createwithmodules(edit=False):
"allowed_values": semestre_id_list, "allowed_values": semestre_id_list,
"labels": semestre_id_labels, "labels": semestre_id_labels,
"explanation": "en BUT, on ne peut pas modifier le semestre après création" "explanation": "en BUT, on ne peut pas modifier le semestre après création"
if formation.is_apc() if is_apc
else "",
"attributes": ['onchange="change_semestre_id();"']
if formation.is_apc()
else "", else "",
"attributes": ['onchange="change_semestre_id();"'] if is_apc else "",
}, },
), ),
) )
@ -386,7 +383,7 @@ def do_formsemestre_createwithmodules(edit=False):
mf = mf_manual mf = mf_manual
for n in range(1, scu.EDIT_NB_ETAPES + 1): for n in range(1, scu.EDIT_NB_ETAPES + 1):
mf["title"] = "Etape Apogée (%d)" % n mf["title"] = f"Etape Apogée ({n})"
modform.append(("etape_apo" + str(n), mf.copy())) modform.append(("etape_apo" + str(n), mf.copy()))
modform.append( modform.append(
( (
@ -443,15 +440,19 @@ def do_formsemestre_createwithmodules(edit=False):
) )
) )
if edit: if edit:
formtit = ( formtit = f"""
""" <p><a href="formsemestre_edit_uecoefs?formsemestre_id={formsemestre_id}"
<p><a href="formsemestre_edit_uecoefs?formsemestre_id=%s">Modifier les coefficients des UE capitalisées</a></p> >Modifier les coefficients des UE capitalisées</a></p>
<h3>Sélectionner les modules, leurs responsables et les étudiants à inscrire:</h3> <h3>Sélectionner les modules, leurs responsables et les étudiants
à inscrire:</h3>
""" """
% formsemestre_id
)
else: else:
formtit = """<h3>Sélectionner les modules et leurs responsables</h3><p class="help">Si vous avez des parcours (options), ne sélectionnez que les modules du tronc commun.</p>""" formtit = """<h3>Sélectionner les modules et leurs responsables</h3>
<p class="help">Si vous avez des parcours (options), dans un premier
ne sélectionnez que les modules du tronc commun, puis après inscriptions,
revenez ajouter les modules de parcours en sélectionnant les groupes d'étudiants
à y inscrire.
</p>"""
modform += [ modform += [
( (
@ -531,12 +532,52 @@ def do_formsemestre_createwithmodules(edit=False):
"explanation": "empêcher le calcul des moyennes d'UE et générale.", "explanation": "empêcher le calcul des moyennes d'UE et générale.",
}, },
), ),
]
# Choix des parcours
if is_apc:
ref_comp = formation.referentiel_competence
if ref_comp:
modform += [
(
"parcours",
{
"input_type": "checkbox",
"vertical": True,
"dom_id": "tf_module_parcours",
"labels": [parcour.libelle for parcour in ref_comp.parcours],
"allowed_values": [
str(parcour.id) for parcour in ref_comp.parcours
],
"explanation": "Parcours proposés dans ce semestre.",
},
)
]
if edit:
sem["parcours"] = [str(parcour.id) for parcour in formsemestre.parcours]
else:
modform += [
(
"parcours",
{
"input_type": "separator",
"title": f"""<span class="fontred">{scu.EMO_WARNING }
Pas de parcours:
<a class="stdlink" href="{ url_for('notes.ue_table',
scodoc_dept=g.scodoc_dept, formation_id=formation.id)
}">vérifier la formation</a>
</span>""",
},
)
]
# Choix des modules
modform += [
( (
"sep", "sep",
{ {
"input_type": "separator", "input_type": "separator",
"title": "", "title": "",
"template": "</table>%s<table>" % formtit, "template": f"</table>{formtit}<table>",
}, },
), ),
] ]
@ -544,8 +585,8 @@ def do_formsemestre_createwithmodules(edit=False):
nbmod = 0 nbmod = 0
for semestre_id in semestre_ids: for semestre_id in semestre_ids:
if formation.is_apc(): if is_apc:
# pour restreindre l'édition aux module du semestre sélectionné # pour restreindre l'édition aux modules du semestre sélectionné
tr_class = f'class="sem{semestre_id}"' tr_class = f'class="sem{semestre_id}"'
else: else:
tr_class = "" tr_class = ""
@ -560,7 +601,7 @@ def do_formsemestre_createwithmodules(edit=False):
"sep", "sep",
{ {
"input_type": "separator", "input_type": "separator",
"title": "<b>Semestre %s</b>" % semestre_id, "title": f"<b>Semestre {semestre_id}</b>",
"template": templ_sep, "template": templ_sep,
}, },
) )
@ -568,13 +609,13 @@ def do_formsemestre_createwithmodules(edit=False):
for mod in mods: for mod in mods:
if mod["semestre_id"] == semestre_id and ( if mod["semestre_id"] == semestre_id and (
(not edit) # creation => tous modules (not edit) # creation => tous modules
or (not formation.is_apc()) # pas BUT, on peut mixer les semestres or (not is_apc) # pas BUT, on peut mixer les semestres
or (semestre_id == formsemestre.semestre_id) # module du semestre or (semestre_id == formsemestre.semestre_id) # module du semestre
or (mod["module_id"] in module_ids_set) # module déjà présent or (mod["module_id"] in module_ids_set) # module déjà présent
): ):
nbmod += 1 nbmod += 1
if edit: if edit:
select_name = "%s!group_id" % mod["module_id"] select_name = f"{mod['module_id']}!group_id"
def opt_selected(gid): def opt_selected(gid):
if gid == vals.get(select_name): if gid == vals.get(select_name):
@ -603,13 +644,16 @@ def do_formsemestre_createwithmodules(edit=False):
group["group_name"], group["group_name"],
) )
fcg += "</select>" fcg += "</select>"
itemtemplate = ( itemtemplate = f"""<tr {tr_class}>
f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td><td>""" <td class="tf-fieldlabel">%(label)s</td>
+ fcg <td class="tf-field">%(elem)s</td>
+ "</td></tr>" <td>{fcg}</td>
) </tr>"""
else: else:
itemtemplate = f"""<tr {tr_class}><td class="tf-fieldlabel">%(label)s</td><td class="tf-field">%(elem)s</td></tr>""" itemtemplate = f"""<tr {tr_class}>
<td class="tf-fieldlabel">%(label)s</td>
<td class="tf-field">%(elem)s</td>
</tr>"""
modform.append( modform.append(
( (
"MI" + str(mod["module_id"]), "MI" + str(mod["module_id"]),
@ -742,7 +786,8 @@ def do_formsemestre_createwithmodules(edit=False):
for module_id in tf[2]["tf-checked"]: for module_id in tf[2]["tf-checked"]:
mod_resp_id = User.get_user_id_from_nomplogin(tf[2][module_id]) mod_resp_id = User.get_user_id_from_nomplogin(tf[2][module_id])
if mod_resp_id is None: if mod_resp_id is None:
# Si un module n'a pas de responsable (ou inconnu), l'affecte au 1er directeur des etudes: # Si un module n'a pas de responsable (ou inconnu),
# l'affecte au 1er directeur des etudes:
mod_resp_id = tf[2]["responsable_id"] mod_resp_id = tf[2]["responsable_id"]
tf[2][module_id] = mod_resp_id tf[2][module_id] = mod_resp_id
@ -763,7 +808,7 @@ def do_formsemestre_createwithmodules(edit=False):
module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]] module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
_formsemestre_check_ue_bonus_unicity(module_ids_checked) _formsemestre_check_ue_bonus_unicity(module_ids_checked)
if not edit: if not edit:
if formation.is_apc(): if is_apc:
_formsemestre_check_module_list( _formsemestre_check_module_list(
module_ids_checked, tf[2]["semestre_id"] module_ids_checked, tf[2]["semestre_id"]
) )
@ -777,14 +822,6 @@ def do_formsemestre_createwithmodules(edit=False):
"responsable_id": tf[2][f"MI{module_id}"], "responsable_id": tf[2][f"MI{module_id}"],
} }
_ = sco_moduleimpl.do_moduleimpl_create(modargs) _ = sco_moduleimpl.do_moduleimpl_create(modargs)
flash("Nouveau semestre créé")
return flask.redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
else: else:
# Modification du semestre: # Modification du semestre:
# on doit creer les modules nouvellement selectionnés # on doit creer les modules nouvellement selectionnés
@ -794,7 +831,7 @@ def do_formsemestre_createwithmodules(edit=False):
module_ids_tocreate = [ module_ids_tocreate = [
x for x in module_ids_checked if not x in module_ids_existing x for x in module_ids_checked if not x in module_ids_existing
] ]
if formation.is_apc(): if is_apc:
_formsemestre_check_module_list( _formsemestre_check_module_list(
module_ids_tocreate, tf[2]["semestre_id"] module_ids_tocreate, tf[2]["semestre_id"]
) )
@ -868,27 +905,46 @@ def do_formsemestre_createwithmodules(edit=False):
modargs, formsemestre_id=formsemestre_id modargs, formsemestre_id=formsemestre_id
) )
mod = sco_edit_module.module_list({"module_id": module_id})[0] mod = sco_edit_module.module_list({"module_id": module_id})[0]
# --- Assocation des parcours
if msg: formsemestre = FormSemestre.query.get(formsemestre_id)
msg_html = ( if "parcours" in tf[2]:
'<div class="ue_warning"><span>Attention !<ul><li>' formsemestre.parcours = [
+ "</li><li>".join(msg) ApcParcours.query.get(int(parcour_id_str))
+ "</li></ul></span></div>" for parcour_id_str in tf[2]["parcours"]
) ]
if ok: db.session.add(formsemestre)
msg_html += "<p>Modification effectuée</p>" db.session.commit()
else: # --- Fin
msg_html += "<p>Modification effectuée (<b>mais modules cités non supprimés</b>)</p>" if edit:
msg_html += ( if msg:
'<a href="formsemestre_status?formsemestre_id=%s">retour au tableau de bord</a>' msg_html = (
% formsemestre_id '<div class="ue_warning"><span>Attention !<ul><li>'
) + "</li><li>".join(msg)
return msg_html + "</li></ul></span></div>"
)
if ok:
msg_html += "<p>Modification effectuée</p>"
else: else:
return flask.redirect( msg_html += "<p>Modification effectuée (<b>mais modules cités non supprimés</b>)</p>"
"formsemestre_status?formsemestre_id=%s&head_message=Semestre modifié" msg_html += (
% formsemestre_id '<a href="formsemestre_status?formsemestre_id=%s">retour au tableau de bord</a>'
) % formsemestre_id
)
return msg_html
else:
return flask.redirect(
"formsemestre_status?formsemestre_id=%s&head_message=Semestre modifié"
% formsemestre_id
)
else:
flash("Nouveau semestre créé")
return flask.redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
def _formsemestre_check_module_list(module_ids, semestre_idx): def _formsemestre_check_module_list(module_ids, semestre_idx):

View File

@ -929,10 +929,18 @@ def formsemestre_status_head(formsemestre_id=None, page_title=None):
}</tt></b>)""" }</tt></b>)"""
) )
H.append("</td></tr>") H.append("</td></tr>")
if sem.parcours:
H.append(
f"""
<tr><td class="fichetitre2">Parcours: </td>
<td style="color: blue;">{', '.join(parcours.code for parcours in sem.parcours)}</td>
</tr>
"""
)
evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id) evals = sco_evaluations.do_evaluation_etat_in_sem(formsemestre_id)
H.append( H.append(
'<tr><td class="fichetitre2">Evaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides' '<tr><td class="fichetitre2">Évaluations: </td><td> %(nb_evals_completes)s ok, %(nb_evals_en_cours)s en cours, %(nb_evals_vides)s vides'
% evals % evals
) )
if evals["last_modif"]: if evals["last_modif"]:

View File

@ -2206,7 +2206,7 @@ ul.notes_module_list {
list-style-type: none; list-style-type: none;
} }
div#ue_choix_niveau { div.ue_choix_niveau {
background-color: rgb(191, 242, 255); background-color: rgb(191, 242, 255);
border: 1px solid blue; border: 1px solid blue;
border-radius: 10px; border-radius: 10px;

View File

@ -0,0 +1,8 @@
/* Page édition module */
$(document).ready(function () {
});

View File

@ -6,11 +6,25 @@
<h1>Associer un référentiel de compétences</h1> <h1>Associer un référentiel de compétences</h1>
<div class="help"> <div class="help">
Association d'un référentiel de compétence à la formation Association d'un référentiel de compétence à la formation
{{formation.titre}} ({{formation.acronyme}}) <a href="{{
url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id)
}}">{{formation.titre}} ({{formation.acronyme}})</a>
</div> </div>
<div class="row"> <div style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-4">
{{ wtf.quick_form(form) }} Référentiel actuellement associé:
{% if formation.referentiel_competence is not none %}
<b>{{ formation.referentiel_competence.specialite_long }}</b>
<a href="{{
url_for('notes.refcomp_desassoc_formation', scodoc_dept=g.scodoc_dept, formation_id=formation.id)
}}" class="stdlink">supprimer</a>
{% else %}
<b>aucun</b>
{% endif %}
<div class="row" style="margin-top: 20px;">
<div class="col-md-4">
{{ wtf.quick_form(form) }}
</div>
</div> </div>
</div> </div>

View File

@ -160,6 +160,20 @@ def refcomp_assoc_formation(formation_id: int):
) )
@bp.route("/referentiel/comp/desassoc_formation/<int:formation_id>", methods=["GET"])
@scodoc
@permission_required(Permission.ScoChangeFormation)
def refcomp_desassoc_formation(formation_id: int):
"""Désassocie la formation de son ref. de compétence"""
formation = Formation.query.get_or_404(formation_id)
formation.referentiel_competence = None
db.session.add(formation)
db.session.commit()
return redirect(
url_for("notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation.id)
)
@bp.route( @bp.route(
"/referentiel/comp/load", defaults={"formation_id": None}, methods=["GET", "POST"] "/referentiel/comp/load", defaults={"formation_id": None}, methods=["GET", "POST"]
) )

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.2.22" SCOVERSION = "9.3a"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"