feat(#357): W4b — Saved-Views/Filters von contacts:read entkoppelt
Check Cross-Plugin Imports / check (push) Has been cancelled

- ENTITY_PLUGIN_OWNERS-Registry: trackt, welches Plugin welche Entity registriert
- get_entity_read_permission(): leitet die modul-korrekte Permission ab
  (contacts -> contacts:read, tasks -> tasks:read, ...) mit Core-Fallback
- registry.activate(): uebergibt plugin_name an register_entity_model
- saved_views.py + saved_filters.py: statische contacts:read-Dependencies
  durch dynamische _check_entity_read() ersetzt — Saved Views/Filters fuer
  fremde Entities brauchen jetzt die richtige modul-spezifische Permission

Verifikation: 10/11 tests/test_saved_filters.py passed (1 Vorbestand-
Failure per Stash bewiesen), create_app OK, ruff modified-files gruen.

fixes #357 (W4b-Teil)
This commit is contained in:
Agent Zero
2026-08-28 12:07:57 +02:00
parent 9bb1dbae03
commit b7b7d41c0c
4 changed files with 128 additions and 54 deletions
+38 -1
View File
@@ -69,6 +69,37 @@ ENTITY_MODELS: dict[str, type] = {
"contact_folder": ContactFolder,
}
# W4b: Tracks which plugin registered which entity_type — used to derive
# the correct module permission (e.g. contacts:read for contacts entities).
ENTITY_PLUGIN_OWNERS: dict[str, str] = {}
def get_entity_read_permission(entity_type: str) -> str:
"""Derive the module read permission for an entity type.
W4b: Saved views/filters must respect the owning plugin's permission
instead of a hardcoded contacts:read. Falls back to contacts:read for
unknown entities (backward compat, pre-plugin behavior).
"""
owner = ENTITY_PLUGIN_OWNERS.get(entity_type)
if owner:
return f"{owner}:read"
# Core entities: derive from module name (e.g. workflows → workflows:read)
module = entity_type.rstrip("s")
candidates = [k for k in _core_module_keys(module, "read")]
return candidates[0] if candidates else "contacts:read"
def _core_module_keys(module: str, action: str) -> list[str]:
"""Find a core permission key matching module+action (lazy import safe)."""
from app.core.permission_registry import CORE_PERMISSIONS
return [
p["key"]
for p in CORE_PERMISSIONS
if p.get("module") == module and p["key"].endswith(f":{action}")
]
# Core models with OwnedMixin (Phase 2 additions)
try:
from app.models.entity_attachment import EntityAttachment
@@ -85,9 +116,15 @@ except ImportError:
# at activation time in main.py:lifespan(). No hardcoded plugin imports here.
def register_entity_model(entity_type: str, model_class: type) -> None:
def register_entity_model(
entity_type: str,
model_class: type,
plugin_name: str | None = None,
) -> None:
"""Register an entity model dynamically (called during plugin activation)."""
ENTITY_MODELS[entity_type] = model_class
if plugin_name:
ENTITY_PLUGIN_OWNERS[entity_type] = plugin_name
def unregister_entity_model(entity_type: str) -> None: