Corrige cascades sur Identite.

This commit is contained in:
Emmanuel Viennet 2023-10-25 23:07:34 +02:00
parent 3ad9ab15d5
commit 20d19a190d
8 changed files with 152 additions and 57 deletions

View File

@ -97,6 +97,7 @@ class ApcReferentielCompetences(db.Model, XMLModel):
validations_annee = db.relationship(
"ApcValidationAnnee",
backref="referentiel_competence",
cascade="all, delete-orphan", # cascade at ORM level
lazy="dynamic",
)

View File

@ -38,8 +38,12 @@ class ApcValidationRCUE(db.Model):
)
"formsemestre origine du RCUE (celui d'où a été émis la validation)"
# Les deux UE associées à ce niveau:
ue1_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
ue2_id = db.Column(db.Integer, db.ForeignKey("notes_ue.id"), nullable=False)
ue1_id = db.Column(
db.Integer, db.ForeignKey("notes_ue.id", ondelete="CASCADE"), nullable=False
)
ue2_id = db.Column(
db.Integer, db.ForeignKey("notes_ue.id", ondelete="CASCADE"), nullable=False
)
# optionnel, le parcours dans lequel se trouve la compétence:
parcours_id = db.Column(
db.Integer, db.ForeignKey("apc_parcours.id", ondelete="set null"), nullable=True

View File

@ -30,15 +30,17 @@ class Identite(db.Model, models.ScoDocModel):
id = db.Column(db.Integer, primary_key=True)
etudid = db.synonym("id")
admission_id = db.Column(db.Integer, db.ForeignKey("admissions.id"), nullable=True)
# ForeignKey ondelete set the cascade at the database level
admission_id = db.Column(
db.Integer, db.ForeignKey("admissions.id", ondelete="CASCADE"), nullable=True
)
admission = db.relationship(
"Admission",
back_populates="etud",
uselist=False,
cascade="all,delete",
cascade="all,delete", # cascade also defined at ORM level
single_parent=True,
)
dept_id = db.Column(
db.Integer, db.ForeignKey("departement.id"), index=True, nullable=False
)
@ -147,7 +149,7 @@ class Identite(db.Model, models.ScoDocModel):
> 0
):
raise ScoValueError(
"""clonage étudiant: un étudiant de même code existe déjà
"""clonage étudiant: un étudiant de même code existe déjà
dans le département destination"""
)
d = dict(self.__dict__)

View File

@ -28,11 +28,12 @@
"""Page accueil département (liste des semestres, etc)
"""
from flask import g, request
from flask import g
from flask import url_for
from flask_login import current_user
import app
from app import log
from app.models import ScolarNews
import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable
@ -99,7 +100,7 @@ def index_html(showcodes=0, showsemtable=0):
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
<a href="Users">passez par la page Utilisateurs</a>.
<br>
Définissez au moins un utilisateur avec le rôle AdminXXX
Définissez au moins un utilisateur avec le rôle AdminXXX
(le responsable du département XXX).
</p>
"""
@ -132,9 +133,9 @@ def index_html(showcodes=0, showsemtable=0):
if not showsemtable:
H.append(
f"""<hr>
<p><a class="stdlink" href="{url_for('scolar.index_html',
<p><a class="stdlink" href="{url_for('scolar.index_html',
scodoc_dept=g.scodoc_dept, showsemtable=1)
}">Voir table des semestres (dont {len(othersems)}
}">Voir table des semestres (dont {len(othersems)}
verrouillé{'s' if len(othersems) else ''})</a>
</p>"""
)
@ -196,7 +197,7 @@ def index_html(showcodes=0, showsemtable=0):
def _sem_table(sems):
"""Affiche liste des semestres, utilisée pour semestres en cours"""
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
<span class="respsem">(%(responsable_name)s)</span>
@ -269,9 +270,9 @@ def _sem_table_gt(sems, showcodes=False):
html_class=html_class,
html_sortable=True,
html_table_attrs=f"""
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
""",
html_with_td_classes=True,
preferences=sco_preferences.SemPreferences(),
@ -329,12 +330,16 @@ def delete_dept(dept_id: int) -> str:
# 1- Create temp tables to store ids
reqs = [
"create temp table etudids_temp as select id from identite where dept_id = %(dept_id)s",
"create temp table formsemestres_temp as select id from notes_formsemestre where dept_id = %(dept_id)s",
"create temp table moduleimpls_temp as select id from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
"create temp table formations_temp as select id from notes_formations where dept_id = %(dept_id)s",
"""create temp table formsemestres_temp as select id
from notes_formsemestre where dept_id = %(dept_id)s""",
"""create temp table moduleimpls_temp as select id from notes_moduleimpl
where formsemestre_id in (select id from formsemestres_temp)""",
"""create temp table formations_temp as
select id from notes_formations where dept_id = %(dept_id)s""",
"create temp table tags_temp as select id from notes_tags where dept_id = %(dept_id)s",
]
for r in reqs:
log(f"delete_dept: {r}")
cursor.execute(r, {"dept_id": dept_id})
# 2- Delete student-related informations
@ -342,7 +347,6 @@ def delete_dept(dept_id: int) -> str:
etud_tables = [
"notes_notes",
"group_membership",
"admissions",
"billet_absence",
"adresse",
"absences",
@ -357,29 +361,45 @@ def delete_dept(dept_id: int) -> str:
"scolar_events",
]
for table in etud_tables:
log(f"delete from {table}")
cursor.execute(
f"delete from {table} where etudid in (select id from etudids_temp)"
)
reqs = [
"""delete from apc_validation_annee where referentiel_competence_id
in (select id from apc_referentiel_competences where dept_id = %(dept_id)s)""",
"delete from apc_referentiel_competences where dept_id = %(dept_id)s",
"delete from identite where dept_id = %(dept_id)s",
"delete from sco_prefs where dept_id = %(dept_id)s",
"delete from notes_semset_formsemestre where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_evaluation where moduleimpl_id in (select id from moduleimpls_temp)",
"delete from notes_modules_enseignants where moduleimpl_id in (select id from moduleimpls_temp)",
"delete from notes_formsemestre_uecoef where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_formsemestre_ue_computation_expr where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_formsemestre_responsables where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_moduleimpl where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_modules_tags where tag_id in (select id from tags_temp)",
"""delete from notes_semset_formsemestre
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from notes_evaluation
where moduleimpl_id in (select id from moduleimpls_temp)""",
"""delete from notes_modules_enseignants
where moduleimpl_id in (select id from moduleimpls_temp)""",
"""delete from notes_formsemestre_uecoef
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from notes_formsemestre_ue_computation_expr
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from notes_formsemestre_responsables
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from notes_moduleimpl
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from notes_modules_tags
where tag_id in (select id from tags_temp)""",
"delete from notes_tags where dept_id = %(dept_id)s",
"delete from notes_modules where formation_id in (select id from formations_temp)",
"delete from notes_matieres where ue_id in (select id from notes_ue where formation_id in (select id from formations_temp))",
"delete from notes_formsemestre_etapes where formsemestre_id in (select id from formsemestres_temp)",
"delete from group_descr where partition_id in (select id from partition where formsemestre_id in (select id from formsemestres_temp))",
"""delete from notes_matieres
where ue_id in (select id from notes_ue
where formation_id in (select id from formations_temp))""",
"""delete from notes_formsemestre_etapes
where formsemestre_id in (select id from formsemestres_temp)""",
"""delete from group_descr where partition_id in
(select id from partition
where formsemestre_id in (select id from formsemestres_temp))""",
"delete from partition where formsemestre_id in (select id from formsemestres_temp)",
"delete from notes_formsemestre_custommenu where formsemestre_id in (select id from formsemestres_temp)",
"""delete from notes_formsemestre_custommenu
where formsemestre_id in (select id from formsemestres_temp)""",
"delete from notes_ue where formation_id in (select id from formations_temp)",
"delete from notes_formsemestre where dept_id = %(dept_id)s",
"delete from scolar_news where dept_id = %(dept_id)s",
@ -393,6 +413,7 @@ def delete_dept(dept_id: int) -> str:
"drop table formsemestres_temp",
]
for r in reqs:
log(f"delete_dept: {r}")
cursor.execute(r, {"dept_id": dept_id})
except Exception as e:
cnx.rollback()

View File

@ -367,7 +367,7 @@ def scolars_import_excel_file(
val = input_civilite(val)
except ScoValueError as exc:
raise ScoValueError(
f"""valeur invalide pour 'civilite'
f"""valeur invalide pour 'civilite'
(doit etre 'M', 'F', ou 'MME', 'H', 'X' mais pas '{
val}') ligne {linenum}, colonne {titleslist[i]}"""
) from exc
@ -376,7 +376,7 @@ def scolars_import_excel_file(
val = input_civilite_etat_civil(val)
except ScoValueError as exc:
raise ScoValueError(
f"""valeur invalide pour 'civilite'
f"""valeur invalide pour 'civilite'
(doit etre 'M', 'F', vide ou 'MME', 'H', 'X' mais pas '{
val}') ligne {linenum}, colonne {titleslist[i]}"""
) from exc
@ -464,9 +464,6 @@ def scolars_import_excel_file(
cursor.execute(
"delete from adresse where etudid=%(etudid)s", {"etudid": etudid}
)
cursor.execute(
"delete from admissions where etudid=%(etudid)s", {"etudid": etudid}
)
cursor.execute(
"delete from group_membership where etudid=%(etudid)s",
{"etudid": etudid},

View File

@ -696,9 +696,6 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident):
cursor.execute(
"delete from adresse where etudid=%(etudid)s", {"etudid": etudid}
)
cursor.execute(
"delete from admissions where etudid=%(etudid)s", {"etudid": etudid}
)
cursor.execute(
"delete from group_membership where etudid=%(etudid)s",
{"etudid": etudid},

View File

@ -1149,7 +1149,7 @@ def _form_dem_of_def(
return f"""
{header}
<h2><font color="#FF0000">{operation_name} de</font> {etud.nomprenom} ({formsemestre.titre_mois()})</h2>
<form action="{operation_method}" method="get">
<div><b>Date de la {operation_name.lower()} (J/M/AAAA):&nbsp;</b>
<input type="text" name="event_date" width=20 value="{nowdmy}">
@ -1162,9 +1162,9 @@ def _form_dem_of_def(
<div class="rappel_decisions">
{'<p class="warning">Attention: il y a des décisions de jury déjà prises !</p>' if validations_descr else ""}
{validations_descr}
{('<p><a class="stdlink" href="'
+ url_for("notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, etudid=etudid)
{('<p><a class="stdlink" href="'
+ url_for("notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, etudid=etudid)
+ '">modifier ces décisions</a></p>') if validations_descr else ""}
</div>
{html_sco_header.sco_footer()}
@ -1870,14 +1870,14 @@ def etudident_delete(etudid, dialog_confirmed=False):
return scu.confirm_dialog(
"""<h2>Confirmer la suppression de l'étudiant <b>{e[nomprenom]}</b> ?</h2>
</p>
<p style="top-margin: 2ex; bottom-margin: 2ex;">Prenez le temps de vérifier
<p style="top-margin: 2ex; bottom-margin: 2ex;">Prenez le temps de vérifier
que vous devez vraiment supprimer cet étudiant !
</p>
<p>Cette opération <font color="red"><b>irréversible</b></font>
efface toute trace de l'étudiant: inscriptions, <b>notes</b>, absences...
efface toute trace de l'étudiant: inscriptions, <b>notes</b>, absences...
dans <b>tous les semestres</b> qu'il a fréquenté.
</p>
<p>Dans la plupart des cas, vous avez seulement besoin de le <ul>désinscrire</ul>
<p>Dans la plupart des cas, vous avez seulement besoin de le <ul>désinscrire</ul>
d'un semestre ? (dans ce cas passez par sa fiche, menu associé au semestre)</p>
<p><a href="{fiche_url}">Vérifier la fiche de {e[nomprenom]}</a>
@ -1896,6 +1896,7 @@ def etudident_delete(etudid, dialog_confirmed=False):
)
log("etudident_delete: etudid=%(etudid)s nomprenom=%(nomprenom)s" % etud)
# delete in all tables !
# c'est l'ancienne façon de gérer les cascades dans notre pseudo-ORM :)
tables = [
"notes_appreciations",
"scolar_autorisation_inscription",
@ -1908,7 +1909,6 @@ def etudident_delete(etudid, dialog_confirmed=False):
"group_membership",
"etud_annotations",
"scolog",
"admissions",
"adresse",
"absences",
"absences_notifications",
@ -2288,24 +2288,24 @@ def form_students_import_infos_admissions(formsemestre_id=None):
<p>A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.</p>
</div>
<p>
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
les autres lignes de la feuille seront ignorées.
Et seules les colonnes intéressant ScoDoc
Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
les autres lignes de la feuille seront ignorées.
Et seules les colonnes intéressant ScoDoc
seront importées: il est inutile d'éliminer les autres.
<br>
<em>Seules les données "admission" seront modifiées
<em>Seules les données "admission" seront modifiées
(et pas l'identité de l'étudiant).</em>
<br>
<em>Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".</em>
</p>
<p>
Avant d'importer vos données, il est recommandé d'enregistrer
Avant d'importer vos données, il est recommandé d'enregistrer
les informations actuelles:
<a class="stdlink" href="{
url_for("scolar.import_generate_admission_sample",
url_for("scolar.import_generate_admission_sample",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
}">exporter les données actuelles de ScoDoc</a>
}">exporter les données actuelles de ScoDoc</a>
(ce fichier peut être -importé après d'éventuelles modifications)
</p>
""",
@ -2334,8 +2334,8 @@ def form_students_import_infos_admissions(formsemestre_id=None):
)
help_text = (
"""<p>Les colonnes importables par cette fonction sont indiquées
dans la table ci-dessous.
"""<p>Les colonnes importables par cette fonction sont indiquées
dans la table ci-dessous.
Seule la première feuille du classeur sera utilisée.
<div id="adm_table_description_format">
"""

View File

@ -0,0 +1,73 @@
"""Ajoute quelques cascades oubliées
Revision ID: fd805feb7ba8
Revises: 497ba81343f7
Create Date: 2023-10-25 18:27:13.222354
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "fd805feb7ba8"
down_revision = "497ba81343f7"
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table("admissions", schema=None) as batch_op:
batch_op.drop_column("etudid")
with op.batch_alter_table("identite", schema=None) as batch_op:
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
batch_op.create_foreign_key(
"admissions_etudid_fkey",
"admissions",
["admission_id"],
["id"],
ondelete="CASCADE",
)
with op.batch_alter_table("apc_validation_rcue", schema=None) as batch_op:
batch_op.drop_constraint("apc_validation_rcue_ue1_id_fkey", type_="foreignkey")
batch_op.drop_constraint("apc_validation_rcue_ue2_id_fkey", type_="foreignkey")
batch_op.create_foreign_key(
"apc_validation_rcue_ue1_id_fkey",
"notes_ue",
["ue1_id"],
["id"],
ondelete="CASCADE",
)
batch_op.create_foreign_key(
"apc_validation_rcue_ue2_id_fkey",
"notes_ue",
["ue2_id"],
["id"],
ondelete="CASCADE",
)
def downgrade():
with op.batch_alter_table("identite", schema=None) as batch_op:
batch_op.drop_constraint("identite_dept_id_fkey", type_="foreignkey")
batch_op.drop_constraint("admissions_etudid_fkey", type_="foreignkey")
batch_op.create_foreign_key(
"admissions_etudid_fkey", "admissions", ["admission_id"], ["id"]
)
with op.batch_alter_table("admissions", schema=None) as batch_op:
batch_op.add_column(
sa.Column("etudid", sa.INTEGER(), autoincrement=False, nullable=True)
)
with op.batch_alter_table("apc_validation_rcue", schema=None) as batch_op:
batch_op.drop_constraint("apc_validation_rcue_ue1_id_fkey", type_="foreignkey")
batch_op.drop_constraint("apc_validation_rcue_ue2_id_fkey", type_="foreignkey")
batch_op.create_foreign_key(
"apc_validation_rcue_ue2_id_fkey", "notes_ue", ["ue2_id"], ["id"]
)
batch_op.create_foreign_key(
"apc_validation_rcue_ue1_id_fkey", "notes_ue", ["ue1_id"], ["id"]
)