feat(#357): custom_field_definitions generisch — W4b-Muster (422/403-Entity-Checks, {items,total}-Shape, ACL-Fix), zentrale Helper, Plural-Ableitungs-Fix

This commit is contained in:
Agent Zero
2026-08-29 01:27:24 +02:00
parent 36dd7c5101
commit b5036a1fc0
8 changed files with 442 additions and 71 deletions
+45 -4
View File
@@ -84,10 +84,15 @@ def get_entity_read_permission(entity_type: str) -> str:
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"
# Core entities: derive from the module name in CORE_PERMISSIONS. Modules
# are mostly plural ("workflows", "addresses") while entity types are
# mostly singular ("workflow", "address") — try exact, singular and
# plural forms before the contacts:read fallback.
for module in (entity_type, entity_type.rstrip("s"), f"{entity_type}s", f"{entity_type}es"):
candidates = _core_module_keys(module, "read")
if candidates:
return candidates[0]
return "contacts:read"
def _core_module_keys(module: str, action: str) -> list[str]:
@@ -100,6 +105,41 @@ def _core_module_keys(module: str, action: str) -> list[str]:
if p.get("module") == module and p["key"].endswith(f":{action}")
]
def validate_entity_type(entity_type: str) -> None:
"""Validate entity_type against ENTITY_MODELS (W4b pattern).
Central helper for entity-typed CRUD (saved filters/views,
custom field definitions). Raises fastapi HTTPException 422
with the valid types so clients can self-correct.
"""
if entity_type not in ENTITY_MODELS:
from fastapi import HTTPException
raise HTTPException(422, detail={
"detail": f"Invalid entity_type: {entity_type}",
"code": "invalid_entity_type",
"valid_types": sorted(ENTITY_MODELS.keys()),
})
def check_entity_read_permission(current_user: dict, entity_type: str) -> None:
"""Check that the user may read the entity type's owning module (W4b).
Raises fastapi HTTPException 403 when the derived module read
permission (e.g. contacts:read, workflows:read) is missing.
"""
from fastapi import HTTPException
from app.core.permissions import check_permission
perm = get_entity_read_permission(entity_type)
if not check_permission(current_user, perm):
raise HTTPException(403, detail={
"detail": f"Permission '{perm}' required",
"code": "forbidden",
})
# Core models with OwnedMixin (Phase 2 additions)
try:
from app.models.entity_attachment import EntityAttachment
@@ -130,6 +170,7 @@ def register_entity_model(
def unregister_entity_model(entity_type: str) -> None:
"""Unregister an entity model (called during plugin deactivation)."""
ENTITY_MODELS.pop(entity_type, None)
ENTITY_PLUGIN_OWNERS.pop(entity_type, None)
def _get_entity_model(entity_type: str) -> type: