Skip to content
Snippets Groups Projects
Select Git revision
  • b556719b94ea17eacf225b20a487cb0c198c7a20
  • main default protected
  • 11-flip-coin
  • 10-generics
  • 09.5-before-main-generic
  • 09-modules
  • 08-Display
  • 07-trait-derive
  • 06-trait
  • 05-unit-tests
  • 04-impl-for-board
  • 03-board-struct
  • 02-typed-squares
  • 01-simple-board
14 results

main.rs

Blame
  • partitions.py 24.14 KiB
    ##############################################################################
    # ScoDoc
    # Copyright (c) 1999 - 2025 Emmanuel Viennet.  All rights reserved.
    # See LICENSE
    ##############################################################################
    
    """
    ScoDoc 9 API : partitions
    
    CATEGORY
    --------
    Groupes et Partitions
    
    """
    from operator import attrgetter
    
    from flask import g, request
    from flask_json import as_json
    from flask_login import login_required
    import sqlalchemy as sa
    from sqlalchemy.exc import IntegrityError
    
    import app
    from app import db, log
    from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
    from app.api import api_permission_required as permission_required
    from app.decorators import scodoc
    from app.scodoc.sco_utils import json_error
    from app.models import FormSemestre, FormSemestreInscription, Identite
    from app.models import GroupDescr, Partition, Scolog
    from app.models.groups import group_membership
    from app.scodoc import sco_cache
    from app.scodoc import sco_groups
    from app.scodoc.sco_exceptions import ScoValueError
    from app.scodoc.sco_permissions import Permission
    from app.scodoc import sco_utils as scu
    
    
    @bp.route("/partition/<int:partition_id>")
    @api_web_bp.route("/partition/<int:partition_id>")
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_info(partition_id: int):
        """Info sur une partition.
    
        SAMPLES
        -------
        /partition/1
        """
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition = query.first_or_404()
        return partition.to_dict(with_groups=True)
    
    
    @bp.route("/formsemestre/<int:formsemestre_id>/partitions")
    @api_web_bp.route("/formsemestre/<int:formsemestre_id>/partitions")
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def formsemestre_partitions(formsemestre_id: int):
        """Liste de toutes les partitions d'un formsemestre.
    
        SAMPLES
        -------
        /formsemestre/1/partitions
        """
        query = FormSemestre.query.filter_by(id=formsemestre_id)
        if g.scodoc_dept:
            query = query.filter_by(dept_id=g.scodoc_dept_id)
        formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
        partitions = sorted(formsemestre.partitions, key=attrgetter("numero"))
        return {
            str(partition.id): partition.to_dict(with_groups=True, str_keys=True)
            for partition in partitions
            if partition.partition_name is not None
        }
    
    
    @bp.route("/group/<int:group_id>/etudiants")
    @api_web_bp.route("/group/<int:group_id>/etudiants")
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_etudiants(group_id: int):
        """
        Retourne la liste des étudiants dans un groupe
        (inscrits au groupe et inscrits au semestre).
    
        PARAMS
        ------
        group_id : l'id d'un groupe
    
        SAMPLES
        -------
        /group/1/etudiants
        """
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group = query.first_or_404()
    
        query = (
            Identite.query.join(group_membership)
            .filter_by(group_id=group_id)
            .join(FormSemestreInscription)
            .filter_by(formsemestre_id=group.partition.formsemestre_id)
        )
    
        return [etud.to_dict_short() for etud in query]
    
    
    @bp.route("/group/<int:group_id>/etudiants/query")
    @api_web_bp.route("/group/<int:group_id>/etudiants/query")
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_etudiants_query(group_id: int):
        """Étudiants du groupe, filtrés par état (aucun, `I`, `D`, `DEF`)
    
        QUERY
        -----
        etat : string
    
        """
        etat = request.args.get("etat")
        if etat not in {None, scu.INSCRIT, scu.DEMISSION, scu.DEF}:
            return json_error(API_CLIENT_ERROR, "etat: valeur invalide")
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group = query.first_or_404()  # just to ckeck that group exists in accessible dept
    
        query = Identite.query.join(FormSemestreInscription).filter_by(
            formsemestre_id=group.partition.formsemestre_id
        )
        if etat is not None:
            query = query.filter_by(etat=etat)
    
        query = query.join(group_membership).filter_by(group_id=group_id)
        return [etud.to_dict_short() for etud in query]
    
    
    @bp.route("/group/<int:group_id>/set_etudiant/<int:etudid>", methods=["POST"])
    @api_web_bp.route("/group/<int:group_id>/set_etudiant/<int:etudid>", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_set_etudiant(group_id: int, etudid: int):
        """Affecte l'étudiant au groupe indiqué."""
        etud = Identite.get_or_404(etudid)
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group = query.first_or_404()
        if not group.partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not group.partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        if etud.id not in {e.id for e in group.partition.formsemestre.etuds}:
            return json_error(404, "etud non inscrit au formsemestre du groupe")
    
        try:
            sco_groups.change_etud_group_in_partition(etudid, group)
        except ScoValueError as exc:
            return json_error(404, exc.args[0])
        except IntegrityError:
            return json_error(404, "échec de l'enregistrement")
        return {"group_id": group_id, "etudid": etudid}
    
    
    @bp.route("/group/<int:group_id>/remove_etudiant/<int:etudid>", methods=["POST"])
    @api_web_bp.route(
        "/group/<int:group_id>/remove_etudiant/<int:etudid>", methods=["POST"]
    )
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_remove_etud(group_id: int, etudid: int):
        """Retire l'étudiant de ce groupe. S'il n'y est pas, ne fait rien."""
        etud = Identite.get_or_404(etudid)
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group = query.first_or_404()
        if not group.partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not group.partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
    
        group.remove_etud(etud)
    
        return {"group_id": group_id, "etudid": etudid}
    
    
    @bp.route(
        "/partition/<int:partition_id>/remove_etudiant/<int:etudid>", methods=["POST"]
    )
    @api_web_bp.route(
        "/partition/<int:partition_id>/remove_etudiant/<int:etudid>", methods=["POST"]
    )
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_remove_etud(partition_id: int, etudid: int):
        """Enlève l'étudiant de tous les groupes de cette partition.
    
        (NB: en principe, un étudiant ne doit être que dans 0 ou 1 groupe d'une partition)
        """
        etud = Identite.get_or_404(etudid)
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition = query.first_or_404()
        if not partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        db.session.execute(
            sa.text(
                """DELETE FROM group_membership
            WHERE etudid=:etudid
            and group_id IN (
                SELECT id FROM group_descr WHERE partition_id = :partition_id
            );
            """
            ),
            {"etudid": etudid, "partition_id": partition_id},
        )
    
        Scolog.logdb(
            method="partition_remove_etud",
            etudid=etud.id,
            msg=f"Retrait de la partition {partition.partition_name}",
            commit=False,
        )
        db.session.commit()
        # Update parcours
        partition.formsemestre.update_inscriptions_parcours_from_groups(etudid=etudid)
        app.set_sco_dept(partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(partition.formsemestre_id)
        return {"partition_id": partition_id, "etudid": etudid}
    
    
    @bp.route("/partition/<int:partition_id>/group/create", methods=["POST"])
    @api_web_bp.route("/partition/<int:partition_id>/group/create", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_create(partition_id: int):  # partition-group-create
        """Création d'un groupe dans une partition.
    
        DATA
        ----
        ```json
        {
            "group_name" : nom_du_groupe,
        }
        ```
    
        SAMPLES
        -------
        /partition/1/group/create;{""group_name"" : ""Nouveau Groupe""}
        """
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition: Partition = query.first_or_404()
        if not partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not partition.groups_editable:
            return json_error(403, "partition non editable")
        if not partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
    
        args = request.get_json(force=True)  # may raise 400 Bad Request
        group_name = args.get("group_name")
        if not isinstance(group_name, str):
            return json_error(API_CLIENT_ERROR, "missing group name or invalid data format")
        args["group_name"] = args["group_name"].strip()
        if not GroupDescr.check_name(partition, args["group_name"]):
            return json_error(API_CLIENT_ERROR, "invalid group_name")
    
        # le numero est optionnel
        numero = args.get("numero")
        if numero is None:
            numeros = [gr.numero or 0 for gr in partition.groups]
            numero = (max(numeros) + 1) if numeros else 0
            args["numero"] = numero
        args["partition_id"] = partition_id
        try:
            group = GroupDescr(**args)
        except TypeError:
            return json_error(API_CLIENT_ERROR, "invalid arguments")
        db.session.add(group)
        db.session.commit()
        log(f"created group {group}")
        app.set_sco_dept(partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(partition.formsemestre_id)
        return group.to_dict(with_partition=True)
    
    
    @bp.route("/group/<int:group_id>/delete", methods=["POST"])
    @api_web_bp.route("/group/<int:group_id>/delete", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_delete(group_id: int):
        """Suppression d'un groupe."""
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group: GroupDescr = query.first_or_404()
        if not group.partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not group.partition.groups_editable:
            return json_error(403, "partition non editable")
        if not group.partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        formsemestre_id = group.partition.formsemestre_id
        log(f"deleting {group}")
        db.session.delete(group)
        db.session.commit()
        app.set_sco_dept(group.partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(formsemestre_id)
        return {"OK": True}
    
    
    @bp.route("/group/<int:group_id>/edit", methods=["POST"])
    @api_web_bp.route("/group/<int:group_id>/edit", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_edit(group_id: int):
        """Édition d'un groupe.
    
        DATA
        ----
        ```json
        {
            "group_name" : "A1"
        }
    
        SAMPLES
        -------
        /group/1/edit;{""group_name"":""A1""}
        """
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group: GroupDescr = query.first_or_404()
        if not group.partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not group.partition.groups_editable:
            return json_error(403, "partition non editable")
        if not group.partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
    
        args = request.get_json(force=True)  # may raise 400 Bad Request
        if "group_name" in args:
            if not isinstance(args["group_name"], str):
                return json_error(API_CLIENT_ERROR, "invalid data format for group_name")
            args["group_name"] = args["group_name"].strip() if args["group_name"] else ""
            if not GroupDescr.check_name(
                group.partition, args["group_name"], existing=True
            ):
                return json_error(API_CLIENT_ERROR, "invalid group_name")
    
        group.from_dict(args)
        db.session.add(group)
        db.session.commit()
        log(f"modified {group}")
    
        app.set_sco_dept(group.partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
        return group.to_dict(with_partition=True)
    
    
    @bp.route("/group/<int:group_id>/set_edt_id/<string:edt_id>", methods=["POST"])
    @api_web_bp.route("/group/<int:group_id>/set_edt_id/<string:edt_id>", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def group_set_edt_id(group_id: int, edt_id: str):
        """Set edt_id du groupe.
    
        Contrairement à `/edit`, peut-être changé pour toute partition
        d'un formsemestre non verrouillé.
    
        SAMPLES
        -------
        /group/1/set_edt_id/EDT_GR1
        """
        query = GroupDescr.query.filter_by(id=group_id)
        if g.scodoc_dept:
            query = (
                query.join(Partition).join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
            )
        group: GroupDescr = query.first_or_404()
        if not group.partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        log(f"group_set_edt_id( {group_id}, '{edt_id}' )")
        group.edt_id = edt_id
        db.session.add(group)
        db.session.commit()
        return group.to_dict(with_partition=True)
    
    
    @bp.route("/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"])
    @api_web_bp.route(
        "/formsemestre/<int:formsemestre_id>/partition/create", methods=["POST"]
    )
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_create(formsemestre_id: int):
        """Création d'une partition dans un semestre.
    
        DATA
        ----
        ```json
        {
            "partition_name": str,
            "numero": int,
            "bul_show_rank": bool,
            "show_in_lists": bool,
            "groups_editable": bool
        }
        ```
        """
        query = FormSemestre.query.filter_by(id=formsemestre_id)
        if g.scodoc_dept:
            query = query.filter_by(dept_id=g.scodoc_dept_id)
        formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
        if not formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        data = request.get_json(force=True)  # may raise 400 Bad Request
        partition_name = data.get("partition_name")
        if partition_name is None:
            return json_error(
                API_CLIENT_ERROR, "missing partition_name or invalid data format"
            )
        if partition_name == scu.PARTITION_PARCOURS:
            return json_error(
                API_CLIENT_ERROR, f"invalid partition_name {scu.PARTITION_PARCOURS}"
            )
        if not Partition.check_name(formsemestre, partition_name):
            return json_error(API_CLIENT_ERROR, "invalid partition_name")
        numero = data.get("numero", 0)
        if not isinstance(numero, int):
            return json_error(API_CLIENT_ERROR, "invalid type for numero")
        args = {
            "formsemestre_id": formsemestre_id,
            "partition_name": partition_name.strip(),
            "numero": numero,
        }
        for boolean_field in ("bul_show_rank", "show_in_lists", "groups_editable"):
            value = data.get(
                boolean_field, False if boolean_field != "groups_editable" else True
            )
            if not isinstance(value, bool):
                return json_error(API_CLIENT_ERROR, f"invalid type for {boolean_field}")
            args[boolean_field] = value
    
        partition = Partition(**args)
        db.session.add(partition)
        db.session.commit()
        log(f"created partition {partition}")
        app.set_sco_dept(formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(formsemestre_id)
        return partition.to_dict(with_groups=True)
    
    
    @bp.route("/formsemestre/<int:formsemestre_id>/partitions/order", methods=["POST"])
    @api_web_bp.route(
        "/formsemestre/<int:formsemestre_id>/partitions/order", methods=["POST"]
    )
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def formsemestre_set_partitions_order(formsemestre_id: int):
        """Modifie l'ordre des partitions du formsemestre.
    
        DATA
        ----
        ```json
        [ partition_id1, partition_id2, ... ]
        ```
        """
        query = FormSemestre.query.filter_by(id=formsemestre_id)
        if g.scodoc_dept:
            query = query.filter_by(dept_id=g.scodoc_dept_id)
        formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
        if not formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        partition_ids = request.get_json(force=True)  # may raise 400 Bad Request
        if not isinstance(partition_ids, list) and not all(
            isinstance(x, int) for x in partition_ids
        ):
            return json_error(
                API_CLIENT_ERROR,
                message="paramètre liste des partitions invalide",
            )
        for p_id, numero in zip(partition_ids, range(len(partition_ids))):
            partition = Partition.get_or_404(p_id)
            partition.numero = numero
            db.session.add(partition)
        db.session.commit()
        app.set_sco_dept(formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(formsemestre_id)
        log(f"formsemestre_set_partitions_order({partition_ids})")
        return [
            partition.to_dict()
            for partition in formsemestre.partitions.order_by(Partition.numero)
            if partition.partition_name is not None
        ]
    
    
    @bp.route("/partition/<int:partition_id>/groups/order", methods=["POST"])
    @api_web_bp.route("/partition/<int:partition_id>/groups/order", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_order_groups(partition_id: int):
        """Modifie l'ordre des groupes de la partition.
    
        DATA
        ----
        ```json
        [ group_id1, group_id2, ... ]
        ```
        """
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition: Partition = query.first_or_404()
        if not partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        group_ids = request.get_json(force=True)  # may raise 400 Bad Request
        if not isinstance(group_ids, list) and not all(
            isinstance(x, int) for x in group_ids
        ):
            return json_error(
                API_CLIENT_ERROR,
                message="paramètre liste de groupe invalide",
            )
        for group_id, numero in zip(group_ids, range(len(group_ids))):
            group = GroupDescr.get_or_404(group_id)
            group.numero = numero
            db.session.add(group)
        db.session.commit()
        app.set_sco_dept(partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(partition.formsemestre_id)
        log(f"partition_order_groups: {partition} : {group_ids}")
        return partition.to_dict(with_groups=True)
    
    
    @bp.route("/partition/<int:partition_id>/edit", methods=["POST"])
    @api_web_bp.route("/partition/<int:partition_id>/edit", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_edit(partition_id: int):
        """Modification d'une partition dans un semestre.
    
        Tous les champs sont optionnels.
    
        DATA
        ----
        ```json
        {
            "partition_name": str,
            "numero":int,
            "bul_show_rank":bool,
            "show_in_lists":bool,
            "groups_editable":bool
        }
        ```
    
        SAMPLES
        -------
        /partition/1/edit;{""bul_show_rank"":1}
        """
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition: Partition = query.first_or_404()
        if not partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        data = request.get_json(force=True)  # may raise 400 Bad Request
        modified = False
        partition_name = data.get("partition_name")
        #
        if partition_name is not None and partition_name != partition.partition_name:
            if partition.is_parcours():
                return json_error(
                    API_CLIENT_ERROR, f"can't rename {scu.PARTITION_PARCOURS}"
                )
            if not Partition.check_name(
                partition.formsemestre, partition_name, existing=True
            ):
                return json_error(API_CLIENT_ERROR, "invalid partition_name")
            partition.partition_name = partition_name.strip()
            modified = True
    
        numero = data.get("numero")
        if numero is not None and numero != partition.numero:
            if not isinstance(numero, int):
                return json_error(API_CLIENT_ERROR, "invalid type for numero")
            partition.numero = numero
            modified = True
    
        for boolean_field in ("bul_show_rank", "show_in_lists", "groups_editable"):
            value = data.get(boolean_field)
            value = scu.to_bool(value) if value is not None else None
            if value is not None and value != getattr(partition, boolean_field):
                if boolean_field == "groups_editable" and partition.is_parcours():
                    return json_error(
                        API_CLIENT_ERROR, f"can't change {scu.PARTITION_PARCOURS}"
                    )
                setattr(partition, boolean_field, value)
                modified = True
    
        if modified:
            db.session.add(partition)
            db.session.commit()
            log(f"modified partition {partition}")
            app.set_sco_dept(partition.formsemestre.departement.acronym)
            sco_cache.invalidate_formsemestre(partition.formsemestre_id)
    
        return partition.to_dict(with_groups=True)
    
    
    @bp.route("/partition/<int:partition_id>/delete", methods=["POST"])
    @api_web_bp.route("/partition/<int:partition_id>/delete", methods=["POST"])
    @login_required
    @scodoc
    @permission_required(Permission.ScoView)
    @as_json
    def partition_delete(partition_id: int):
        """Suppression d'une partition (et de tous ses groupes).
    
        * Note 1: La partition par défaut (tous les étudiants du sem.) ne peut
            pas être supprimée.
        * Note 2: Si la partition de parcours est supprimée, les étudiants
            sont désinscrits des parcours.
        """
        query = Partition.query.filter_by(id=partition_id)
        if g.scodoc_dept:
            query = query.join(FormSemestre).filter_by(dept_id=g.scodoc_dept_id)
        partition: Partition = query.first_or_404()
        if not partition.formsemestre.etat:
            return json_error(403, "formsemestre verrouillé")
        if not partition.formsemestre.can_change_groups():
            return json_error(403, "opération non autorisée")
        if not partition.partition_name:
            return json_error(
                API_CLIENT_ERROR, "ne peut pas supprimer la partition par défaut"
            )
        is_parcours = partition.is_parcours()
        formsemestre: FormSemestre = partition.formsemestre
        log(f"deleting partition {partition}")
        db.session.delete(partition)
        db.session.commit()
        app.set_sco_dept(partition.formsemestre.departement.acronym)
        sco_cache.invalidate_formsemestre(formsemestre.id)
        if is_parcours:
            formsemestre.update_inscriptions_parcours_from_groups()
        return {"OK": True}