DeviceTrashEntry · Modelo network
Propósito
Un “dispositivo” en CreaRack Pro no es una fila: son hasta tres modelos enlazados por la IP de gestión (DeviceProfile la ficha descubierta, MonitoringTarget el objetivo de sondeo, racks.Device la colocación en rack). Añadir soft-delete (deleted_at) a los tres obligaría a filtrarlo en cada consulta de esos modelos, incluidas las que usa el Local Agent. DeviceTrashEntry evita eso: al borrar, guarda una instantánea JSON con todo lo necesario para recrear las tres caras, y borra de verdad los originales. Restaurar = recrear desde la instantánea.
Límite honesto (documentado en el propio docstring del modelo): el histórico de métricas del MonitoringTarget no vuelve — se pierde en cascada al borrar, igual que ya pasaba con el borrado de Devices desde la v1.35.0.
Contrato
network/models/trash.py:
| Campo | Tipo | Notas |
|---|---|---|
organization | FK → core.Organization | on_delete=CASCADE, related_name="device_trash_entries" |
name | CharField(200) | nombre del dispositivo en el momento del borrado |
ip_address | CharField(45) | IP de gestión; vacía si la entrada es solo una colocación en rack sin IP |
kind | CharField(20) | assigned_page del perfil (wireless/ups/signage) o "rack" |
snapshot | JSONField | {"profile": {...}|null, "targets": [...], "rack_devices": [...]} |
deleted_at | DateTimeField(auto_now_add=True) | ordena la papelera (-deleted_at) |
deleted_by | FK → core.User, SET_NULL | quién borró |
deleted_from | CharField(40) | superficie de origen (Observatory, Wireless, Rack Editor…) |
Índice compuesto (organization, deleted_at). Propiedad summary arma un texto corto (“profile · monitoring · rack X”) a partir de lo que contenga la instantánea.
Dependencias
Qué usa — network/services/device_trash.py:
trash_device(org, ip=..., rack_device=..., user=..., source=...): localizaDeviceProfile+MonitoringTarget(s) +racks.Device(s) que comparten la IP, los serializa (_snapshot, que copia campos concretos y convierte FKs a<campo>_id) y los borra de verdad en orden (targets → colocaciones → ficha).restore_entry(entry): recrea desde la instantánea lo que no exista ya; filtra la instantánea a los campos que el modelo tiene HOY (_restore_kwargs, tolera columnas retiradas desde el borrado). Cada pieza puede saltarse y quedar enresult["skipped"]por tres motivos: la IP ya está ocupada por una ficha/target re-descubierto, la posición U del rack ya no está libre (_placement_conflict, valida con el mismo validador canónico que usa el Rack Editor,racks.utils.placement.validate_device_placements— cubre solape y que el rack no haya encogido), o el rack destino está en la papelera de armarios. Fix v1.125.0 (Ronda 30-08 Tanda 4, #281 pt.6): la búsqueda del rack destino (Rack.objects.filter(organization=org, id=d.get("rack_id"))) no comprobabadeleted_at__isnull=True, así que un equipo podía “renacer” dentro de un armario invisible (borrado pero no purgado). Ahora ese caso se salta con motivo ("no longer exists") igual que un rack purgado de verdad, y la entrada de papelera se conserva. Fix v1.96.0 (Tanda 7, #482): la entrada de papelera solo se borra si se recreó algo de verdad (result["restored"]); antes se borraba siempre al terminar, así que un restore que se saltaba todo (IP ocupada, rack desaparecido) destruía la única copia del equipo sin recrear nada — yPOST /api/trash/devices/{id}/restorerespondía igualmente"Device restored"consuccess: true. Ahora, sirestoredesfalse, la entrada sigue en la papelera y el endpoint respondesuccess: falsecon el detalle de qué se saltó.
Quién la usa:
network/api/device_trash.py→POST /api/network/device-trash(trash_device_everywhere): entrada desde Observatory/Wireless/UPS/Signage y desderacks/services/racks.pyal guardar un rack con dispositivos retirados.racks/api/trash.py→GET /api/trash/devices,POST /api/trash/devices/{id}/restore,DELETE /api/trash/devices/{id},DELETE /api/trash/devices(listar, restaurar, borrar una entrada, vaciar). Estos endpoints conviven con los ya existentes de racks y blueprints bajo el mismo router de papelera (GET /api/trash/countahora suma tambiéndevices).
Ejemplo de uso
from network.services.device_trash import trash_device, restore_entry
entry = trash_device(org, ip="10.0.0.5", user=request.user, source="observatory")
# ...
result = restore_entry(entry) # recrea lo que no exista ya; result["restored"] indica si de verdad recreó algo
Véase también
- [[entity—network—model—deviceprofile]]
- [[entity—monitoring—model—monitoringtarget]]
- [[entity—racks—model—device]]
- [[crearack—network—device-page]]
- [[crearack—racks—trash-and-restore]]
- [[concept—saas—multi-tenancy]]
- [[decision—20260403—multi-tenancy-rls]]
- [[feature—network—papelera-dispositivos]]