diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 988f041d7..ef321bb97 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -236,11 +236,16 @@ class FormSemestre(models.ScoDocModel): def create_formsemestre(cls, args: dict, silent=False) -> "FormSemestre": """Création d'un formsemestre, avec toutes les valeurs par défaut et notification (sauf si silent). + Arguments: + - responsables peut être une liste de User ou de user_id. Crée la partition par défaut. + Commit all changes. """ # was sco_formsemestre.do_formsemestre_create if "dept_id" not in args: args["dept_id"] = g.scodoc_dept_id + if "formation_id" not in args: + raise ScoValueError("create_formsemestre: no formation_id") formsemestre: "FormSemestre" = cls.create_from_dict(args) db.session.flush() for etape in args.get("etapes") or []: @@ -1475,8 +1480,9 @@ notes_formsemestre_responsables = db.Table( "formsemestre_id", db.Integer, db.ForeignKey("notes_formsemestre.id"), + primary_key=True, ), - db.Column("responsable_id", db.Integer, db.ForeignKey("user.id")), + db.Column("responsable_id", db.Integer, db.ForeignKey("user.id"), primary_key=True), ) diff --git a/migrations/versions/e0824c4f1b0b_responsables_assoc_unicity.py b/migrations/versions/e0824c4f1b0b_responsables_assoc_unicity.py new file mode 100644 index 000000000..3f86328d0 --- /dev/null +++ b/migrations/versions/e0824c4f1b0b_responsables_assoc_unicity.py @@ -0,0 +1,66 @@ +"""responsables_assoc_unicity + +Revision ID: e0824c4f1b0b +Revises: bc85a55e63e1 +Create Date: 2025-02-03 16:45:13.082716 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "e0824c4f1b0b" +down_revision = "bc85a55e63e1" +branch_labels = None +depends_on = None + + +def upgrade(): + # Suppression des doublons dans la table d'association notes_formsemestre_responsables + op.execute( + """ + WITH duplicates AS ( + SELECT + formsemestre_id, + responsable_id, + ctid AS row_identifier, + row_number() OVER ( + PARTITION BY formsemestre_id, responsable_id + ORDER BY ctid + ) AS rn + FROM notes_formsemestre_responsables + ) + DELETE FROM notes_formsemestre_responsables + WHERE ctid IN ( + SELECT row_identifier FROM duplicates WHERE rn > 1 + ); + """ + ) + + with op.batch_alter_table( + "notes_formsemestre_responsables", schema=None + ) as batch_op: + batch_op.alter_column( + "formsemestre_id", existing_type=sa.INTEGER(), nullable=False + ) + batch_op.alter_column( + "responsable_id", existing_type=sa.INTEGER(), nullable=False + ) + batch_op.create_unique_constraint( + "uq_notes_formsemestre_responsables", ["formsemestre_id", "responsable_id"] + ) + + +def downgrade(): + with op.batch_alter_table( + "notes_formsemestre_responsables", schema=None + ) as batch_op: + batch_op.alter_column( + "responsable_id", existing_type=sa.INTEGER(), nullable=True + ) + batch_op.alter_column( + "formsemestre_id", existing_type=sa.INTEGER(), nullable=True + ) + batch_op.drop_constraint("uq_notes_formsemestre_responsables", type_="unique") diff --git a/sco_version.py b/sco_version.py index 02a1ea251..eb6d6e298 100644 --- a/sco_version.py +++ b/sco_version.py @@ -3,7 +3,7 @@ "Infos sur version ScoDoc" -SCOVERSION = "9.7.59" +SCOVERSION = "9.7.60" SCONAME = "ScoDoc" diff --git a/tests/unit/test_formsemestre.py b/tests/unit/test_formsemestre.py index 9d0313624..45d799bfe 100644 --- a/tests/unit/test_formsemestre.py +++ b/tests/unit/test_formsemestre.py @@ -8,6 +8,7 @@ import pytest import app from app import db +from app.auth.models import User from app.formations import edit_ue, formation_versions from app.models import Formation, FormSemestre, FormSemestreDescription from app.scodoc import ( @@ -207,6 +208,44 @@ def test_formsemestre_misc_views(test_client): # pas de test des indicateurs de suivi BUT +def test_formsemestre_edit(test_client): + """Test modification formsemestre""" + u1 = User.query.first() + u2 = User(user_name="un_autre_responsable") + db.session.add(u2) + formation = Formation( + dept_id=1, + acronyme="FORM", + titre="Formation", + titre_officiel="La formation test", + ) + db.session.add(formation) + db.session.flush() + formsemestre = FormSemestre.create_formsemestre( + { + "dept_id": 1, + "titre": "test edit", + "date_debut": "2024-08-01", + "date_fin": "2024-08-31", + "formation_id": formation.id, + "responsables": [u1, -1, -1, -1], + }, + silent=True, + ) + assert formsemestre + assert formsemestre.responsables[0].user_name == u1.user_name + assert formsemestre.from_dict({"responsables": [u2.id, u1, -1, -1]}) + db.session.commit() + assert len(formsemestre.responsables) == 2 + assert sorted(u.user_name for u in formsemestre.responsables) == sorted( + [u1.user_name, u2.user_name] + ) + u3 = User(user_name="un_troisieme_responsable") + assert formsemestre.from_dict({"responsables": [u3]}) + db.session.commit() + assert len(formsemestre.responsables) == 1 + + def test_formsemestre_description(test_client): """Test FormSemestreDescription""" app.set_sco_dept(DEPT)