roosterwijs/backend/plugins/services.py

106 lines
3.4 KiB
Python

"""
Bedrijfslogica rond modules: status uitlezen en veilig aan/uit zetten,
met respect voor onderlinge afhankelijkheden.
"""
from __future__ import annotations
from .models import ModuleState
from .registry import ModuleSpec, registry
class ModuleError(Exception):
"""Een aan/uit-actie is niet toegestaan."""
def _state_map() -> dict[str, bool]:
return {s.key: s.enabled for s in ModuleState.objects.all()}
def is_enabled(key: str) -> bool:
spec = registry.get(key)
if spec is None:
return False
if spec.core:
return True
state = ModuleState.objects.filter(key=key).first()
return bool(state and state.enabled)
def sync_states() -> None:
"""Zorg dat elke geregistreerde module een rij in de database heeft.
get_or_create is race-bestendig: meerdere gunicorn-workers kunnen dit
tegelijk aanroepen zonder een unique-constraint-fout te veroorzaken.
"""
existing = set(ModuleState.objects.values_list("key", flat=True))
for spec in registry.all():
if spec.core:
continue
if spec.key not in existing:
ModuleState.objects.get_or_create(
key=spec.key, defaults={"enabled": spec.default_enabled}
)
def describe_all() -> list[dict]:
"""Alle modules met metadata + actuele status, voor de API/het beheerscherm."""
states = _state_map()
result: list[dict] = []
for spec in registry.all():
enabled = True if spec.core else states.get(spec.key, spec.default_enabled)
result.append(
{
"key": spec.key,
"name": spec.name,
"description": spec.description,
"version": spec.version,
"category": spec.category,
"core": spec.core,
"enabled": enabled,
"depends_on": list(spec.depends_on),
"menu_items": [
{
"label": m.label,
"path": m.path,
"icon": m.icon,
"order": m.order,
}
for m in spec.menu_items
]
if enabled
else [],
}
)
return result
def set_enabled(key: str, enabled: bool) -> ModuleSpec:
"""Zet een module aan of uit, met controle op afhankelijkheden."""
spec = registry.get(key)
if spec is None:
raise ModuleError(f"Onbekende module: {key}")
if spec.core:
raise ModuleError(f"Kernmodule '{key}' kan niet worden uitgeschakeld.")
if enabled:
# Vereiste modules moeten ook aan staan.
for dep in spec.depends_on:
if not is_enabled(dep):
dep_spec = registry.get(dep)
dep_name = dep_spec.name if dep_spec else dep
raise ModuleError(
f"Module '{spec.name}' vereist eerst '{dep_name}'."
)
else:
# Modules die hiervan afhangen mogen niet meer aan staan.
for dependent in registry.dependents_of(key):
if is_enabled(dependent.key):
raise ModuleError(
f"'{dependent.name}' heeft '{spec.name}' nodig; "
f"schakel die eerst uit."
)
state, _ = ModuleState.objects.get_or_create(key=key)
state.enabled = enabled
state.save(update_fields=["enabled", "updated_at"])
return spec