Propósito
Punto único que cierra, justo después del commit, las conexiones WebSocket que ya estaban abiertas de una organización que se acaba de suspender o borrar. Antes de este cambio (mega-auditoría B-31), suspender una organización impedía abrir conexiones NUEVAS, pero las que ya estaban vivas seguían funcionando hasta la siguiente reconexión: un Agente seguía mandando medidas y un navegador seguía recibiendo el canal en vivo de una organización ya suspendida.
Contrato
close_organization_connections(org_ids) -> None
Programa el aviso con transaction.on_commit (para no avisar antes de que el cambio esté realmente guardado) y, para cada id, manda un mensaje {"type": "organization_suspended"} por el channel layer a dos grupos:
agent_targets_<id>— los Agentes de esa organización.FleetLifecycleMixin.organization_suspendedcierra con el código 4005, sin relevo de Primary; el Agente reintenta solo yconnectlo rechaza mientras la organización siga suspendida.monitoring_<id>— los navegadores conectados al panel de monitorización.MonitoringConsumer.organization_suspendedcorta la conexión (el propioconnectdel consumer ya rechaza a quien intente entrar a una organización suspendida).
Un fallo al avisar a un grupo concreto (excepción del channel layer) se registra como aviso en el log y no interrumpe el resto — la organización ya quedó guardada como suspendida, así que no hay nada que deshacer.
Dependencias
Lo llaman:
core/signals.py::close_connections_on_suspension—post_savedeOrganization, solo cuando pasa a inactiva o borrada (mira los camposis_active/deleted_atenupdate_fieldspara no avisar en un guardado que no toca el estado, como el que hace el webhook de Stripe).core/signals.py::close_connections_on_delete—post_deletedeOrganization.- Las acciones masivas del admin (Disable, Soft-delete), que usan
queryset.update()y por eso NO disparan señales — llaman al servicio directamente.
Usa:
channels.layers.get_channel_layer()+asgiref.sync.async_to_syncpara mandar el mensaje al grupo desde código síncrono (señales, admin).- Los consumers
FleetLifecycleMixin(terminal) yMonitoringConsumer(monitoring), que son quienes de verdad cierran el socket al recibir el mensaje.
Ejemplo de uso
from core.services.org_connections import close_organization_connections
close_organization_connections([org.pk]) # tras guardar o borrar la organización
Límite conocido
Si el channel layer no logra alcanzar a un consumer que vive en OTRO proceso Daphne, ese aviso se pierde en silencio (solo queda un warning en el log, sin reintento) — la conexión sigue viva hasta su próximo evento natural (ping, reconexión). No hay test de ese escenario concreto.
Véase también
- [[entity—core—model—organization]]
- [[concept—general—como-esta-implementado-el-aislamiento-multi-tenant]]