forked from ScoDoc/ScoDoc
BUT: ECTS par UE dépendant du parcours.
This commit is contained in:
parent
657b1e1f1e
commit
a533c40267
@ -37,6 +37,7 @@ from jinja2 import select_autoescape
|
||||
import sqlalchemy as sa
|
||||
|
||||
from flask_cas import CAS
|
||||
import werkzeug.debug
|
||||
|
||||
from app.scodoc.sco_exceptions import (
|
||||
AccessDenied,
|
||||
@ -274,6 +275,14 @@ def create_app(config_class=DevConfig):
|
||||
# flask_sqlalchemy/query (pb deprecation du model.get())
|
||||
warnings.filterwarnings("error", module="flask_sqlalchemy/query")
|
||||
# warnings.filterwarnings("ignore", module="json/provider.py") xxx sans effet en test
|
||||
if app.config["DEBUG"]:
|
||||
# comme on a désactivé ci-dessus les logs de werkzeug,
|
||||
# on affiche nous même le PIN en mode debug:
|
||||
print(
|
||||
f""" * Debugger is active!
|
||||
* Debugger PIN: {werkzeug.debug.get_pin_and_cookie_name(app)[0]}
|
||||
"""
|
||||
)
|
||||
# Vérifie/crée lien sym pour les URL statiques
|
||||
link_filename = f"{app.root_path}/static/links/{sco_version.SCOVERSION}"
|
||||
if not os.path.exists(link_filename):
|
||||
|
@ -7,10 +7,10 @@
|
||||
"""
|
||||
Edition associations UE <-> Ref. Compétence
|
||||
"""
|
||||
from flask import g, render_template, url_for
|
||||
from flask import g, url_for
|
||||
|
||||
from app.models import ApcReferentielCompetences, UniteEns
|
||||
from app.scodoc import codes_cursus
|
||||
from app.forms.formation.ue_parcours_niveau import UEParcoursNiveauForm
|
||||
|
||||
|
||||
def form_ue_choix_niveau(ue: UniteEns) -> str:
|
||||
@ -28,69 +28,17 @@ def form_ue_choix_niveau(ue: UniteEns) -> str:
|
||||
}">associer un référentiel de compétence</a>
|
||||
</div>
|
||||
</div>"""
|
||||
# Les parcours:
|
||||
parcours_options = []
|
||||
for parcour in ref_comp.parcours:
|
||||
parcours_options.append(
|
||||
f"""<option value="{parcour.id}" {
|
||||
'selected' if parcour in ue.parcours else ''}
|
||||
>{parcour.libelle} ({parcour.code})
|
||||
</option>"""
|
||||
)
|
||||
|
||||
newline = "\n"
|
||||
return f"""
|
||||
<div class="ue_choix_niveau">
|
||||
<form class="form_ue_choix_niveau">
|
||||
<div class="cont_ue_choix_niveau">
|
||||
<div>
|
||||
<b>Parcours :</b>
|
||||
<select class="select_parcour multiselect"
|
||||
onchange="set_ue_parcour(this);"
|
||||
data-ue_id="{ue.id}"
|
||||
data-setter="{
|
||||
url_for( "apiweb.set_ue_parcours", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
}">
|
||||
<option value="" {
|
||||
'selected' if not ue.parcours else ''
|
||||
}>Tous</option>
|
||||
{newline.join(parcours_options)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<b>Niveau de compétence :</b>
|
||||
<select class="select_niveau_ue"
|
||||
onchange="set_ue_niveau_competence(this);"
|
||||
data-ue_id="{ue.id}"
|
||||
data-setter="{
|
||||
url_for( "notes.set_ue_niveau_competence", scodoc_dept=g.scodoc_dept)
|
||||
}">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
# Nouvelle version XXX WIP
|
||||
def form_ue_choix_parcours_niveau(ue: UniteEns):
|
||||
"""formulaire (div) pour choix association des parcours et du niveau de compétence d'une UE"""
|
||||
if ue.type != codes_cursus.UE_STANDARD:
|
||||
return ""
|
||||
ref_comp = ue.formation.referentiel_competence
|
||||
if ref_comp is None:
|
||||
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>"""
|
||||
parcours = ue.formation.referentiel_competence.parcours
|
||||
form = UEParcoursNiveauForm(ue, parcours)
|
||||
return f"""<div class="ue_choix_niveau">
|
||||
{ render_template( "pn/ue_choix_parcours_niveau.j2", form_ue_parcours_niveau=form ) }
|
||||
<div class="ue_advanced">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="stdlink" href="{
|
||||
url_for("notes.ue_parcours_ects",
|
||||
scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
}">définir des ECTS différents dans chaque parcours</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
35
app/forms/formation/ue_parcours_ects.py
Normal file
35
app/forms/formation/ue_parcours_ects.py
Normal file
@ -0,0 +1,35 @@
|
||||
from flask import g, url_for
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import FieldList, Form, DecimalField, validators
|
||||
|
||||
from app.models import ApcParcours, ApcReferentielCompetences, UniteEns
|
||||
|
||||
|
||||
class _UEParcoursECTSForm(FlaskForm):
|
||||
"Formulaire association ECTS par parcours à une UE"
|
||||
# construit dynamiquement ci-dessous
|
||||
|
||||
|
||||
def UEParcoursECTSForm(ue: UniteEns) -> FlaskForm:
|
||||
"Génère formulaire association ECTS par parcours à une UE"
|
||||
|
||||
class F(_UEParcoursECTSForm):
|
||||
pass
|
||||
|
||||
parcours: list[ApcParcours] = ue.formation.referentiel_competence.parcours
|
||||
# Initialise un champs de saisie par parcours
|
||||
for parcour in parcours:
|
||||
ects = ue.get_ects(parcour, only_parcours=True)
|
||||
setattr(
|
||||
F,
|
||||
f"ects_parcour_{parcour.id}",
|
||||
DecimalField(
|
||||
f"Parcours {parcour.code}",
|
||||
validators=[
|
||||
validators.Optional(),
|
||||
validators.NumberRange(min=0, max=30),
|
||||
],
|
||||
default=ects,
|
||||
),
|
||||
)
|
||||
return F()
|
@ -1,39 +0,0 @@
|
||||
from flask import g, url_for
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms.fields import SelectField, SelectMultipleField
|
||||
|
||||
from app.models import ApcParcours, ApcReferentielCompetences, UniteEns
|
||||
|
||||
|
||||
class UEParcoursNiveauForm(FlaskForm):
|
||||
"Formulaire association parcours et niveau de compétence à une UE"
|
||||
niveau_select = SelectField(
|
||||
"Niveau de compétence:", render_kw={"class": "niveau_select"}
|
||||
)
|
||||
parcours_multiselect = SelectMultipleField(
|
||||
"Parcours :",
|
||||
coerce=int,
|
||||
option_widget={"class": "form-check-input"},
|
||||
# widget_attrs={"class": "form-check"},
|
||||
render_kw={"class": "multiselect select_ue_parcours", "multiple": "multiple"},
|
||||
)
|
||||
|
||||
def __init__(self, ue: UniteEns, parcours: list[ApcParcours], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Initialise le menu des niveaux:
|
||||
self.niveau_select.render_kw["data-ue_id"] = ue.id
|
||||
self.niveau_select.choices = [
|
||||
(r.id, f"{r.type_titre} {r.specialite_long} ({r.get_version()})")
|
||||
for r in ApcReferentielCompetences.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
]
|
||||
# Initialise le menu des parcours
|
||||
self.parcours_multiselect.render_kw["data-set_ue_parcours"] = url_for(
|
||||
"apiweb.set_ue_parcours", ue_id=ue.id, scodoc_dept=g.scodoc_dept
|
||||
)
|
||||
parcours_options = [(str(p.id), f"{p.libelle} ({p.code})") for p in parcours]
|
||||
self.parcours_multiselect.choices = parcours_options
|
||||
|
||||
# initialize checked items based on u instance
|
||||
parcours_selected = [str(p.id) for p in ue.parcours]
|
||||
self.parcours_multiselect.process_data(parcours_selected)
|
@ -38,7 +38,7 @@ class UniteEns(db.Model):
|
||||
server_default=db.text("notes_newid_ucod()"),
|
||||
nullable=False,
|
||||
)
|
||||
ects = db.Column(db.Float) # nombre de credits ECTS
|
||||
ects = db.Column(db.Float) # nombre de credits ECTS (sauf si parcours spécifié)
|
||||
is_external = db.Column(db.Boolean(), default=False, server_default="false")
|
||||
# id de l'element pedagogique Apogee correspondant:
|
||||
code_apogee = db.Column(db.String(APO_CODE_STR_LEN))
|
||||
@ -100,8 +100,7 @@ class UniteEns(db.Model):
|
||||
return ue
|
||||
|
||||
def to_dict(self, convert_objects=False, with_module_ue_coefs=True):
|
||||
"""as a dict, with the same conversions as in ScoDoc7
|
||||
(except ECTS: keep None)
|
||||
"""as a dict, with the same conversions as in ScoDoc7.
|
||||
If convert_objects, convert all attributes to native types
|
||||
(suitable for json encoding).
|
||||
"""
|
||||
@ -111,7 +110,12 @@ class UniteEns(db.Model):
|
||||
# ScoDoc7 output_formators
|
||||
e["ue_id"] = self.id
|
||||
e["numero"] = e["numero"] if e["numero"] else 0
|
||||
e["ects"] = e["ects"]
|
||||
e["ects"] = e["ects"] # legacy
|
||||
e["ects_by_parcours"] = {}
|
||||
for up in UEParcours.query.filter_by(ue_id=self.id):
|
||||
p = ApcParcours.query.get(up.parcours_id)
|
||||
e["ects_by_parcours"][p.code] = self.get_ects(p)
|
||||
|
||||
e["coefficient"] = e["coefficient"] if e["coefficient"] else 0.0
|
||||
e["code_apogee"] = e["code_apogee"] or "" # pas de None
|
||||
e["parcours"] = [
|
||||
@ -164,6 +168,44 @@ class UniteEns(db.Model):
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
def get_ects(self, parcour: ApcParcours = None, only_parcours=False) -> float:
|
||||
"""Crédits ECTS associés à cette UE.
|
||||
En BUT, cela peut quelquefois dépendre du parcours.
|
||||
Si only_parcours, renvoie None si pas de valeur spéciquement définie dans
|
||||
le parcours indiqué.
|
||||
"""
|
||||
if parcour is not None:
|
||||
ue_parcour = UEParcours.query.filter_by(
|
||||
ue_id=self.id, parcours_id=parcour.id
|
||||
).first()
|
||||
if ue_parcour is not None and ue_parcour.ects is not None:
|
||||
return ue_parcour.ects
|
||||
if only_parcours:
|
||||
return None
|
||||
return self.ects
|
||||
|
||||
def set_ects(self, ects: float, parcour: ApcParcours = None):
|
||||
"""Fixe les crédits. Do not commit.
|
||||
Si le parcours n'est pas spécifié, affecte les ECTS par défaut de l'UE.
|
||||
Si ects est None et parcours indiqué, efface l'association.
|
||||
"""
|
||||
if parcour is not None:
|
||||
ue_parcour = UEParcours.query.filter_by(
|
||||
ue_id=self.id, parcours_id=parcour.id
|
||||
).first()
|
||||
if ects is None:
|
||||
if ue_parcour:
|
||||
db.session.delete(ue_parcour)
|
||||
else:
|
||||
if ue_parcour is None:
|
||||
ue_parcour = UEParcours(parcours_id=parcour.id, ue_id=self.id)
|
||||
ue_parcour.ects = float(ects)
|
||||
db.session.add(ue_parcour)
|
||||
else:
|
||||
self.ects = ects
|
||||
log(f"ue.set_ects( ue_id={self.id}, acronyme={self.acronyme}, ects={ects} )")
|
||||
db.session.add(self)
|
||||
|
||||
def get_ressources(self):
|
||||
"Liste des modules ressources rattachés à cette UE"
|
||||
return self.modules.filter_by(module_type=scu.ModuleType.RESSOURCE).all()
|
||||
@ -334,12 +376,21 @@ class UEParcours(db.Model):
|
||||
"""Association ue <-> parcours, indiquant les ECTS"""
|
||||
|
||||
__tablename__ = "ue_parcours"
|
||||
ue_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), primary_key=True)
|
||||
ue_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("notes_ue.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
)
|
||||
parcours_id = db.Column(
|
||||
db.Integer, db.ForeignKey("apc_parcours.id"), primary_key=True
|
||||
db.Integer,
|
||||
db.ForeignKey("apc_parcours.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
)
|
||||
ects = db.Column(db.Float, nullable=True) # si NULL, on prendra les ECTS de l'UE
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UEParcours( ue_id={self.ue_id}, parcours_id={self.parcours_id}, ects={self.ects})>"
|
||||
|
||||
|
||||
class DispenseUE(db.Model):
|
||||
"""Dispense d'UE
|
||||
|
@ -111,7 +111,6 @@ def html_edit_formation_apc(
|
||||
icons=icons,
|
||||
ues_by_sem=ues_by_sem,
|
||||
ects_by_sem=ects_by_sem,
|
||||
form_ue_choix_parcours_niveau=apc_edit_ue.form_ue_choix_parcours_niveau,
|
||||
scu=scu,
|
||||
codes_cursus=codes_cursus,
|
||||
),
|
||||
|
@ -369,7 +369,12 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
|
||||
"min_value": 0,
|
||||
"max_value": 1000,
|
||||
"title": "ECTS",
|
||||
"explanation": "nombre de crédits ECTS (indiquer 0 si UE bonus)",
|
||||
"explanation": "nombre de crédits ECTS (indiquer 0 si UE bonus)"
|
||||
+ (
|
||||
". (si les ECTS dépendent du parcours, voir plus bas.)"
|
||||
if is_apc
|
||||
else ""
|
||||
),
|
||||
"allow_null": not is_apc, # ects requis en APC
|
||||
},
|
||||
),
|
||||
|
@ -103,12 +103,12 @@ div.competence {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.titre_niveau span.parcs {
|
||||
span.parcs {
|
||||
margin-left: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.titre_niveau span.parc {
|
||||
span.parc {
|
||||
font-size: 75%;
|
||||
font-weight: bold;
|
||||
/* color: rgb(92, 87, 255); */
|
||||
|
@ -2530,6 +2530,15 @@ div.cont_ue_choix_niveau select.select_niveau_ue {
|
||||
width: 490px;
|
||||
}
|
||||
|
||||
div.ue_advanced {
|
||||
background-color: rgb(244, 253, 255);
|
||||
border: 1px solid blue;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
div#ue_list_modules {
|
||||
background-color: rgb(251, 225, 165);
|
||||
border: 1px solid blue;
|
||||
|
@ -116,11 +116,32 @@ Choisissez un parcours...
|
||||
</div>
|
||||
<div><a class="stdlink" href="{{
|
||||
url_for('notes.refcomp_show',
|
||||
scodoc_dept=g.scodoc_dept, refcomp_id=parcour.referentiel.id )
|
||||
scodoc_dept=g.scodoc_dept, refcomp_id=formation.referentiel_competence.id )
|
||||
}}">Référentiel de compétences</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if parcour %}
|
||||
<div class="help">
|
||||
|
||||
<p> Cette page représente le parcours <span class="parc">{{parcour.code}}</span>
|
||||
du référentiel de compétence {{formation.referentiel_competence.specialite}}, et permet
|
||||
d'associer à chaque semestre d'un niveau de compétence une UE de la formation
|
||||
<a class="stdlink"
|
||||
href="{{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept, formation_id=formation.id )
|
||||
}}">{{formation.to_html()}}
|
||||
</a>.</p>
|
||||
|
||||
<p>Le symbole <span class="parc">TC</span> désigne un niveau du tronc commun
|
||||
(c'est à dire présent dans tous les parcours de la spécialité). </p>
|
||||
|
||||
<p>Ce formulaire ne vérifie pas si l'UE est bien conçue pour ce parcours.</p>
|
||||
|
||||
<p>Les modifications sont enregistrées au fur et à mesure.</p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function assoc_ue_niveau(event, niveau_id) {
|
||||
let ue_id = event.target.value;
|
||||
|
40
app/templates/formation/ue_assoc_parcours_ects.j2
Normal file
40
app/templates/formation/ue_assoc_parcours_ects.j2
Normal file
@ -0,0 +1,40 @@
|
||||
{# Association d'ECTS à une UE par parcours #}
|
||||
{% extends "sco_page.j2" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>ECTS par parcours pour l'UE {{ue.acronyme}}</h2>
|
||||
|
||||
|
||||
<div class="help">
|
||||
<p>
|
||||
Utilisez ce formulaire dans le cas (assez rare) où l'UE {{ue.acronyme}}
|
||||
doit avoir un nombre d'ECTS qui dépend du parcours de l'étudiant.
|
||||
<p>
|
||||
Si un champ est laissé sont vide, la valeur par défaut spécifiée pour l'UE
|
||||
(actuellement {{ue.ects or 0}} ECTS) sera utilisée pour ce parcours.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
{% for field in form %}
|
||||
{% if field.name != 'csrf_token' %}
|
||||
<div>
|
||||
<label for="{{ field.id }}">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<div class="error-message">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ form.csrf_token }}
|
||||
<input type="submit" name="submit" value="Enregistrer">
|
||||
<input type="submit" name="cancel" value="Annuler">
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -65,7 +65,6 @@
|
||||
}}">modifier</a>
|
||||
{% endif %}
|
||||
|
||||
{# form_ue_choix_parcours_niveau(ue)|safe #}
|
||||
{% if ue.type != codes_cursus.UE_SPORT %}
|
||||
<div class="ue_choix_niveau">
|
||||
{% if ue.niveau_competence %}
|
||||
|
@ -1,13 +0,0 @@
|
||||
{# inclu par form_ues.j2 #}
|
||||
|
||||
<form method="POST" action="">
|
||||
{{ form_ue_parcours_niveau.csrf_token }}
|
||||
<div class="form-group">
|
||||
{{ form_ue_parcours_niveau.niveau_select.label }}
|
||||
{{ form_ue_parcours_niveau.niveau_select }}
|
||||
|
||||
{{ form_ue_parcours_niveau.parcours_multiselect.label }}
|
||||
{{ form_ue_parcours_niveau.parcours_multiselect }}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -28,20 +28,23 @@ Vues sur les formations BUT
|
||||
Emmanuel Viennet, 2023
|
||||
"""
|
||||
|
||||
from flask import g, render_template
|
||||
from flask import flash, g, redirect, render_template, request, url_for
|
||||
|
||||
from app import log
|
||||
from app import db, log
|
||||
from app.decorators import (
|
||||
scodoc,
|
||||
permission_required,
|
||||
)
|
||||
|
||||
from app.forms.formation.ue_parcours_ects import UEParcoursECTSForm
|
||||
|
||||
from app.models import (
|
||||
ApcCompetence,
|
||||
ApcNiveau,
|
||||
ApcParcours,
|
||||
ApcReferentielCompetences,
|
||||
Formation,
|
||||
UniteEns,
|
||||
)
|
||||
from app.scodoc.codes_cursus import UE_STANDARD
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -168,3 +171,40 @@ def parcour_formation_competences(parcour: ApcParcours, formation: Formation) ->
|
||||
for competence in parcour.query_competences()
|
||||
]
|
||||
return competences
|
||||
|
||||
|
||||
@bp.route("/ue_parcours_ects/<int:ue_id>", methods=["GET", "POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoChangeFormation)
|
||||
def ue_parcours_ects(ue_id: int):
|
||||
"""formulaire (div) pour associer des ECTS par parcours d'une UE"""
|
||||
ue: UniteEns = (
|
||||
UniteEns.query.filter_by(id=ue_id)
|
||||
.join(Formation)
|
||||
.filter_by(dept_id=g.scodoc_dept_id)
|
||||
.first_or_404()
|
||||
)
|
||||
if ue.type != UE_STANDARD:
|
||||
raise ScoValueError("Pas d'ECTS / Parcours pour ce type d'UE")
|
||||
ref_comp = ue.formation.referentiel_competence
|
||||
if ref_comp is None:
|
||||
raise ScoValueError("Pas référentiel de compétence pour cette UE !")
|
||||
form = UEParcoursECTSForm(ue)
|
||||
|
||||
edit_url = url_for("notes.ue_edit", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
|
||||
if request.method == "POST":
|
||||
if request.form.get("submit"):
|
||||
if form.validate():
|
||||
for parcour in ue.formation.referentiel_competence.parcours:
|
||||
field = getattr(form, f"ects_parcour_{parcour.id}")
|
||||
if field:
|
||||
ue.set_ects(field.data, parcour=parcour)
|
||||
db.session.commit()
|
||||
flash("ECTS enregistrés")
|
||||
return redirect(edit_url)
|
||||
elif request.form.get("cancel"):
|
||||
return redirect(edit_url)
|
||||
|
||||
return render_template(
|
||||
"formation/ue_assoc_parcours_ects.j2", form=form, sco=ScoData(), ue=ue
|
||||
)
|
||||
|
@ -30,15 +30,12 @@ def upgrade():
|
||||
sa.Column("parcours_id", sa.Integer(), nullable=False),
|
||||
sa.Column("ects", sa.Float(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["parcours_id"],
|
||||
["apc_parcours.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["ue_id"],
|
||||
["notes_ue.id"],
|
||||
["parcours_id"], ["apc_parcours.id"], ondelete="CASCADE"
|
||||
),
|
||||
sa.ForeignKeyConstraint(["ue_id"], ["notes_ue.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("ue_id", "parcours_id"),
|
||||
)
|
||||
|
||||
#
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
|
Loading…
Reference in New Issue
Block a user