diff --git a/app/api/partitions.py b/app/api/partitions.py index f1092a7ab7..fd94ab96b0 100644 --- a/app/api/partitions.py +++ b/app/api/partitions.py @@ -7,12 +7,13 @@ """ ScoDoc 9 API : partitions """ -from flask import abort, jsonify, request +from flask import jsonify, request import app from app import db, log from app.api import bp from app.api.auth import permission_required_api +from app.api.errors import error_response from app.models import FormSemestre, FormSemestreInscription, Identite from app.models import GroupDescr, Partition from app.models.groups import group_membership @@ -112,7 +113,7 @@ def etud_in_group_query(group_id: int): """Etudiants du groupe, filtrés par état""" etat = request.args.get("etat") if etat not in {scu.INSCRIT, scu.DEMISSION, scu.DEF}: - abort(404, "etat invalid") + return error_response(404, "etat: valeur invalide") group = GroupDescr.query.get_or_404(group_id) query = ( Identite.query.join(FormSemestreInscription) @@ -131,7 +132,7 @@ def set_etud_group(etudid: int, group_id: int): etud = Identite.query.get_or_404(etudid) group = GroupDescr.query.get_or_404(group_id) if etud.id not in {e.id for e in group.partition.formsemestre.etuds}: - abort(404, "etud non inscrit au formsemestre du groupe") + return error_response(404, "etud non inscrit au formsemestre du groupe") groups = ( GroupDescr.query.filter_by(partition_id=group.partition.id) .join(group_membership) @@ -180,13 +181,13 @@ def group_create(partition_id: int): """ partition: Partition = Partition.query.get_or_404(partition_id) if not partition.groups_editable: - abort(404, "partition non editable") + return error_response(404, "partition non editable") data = request.get_json(force=True) # may raise 400 Bad Request group_name = data.get("group_name") if group_name is None: - abort(404, "missing group name or invalid data format") + return error_response(404, "missing group name or invalid data format") if not GroupDescr.check_name(partition, group_name): - abort(404, "invalid group_name") + return error_response(404, "invalid group_name") group_name = group_name.strip() group = GroupDescr(group_name=group_name, partition_id=partition_id) @@ -204,7 +205,7 @@ def group_delete(group_id: int): """Suppression d'un groupe""" group = GroupDescr.query.get_or_404(group_id) if not group.partition.groups_editable: - abort(404, "partition non editable") + return error_response(404, "partition non editable") formsemestre_id = group.partition.formsemestre_id log(f"deleting {group}") db.session.delete(group) @@ -220,12 +221,12 @@ def group_edit(group_id: int): """Edit a group""" group: GroupDescr = GroupDescr.query.get_or_404(group_id) if not group.partition.groups_editable: - abort(404, "partition non editable") + return error_response(404, "partition non editable") data = request.get_json(force=True) # may raise 400 Bad Request group_name = data.get("group_name") if group_name is not None: if not GroupDescr.check_name(group.partition, group_name, existing=True): - abort(404, "invalid group_name") + return error_response(404, "invalid group_name") group.group_name = group_name.strip() db.session.add(group) db.session.commit() @@ -253,12 +254,12 @@ def partition_create(formsemestre_id: int): data = request.get_json(force=True) # may raise 400 Bad Request partition_name = data.get("partition_name") if partition_name is None: - abort(404, "missing partition_name or invalid data format") + return error_response(404, "missing partition_name or invalid data format") if not Partition.check_name(formsemestre, partition_name): - abort(404, "invalid partition_name") + return error_response(404, "invalid partition_name") numero = data.get("numero", 0) if not isinstance(numero, int): - abort(404, "invalid type for numero") + return error_response(404, "invalid type for numero") args = { "formsemestre_id": formsemestre_id, "partition_name": partition_name.strip(), @@ -269,7 +270,7 @@ def partition_create(formsemestre_id: int): boolean_field, False if boolean_field != "groups_editable" else True ) if not isinstance(value, bool): - abort(404, f"invalid type for {boolean_field}") + return error_response(404, f"invalid type for {boolean_field}") args[boolean_field] = value partition = Partition(**args) @@ -281,6 +282,52 @@ def partition_create(formsemestre_id: int): return jsonify(partition.to_dict(with_groups=True)) +@bp.route("/formsemestre//partitions/order", methods=["POST"]) +@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups) +def formsemestre_order_partitions(formsemestre_id: int): + """Modifie l'ordre des partitions du formsemestre + JSON args: [partition_id1, partition_id2, ...] + """ + formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id) + partition_ids = request.get_json(force=True) # may raise 400 Bad Request + if not isinstance(partition_ids, int) and not all( + isinstance(x, int) for x in partition_ids + ): + return error_response( + 404, + message="paramètre liste des partitions invalide", + ) + for p_id, numero in zip(partition_ids, range(len(partition_ids))): + p = Partition.query.get_or_404(p_id) + p.numero = numero + db.session.add(p) + db.session.commit() + return jsonify(formsemestre.to_dict()) + + +@bp.route("/partition//groups/order", methods=["POST"]) +@permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups) +def partition_order_groups(partition_id: int): + """Modifie l'ordre des groupes de la partition + JSON args: [group_id1, group_id2, ...] + """ + partition = Partition.query.get_or_404(partition_id) + group_ids = request.get_json(force=True) # may raise 400 Bad Request + if not isinstance(group_ids, int) and not all( + isinstance(x, int) for x in group_ids + ): + return error_response( + 404, + message="paramètre liste de groupe invalide", + ) + for group_id, numero in zip(group_ids, range(len(group_ids))): + group = GroupDescr.query.get_or_404(group_id) + group.numero = numero + db.session.add(group) + db.session.commit() + return jsonify(partition.to_dict(with_groups=True)) + + @bp.route("/partition//edit", methods=["POST"]) @permission_required_api(Permission.ScoEtudChangeGroups, Permission.APIEditGroups) def partition_edit(partition_id: int): @@ -304,14 +351,14 @@ def partition_edit(partition_id: int): if not Partition.check_name( partition.formsemestre, partition_name, existing=True ): - abort(404, "invalid partition_name") + return error_response(404, "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): - abort(404, "invalid type for numero") + return error_response(404, "invalid type for numero") partition.numero = numero modified = True @@ -319,7 +366,7 @@ def partition_edit(partition_id: int): value = data.get(boolean_field) if value is not None and value != getattr(partition, boolean_field): if not isinstance(value, bool): - abort(404, f"invalid type for {boolean_field}") + return error_response(404, f"invalid type for {boolean_field}") setattr(partition, boolean_field, value) modified = True @@ -345,7 +392,7 @@ def partition_delete(partition_id: int): """ partition = Partition.query.get_or_404(partition_id) if not partition.partition_name: - abort(404, "ne peut pas supprimer la partition par défaut") + return error_response(404, "ne peut pas supprimer la partition par défaut") is_parcours = partition.is_parcours() formsemestre: FormSemestre = partition.formsemestre log(f"deleting partition {partition}") diff --git a/app/models/groups.py b/app/models/groups.py index d6682d5a01..fe5d10414d 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -90,7 +90,7 @@ class Partition(db.Model): d.pop("formsemestre", None) if with_groups: - groups = sorted(self.groups, key=lambda g: g.group_name) + groups = sorted(self.groups, key=lambda g: (g.numero or 0, g.group_name)) # un dict et non plus une liste, pour JSON d["groups"] = { group.id: group.to_dict(with_partition=False) for group in groups @@ -109,6 +109,8 @@ class GroupDescr(db.Model): partition_id = db.Column(db.Integer, db.ForeignKey("partition.id")) # "A", "C2", ... (NULL for 'all'): group_name = db.Column(db.String(GROUPNAME_STR_LEN)) + # Numero = ordre de presentation) + numero = db.Column(db.Integer) etuds = db.relationship( "Identite", @@ -131,9 +133,12 @@ class GroupDescr(db.Model): "id": self.id, "partition_id": self.partition_id, "name": self.group_name, + "numero": self.numero, } if with_partition: - d["partition"] = self.partition.to_dict(with_groups=False) + d["partition"] = sorted( + self.partition, key=lambda p: p.numero or 0 + ).to_dict(with_groups=False) return d @classmethod diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index b1044aea5f..2a9b4dac20 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -87,7 +87,7 @@ partitionEditor = ndb.EditableTable( ) groupEditor = ndb.EditableTable( - "group_descr", "group_id", ("group_id", "partition_id", "group_name") + "group_descr", "group_id", ("group_id", "partition_id", "group_name", "numero") ) group_list = groupEditor.list diff --git a/tests/api/exemple-api-basic.py b/tests/api/exemple-api-basic.py index 112542979c..f43ce833b7 100644 --- a/tests/api/exemple-api-basic.py +++ b/tests/api/exemple-api-basic.py @@ -181,6 +181,20 @@ POST_JSON( POST_JSON(f"/partition/{2379}/delete") +# +POST_JSON( + "/partition/2264/groups/order", + data=[5563, 5562, 5561, 5560, 5558, 5557, 5316, 5315], +) + +POST_JSON( + "/formsemestre/1063/partitions/order", + data=[2264, 2263, 2265, 2266, 2267, 2372, 2378], +) + + +GET(f"/partition/2264") + # Recherche de formsemestres sems = GET(f"/formsemestres/query?etape_apo=V1RT&annee_scolaire=2021")