forked from ScoDoc/ScoDoc
WIP: PN BUT
This commit is contained in:
parent
14ab816bee
commit
58a7508043
14
README.md
14
README.md
@ -89,6 +89,17 @@ exemple pour travailler en mode "développement" avec `FLASK_ENV=development`.
|
||||
|
||||
### Tests unitaires
|
||||
|
||||
Les tests unitaires utilisent normalement la base postgresql `SCODOC_TEST`.
|
||||
Avant le premier lancement, créer cette base ainsi:
|
||||
|
||||
./tools/create_database.sh SCODOC_TEST
|
||||
export FLASK_ENV=test
|
||||
flask db upgrade
|
||||
|
||||
Cette commande n'est nécessaire que la première fois (le contenu de la base
|
||||
est effacé au début de chaque test, mais son schéma reste) et aussi si des
|
||||
migrations (changements de schéma) ont eu lieu dans le code.
|
||||
|
||||
Certains tests ont besoin d'un département déjà créé, qui n'est pas créé par les
|
||||
scripts de tests:
|
||||
Lancer au préalable:
|
||||
@ -109,7 +120,8 @@ On peut aussi utiliser les tests unitaires pour mettre la base
|
||||
de données de développement dans un état connu, par exemple pour éviter de
|
||||
recréer à la main étudiants et semestres quand on développe.
|
||||
|
||||
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
|
||||
Il suffit de positionner une variable d'environnement indiquant la BD
|
||||
utilisée par les tests:
|
||||
|
||||
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
||||
|
||||
|
75
app/comp/moy_mod.py
Normal file
75
app/comp/moy_mod.py
Normal file
@ -0,0 +1,75 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Fonctions de calcul des moyennes de modules (modules, ressources ou SAÉ)
|
||||
|
||||
Rappel: pour éviter les confusions, on appelera *poids* les coefficients d'une
|
||||
évaluation dans un module, et *coefficients* ceux utilisés pour le calcul de la
|
||||
moyenne générale d'une UE.
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from app import db
|
||||
from app import models
|
||||
|
||||
|
||||
def df_load_evaluations_poids(moduleimpl_id: int, default_poids=1.0) -> pd.DataFrame:
|
||||
"""Charge poids des évaluations d'un module et retourne un dataframe
|
||||
rows = evaluations, columns = UE, value = poids (float).
|
||||
Les valeurs manquantes (évaluations sans coef vers des UE) sont
|
||||
remplies par default_poids.
|
||||
"""
|
||||
modimpl = models.ModuleImpl.query.get(moduleimpl_id)
|
||||
evaluations = models.Evaluation.query.filter_by(moduleimpl_id=moduleimpl_id).all()
|
||||
ues = modimpl.formsemestre.query_ues().all()
|
||||
ue_ids = [ue.id for ue in ues]
|
||||
evaluation_ids = [evaluation.id for evaluation in evaluations]
|
||||
df = pd.DataFrame(columns=ue_ids, index=evaluation_ids, dtype=float)
|
||||
for eval_poids in models.EvaluationUEPoids.query.join(
|
||||
models.EvaluationUEPoids.evaluation
|
||||
).filter_by(moduleimpl_id=moduleimpl_id):
|
||||
df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids
|
||||
if default_poids is not None:
|
||||
df.fillna(value=default_poids, inplace=True)
|
||||
return df
|
||||
|
||||
|
||||
def check_moduleimpl_conformity(
|
||||
moduleimpl, evals_poids: pd.DataFrame, modules_coefficients: pd.DataFrame
|
||||
) -> bool:
|
||||
"""Vérifie que les évaluations de ce moduleimpl sont bien conformes
|
||||
au PN.
|
||||
Un module est dit *conforme* si et seulement si la somme des poids de ses
|
||||
évaluations vers une UE de coefficient non nul est non nulle.
|
||||
"""
|
||||
module_evals_poids = evals_poids.transpose().sum(axis=1).to_numpy() != 0
|
||||
check = all(
|
||||
(modules_coefficients[moduleimpl.module.id].to_numpy() != 0)
|
||||
== module_evals_poids
|
||||
)
|
||||
return check
|
@ -34,21 +34,22 @@ from app import db
|
||||
from app import models
|
||||
|
||||
|
||||
def df_load_ue_coefs(formation_id):
|
||||
def df_load_ue_coefs(formation_id: int, semestre_idx: int) -> pd.DataFrame:
|
||||
"""Load coefs of all modules in formation and returns a DataFrame
|
||||
rows = modules, columns = UE, value = coef.
|
||||
rows = UEs, columns = modules, value = coef.
|
||||
On considère toutes les UE et modules du semestre.
|
||||
Unspecified coefs (not defined in db) are set to zero.
|
||||
"""
|
||||
ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
|
||||
modules = models.Module.query.filter_by(formation_id=formation_id).all()
|
||||
ues = models.UniteEns.query.filter_by(formation_id=formation_id)
|
||||
modules = models.Module.query.filter_by(formation_id=formation_id)
|
||||
ue_ids = [ue.id for ue in ues]
|
||||
module_ids = [module.id for module in modules]
|
||||
df = pd.DataFrame(columns=ue_ids, index=module_ids, dtype=float)
|
||||
df = pd.DataFrame(columns=module_ids, index=ue_ids, dtype=float)
|
||||
for mod_coef in (
|
||||
db.session.query(models.ModuleUECoef)
|
||||
.filter(models.UniteEns.formation_id == formation_id)
|
||||
.filter(models.ModuleUECoef.ue_id == models.UniteEns.id)
|
||||
):
|
||||
df[mod_coef.ue_id][mod_coef.module_id] = mod_coef.coef
|
||||
df[mod_coef.module_id][mod_coef.ue_id] = mod_coef.coef
|
||||
df.fillna(value=0, inplace=True)
|
||||
return df
|
||||
|
@ -37,7 +37,6 @@ from app.models.formations import (
|
||||
Module,
|
||||
ModuleUECoef,
|
||||
NotesTag,
|
||||
notes_modules_tags,
|
||||
)
|
||||
from app.models.formsemestre import (
|
||||
FormSemestre,
|
||||
|
@ -36,6 +36,7 @@ class Formation(db.Model):
|
||||
ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
|
||||
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
|
||||
ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")
|
||||
modules = db.relationship("Module", lazy="dynamic", backref="formation")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}(id={self.id}, dept_id={self.dept_id}, acronyme='{self.acronyme}')>"
|
||||
@ -133,6 +134,12 @@ class Module(db.Model):
|
||||
# Relations:
|
||||
modimpls = db.relationship("ModuleImpl", backref="module", lazy="dynamic")
|
||||
ues_apc = db.relationship("UniteEns", secondary="module_ue_coef", viewonly=True)
|
||||
tags = db.relationship(
|
||||
"NotesTag",
|
||||
secondary="notes_modules_tags",
|
||||
lazy=True,
|
||||
backref=db.backref("modules", lazy=True),
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.ue_coefs = []
|
||||
|
@ -4,6 +4,8 @@
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
import flask_sqlalchemy
|
||||
|
||||
from app import db
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
@ -12,6 +14,8 @@ from app.models import UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.models.formations import UniteEns, Module
|
||||
from app.models.moduleimpls import ModuleImpl
|
||||
|
||||
|
||||
class FormSemestre(db.Model):
|
||||
@ -87,8 +91,24 @@ class FormSemestre(db.Model):
|
||||
if self.modalite is None:
|
||||
self.modalite = FormationModalite.DEFAULT_MODALITE
|
||||
|
||||
def get_ues(self):
|
||||
"UE des modules de ce semestre"
|
||||
def query_ues(self) -> flask_sqlalchemy.BaseQuery:
|
||||
"""UE des modules de ce semestre.
|
||||
- Formations classiques: les UEs auxquelles appartiennent
|
||||
les modules mis en place dans ce semestre.
|
||||
- Formations APC / BUT: les UEs de la formation qui ont
|
||||
le même numéro de semestre que ce formsemestre.
|
||||
"""
|
||||
if self.formation.get_parcours().APC_SAE:
|
||||
sem_ues = UniteEns.query.filter_by(
|
||||
formation=self.formation, semestre_idx=self.semestre_id
|
||||
)
|
||||
else:
|
||||
sem_ues = db.session.query(UniteEns).filter(
|
||||
ModuleImpl.formsemestre_id == self.id,
|
||||
Module.id == ModuleImpl.module_id,
|
||||
UniteEns.id == Module.ue_id,
|
||||
)
|
||||
return sem_ues
|
||||
|
||||
|
||||
# Association id des utilisateurs responsables (aka directeurs des etudes) du semestre
|
||||
|
122
app/scodoc/sco_edit_apc.py
Normal file
122
app/scodoc/sco_edit_apc.py
Normal file
@ -0,0 +1,122 @@
|
||||
##############################################################################
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""Édition formation APC (BUT)
|
||||
"""
|
||||
import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
from app.models.formations import Formation, UniteEns, Matiere, Module
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
|
||||
|
||||
def html_edit_formation_apc(
|
||||
formation,
|
||||
editable=True,
|
||||
tag_editable=True,
|
||||
):
|
||||
"""Formulaire html pour visualisation ou édition d'une formation APC.
|
||||
- Les UEs
|
||||
- Les ressources
|
||||
- Les SAÉs
|
||||
"""
|
||||
parcours = formation.get_parcours()
|
||||
assert parcours.APC_SAE
|
||||
ressources = formation.modules.filter_by(module_type=ModuleType.RESSOURCE)
|
||||
saes = formation.modules.filter_by(module_type=ModuleType.SAE)
|
||||
other_modules = formation.modules.filter(
|
||||
Module.module_type != ModuleType.SAE
|
||||
and Module.module_type != ModuleType.RESSOURCE
|
||||
)
|
||||
arrow_up, arrow_down, arrow_none = sco_groups.get_arrow_icons_tags()
|
||||
delete_icon = scu.icontag(
|
||||
"delete_small_img", title="Supprimer (module inutilisé)", alt="supprimer"
|
||||
)
|
||||
delete_disabled_icon = scu.icontag(
|
||||
"delete_small_dis_img", title="Suppression impossible (module utilisé)"
|
||||
)
|
||||
H = [
|
||||
render_template(
|
||||
"pn/form_ues.html",
|
||||
formation=formation,
|
||||
editable=editable,
|
||||
arrow_up=arrow_up,
|
||||
arrow_down=arrow_down,
|
||||
arrow_none=arrow_none,
|
||||
delete_icon=delete_icon,
|
||||
delete_disabled_icon=delete_disabled_icon,
|
||||
),
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
formation=formation,
|
||||
titre="Ressources",
|
||||
create_element_msg="créer une nouvelle ressource",
|
||||
modules=ressources,
|
||||
module_type=ModuleType.RESSOURCE,
|
||||
editable=editable,
|
||||
arrow_up=arrow_up,
|
||||
arrow_down=arrow_down,
|
||||
arrow_none=arrow_none,
|
||||
delete_icon=delete_icon,
|
||||
delete_disabled_icon=delete_disabled_icon,
|
||||
scu=scu,
|
||||
),
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
formation=formation,
|
||||
titre="Situations d'Apprentissage et d'Évaluation (SAÉs)",
|
||||
create_element_msg="créer une nouvelle SAÉ",
|
||||
modules=saes,
|
||||
module_type=ModuleType.SAE,
|
||||
editable=editable,
|
||||
arrow_up=arrow_up,
|
||||
arrow_down=arrow_down,
|
||||
arrow_none=arrow_none,
|
||||
delete_icon=delete_icon,
|
||||
delete_disabled_icon=delete_disabled_icon,
|
||||
scu=scu,
|
||||
),
|
||||
render_template(
|
||||
"pn/form_mods.html",
|
||||
formation=formation,
|
||||
titre="Autres modules (non BUT)",
|
||||
create_element_msg="créer un nouveau module",
|
||||
modules=other_modules,
|
||||
module_type=ModuleType.STANDARD,
|
||||
editable=editable,
|
||||
arrow_up=arrow_up,
|
||||
arrow_down=arrow_down,
|
||||
arrow_none=arrow_none,
|
||||
delete_icon=delete_icon,
|
||||
delete_disabled_icon=delete_disabled_icon,
|
||||
scu=scu,
|
||||
),
|
||||
]
|
||||
|
||||
return "\n".join(H)
|
@ -32,6 +32,7 @@ import flask
|
||||
from flask import url_for, render_template
|
||||
from flask import g, request
|
||||
from flask_login import current_user
|
||||
from app.models.formations import Matiere, UniteEns
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -106,80 +107,99 @@ def do_module_create(args) -> int:
|
||||
return r
|
||||
|
||||
|
||||
def module_create(matiere_id=None):
|
||||
def module_create(matiere_id=None, module_type=None, semestre_id=None):
|
||||
"""Création d'un module"""
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_edit_ue
|
||||
|
||||
if matiere_id is None:
|
||||
matiere = Matiere.query.get(matiere_id)
|
||||
if matiere is None:
|
||||
raise ScoValueError("invalid matiere !")
|
||||
matiere = sco_edit_matiere.matiere_list(args={"matiere_id": matiere_id})[0]
|
||||
UE = sco_edit_ue.ue_list(args={"ue_id": matiere["ue_id"]})[0]
|
||||
formation = sco_formations.formation_list(
|
||||
args={"formation_id": UE["formation_id"]}
|
||||
)[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
|
||||
ue = matiere.ue
|
||||
parcours = ue.formation.get_parcours()
|
||||
is_apc = parcours.APC_SAE
|
||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||
if is_apc and module_type is not None:
|
||||
object_name = scu.MODULE_TYPE_NAMES[module_type]
|
||||
else:
|
||||
object_name = "Module"
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Création d'un module"),
|
||||
"""<h2>Création d'un module dans la matière %(titre)s""" % matiere,
|
||||
""" (UE %(acronyme)s)</h2>""" % UE,
|
||||
render_template("scodoc/help/modules.html", is_apc=is_apc),
|
||||
html_sco_header.sco_header(page_title=f"Création {object_name}"),
|
||||
f"""<h2>Création {object_name} dans la matière {matiere.titre},
|
||||
(UE {ue.acronyme})</h2>
|
||||
""",
|
||||
render_template(
|
||||
"scodoc/help/modules.html",
|
||||
is_apc=is_apc,
|
||||
ue=ue,
|
||||
semestre_id=semestre_id,
|
||||
),
|
||||
]
|
||||
# cherche le numero adéquat (pour placer le module en fin de liste)
|
||||
modules = module_list(args={"matiere_id": matiere_id})
|
||||
modules = Matiere.query.get(1).modules.all()
|
||||
if modules:
|
||||
default_num = max([m["numero"] for m in modules]) + 10
|
||||
default_num = max([m.numero for m in modules]) + 10
|
||||
else:
|
||||
default_num = 10
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
descr = [
|
||||
(
|
||||
"code",
|
||||
{
|
||||
"size": 10,
|
||||
"explanation": "code du module (doit être unique dans la formation)",
|
||||
"allow_null": False,
|
||||
"validator": lambda val, field, formation_id=ue.formation_id: check_module_code_unicity(
|
||||
val, field, formation_id
|
||||
),
|
||||
},
|
||||
),
|
||||
("titre", {"size": 30, "explanation": "nom du module"}),
|
||||
("abbrev", {"size": 20, "explanation": "nom abrégé (pour bulletins)"}),
|
||||
(
|
||||
"module_type",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "Type",
|
||||
"explanation": "",
|
||||
"labels": [x.name.capitalize() for x in scu.ModuleType],
|
||||
"allowed_values": [str(int(x)) for x in scu.ModuleType],
|
||||
},
|
||||
),
|
||||
(
|
||||
"heures_cours",
|
||||
{"size": 4, "type": "float", "explanation": "nombre d'heures de cours"},
|
||||
),
|
||||
(
|
||||
"heures_td",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"explanation": "nombre d'heures de Travaux Dirigés",
|
||||
},
|
||||
),
|
||||
(
|
||||
"heures_tp",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"explanation": "nombre d'heures de Travaux Pratiques",
|
||||
},
|
||||
),
|
||||
]
|
||||
if is_apc:
|
||||
descr += [
|
||||
(
|
||||
"code",
|
||||
"sep_ue_coefs",
|
||||
{
|
||||
"size": 10,
|
||||
"explanation": "code du module (doit être unique dans la formation)",
|
||||
"allow_null": False,
|
||||
"validator": lambda val, field, formation_id=formation[
|
||||
"formation_id"
|
||||
]: check_module_code_unicity(val, field, formation_id),
|
||||
},
|
||||
),
|
||||
("titre", {"size": 30, "explanation": "nom du module"}),
|
||||
("abbrev", {"size": 20, "explanation": "nom abrégé (pour bulletins)"}),
|
||||
(
|
||||
"module_type",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"title": "Type",
|
||||
"explanation": "",
|
||||
"labels": [x.name.capitalize() for x in scu.ModuleType],
|
||||
"allowed_values": [str(int(x)) for x in scu.ModuleType],
|
||||
},
|
||||
),
|
||||
(
|
||||
"heures_cours",
|
||||
{"size": 4, "type": "float", "explanation": "nombre d'heures de cours"},
|
||||
),
|
||||
(
|
||||
"heures_td",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"explanation": "nombre d'heures de Travaux Dirigés",
|
||||
},
|
||||
),
|
||||
(
|
||||
"heures_tp",
|
||||
{
|
||||
"size": 4,
|
||||
"type": "float",
|
||||
"explanation": "nombre d'heures de Travaux Pratiques",
|
||||
"input_type": "separator",
|
||||
"title": """
|
||||
<div>(<em>les coefficients vers les UE se fixent sur la page dédiée</em>)
|
||||
</div>""",
|
||||
},
|
||||
),
|
||||
]
|
||||
else:
|
||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||
descr += [
|
||||
(
|
||||
"coefficient",
|
||||
{
|
||||
@ -189,10 +209,6 @@ def module_create(matiere_id=None):
|
||||
"allow_null": False,
|
||||
},
|
||||
),
|
||||
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }),
|
||||
("formation_id", {"default": UE["formation_id"], "input_type": "hidden"}),
|
||||
("ue_id", {"default": matiere["ue_id"], "input_type": "hidden"}),
|
||||
("matiere_id", {"default": matiere["matiere_id"], "input_type": "hidden"}),
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
@ -205,24 +221,35 @@ def module_create(matiere_id=None):
|
||||
"allowed_values": semestres_indices,
|
||||
},
|
||||
),
|
||||
(
|
||||
"code_apogee",
|
||||
{
|
||||
"title": "Code Apogée",
|
||||
"size": 25,
|
||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||
},
|
||||
),
|
||||
(
|
||||
"numero",
|
||||
{
|
||||
"size": 2,
|
||||
"explanation": "numéro (1,2,3,4...) pour ordre d'affichage",
|
||||
"type": "int",
|
||||
"default": default_num,
|
||||
},
|
||||
),
|
||||
]
|
||||
descr += [
|
||||
# ('ects', { 'size' : 4, 'type' : 'float', 'title' : 'ECTS', 'explanation' : 'nombre de crédits ECTS (inutilisés: les crédits sont associés aux UE)' }),
|
||||
("formation_id", {"default": ue.formation_id, "input_type": "hidden"}),
|
||||
("ue_id", {"default": ue.id, "input_type": "hidden"}),
|
||||
("matiere_id", {"default": matiere.id, "input_type": "hidden"}),
|
||||
(
|
||||
"code_apogee",
|
||||
{
|
||||
"title": "Code Apogée",
|
||||
"size": 25,
|
||||
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules",
|
||||
},
|
||||
),
|
||||
(
|
||||
"numero",
|
||||
{
|
||||
"size": 2,
|
||||
"explanation": "numéro (1,2,3,4...) pour ordre d'affichage",
|
||||
"type": "int",
|
||||
"default": default_num,
|
||||
},
|
||||
),
|
||||
]
|
||||
args = scu.get_request_args()
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
args,
|
||||
descr,
|
||||
submitlabel="Créer ce module",
|
||||
)
|
||||
if tf[0] == 0:
|
||||
@ -233,7 +260,7 @@ def module_create(matiere_id=None):
|
||||
url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=UE["formation_id"],
|
||||
formation_id=ue.formation_id,
|
||||
)
|
||||
)
|
||||
|
||||
@ -344,6 +371,7 @@ def module_edit(module_id=None):
|
||||
if not modules:
|
||||
raise ScoValueError("invalid module !")
|
||||
module = modules[0]
|
||||
a_module = models.Module.query.get(module_id)
|
||||
unlocked = not module_is_locked(module_id)
|
||||
formation_id = module["formation_id"]
|
||||
formation = sco_formations.formation_list(args={"formation_id": formation_id})[0]
|
||||
@ -434,7 +462,6 @@ def module_edit(module_id=None):
|
||||
),
|
||||
]
|
||||
if is_apc:
|
||||
a_module = models.Module.query.get(module_id)
|
||||
coefs_descr = a_module.ue_coefs_descr()
|
||||
if coefs_descr:
|
||||
coefs_descr_txt = ", ".join(["%s: %s" % x for x in coefs_descr])
|
||||
@ -479,19 +506,36 @@ def module_edit(module_id=None):
|
||||
"enabled": unlocked,
|
||||
},
|
||||
),
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"type": "int",
|
||||
"title": parcours.SESSION_NAME.capitalize(),
|
||||
"explanation": "%s de début du module dans la formation standard"
|
||||
% parcours.SESSION_NAME,
|
||||
"labels": [str(x) for x in semestres_indices],
|
||||
"allowed_values": semestres_indices,
|
||||
"enabled": unlocked,
|
||||
},
|
||||
),
|
||||
]
|
||||
if is_apc:
|
||||
# le semestre du module est toujours celui de son UE
|
||||
descr += [
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
"input_type": "hidden",
|
||||
"type": "int",
|
||||
"readonly": True,
|
||||
},
|
||||
)
|
||||
]
|
||||
else:
|
||||
descr += [
|
||||
(
|
||||
"semestre_id",
|
||||
{
|
||||
"input_type": "menu",
|
||||
"type": "int",
|
||||
"title": parcours.SESSION_NAME.capitalize(),
|
||||
"explanation": "%s de début du module dans la formation standard"
|
||||
% parcours.SESSION_NAME,
|
||||
"labels": [str(x) for x in semestres_indices],
|
||||
"allowed_values": semestres_indices,
|
||||
"enabled": unlocked,
|
||||
},
|
||||
)
|
||||
]
|
||||
descr += [
|
||||
(
|
||||
"code_apogee",
|
||||
{
|
||||
@ -509,7 +553,8 @@ def module_edit(module_id=None):
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
# force module semestre_idx to its UE
|
||||
module["semestre_id"] = a_module.ue.semestre_idx
|
||||
tf = TrivialFormulator(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
@ -538,7 +583,7 @@ def module_edit(module_id=None):
|
||||
def edit_module_set_code_apogee(id=None, value=None):
|
||||
"Set UE code apogee"
|
||||
module_id = id
|
||||
value = value.strip("-_ \t")
|
||||
value = str(value).strip("-_ \t")
|
||||
log("edit_module_set_code_apogee: module_id=%s code_apogee=%s" % (module_id, value))
|
||||
|
||||
modules = module_list(args={"module_id": module_id})
|
||||
|
@ -45,6 +45,7 @@ from app.scodoc.sco_exceptions import ScoValueError, ScoLockedFormError
|
||||
from app.scodoc import html_sco_header
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_edit_apc
|
||||
from app.scodoc import sco_edit_formation
|
||||
from app.scodoc import sco_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
@ -382,27 +383,32 @@ def ue_edit(ue_id=None, create=False, formation_id=None):
|
||||
)
|
||||
|
||||
|
||||
def _add_ue_semestre_id(ues):
|
||||
def _add_ue_semestre_id(ues: list[dict], is_apc):
|
||||
"""ajoute semestre_id dans les ue, en regardant
|
||||
semestre_idx ou à défaut le premier module de chacune.
|
||||
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
|
||||
semestre_idx ou à défaut, pour les formations non APC, le premier module
|
||||
de chacune.
|
||||
Les UE sans modules se voient attribuer le numero UE_SEM_DEFAULT (1000000),
|
||||
qui les place à la fin de la liste.
|
||||
"""
|
||||
for ue in ues:
|
||||
if ue["semestre_idx"] is not None:
|
||||
ue["semestre_id"] = ue["semestre_idx"]
|
||||
elif is_apc:
|
||||
ue["semestre_id"] = sco_codes_parcours.UE_SEM_DEFAULT
|
||||
else:
|
||||
# était le comportement ScoDoc7
|
||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
if modules:
|
||||
ue["semestre_id"] = modules[0]["semestre_id"]
|
||||
else:
|
||||
ue["semestre_id"] = 1000000
|
||||
ue["semestre_id"] = sco_codes_parcours.UE_SEM_DEFAULT
|
||||
|
||||
|
||||
def next_ue_numero(formation_id, semestre_id=None):
|
||||
"""Numero d'une nouvelle UE dans cette formation.
|
||||
Si le semestre est specifie, cherche les UE ayant des modules de ce semestre
|
||||
"""
|
||||
formation = Formation.query.get(formation_id)
|
||||
ues = ue_list(args={"formation_id": formation_id})
|
||||
if not ues:
|
||||
return 0
|
||||
@ -410,7 +416,7 @@ def next_ue_numero(formation_id, semestre_id=None):
|
||||
return ues[-1]["numero"] + 1000
|
||||
else:
|
||||
# Avec semestre: (prend le semestre du 1er module de l'UE)
|
||||
_add_ue_semestre_id(ues)
|
||||
_add_ue_semestre_id(ues, formation.get_parcours().APC_SAE)
|
||||
ue_list_semestre = [ue for ue in ues if ue["semestre_id"] == semestre_id]
|
||||
if ue_list_semestre:
|
||||
return ue_list_semestre[-1]["numero"] + 10
|
||||
@ -447,27 +453,27 @@ def ue_table(formation_id=None, msg=""): # was ue_list
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre_validation
|
||||
|
||||
F = sco_formations.formation_list(args={"formation_id": formation_id})
|
||||
if not F:
|
||||
formation = Formation.query.get(formation_id)
|
||||
if not formation:
|
||||
raise ScoValueError("invalid formation_id")
|
||||
F = F[0]
|
||||
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
|
||||
parcours = formation.get_parcours()
|
||||
is_apc = parcours.APC_SAE
|
||||
locked = sco_formations.formation_has_locked_sems(formation_id)
|
||||
|
||||
ues = ue_list(args={"formation_id": formation_id, "is_external": False})
|
||||
ues_externes = ue_list(args={"formation_id": formation_id, "is_external": True})
|
||||
# tri par semestre et numero:
|
||||
_add_ue_semestre_id(ues)
|
||||
_add_ue_semestre_id(ues_externes)
|
||||
_add_ue_semestre_id(ues, is_apc)
|
||||
_add_ue_semestre_id(ues_externes, is_apc)
|
||||
ues.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||
ues_externes.sort(key=lambda u: (u["semestre_id"], u["numero"]))
|
||||
has_duplicate_ue_codes = len(set([ue["ue_code"] for ue in ues])) != len(ues)
|
||||
|
||||
has_perm_change = current_user.has_permission(Permission.ScoChangeFormation)
|
||||
# editable = (not locked) and has_perm_change
|
||||
# On autorise maintanant la modification des formations qui ont des semestres verrouillés,
|
||||
# sauf si cela affect les notes passées (verrouillées):
|
||||
# On autorise maintenant la modification des formations qui ont
|
||||
# des semestres verrouillés, sauf si cela affect les notes passées
|
||||
# (verrouillées):
|
||||
# - pas de modif des modules utilisés dans des semestres verrouillés
|
||||
# - pas de changement des codes d'UE utilisés dans des semestres verrouillés
|
||||
editable = has_perm_change
|
||||
@ -496,12 +502,13 @@ def ue_table(formation_id=None, msg=""): # was ue_list
|
||||
"libjs/jQuery-tagEditor/jquery.caret.min.js",
|
||||
"js/module_tag_editor.js",
|
||||
],
|
||||
page_title="Programme %s" % F["acronyme"],
|
||||
page_title=f"Programme {formation.acronyme}",
|
||||
),
|
||||
"""<h2>Formation %(titre)s (%(acronyme)s) [version %(version)s] code %(formation_code)s"""
|
||||
% F,
|
||||
lockicon,
|
||||
"</h2>",
|
||||
f"""<h2>Formation {formation.titre} ({formation.acronyme})
|
||||
[version {formation.version}] code {formation.formation_code}
|
||||
{lockicon}
|
||||
</h2>
|
||||
""",
|
||||
]
|
||||
if locked:
|
||||
H.append(
|
||||
@ -533,41 +540,41 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
# Description de la formation
|
||||
H.append('<div class="formation_descr">')
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Titre:</span><span class="fd_v">%(titre)s</span></div>'
|
||||
% F
|
||||
)
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Titre officiel:</span><span class="fd_v">%(titre_officiel)s</span></div>'
|
||||
% F
|
||||
)
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Acronyme:</span><span class="fd_v">%(acronyme)s</span></div>'
|
||||
% F
|
||||
)
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Code:</span><span class="fd_v">%(formation_code)s</span></div>'
|
||||
% F
|
||||
)
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Version:</span><span class="fd_v">%(version)s</span></div>'
|
||||
% F
|
||||
)
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t">Type parcours:</span><span class="fd_v">%s</span></div>'
|
||||
% parcours.__doc__
|
||||
f"""<div class="fd_d"><span class="fd_t">Titre:
|
||||
</span><span class="fd_v">{formation.titre}</span>
|
||||
</div>
|
||||
<div class="fd_d"><span class="fd_t">Titre officiel:</span>
|
||||
<span class="fd_v">{formation.titre_officiel}</span>
|
||||
</div>
|
||||
<div class="fd_d"><span class="fd_t">Acronyme:</span>
|
||||
<span class="fd_v">{formation.acronyme}</span>
|
||||
</div>
|
||||
<div class="fd_d"><span class="fd_t">Code:</span>
|
||||
<span class="fd_v">{formation.formation_code}</span>
|
||||
</div>
|
||||
<div class="fd_d"><span class="fd_t">Version:</span>
|
||||
<span class="fd_v">{formation.version}</span>
|
||||
</div>
|
||||
<div class="fd_d"><span class="fd_t">Type parcours:</span>
|
||||
<span class="fd_v">{parcours.__doc__}</span>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
if parcours.UE_IS_MODULE:
|
||||
H.append(
|
||||
'<div class="fd_d"><span class="fd_t"> </span><span class="fd_n">(Chaque module est une UE)</span></div>'
|
||||
"""<div class="fd_d"><span class="fd_t"> </span>
|
||||
<span class="fd_n">(Chaque module est une UE)</span></div>"""
|
||||
)
|
||||
|
||||
if editable:
|
||||
H.append(
|
||||
'<div><a href="formation_edit?formation_id=%(formation_id)s" class="stdlink">modifier ces informations</a></div>'
|
||||
% F
|
||||
f"""<div><a href="{
|
||||
url_for('notes.formation_edit', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id)
|
||||
}" class="stdlink">modifier ces informations</a></div>"""
|
||||
)
|
||||
|
||||
H.append("</div>")
|
||||
|
||||
# Formation APC (BUT) ?
|
||||
if is_apc:
|
||||
H.append(
|
||||
@ -575,52 +582,32 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
<div class="ue_list_tit">Formation par compétences (BUT)</div>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=None)
|
||||
}">éditer les coefficients des ressources et SAÉs</a></li>
|
||||
</ul>
|
||||
</div>"""
|
||||
)
|
||||
# Description des UE/matières/modules
|
||||
H.append('<div class="formation_ue_list">')
|
||||
H.append('<div class="ue_list_tit">Programme pédagogique:</div>')
|
||||
|
||||
H.append(
|
||||
'<form><input type="checkbox" class="sco_tag_checkbox">montrer les tags</input></form>'
|
||||
"""
|
||||
<div class="formation_ue_list">
|
||||
<div class="ue_list_tit">Programme pédagogique:</div>
|
||||
<form>
|
||||
<input type="checkbox" class="sco_tag_checkbox">montrer les tags</input>
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
_ue_table_ues(
|
||||
parcours,
|
||||
ues,
|
||||
editable,
|
||||
tag_editable,
|
||||
has_perm_change,
|
||||
arrow_up,
|
||||
arrow_down,
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
)
|
||||
)
|
||||
if editable:
|
||||
if is_apc:
|
||||
H.append(
|
||||
'<ul><li><a class="stdlink" href="ue_create?formation_id=%s">Ajouter une UE</a></li>'
|
||||
% formation_id
|
||||
)
|
||||
H.append(
|
||||
'<li><a href="formation_add_malus_modules?formation_id=%(formation_id)s" class="stdlink">Ajouter des modules de malus dans chaque UE</a></li></ul>'
|
||||
% F
|
||||
)
|
||||
H.append("</div>") # formation_ue_list
|
||||
|
||||
if ues_externes:
|
||||
H.append('<div class="formation_ue_list formation_ue_list_externes">')
|
||||
H.append(
|
||||
'<div class="ue_list_tit">UE externes déclarées (pour information):</div>'
|
||||
sco_edit_apc.html_edit_formation_apc(
|
||||
formation, editable=editable, tag_editable=tag_editable
|
||||
)
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
_ue_table_ues(
|
||||
parcours,
|
||||
ues_externes,
|
||||
ues,
|
||||
editable,
|
||||
tag_editable,
|
||||
has_perm_change,
|
||||
@ -631,28 +618,84 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
delete_disabled_icon,
|
||||
)
|
||||
)
|
||||
H.append("</div>") # formation_ue_list
|
||||
if editable:
|
||||
H.append(
|
||||
f"""<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.ue_create', scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||
}">Ajouter une UE</a>
|
||||
</li>
|
||||
<li><a href="{
|
||||
url_for('notes.formation_add_malus_modules',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||
}" class="stdlink">Ajouter des modules de malus dans chaque UE</a>
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("</div>") # formation_ue_list
|
||||
|
||||
if ues_externes:
|
||||
H.append(
|
||||
f"""
|
||||
<div class="formation_ue_list formation_ue_list_externes">
|
||||
<div class="ue_list_tit">UE externes déclarées (pour information):
|
||||
</div>
|
||||
{_ue_table_ues(
|
||||
parcours,
|
||||
ues_externes,
|
||||
editable,
|
||||
tag_editable,
|
||||
has_perm_change,
|
||||
arrow_up,
|
||||
arrow_down,
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
)}
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
H.append("<p><ul>")
|
||||
if editable:
|
||||
H.append(
|
||||
"""
|
||||
<li><a class="stdlink" href="formation_create_new_version?formation_id=%(formation_id)s">Créer une nouvelle version (non verrouillée)</a></li>
|
||||
"""
|
||||
% F
|
||||
f"""
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_create_new_version',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
)
|
||||
}">Créer une nouvelle version (non verrouillée)</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
"""
|
||||
<li><a class="stdlink" href="formation_table_recap?formation_id=%(formation_id)s">Table récapitulative de la formation</a></li>
|
||||
|
||||
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=xml">Export XML de la formation</a> (permet de la sauvegarder pour l'échanger avec un autre site)</li>
|
||||
f"""
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_table_recap', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id)
|
||||
}">Table récapitulative de la formation</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, format='xml')
|
||||
}">Export XML de la formation</a>
|
||||
(permet de la sauvegarder pour l'échanger avec un autre site)
|
||||
</li>
|
||||
|
||||
<li><a class="stdlink" href="formation_export?formation_id=%(formation_id)s&format=json">Export JSON de la formation</a></li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formation_export', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, format='json')
|
||||
}">Export JSON de la formation</a>
|
||||
</li>
|
||||
|
||||
<li><a class="stdlink" href="module_list?formation_id=%(formation_id)s">Liste détaillée des modules de la formation</a> (debug) </li>
|
||||
</ul>
|
||||
</p>"""
|
||||
% F
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.module_table', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id)
|
||||
}">Liste détaillée des modules de la formation</a> (debug)
|
||||
</li>
|
||||
</ul>
|
||||
</p>"""
|
||||
)
|
||||
if has_perm_change:
|
||||
H.append(
|
||||
@ -679,12 +722,13 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
|
||||
if current_user.has_permission(Permission.ScoImplement):
|
||||
H.append(
|
||||
"""<ul>
|
||||
<li><a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&semestre_id=1">Mettre en place un nouveau semestre de formation %(acronyme)s</a>
|
||||
</li>
|
||||
|
||||
</ul>"""
|
||||
% F
|
||||
f"""<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, semestre_id=1)
|
||||
}">Mettre en place un nouveau semestre de formation %(acronyme)s</a>
|
||||
</li>
|
||||
</ul>"""
|
||||
)
|
||||
# <li>(debug) <a class="stdlink" href="check_form_integrity?formation_id=%(formation_id)s">Vérifier cohérence</a></li>
|
||||
|
||||
@ -707,7 +751,9 @@ def _ue_table_ues(
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
):
|
||||
"""Édition de programme: liste des UEs (avec leurs matières et modules)."""
|
||||
"""Édition de programme: liste des UEs (avec leurs matières et modules).
|
||||
Pour les formations classiques (non APC/BUT)
|
||||
"""
|
||||
H = []
|
||||
cur_ue_semestre_id = None
|
||||
iue = 0
|
||||
@ -802,6 +848,7 @@ def _ue_table_ues(
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=module_type,
|
||||
)
|
||||
)
|
||||
return "\n".join(H)
|
||||
@ -817,6 +864,7 @@ def _ue_table_matieres(
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=None,
|
||||
):
|
||||
"""Édition de programme: liste des matières (et leurs modules) d'une UE."""
|
||||
H = []
|
||||
@ -883,6 +931,7 @@ def _ue_table_ressources_saes(
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=None,
|
||||
):
|
||||
"""Édition de programme: liste des ressources et SAÉs d'une UE.
|
||||
(pour les parcours APC_SAE)
|
||||
@ -905,7 +954,7 @@ def _ue_table_ressources_saes(
|
||||
"""
|
||||
]
|
||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
for titre, element_name, module_type in (
|
||||
for titre, element_name, element_type in (
|
||||
("Ressources", "ressource", scu.ModuleType.RESSOURCE),
|
||||
("SAÉs", "SAÉ", scu.ModuleType.SAE),
|
||||
("Autres modules", "xxx", None),
|
||||
@ -914,9 +963,9 @@ def _ue_table_ressources_saes(
|
||||
elements = [
|
||||
m
|
||||
for m in modules
|
||||
if module_type == m["module_type"]
|
||||
if element_type == m["module_type"]
|
||||
or (
|
||||
(module_type is None)
|
||||
(element_type is None)
|
||||
and m["module_type"]
|
||||
not in (scu.ModuleType.RESSOURCE, scu.ModuleType.SAE)
|
||||
)
|
||||
@ -933,6 +982,7 @@ def _ue_table_ressources_saes(
|
||||
arrow_none,
|
||||
delete_icon,
|
||||
delete_disabled_icon,
|
||||
module_type=module_type,
|
||||
empty_list_msg="Aucune " + element_name,
|
||||
create_element_msg="créer une " + element_name,
|
||||
add_suppress_link=False,
|
||||
@ -1273,5 +1323,5 @@ def ue_list_semestre_ids(ue):
|
||||
Mais cela n'a pas toujours été le cas dans les programmes pédagogiques officiels,
|
||||
aussi ScoDoc laisse le choix.
|
||||
"""
|
||||
Modlist = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
return sorted(list(set([mod["semestre_id"] for mod in Modlist])))
|
||||
modules = sco_edit_module.module_list(args={"ue_id": ue["ue_id"]})
|
||||
return sorted(list(set([mod["semestre_id"] for mod in modules])))
|
||||
|
@ -39,6 +39,7 @@ from flask import request
|
||||
from app import db
|
||||
from app import log
|
||||
from app import models
|
||||
from app.models.formsemestre import FormSemestre
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
@ -64,11 +65,8 @@ def evaluation_create_form(
|
||||
modimpl = sco_moduleimpl.moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
|
||||
mod = modimpl["module"]
|
||||
formsemestre_id = modimpl["formsemestre_id"]
|
||||
sem_ues = db.session.query(models.UniteEns).filter(
|
||||
models.ModuleImpl.formsemestre_id == formsemestre_id,
|
||||
models.Module.id == models.ModuleImpl.module_id,
|
||||
models.UniteEns.id == models.Module.ue_id,
|
||||
)
|
||||
sem = FormSemestre.query.get(formsemestre_id)
|
||||
sem_ues = sem.query_ues().all()
|
||||
is_malus = mod["module_type"] == ModuleType.MALUS
|
||||
is_apc = mod["module_type"] in (ModuleType.RESSOURCE, ModuleType.SAE)
|
||||
|
||||
|
@ -263,7 +263,8 @@ div.logo-logo {
|
||||
}
|
||||
div.logo-logo img {
|
||||
margin-top: 20px;
|
||||
width: 100px;
|
||||
width: 55px; /* 100px */
|
||||
padding-right: 50px;
|
||||
}
|
||||
div.sidebar-bottom {
|
||||
margin-top: 10px;
|
||||
@ -1480,7 +1481,40 @@ div.formation_ue_list {
|
||||
margin-right: 12px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
div.formation_list_ues_titre {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
div.formation_list_modules {
|
||||
border-radius: 18px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
div.formation_list_modules_titre {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
}
|
||||
div.formation_list_modules_RESSOURCE {
|
||||
background-color: #f8c844;
|
||||
}
|
||||
div.formation_list_modules_SAE {
|
||||
background-color: #c6ffab;
|
||||
}
|
||||
div.formation_list_modules_STANDARD {
|
||||
background-color: #afafc2;
|
||||
}
|
||||
div.formation_list_modules ul.notes_module_list {
|
||||
margin-top: 0px;
|
||||
margin-bottom: -1px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
li.module_malus span.formation_module_tit {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
@ -1498,7 +1532,6 @@ div.ue_list_tit {
|
||||
|
||||
ul.notes_ue_list {
|
||||
background-color: rgb(240,240,240);
|
||||
font-weight: bold;
|
||||
margin-top: 4px;
|
||||
margin-right: 1em;
|
||||
}
|
||||
@ -1519,7 +1552,10 @@ span.ue_type {
|
||||
margin-left: 1.5em;
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
|
||||
ul.notes_module_list span.ue_coefs_list {
|
||||
color: blue;
|
||||
font-size: 70%;
|
||||
}
|
||||
div.formation_ue_list_externes {
|
||||
background-color: #98cc98;
|
||||
}
|
||||
@ -1572,7 +1608,7 @@ div#ue_list_code {
|
||||
}
|
||||
|
||||
ul.notes_module_list {
|
||||
list-style-type: none;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div#ue_list_etud_validations {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 7.8 KiB |
82
app/templates/pn/form_mods.html
Normal file
82
app/templates/pn/form_mods.html
Normal file
@ -0,0 +1,82 @@
|
||||
{# Édition liste modules APC (SAÉ ou ressources) #}
|
||||
|
||||
<div class="formation_list_modules formation_list_modules_{{module_type.name}}">
|
||||
<div class="formation_list_modules_titre">{{titre}}</div>
|
||||
|
||||
<ul class="notes_module_list">
|
||||
|
||||
{% if not formation.ues.count() %}
|
||||
<li class="notes_module_list"><em>aucune UE</em></li>
|
||||
{% else %}
|
||||
{% for mod in modules %}
|
||||
<li class="notes_module_list module_{{mod.type_name()}}">
|
||||
<span class="notes_module_list_buts">
|
||||
{% if editable and not loop.first %}
|
||||
<a href="{{ url_for('notes.module_move',
|
||||
scodoc_dept=g.scodoc_dept, module_id=mod.id, after=0 )
|
||||
}}" class="aud">{{arrow_up|safe}}</a>
|
||||
{% else %}
|
||||
{{arrow_none|safe}}
|
||||
{% endif %}
|
||||
{% if editable and not loop.last %}
|
||||
<a href="{{ url_for('notes.module_move',
|
||||
scodoc_dept=g.scodoc_dept, module_id=mod.id, after=1 )
|
||||
}}" class="aud">{{arrow_down|safe}}</a>
|
||||
{% else %}
|
||||
{{arrow_none|safe}}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if editable and not mod.modimpls.count() %}
|
||||
<a class="smallbutton" href="{{ url_for('notes.module_delete',
|
||||
scodoc_dept=g.scodoc_dept, module_id=mod.id)
|
||||
}}">{{delete_icon|safe}}</a>
|
||||
{% else %}
|
||||
{{delete_disabled_icon|safe}}
|
||||
{% endif %}
|
||||
|
||||
{% if editable %}
|
||||
<a class="discretelink" title="Modifier le module {{mod.code}},
|
||||
utilisé par {{mod.modimpls.count()}} sessions"
|
||||
href="{{ url_for('notes.module_edit',
|
||||
scodoc_dept=g.scodoc_dept, module_id=mod.id)
|
||||
}}">
|
||||
{% endif %}
|
||||
<span class="formation_module_tit">{{mod.code}} {{mod.titre|default("", true)}}</span>
|
||||
{% if editable %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{{formation.get_parcours().SESSION_NAME}} {{mod.semestre_id}}
|
||||
|
||||
({{mod.heures_cours}}/{{mod.heures_td}}/{{mod.heures_tp}},
|
||||
|
||||
Apo:<span class="{% if editable %}span_apo_edit{% endif %}"
|
||||
data-url="edit_module_set_code_apogee"
|
||||
id="{{mod.id}}"
|
||||
data-placeholder="{{scu.APO_MISSING_CODE_STR}}">
|
||||
{{mod.code_apogee|default("", true)}}</span>)
|
||||
|
||||
<span class="ue_coefs_list">
|
||||
{% for coef in mod.ue_coefs %}
|
||||
<span>{{coef.ue.acronyme}}:{{coef.coef}}</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
|
||||
<span class="sco_tag_edit"><form><textarea data-module_id="{{mod.id}}"
|
||||
class="{% if editable %}module_tag_editor{% else %}module_tag_editor_ro{% endif %}">{{mod.tags|join(', ', attribute='title')}}</textarea></form></span>
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if editable %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for("notes.module_create",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
module_type=module_type|int,
|
||||
)}}"
|
||||
>{{create_element_msg}}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
@ -15,13 +15,35 @@
|
||||
<h2>Formation {{formation.titre}} ({{formation.acronyme}})
|
||||
[version {{formation.version}}] code {{formation.code}}</h2>
|
||||
|
||||
<form onchange="change_semestre()">Semestre:
|
||||
<select name="semestre_idx" id="semestre_idx" >
|
||||
{% for i in semestre_ids %}
|
||||
<option value="{{i}}" {%if semestre_idx == i%}selected{%endif%}>{{i}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<div class="tableau"></div>
|
||||
|
||||
<script>
|
||||
function change_semestre() {
|
||||
let semestre_idx = $("#semestre_idx")[0].value;
|
||||
let url = window.location.href.replace( /\/[\-0-9]*$/, "/" + semestre_idx);
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
|
||||
$(function () {
|
||||
$.getJSON("{{data_source}}", function (data) {
|
||||
build_table(data);
|
||||
});
|
||||
let semestre_idx = $("#semestre_idx")[0].value;
|
||||
if (semestre_idx > -10) {
|
||||
let base_url = "{{data_source}}";
|
||||
let data_url = base_url.replace( /\/[\-0-9]*$/, "/" + semestre_idx);
|
||||
console.log("data_url=", data_url );
|
||||
$.getJSON(data_url, function (data) {
|
||||
console.log("build_table")
|
||||
build_table(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
function save(obj) {
|
||||
var value = obj.innerText.trim();
|
||||
|
49
app/templates/pn/form_ues.html
Normal file
49
app/templates/pn/form_ues.html
Normal file
@ -0,0 +1,49 @@
|
||||
{# Édition liste UEs APC #}
|
||||
<div class="formation_list_ues">
|
||||
<div class="formation_list_ues_titre">Unités d'Enseignement (UEs)</div>
|
||||
<ul class="notes_ue_list">
|
||||
{% if not formation.ues.count() %}
|
||||
<li class="notes_ue_list"><em>aucune UE</em></li>
|
||||
{% else %}
|
||||
{% for ue in formation.ues %}
|
||||
<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">{{arrow_up|safe}}</a>
|
||||
{% else %}
|
||||
{{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">{{arrow_down|safe}}</a>
|
||||
{% else %}
|
||||
{{arrow_none|safe}}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if editable and not ue.modules.count() %}
|
||||
<a class="smallbutton" href="{{ url_for('notes.ue_delete',
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
}}">{{delete_icon|safe}}</a>
|
||||
{% else %}
|
||||
{{delete_disabled_icon|safe}}
|
||||
{% endif %}
|
||||
|
||||
<b>{{ue.acronyme}}</b> {{ue.titre}}
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if editable %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for("notes.ue_create",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation.id,
|
||||
)}}"
|
||||
>ajouter une UE</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
@ -429,6 +429,7 @@ sco_publish(
|
||||
"/edit_module_set_code_apogee",
|
||||
sco_edit_module.edit_module_set_code_apogee,
|
||||
Permission.ScoChangeFormation,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
sco_publish("/module_list", sco_edit_module.module_table, Permission.ScoView)
|
||||
sco_publish("/module_tag_search", sco_tag_module.module_tag_search, Permission.ScoView)
|
||||
|
@ -66,16 +66,20 @@ from app.scodoc import html_sco_header
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
|
||||
|
||||
@bp.route("/table_modules_ue_coefs/<formation_id>")
|
||||
@bp.route("/table_modules_ue_coefs/<int:formation_id>/<semestre_idx>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def table_modules_ue_coefs(formation_id):
|
||||
def table_modules_ue_coefs(formation_id, semestre_idx):
|
||||
"""Description JSON de la table des coefs modules/UE dans une formation"""
|
||||
_ = models.Formation.query.get_or_404(formation_id) # check
|
||||
|
||||
df = moy_ue.df_load_ue_coefs(formation_id)
|
||||
ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
|
||||
modules = models.Module.query.filter_by(formation_id=formation_id).all()
|
||||
df = moy_ue.df_load_ue_coefs(formation_id, semestre_idx)
|
||||
ues = models.UniteEns.query.filter_by(
|
||||
formation_id=formation_id, semestre_idx=semestre_idx
|
||||
)
|
||||
modules = models.Module.query.filter_by(
|
||||
formation_id=formation_id, semestre_id=semestre_idx
|
||||
)
|
||||
# Titre des modules, en ligne
|
||||
col_titres_mods = [
|
||||
{
|
||||
@ -108,7 +112,7 @@ def table_modules_ue_coefs(formation_id):
|
||||
"x": col,
|
||||
"y": row,
|
||||
"style": "champs",
|
||||
"data": df[ue.id][mod.id] or "",
|
||||
"data": df[mod.id][ue.id] or "",
|
||||
"editable": True,
|
||||
"module_id": mod.id,
|
||||
"ue_id": ue.id,
|
||||
@ -146,10 +150,11 @@ def set_module_ue_coef():
|
||||
return scu.json_error("ok", success=True, status=201)
|
||||
|
||||
|
||||
@bp.route("/edit_modules_ue_coefs/<formation_id>")
|
||||
@bp.route("/edit_modules_ue_coefs/<formation_id>", defaults={"semestre_idx": -100})
|
||||
@bp.route("/edit_modules_ue_coefs/<formation_id>/<semestre_idx>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
def edit_modules_ue_coefs(formation_id):
|
||||
def edit_modules_ue_coefs(formation_id, semestre_idx=None):
|
||||
"""Formulaire édition grille coefs EU/modules"""
|
||||
formation = models.Formation.query.filter_by(
|
||||
formation_id=formation_id
|
||||
@ -161,9 +166,12 @@ def edit_modules_ue_coefs(formation_id):
|
||||
"notes.table_modules_ue_coefs",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id,
|
||||
semestre_idx=semestre_idx or "",
|
||||
),
|
||||
data_save=url_for(
|
||||
"notes.set_module_ue_coef",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
),
|
||||
semestre_idx=int(semestre_idx),
|
||||
semestre_ids=range(1, formation.get_parcours().NB_SEM + 1),
|
||||
)
|
||||
|
@ -281,7 +281,7 @@ def list_depts(depts=""): # list-dept
|
||||
"--name",
|
||||
is_flag=True,
|
||||
help="show database name instead of connexion string (required for "
|
||||
"dropdb/createddb commands)",
|
||||
"dropdb/createdb commands)",
|
||||
)
|
||||
def scodoc_database(name): # list-dept
|
||||
"""print the database connexion string"""
|
||||
|
@ -166,6 +166,7 @@ class ScoFake(object):
|
||||
is_external=None,
|
||||
code_apogee=None,
|
||||
coefficient=None,
|
||||
semestre_idx=None,
|
||||
):
|
||||
"""Crée une UE"""
|
||||
if numero is None:
|
||||
|
@ -1,10 +1,15 @@
|
||||
"""
|
||||
Test modèles évaluations avec poids BUT
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from tests.unit import sco_fake_gen
|
||||
from app import db
|
||||
from app import models
|
||||
from app.comp import moy_mod
|
||||
from app.comp import moy_ue
|
||||
from app.scodoc import sco_codes_parcours
|
||||
|
||||
"""
|
||||
mapp.set_sco_dept("RT")
|
||||
@ -18,11 +23,24 @@ login_user(admin_user)
|
||||
def setup_formation_test():
|
||||
G = sco_fake_gen.ScoFake(verbose=False)
|
||||
_f = G.create_formation(
|
||||
acronyme="F3", titre="Formation 2", titre_officiel="Titre officiel 2"
|
||||
acronyme="F3",
|
||||
titre="Formation 2",
|
||||
titre_officiel="Titre officiel 2",
|
||||
type_parcours=sco_codes_parcours.ParcoursBUT.TYPE_PARCOURS,
|
||||
)
|
||||
_ue1 = G.create_ue(
|
||||
formation_id=_f["formation_id"], acronyme="UE1", titre="ue 1", semestre_idx=2
|
||||
)
|
||||
_ue2 = G.create_ue(
|
||||
formation_id=_f["formation_id"], acronyme="UE2", titre="ue 2", semestre_idx=2
|
||||
)
|
||||
_ue3 = G.create_ue(
|
||||
formation_id=_f["formation_id"], acronyme="UE3", titre="ue 3", semestre_idx=2
|
||||
)
|
||||
# une 4eme UE en dehors du semestre 2
|
||||
_ = G.create_ue(
|
||||
formation_id=_f["formation_id"], acronyme="UE41", titre="ue 41", semestre_idx=4
|
||||
)
|
||||
_ue1 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE1", titre="ue 1")
|
||||
_ue2 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE2", titre="ue 2")
|
||||
_ue3 = G.create_ue(formation_id=_f["formation_id"], acronyme="UE3", titre="ue 3")
|
||||
_mat = G.create_matiere(ue_id=_ue1["ue_id"], titre="matière test")
|
||||
_mod = G.create_module(
|
||||
matiere_id=_mat["matiere_id"],
|
||||
@ -31,6 +49,7 @@ def setup_formation_test():
|
||||
titre="module test",
|
||||
ue_id=_ue1["ue_id"],
|
||||
formation_id=_f["formation_id"],
|
||||
semestre_id=2,
|
||||
)
|
||||
return G, _f["id"], _ue1["id"], _ue2["id"], _ue3["id"], _mod["id"]
|
||||
|
||||
@ -66,7 +85,9 @@ def test_evaluation_poids(test_client):
|
||||
e1.set_ue_poids(ue1, p1)
|
||||
db.session.commit()
|
||||
assert e1.get_ue_poids_dict()[ue1_id] == p1
|
||||
ues = models.UniteEns.query.filter_by(formation_id=formation_id).all()
|
||||
ues = models.UniteEns.query.filter_by(
|
||||
formation_id=formation_id, semestre_idx=2
|
||||
).all()
|
||||
poids = [1.0, 2.0, 3.0]
|
||||
for (ue, p) in zip(ues, poids):
|
||||
e1.set_ue_poids(ue, p)
|
||||
@ -109,3 +130,60 @@ def test_modules_coefs(test_client):
|
||||
mod.set_ue_coef(ue2, 0.0)
|
||||
db.session.commit()
|
||||
assert len(mod.ue_coefs) == 0
|
||||
|
||||
|
||||
def test_modules_conformity(test_client):
|
||||
"""Vérification coefficients module<->UE vs poids des évaluations"""
|
||||
G, formation_id, ue1_id, ue2_id, ue3_id, module_id = setup_formation_test()
|
||||
ue1 = models.UniteEns.query.get(ue1_id)
|
||||
ue2 = models.UniteEns.query.get(ue2_id)
|
||||
ue3 = models.UniteEns.query.get(ue3_id)
|
||||
mod = models.Module.query.get(module_id)
|
||||
nb_ues = 3 # 3 UEs dans ce test
|
||||
nb_mods = 1 # 1 seul module
|
||||
# Coef du module vers les UE
|
||||
c1, c2, c3 = 1.0, 2.0, 3.0
|
||||
coefs_mod = {ue1.id: c1, ue2.id: c2, ue3.id: c3}
|
||||
mod.set_ue_coef_dict(coefs_mod)
|
||||
assert mod.get_ue_coef_dict() == coefs_mod
|
||||
# Mise en place:
|
||||
sem = G.create_formsemestre(
|
||||
formation_id=formation_id,
|
||||
semestre_id=2,
|
||||
date_debut="01/01/2021",
|
||||
date_fin="30/06/2021",
|
||||
)
|
||||
mi = G.create_moduleimpl(
|
||||
module_id=module_id,
|
||||
formsemestre_id=sem["formsemestre_id"],
|
||||
)
|
||||
moduleimpl_id = mi["id"]
|
||||
modimpl = models.ModuleImpl.query.get(moduleimpl_id)
|
||||
assert modimpl.formsemestre.formation.get_parcours().APC_SAE # BUT
|
||||
# Check ModuleImpl
|
||||
ues = modimpl.formsemestre.query_ues().all()
|
||||
assert len(ues) == 3
|
||||
#
|
||||
_e1 = G.create_evaluation(
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
jour="01/01/2021",
|
||||
description="evaluation 1",
|
||||
coefficient=0,
|
||||
)
|
||||
evaluation_id = _e1["evaluation_id"]
|
||||
nb_evals = 1 # 1 seule evaluation pour l'instant
|
||||
p1, p2, p3 = 1.0, 2.0, 0.0 # poids de l'éval vers les UE 1, 2 et 3
|
||||
evaluation = models.Evaluation.query.get(evaluation_id)
|
||||
evaluation.set_ue_poids_dict({ue1.id: p1, ue2.id: p2})
|
||||
assert evaluation.get_ue_poids_dict() == {ue1.id: p1, ue2.id: p2}
|
||||
# On n'est pas conforme car p3 est nul alors que c3 est non nul
|
||||
modules_coefficients = moy_ue.df_load_ue_coefs(formation_id)
|
||||
assert isinstance(modules_coefficients, pd.DataFrame)
|
||||
assert modules_coefficients.shape == (nb_ues, nb_mods)
|
||||
evals_poids = moy_mod.df_load_evaluations_poids(moduleimpl_id)
|
||||
assert isinstance(evals_poids, pd.DataFrame)
|
||||
assert all(evals_poids.dtypes == np.float64)
|
||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||
assert not moy_mod.check_moduleimpl_conformity(
|
||||
modimpl, evals_poids, modules_coefficients
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user