added backends, improved templating, rbac

This commit is contained in:
2026-06-10 14:40:22 +02:00
parent d9ca48addc
commit ce43f2658f
28 changed files with 1183 additions and 78 deletions

View File

@@ -0,0 +1,62 @@
from __future__ import annotations
from typing import Any
from .models import CampaignConfig, EntryConfig, FieldDefinition
def field_definitions_by_name(config: CampaignConfig) -> dict[str, FieldDefinition]:
"""Return campaign field definitions keyed by field id/name."""
return {field.name: field for field in config.fields}
def field_can_override(config: CampaignConfig, field_name: str) -> bool:
"""Return whether a recipient/entry value may override the global value.
Unknown fields remain overridable for backwards compatibility with older
campaigns and ad-hoc external mappings. Semantic validation reports unknown
field usage separately when a field list is configured.
"""
field = field_definitions_by_name(config).get(field_name)
if field is None:
return True
return field.can_override
def ignored_entry_field_overrides(config: CampaignConfig, entry: EntryConfig) -> list[str]:
"""Return recipient field keys that are ignored by the override policy."""
return sorted(name for name in entry.fields if not field_can_override(config, name))
def effective_entry_field_values(config: CampaignConfig, entry: EntryConfig) -> dict[str, Any]:
"""Return the local/effective field value map for one message entry.
Global values act as defaults for local template placeholders. Recipient
values replace those defaults only when the corresponding field allows
overrides. Fields that are unknown to the campaign definition keep the old
permissive behavior and remain usable as local values.
"""
values: dict[str, Any] = dict(config.global_values)
for key, value in entry.fields.items():
if field_can_override(config, key) and entry_field_has_override_value(value):
values[key] = value
return values
def entry_field_has_override_value(value: Any) -> bool:
"""Return whether an entry field should override a global default.
Empty recipient values are treated as "not set" so global_values remain the
effective local defaults. Numeric zero and boolean false are valid explicit
overrides.
"""
if value is None:
return False
if isinstance(value, str):
return value.strip() != ""
return True