forked from ScoDoc/ScoDoc
chargement/association ref. comp. BUT
This commit is contained in:
parent
e88e280994
commit
4d857a1567
35
app/but/forms/refcomp_forms.py
Normal file
35
app/but/forms/refcomp_forms.py
Normal file
@ -0,0 +1,35 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""ScoDoc 9 : Formulaires / référentiel de compétence
|
||||
"""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed, FileRequired
|
||||
from wtforms import SelectField, SubmitField
|
||||
|
||||
|
||||
class FormationRefCompForm(FlaskForm):
|
||||
referentiel_competence = SelectField("Référentiels déjà chargés")
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler")
|
||||
|
||||
|
||||
class RefCompLoadForm(FlaskForm):
|
||||
upload = FileField(
|
||||
label="Sélectionner un fichier XML Orébut",
|
||||
validators=[
|
||||
FileRequired(),
|
||||
FileAllowed(
|
||||
[
|
||||
"xml",
|
||||
],
|
||||
"Fichier XML Orébut seulement",
|
||||
),
|
||||
],
|
||||
)
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler")
|
@ -17,14 +17,15 @@ from app.models.but_refcomp import (
|
||||
from app.scodoc.sco_exceptions import FormatError
|
||||
|
||||
|
||||
def orebut_import_refcomp(xml_file: TextIO):
|
||||
def orebut_import_refcomp(xml_file: TextIO, dept_id: int, orig_filename=None):
|
||||
tree = ElementTree.parse(xml_file)
|
||||
root = tree.getroot()
|
||||
if root.tag != "referentiel_competence":
|
||||
raise FormatError("élément racine 'referentiel_competence' manquant")
|
||||
ref = ApcReferentielCompetences(
|
||||
**ApcReferentielCompetences.attr_from_xml(root.attrib)
|
||||
)
|
||||
args = ApcReferentielCompetences.attr_from_xml(root.attrib)
|
||||
args["dept_id"] = dept_id
|
||||
args["scodoc_orig_filename"] = orig_filename
|
||||
ref = ApcReferentielCompetences(**args)
|
||||
db.session.add(ref)
|
||||
competences = root.find("competences")
|
||||
if not competences:
|
||||
@ -99,7 +100,7 @@ competence = competences.findall("competence")[0] # XXX
|
||||
|
||||
from app.but.import_refcomp import *
|
||||
f = open("but-RT-refcomp-30112021.xml")
|
||||
ref = orebut_import_refcomp(f)
|
||||
ref = orebut_import_refcomp(f, 0)
|
||||
#------
|
||||
from app.but.import_refcomp import *
|
||||
ref = ApcReferentielCompetences.query.first()
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""ScoDoc 9 models : Référentiel Compétence BUT 2021
|
||||
"""
|
||||
from datetime import datetime
|
||||
from enum import unique
|
||||
from typing import Any
|
||||
|
||||
@ -31,9 +32,13 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
specialite = db.Column(db.Text())
|
||||
specialite_long = db.Column(db.Text())
|
||||
type_titre = db.Column(db.Text())
|
||||
_xml_attribs = { # xml_attrib : attribute
|
||||
_xml_attribs = { # Orébut xml attrib : attribute
|
||||
"type": "type_titre",
|
||||
}
|
||||
# ScoDoc specific fields:
|
||||
scodoc_date_loaded = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
scodoc_orig_filename = db.Column(db.Text())
|
||||
# Relations:
|
||||
competences = db.relationship(
|
||||
"ApcCompetence",
|
||||
backref="referentiel",
|
||||
@ -56,17 +61,26 @@ class ApcReferentielCompetences(db.Model, XMLModel):
|
||||
"specialite": self.specialite,
|
||||
"specialite_long": self.specialite_long,
|
||||
"type_titre": self.type_titre,
|
||||
"scodoc_date_loaded": self.scodoc_date_loaded.isoformat() + "Z",
|
||||
"scodoc_orig_filename": self.scodoc_orig_filename,
|
||||
"competences": {x.titre: x.to_dict() for x in self.competences},
|
||||
"parcours": {x.code: x.to_dict() for x in self.parcours},
|
||||
}
|
||||
|
||||
|
||||
class ApcCompetence(db.Model, XMLModel):
|
||||
__table_args__ = (
|
||||
# les compétences dans Orébut sont identifiées par leur "titre"
|
||||
# unique au sein d'un référentiel:
|
||||
db.UniqueConstraint(
|
||||
"referentiel_id", "titre", name="apc_competence_referentiel_id_titre_key"
|
||||
),
|
||||
)
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
referentiel_id = db.Column(
|
||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id"), nullable=False
|
||||
)
|
||||
titre = db.Column(db.Text(), nullable=False)
|
||||
titre = db.Column(db.Text(), nullable=False, index=True)
|
||||
titre_long = db.Column(db.Text())
|
||||
couleur = db.Column(db.Text())
|
||||
numero = db.Column(db.Integer) # ordre de présentation
|
||||
@ -158,7 +172,7 @@ class ApcAppCritique(db.Model, XMLModel):
|
||||
"Apprentissage Critique BUT"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
niveau_id = db.Column(db.Integer, db.ForeignKey("apc_niveau.id"), nullable=False)
|
||||
code = db.Column(db.Text(), nullable=False)
|
||||
code = db.Column(db.Text(), nullable=False, index=True)
|
||||
libelle = db.Column(db.Text())
|
||||
|
||||
modules = db.relationship(
|
||||
@ -175,14 +189,14 @@ class ApcAppCritique(db.Model, XMLModel):
|
||||
return self.code + " - " + self.titre
|
||||
|
||||
def __repr__(self):
|
||||
return "<AC {}>".format(self.code)
|
||||
return "<AppCritique {}>".format(self.code)
|
||||
|
||||
def get_saes(self):
|
||||
"""Liste des SAE associées"""
|
||||
return [m for m in self.modules if m.module_type == ModuleType.SAE]
|
||||
|
||||
|
||||
ApcModulesACs = db.Table(
|
||||
ApcAppCritiqueModules = db.Table(
|
||||
"apc_modules_acs",
|
||||
db.Column("module_id", db.ForeignKey("notes_modules.id")),
|
||||
db.Column("app_crit_id", db.ForeignKey("apc_app_critique.id")),
|
||||
|
@ -39,7 +39,9 @@ class Formation(db.Model):
|
||||
referentiel_competence_id = db.Column(
|
||||
db.Integer, db.ForeignKey("apc_referentiel_competences.id")
|
||||
)
|
||||
|
||||
referentiel_competence = db.relationship( # one-to-one
|
||||
"ApcReferentielCompetences", backref="formation", uselist=False
|
||||
)
|
||||
ues = db.relationship("UniteEns", backref="formation", lazy="dynamic")
|
||||
formsemestres = db.relationship("FormSemestre", lazy="dynamic", backref="formation")
|
||||
ues = db.relationship("UniteEns", lazy="dynamic", backref="formation")
|
||||
|
@ -252,6 +252,7 @@ class TypeParcours(object):
|
||||
UE_TYPE_NAME.keys()
|
||||
) # par defaut, autorise tous les types d'UE
|
||||
APC_SAE = False # Approche par compétences avec ressources et SAÉs
|
||||
USE_REFERENTIEL_COMPETENCES = False # Lien avec ref. comp.
|
||||
|
||||
def check(self, formation=None):
|
||||
return True, "" # status, diagnostic_message
|
||||
@ -307,6 +308,7 @@ class ParcoursBUT(TypeParcours):
|
||||
NB_SEM = 6
|
||||
COMPENSATION_UE = False
|
||||
APC_SAE = True
|
||||
USE_REFERENTIEL_COMPETENCES = True
|
||||
ALLOWED_UE_TYPES = [UE_STANDARD, UE_SPORT]
|
||||
|
||||
|
||||
|
@ -575,12 +575,23 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
if formation.referentiel_competence is None:
|
||||
descr_refcomp = ""
|
||||
msg_refcomp = "associer à un référentiel de compétences"
|
||||
else:
|
||||
descr_refcomp = f"{formation.referentiel_competence.type_titre} {formation.referentiel_competence.specialite_long}"
|
||||
msg_refcomp = "changer"
|
||||
H.append(
|
||||
f"""
|
||||
<ul>
|
||||
<li>{descr_refcomp} <a class="stdlink" href="{url_for('notes.refcomp_assoc',
|
||||
scodoc_dept=g.scodoc_dept, formation_id=formation_id)
|
||||
}">{msg_refcomp}</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for('notes.edit_modules_ue_coefs', scodoc_dept=g.scodoc_dept, formation_id=formation_id, semestre_idx=semestre_idx)
|
||||
}">éditer les coefficients des ressources et SAÉs</a></li>
|
||||
}">éditer les coefficients des ressources et SAÉs</a>
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
|
23
app/templates/but/refcomp_assoc.html
Normal file
23
app/templates/but/refcomp_assoc.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Associer un référentiel de compétences</h1>
|
||||
<div class="help">
|
||||
Association d'un référentiel de compétence à la formation
|
||||
{{formation.titre}} ({{formation.acronyme}})
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help">
|
||||
Pour charger un nouveau référentiel de compétences Orébut,
|
||||
<a href="{{url_for(
|
||||
'notes.refcomp_load', scodoc_dept=g.scodoc_dept, formation_id=formation.id)
|
||||
}}">passer par cette page</a>.
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
14
app/templates/but/refcomp_load.html
Normal file
14
app/templates/but/refcomp_load.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Charger un référentiel de compétences</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ wtf.quick_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -4,12 +4,13 @@ PN / Référentiel de compétences
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
from flask import url_for
|
||||
from flask import url_for, flash
|
||||
from flask import jsonify
|
||||
from flask import current_app, g, request
|
||||
from flask.templating import render_template
|
||||
from flask_login import current_user
|
||||
from werkzeug.utils import redirect
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from config import Config
|
||||
|
||||
@ -17,14 +18,88 @@ from app import db
|
||||
from app import models
|
||||
|
||||
from app.decorators import scodoc, permission_required
|
||||
from app.models.formations import Formation
|
||||
from app.models.but_refcomp import ApcReferentielCompetences
|
||||
from app.but.import_refcomp import orebut_import_refcomp
|
||||
from app.but.forms.refcomp_forms import FormationRefCompForm, RefCompLoadForm
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes_bp as bp
|
||||
|
||||
|
||||
@bp.route("/pn/comp/<int:refcomp_id>")
|
||||
@bp.route("/referentiel/comp/<int:refcomp_id>")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def refcomp(refcomp_id):
|
||||
ref = ApcReferentielCompetences.query.get_or_404(refcomp_id)
|
||||
return jsonify(ref.to_dict())
|
||||
|
||||
|
||||
@bp.route("/refcomp_assoc/<int:formation_id>", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
def refcomp_assoc(formation_id: int):
|
||||
"""Formulaire association ref. compétence"""
|
||||
formation = Formation.query.get_or_404(formation_id)
|
||||
form = FormationRefCompForm()
|
||||
form.referentiel_competence.choices = [
|
||||
(r.id, f"{r.type_titre} {r.specialite_long}")
|
||||
for r in ApcReferentielCompetences.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
]
|
||||
if request.method == "POST" and form.cancel.data: # cancel button
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
)
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
referentiel_competence_id = form.referentiel_competence.data
|
||||
assert (
|
||||
ApcReferentielCompetences.query.get(referentiel_competence_id) is not None
|
||||
)
|
||||
formation.referentiel_competence_id = referentiel_competence_id
|
||||
db.session.add(formation)
|
||||
db.session.commit()
|
||||
flash("nouveau référentiel de compétences associé")
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.ue_table", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
"but/refcomp_assoc.html",
|
||||
form=form,
|
||||
referentiel_competence_id=formation.referentiel_competence_id,
|
||||
formation=formation,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/refcomp_load/<int:formation_id>", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
def refcomp_load(formation_id=None):
|
||||
"""Formulaire association ref. compétence"""
|
||||
if formation_id is not None:
|
||||
formation = Formation.query.get_or_404(formation_id)
|
||||
else:
|
||||
formation = None
|
||||
form = RefCompLoadForm()
|
||||
if form.validate_on_submit():
|
||||
f = form.upload.data
|
||||
filename = secure_filename(f.filename)
|
||||
ref = orebut_import_refcomp(f, dept_id=g.scodoc_dept_id, orig_filename=filename)
|
||||
if formation is not None:
|
||||
return redirect(
|
||||
url_for(
|
||||
"notes.refcomp_assoc",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation.formation_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
|
||||
|
||||
return render_template(
|
||||
"but/refcomp_load.html",
|
||||
form=form,
|
||||
formation=formation,
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""refcomp
|
||||
"""refcomp index
|
||||
|
||||
Revision ID: eed6d50fd9cb
|
||||
Revision ID: 92789d50f6b6
|
||||
Revises: 00ad500fb118
|
||||
Create Date: 2021-12-02 17:34:10.999132
|
||||
Create Date: 2021-12-03 10:56:43.921559
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'eed6d50fd9cb'
|
||||
revision = '92789d50f6b6'
|
||||
down_revision = '00ad500fb118'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -18,27 +18,38 @@ depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('app_crit')
|
||||
op.drop_table('modules_acs')
|
||||
op.drop_table('app_crit')
|
||||
op.add_column('apc_annee_parcours', sa.Column('ordre', sa.Integer(), nullable=True))
|
||||
op.drop_column('apc_annee_parcours', 'numero')
|
||||
op.create_index(op.f('ix_apc_app_critique_code'), 'apc_app_critique', ['code'], unique=False)
|
||||
op.create_unique_constraint('apc_competence_referentiel_id_titre_key', 'apc_competence', ['referentiel_id', 'titre'])
|
||||
op.create_index(op.f('ix_apc_competence_titre'), 'apc_competence', ['titre'], unique=False)
|
||||
op.add_column('apc_referentiel_competences', sa.Column('scodoc_date_loaded', sa.DateTime(), nullable=True))
|
||||
op.add_column('apc_referentiel_competences', sa.Column('scodoc_orig_filename', sa.Text(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('apc_referentiel_competences', 'scodoc_orig_filename')
|
||||
op.drop_column('apc_referentiel_competences', 'scodoc_date_loaded')
|
||||
op.drop_index(op.f('ix_apc_competence_titre'), table_name='apc_competence')
|
||||
op.drop_constraint('apc_competence_referentiel_id_titre_key', 'apc_competence', type_='unique')
|
||||
op.drop_index(op.f('ix_apc_app_critique_code'), table_name='apc_app_critique')
|
||||
op.add_column('apc_annee_parcours', sa.Column('numero', sa.INTEGER(), autoincrement=False, nullable=True))
|
||||
op.drop_column('apc_annee_parcours', 'ordre')
|
||||
op.create_table('app_crit',
|
||||
sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('app_crit_id_seq'::regclass)"), autoincrement=True, nullable=False),
|
||||
sa.Column('code', sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.Column('titre', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.PrimaryKeyConstraint('id', name='app_crit_pkey'),
|
||||
postgresql_ignore_search_path=False
|
||||
)
|
||||
op.create_table('modules_acs',
|
||||
sa.Column('module_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('ac_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['ac_id'], ['app_crit.id'], name='modules_acs_ac_id_fkey'),
|
||||
sa.ForeignKeyConstraint(['module_id'], ['notes_modules.id'], name='modules_acs_module_id_fkey')
|
||||
)
|
||||
op.create_table('app_crit',
|
||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||
sa.Column('code', sa.TEXT(), autoincrement=False, nullable=False),
|
||||
sa.Column('titre', sa.TEXT(), autoincrement=False, nullable=True),
|
||||
sa.PrimaryKeyConstraint('id', name='app_crit_pkey')
|
||||
)
|
||||
# ### end Alembic commands ###
|
@ -44,7 +44,7 @@ ref_xml = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
def test_but_refcomp(test_client):
|
||||
"""modèles ref. comp."""
|
||||
f = io.StringIO(ref_xml)
|
||||
ref = orebut_import_refcomp(f)
|
||||
ref = orebut_import_refcomp(0, f)
|
||||
assert ref.references.count() == 2
|
||||
assert ref.competences[0].situations.count() == 2
|
||||
assert ref.competences[0].situations[0].libelle.startswith("Conception ")
|
||||
|
Loading…
Reference in New Issue
Block a user