Endpoint POST /api/blueprints/reorder (Reorden de secciones)
Descripción
POST /api/blueprints/reorder es el controlador backend que actualiza el orden manual (sort_order) de las secciones del Dashboard (Maps/Rows). Se invoca al soltar un drag&drop en el frontend.
Firma
# blueprints/api/blueprints.py
@router.post("/reorder", response={200: dict})
def reorder_blueprints(request, payload: BlueprintsReorderSchema):
"""POST /api/blueprints/reorder — orden manual de las secciones del Dashboard.
Asigna sort_order = índice en blueprint_ids (scoped a la org; ids ajenos o
borrados se ignoran). Los blueprints no incluidos conservan su sort_order
(los nuevos, NULL, siguen al final). Ruta estática ANTES de las dinámicas.
"""
Parámetros
Request Payload
class BlueprintsReorderSchema(Schema):
blueprint_ids: list[int]
blueprint_ids: lista ordenada de IDs de blueprints (maps/rows).- Orden: el índice en la lista se asigna como
sort_orderen la BD. - IDs ajenos (otra organización): ignorados.
- IDs borrados (
deleted_at != NULL): ignorados. - IDs no existentes: ignorados.
- Orden: el índice en la lista se asigna como
Response
Status 200 (éxito):
{
"ok": true,
"count": 3
}
count: número de blueprints actualizados (≤ longitud deblueprint_ids).
Status 403 (sin permisos):
{
"detail": "Permission denied: blueprints.edit"
}
Autorización
- Requerida:
require_perm(request, "blueprints", "edit")(editor de maps/filas). - Scoping: automático a
organization=current_org(request). - Readonly (viewer): recibe 403, no actualiza.
Lógica
- Validación de permisos:
require_perm(request, "blueprints", "edit"). - Obtener org actual:
org = get_current_org(request). - Transacción atómica:
with transaction.atomic():. - Lock para concurrencia:
select_for_update(). - Filtrar por org + no borrados:
bps = {b.id: b for b in Blueprint.objects .select_for_update() .filter(organization=org, deleted_at__isnull=True) } - Asignar índices:
for idx, bp_id in enumerate(payload.blueprint_ids): bp = bps.get(bp_id) # None si ajena o borrada if bp: bp.sort_order = idx to_update.append(bp) - Bulk update en una sola query:
Blueprint.objects.bulk_update(to_update, ["sort_order"]). - Respuesta:
{ "ok": True, "count": len(to_update) }.
Comportamiento importante
- Blueprints NO incluidos en
blueprint_idsconservan susort_orderanterior (ejemplo: si existen 5 maps pero solo reordenas 3, los 2 restantes no se tocan). - Blueprints nuevos (sin mapeo a drag&drop aún) tienen
sort_order=NULL→ aparecen al final. - Sin error en IDs ajenos: la lista se filtra en silencio (idempotente, seguro).
- Sin error en IDs duplicados: aunque teóricamente no debería haber, se asignaría el último índice recibido.
Concurrencia
select_for_update()bloquea para lectura y escritura durante la transacción.- Múltiples reorder simultáneos se ejecutan en serie (FIFO).
- El navegador cliente, si falla, hace
window.location.reload()para restaurar el estado real.
Tests
tests/api/test_blueprints_reorder.py:
def test_reorder_applies_index_order(admin_user, organization):
# Verifica que C, A, B → sort_order (0, 1, 2)
def test_foreign_and_deleted_ids_ignored(admin_user, organization):
# Verifica que IDs ajenas/borradas no cuentan; count=1
def test_viewer_cannot_reorder(viewer_user, organization):
# Verifica que viewer 403
Todos verdes ✅.
Histórico
- v1.50.0 (2026-07-13): creación de endpoint (Edu + Claude Code).
Véase también
- [[feature—dashboard—reorder-sections-dragdrop]]
- [[entity—blueprints—model—blueprint-sort-order]]
- [[entity—blueprints—model—blueprint]]
- [[entity—core—model—organization]]