SaaSModule · Modelo core
Propósito
SaaSModule es el registro de módulos de aplicación que pueden habilitarse o deshabilitarse por organización. Cada instancia representa una funcionalidad discreta (racks, network-maps, digital-signage, etc.) identificada por un slug único. El middleware ModuleGatingMiddleware consulta este modelo en cada request para decidir si una URL está accesible. Los módulos con is_core=True están siempre activos sin importar el plan.
Contrato
Campos:
| Campo | Tipo | Notas |
|---|---|---|
slug | SlugField(50, unique) | Identificador canónico usado en registry y middleware |
name | CharField(100) | Nombre legible para admin |
description | TextField(blank) | Descripción opcional |
url_prefixes | JSONField(default=list) | Referencia documental; la fuente de verdad runtime es module_registry.URL_TO_MODULE |
is_core | BooleanField(default=False) | Si True, acceso incondicional |
sort_order | IntegerField(default=0) | Orden de presentación |
Meta: ordering = ["sort_order", "name"], verbose_name = "Module". Sin manager personalizado.
Nota:
ModulePermissionNO tiene FK directo aSaaSModule. Almacena permisos granulares en unJSONFieldlibre ({"scope": "level"}), sin relación de BD.
Dependencias entrantes
| Archivo | Uso |
|---|---|
core/models.py:110 (Organization.get_active_modules) | SaaSModule.objects.filter(is_core=True).values_list("slug", flat=True) |
core/middleware/module_gating.py | Invoca org.get_active_modules() en cada request no exento |
core/api/signup.py | Al crear organización añade módulos core a extra_modules |
core/admin.py | SaaSModuleAdmin y OrganizationAdmin.save_related sincronizan core modules |
tests/conftest.py, tests/api/test_tenant_isolation.py | Enlaza todos los módulos a organizaciones de test |
Dependencias salientes
Plan.modules— M2M inversa (related_name="plans"). Un módulo puede estar en cero, uno o varios planes.Organization.extra_modules— M2M inversa (related_name="extra_organizations") para módulos à la carte o core pre-asignados en signup.core.module_registry.URL_TO_MODULE— diccionario estático prefijo→slug con longest-prefix-match víaresolve_module(path).
Ejemplos
# Flujo gating en ModuleGatingMiddleware (core/middleware/module_gating.py:62-79)
active = org.get_active_modules()
request._active_modules = active
required_module = resolve_module(path)
if required_module and required_module not in active:
if path.startswith("/api/"):
return JsonResponse({"error": f"Module '{required_module}' is not available"}, status=403)
return render(request, "errors/module_disabled.html", {"module_slug": required_module}, status=403)
# Construcción del set activo (core/models.py:110-112)
active = set(SaaSModule.objects.filter(is_core=True).values_list("slug", flat=True))
active.update(self.extra_modules.values_list("slug", flat=True))
return active
Related
entity--core--model--plan— contiene la M2MPlan.modules → SaaSModule.entity--core--model--organization— consume víaextra_modulesyget_active_modules().concept--saas--module-gating— patrón arquitectónico implementado por middleware + registry.
Véase también
- [[entity—core—model—plan]]
- [[entity—core—model—organization]]
- [[concept—saas—module-gating]]