From b5b606b1f6e480b30ea5c47f907f77ed572c0c10 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 12 Mar 2023 21:22:19 +0100 Subject: [PATCH] =?UTF-8?q?Fix:=20d=C3=A9claration=20table=20Identite=20/?= =?UTF-8?q?=20Unicite=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/etudiants.py | 5 +- app/scodoc/sco_etud.py | 27 +++--- app/views/scolar.py | 4 +- .../ae9bb0feea7a_contraintes_identite.py | 97 +++++++++++++++++++ 4 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 migrations/versions/ae9bb0feea7a_contraintes_identite.py diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 77d07250..9cfc56a4 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -27,6 +27,7 @@ class Identite(db.Model): __table_args__ = ( db.UniqueConstraint("dept_id", "code_nip"), db.UniqueConstraint("dept_id", "code_ine"), + db.CheckConstraint("civilite IN ('M', 'F', 'X')"), ) id = db.Column(db.Integer, primary_key=True) @@ -36,10 +37,8 @@ class Identite(db.Model): nom = db.Column(db.Text()) prenom = db.Column(db.Text()) nom_usuel = db.Column(db.Text()) - # optionnel (si present, affiché à la place du nom) + "optionnel (si present, affiché à la place du nom)" civilite = db.Column(db.String(1), nullable=False) - __table_args__ = (db.CheckConstraint("civilite IN ('M', 'F', 'X')"),) - date_naissance = db.Column(db.Date) lieu_naissance = db.Column(db.Text()) dept_naissance = db.Column(db.Text()) diff --git a/app/scodoc/sco_etud.py b/app/scodoc/sco_etud.py index 39602868..4b2f5e88 100644 --- a/app/scodoc/sco_etud.py +++ b/app/scodoc/sco_etud.py @@ -301,24 +301,27 @@ def check_nom_prenom(cnx, nom="", prenom="", etudid=None): def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True): + """Vérifie que le code n'est pas dupliqué""" etudid = args.get("etudid", None) if args.get(code_name, None): etuds = identite_list(cnx, {code_name: str(args[code_name])}) - # log('etuds=%s'%etuds) - nb_max = 0 + duplicate = False if edit: - nb_max = 1 - if len(etuds) > nb_max: + duplicate = (len(etuds) > 1) or ( + (len(etuds) == 1) and etuds[0]["id"] != args["etudid"] + ) + else: + duplicate = len(etuds) > 0 + if duplicate: listh = [] # liste des doubles for e in etuds: listh.append( - """Autre étudiant: """ - % url_for( - "scolar.ficheEtud", - scodoc_dept=g.scodoc_dept, - etudid=e["etudid"], - ) - + """%(nom)s %(prenom)s""" % e + f"""Autre étudiant: {e['nom']} {e['prenom']}""" ) if etudid: OK = "retour à la fiche étudiant" @@ -349,7 +352,7 @@ def _check_duplicate_code(cnx, args, code_name, disable_notify=False, edit=True) """ else: err_page = f"""

Code étudiant ({code_name}) dupliqué !

""" - log("*** error: code %s duplique: %s" % (code_name, args[code_name])) + log(f"*** error: code {code_name} duplique: {args[code_name]}") raise ScoGenError(err_page) diff --git a/app/views/scolar.py b/app/views/scolar.py index 62006616..38c33ae8 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -1732,7 +1732,9 @@ def _etudident_create_or_edit_form(edit): formsemestre_id=formsemestre_id ) # > etudident_create_or_edit # - return flask.redirect("ficheEtud?etudid=" + str(etudid)) + return flask.redirect( + url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid) + ) @bp.route("/etudident_delete", methods=["GET", "POST"]) diff --git a/migrations/versions/ae9bb0feea7a_contraintes_identite.py b/migrations/versions/ae9bb0feea7a_contraintes_identite.py new file mode 100644 index 00000000..4688f92f --- /dev/null +++ b/migrations/versions/ae9bb0feea7a_contraintes_identite.py @@ -0,0 +1,97 @@ +"""contraintes identite + +Revision ID: ae9bb0feea7a +Revises: 5731e904baac +Create Date: 2023-03-12 19:00:58.544873 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm import sessionmaker # added by ev + + +# revision identifiers, used by Alembic. +revision = "ae9bb0feea7a" +down_revision = "5731e904baac" +branch_labels = None +depends_on = None + +Session = sessionmaker() + + +def upgrade(): + # On répare une erreur (typo) dans la déclaration de la table Identite + # qui faisait que les contraintes d'unicité des couples (ine, dept) + # et (nip, dept) n'était pas prises en compte. + # On commence par chercher les éventuels (rares) doublons, changer leurs codes + # avant de créer les contraintes. + bind = op.get_bind() + session = Session(bind=bind) + # Corrige NIP + dups = session.execute( + """SELECT dept_id, code_nip + FROM identite + WHERE code_nip IS NOT NULL + GROUP BY dept_id, code_nip + HAVING COUNT(*) > 1;""" + ).all() + for dept_id, code_nip in dups: + etuds_dups = session.execute( + """SELECT id, nom, prenom FROM identite + WHERE code_nip=:code_nip""", + {"code_nip": code_nip}, + ).all() + for i, (etudid, nom, prenom) in enumerate(etuds_dups[1:], start=1): + session.execute( + """UPDATE identite SET code_nip=:code_nip WHERE id=:etudid""", + { + "code_nip": f"{code_nip}-{i}", + "etudid": etudid, + }, + ) + print( + f"Warning: duplication de code NIP détectée: vérifier {nom} {prenom} NIP={code_nip}" + ) + session.commit() + # Corrige INE + dups = session.execute( + """SELECT dept_id, code_ine + FROM identite + WHERE code_ine IS NOT NULL + GROUP BY dept_id, code_ine + HAVING COUNT(*) > 1;""" + ).all() + for dept_id, code_ine in dups: + etuds_dups = session.execute( + """SELECT id, nom, prenom FROM identite + WHERE code_ine=:code_ine""", + {"code_ine": code_ine}, + ).all() + for i, (etudid, nom, prenom) in enumerate(etuds_dups[1:], start=1): + session.execute( + """UPDATE identite SET code_ine=:code_ine WHERE id=:etudid""", + { + "code_ine": f"{code_ine}-{i}", + "etudid": etudid, + }, + ) + print( + f"Warning: duplication de code INE détectée: vérifier {nom} {prenom} NIP={code_ine}" + ) + session.commit() + + # CREATION DES CONTRAINTES + op.create_unique_constraint( + "identite_dept_id_code_nip_key", "identite", ["dept_id", "code_nip"] + ) + op.create_unique_constraint( + "identite_dept_id_code_ine_key", "identite", ["dept_id", "code_ine"] + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("identite_dept_id_code_nip_key", "identite", type_="unique") + op.drop_constraint("identite_dept_id_code_ine_key", "identite", type_="unique") + # ### end Alembic commands ###