{
“last_verified”: “2026-09-06T00:00:00Z”,
“tags”: [“racks”, “model”, “saas”, “papelera”, “soft-delete”, “access-control”, “security”],
“sources”: [
{“type”: “code”, “ref”: “racks/models.py”, “last_seen”: “2026-04-21T14:00:00Z”},
{“type”: “code”, “ref”: “racks/services/racks.py”, “last_seen”: “2026-09-06T00:00:00Z”},
{“type”: “code”, “ref”: “racks/api/racks.py”, “last_seen”: “2026-09-06T00:00:00Z”},
{“type”: “code”, “ref”: “racks/api/labels.py”, “last_seen”: “2026-09-06T00:00:00Z”},
{“type”: “code”, “ref”: “racks/api/export/csv_export.py”, “last_seen”: “2026-09-06T00:00:00Z”},
{“type”: “code”, “ref”: “racks/views.py”, “last_seen”: “2026-09-06T00:00:00Z”},
{“type”: “commit”, “ref”: “61545aa71a80676d6ed23422a941787a4240aed7”, “last_seen”: “2026-09-06T00:00:00Z”}
],
“content”: ”# Rack · Modelo racks\n\n## Propósito\n\nRack representa un armario o gabinete servidor dentro de un tenant SaaS. Es la entidad central del editor de racks: contiene Devices, soporta jerarquía padre-hijo, soft-delete (papelera) y agrupación lógica mediante RackGroup.\n\n## Contrato\n\nCampos:\n\n| Campo | Tipo | Notas |\n|---|---|---|\n| name | CharField(100) | Nombre visible |\n| location | CharField(100) | Ubicación física, opcional |\n| notes | TextField | Notas libres, opcional |\n| height_u | IntegerField | Altura en unidades de rack; default 42 |\n| power_consumption | FloatField | Consumo estimado en kWh; default 0.0 |\n| status | CharField(50) | Choices: active, planned, deprecated, maintenance; default active |\n| organization | FK(Organization, CASCADE) | Aislamiento SaaS obligatorio |\n| parent_rack | FK(\"self\", SET_NULL, null) | Jerarquía padre-hijo; related_name children_racks |\n| groups | M2M(RackGroup) | Agrupación lógica; opcional |\n| created_at / updated_at | DateTimeField | Auto-gestionados |\n| deleted_at | DateTimeField(null, db_index) | Soft-delete: non-null indica papelera |\n| is_template | BooleanField(default=False, db_index) | Marca el rack como plantilla |\n\nMétodos públicos:\n\n- soft_delete() — deleted_at = timezone.now() y guarda con update_fields (racks/models.py:121).\n- restore() — deleted_at = None y guarda (racks/models.py:129).\n- is_deleted (property) — True si deleted_at is not None.\n- __str__ — devuelve \"{name} @ {organization}\".\n\nMeta:\n\n- Índice compuesto idx_rack_org_deleted sobre (organization, deleted_at) para filtrar racks activos por tenant.\n- Sin unique_together: el nombre puede repetirse entre organizaciones distintas.\n\n## Papelera: invariante de acceso obligatorio (tasks #295, #282.2)\n\nTodo acceso a un Rack por id — fuera de los listados que ya excluían la papelera — debe incluir deleted_at__isnull=True en el filtro. Hasta el commit 61545aa7 (v1.123.0, PR #516) faltaba en 7 puntos: un armario movido a la papelera seguía siendo accesible por URL/API directa (se podía abrir, editar, clonar, exportar o re-etiquetar).\n\n| Punto de acceso corregido | Archivo |\n|---|---|\n| get_rack (detalle) | racks/api/racks.py |\n| rename_rack_legacy | racks/api/racks.py |\n| delete_rack (evitaba re-estampar deleted_at en doble borrado) | racks/api/racks.py |\n| clone_rack | racks/api/racks.py |\n| bulk_assign_groups | racks/api/racks.py |\n| RackService.update_rack | racks/services/racks.py |\n| Editor del rack (rack_editor) | racks/views.py |\n| Etiquetas QR (rack individual y equipos) | racks/api/labels.py |\n| Exportación CSV (inventario y rack individual) | racks/api/export/csv_export.py |\n\nUn test de contrato (tests/racks/test_ronda_0906_papelera.py, 12 casos) fija el comportamiento: rack en papelera → 404 y sin cambios en BD; rack vivo → comportamiento idéntico al previo.\n\n## Dependencias entrantes\n\n| Archivo | Uso |\n|---|---|\n| racks/services/racks.py | RackService CRUD + bulk_update_devices |\n| core/htmx_views.py | CRUD HTMX: create, duplicate, soft-delete, restore, empty trash |\n| core/views.py | Editor carga racks activos con prefetch_related |\n| blueprints/models.py | RackPlacement.rack FK |\n| blueprints/services/blueprints.py | Resuelve racks con Rack.objects.in_bulk |\n| blueprints/services/autoplan.py | Crea racks desde análisis IA |\n| core/services/backup_service.py | Serializa racks en backup de organización |\n| core/api/search.py | Búsqueda global por nombre |\n| core/api/status.py | Cuenta racks por organización |\n| core/workspace_api.py | Métricas del workspace |\n| racks/api/racks.py | Endpoints Ninja: detalle, rename, delete, clone, bulk_assign_groups |\n| racks/api/labels.py | Etiquetas QR de racks y equipos |\n| racks/api/export/csv_export.py | Exportación CSV de inventario |\n| racks/views.py | Vista del editor (rack_editor) |\n\n## Dependencias salientes\n\n- core.models.Organization — FK obligatoria.\n- racks.models.RackGroup — M2M opcional.\n- racks.models.Rack (self) — FK auto-referencial.\n- django.utils.timezone — importado dentro de soft_delete().\n- network.models.DeviceProfile — referenciado indirectamente desde RackService._try_link_device_profile.\n- network.services.auto_config.AutoConfigService — invocado tras link de DeviceProfile.\n\n## Ejemplos\n\npython\n# Creación atómica (racks/services/racks.py:88-102)\n@staticmethod\n@transaction.atomic\ndef create_rack(organization: Organization, data: dict) -> Rack:\n rack = Rack(\n name=data.get(\"name\", \"New Rack\"),\n height_u=data.get(\"height_u\", 42),\n status=data.get(\"status\", \"active\"),\n organization=organization,\n )\n rack.save()\n return rack\n\n# Soft-delete (racks/models.py:121-135)\ndef soft_delete(self):\n from django.utils import timezone\n self.deleted_at = timezone.now()\n self.save(update_fields=[\"deleted_at\"])\n\n\n## Related\n\n- entity--racks--model--device — hijo vía FK CASCADE; bulk_update_devices sincroniza atómicamente.\n- entity--racks--model--stencil — plantillas referenciadas desde model_data de Device.\n- entity--core--model--organization — propietario del rack.\n- concept--racks--editor — consumidor primario del modelo.\n\n## Véase también\n\n- [[entity—racks—model—device]]\n- [[entity—racks—model—stencil]]\n- [[entity—core—model—organization]]\n- [[concept—racks—editor]]\n”
}
Referenciado desde
- ¿Cómo está modelado un Rack y los dispositivos/stencils que se colocan en él en la app racks? ¿Cómo se representan las unidades U y las medias unidades (half-U), y existe ya algún concepto de orientac
- Agente · dev-rack-editor
- Auditoría Suprema — Racks: 6 arreglos de seguridad ALTA (s104)
- Auditoría Suprema 2 · Tanda 1 (core) — cerrar gates de permiso que faltaban
- Blueprint · Modelo blueprints
- Clonar un rack de la Fila con un clic (v1.78.0)
- Device · Modelo racks
- Front/Back emparejados en el editor de racks — la trasera es la espalda del mismo aparato (v1.80.0)
- Incidente: el drop en la vista Back moría en un TypeError silencioso (targetU const)
- Modelo RackIntegritySnapshot — foto diaria de integridad plano↔realidad
- Organization · Modelo core
- Rack Editor
- Rack Editor - Documentación Técnica
- Restauración aditiva con confirmación (v1.66.2 · Fix integridad datos)
- Servicio de secciones del Dashboard (drag&drop)
- Servicio de seguridad en filesystem (safe-fs)
- Servicio: Motor de Integridad (compute + gate)
- Stencil · Modelo racks
- Toggle Stencils/Descriptions en la vista de Fila