added backends, improved templating, rbac
This commit is contained in:
@@ -17,6 +17,7 @@ from app.mailer.attachments.resolver import (
|
||||
resolve_entry_attachments,
|
||||
)
|
||||
from app.mailer.campaign.entries import load_campaign_entries
|
||||
from app.mailer.campaign.field_values import effective_entry_field_values, ignored_entry_field_overrides
|
||||
from app.mailer.campaign.models import (
|
||||
Behavior,
|
||||
BuildStatus,
|
||||
@@ -38,7 +39,26 @@ from .models import (
|
||||
MessageValidationStatus,
|
||||
)
|
||||
|
||||
_FIELD_PATTERN = re.compile(r"(?<!\\)\$\{(.*?)(?<!\\)\}")
|
||||
_DOLLAR_FIELD_PATTERN = re.compile(r"(?<!\\)\$\{(.*?)(?<!\\)\}")
|
||||
_BRACE_FIELD_PATTERN = re.compile(r"(?<!\\)\{\{\s*(.*?)\s*\}\}")
|
||||
|
||||
|
||||
def _normalize_template_key(raw: str) -> str:
|
||||
key = raw.strip()
|
||||
if key.startswith("fields."):
|
||||
key = key.removeprefix("fields.")
|
||||
elif key.startswith("local."):
|
||||
key = "local::" + key.removeprefix("local.")
|
||||
elif key.startswith("global."):
|
||||
key = "global::" + key.removeprefix("global.")
|
||||
|
||||
if key.startswith("local::") or key.startswith("global::"):
|
||||
return key
|
||||
if key.startswith("local:"):
|
||||
return "local::" + key.removeprefix("local:")
|
||||
if key.startswith("global:"):
|
||||
return "global::" + key.removeprefix("global:")
|
||||
return key
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -70,20 +90,25 @@ def _read_text(campaign_file: str | Path, raw_path: str | None, encoding: str =
|
||||
|
||||
def _render_template(template: str, values: dict[str, Any], *, keep_missing: bool = True) -> str:
|
||||
def replace(match: re.Match[str]) -> str:
|
||||
key = match.group(1)
|
||||
key = _normalize_template_key(match.group(1))
|
||||
if key in values:
|
||||
value = values[key]
|
||||
return "" if value is None else str(value)
|
||||
return match.group(0) if keep_missing else ""
|
||||
|
||||
rendered = _FIELD_PATTERN.sub(replace, template)
|
||||
rendered = _DOLLAR_FIELD_PATTERN.sub(replace, template)
|
||||
rendered = _BRACE_FIELD_PATTERN.sub(replace, rendered)
|
||||
return rendered.replace(r"\${", "${").replace(r"\}", "}")
|
||||
|
||||
|
||||
def _find_unresolved_placeholders(text: str | None) -> set[str]:
|
||||
if not text:
|
||||
return set()
|
||||
return set(_FIELD_PATTERN.findall(text))
|
||||
return {
|
||||
_normalize_template_key(match.group(1))
|
||||
for pattern in (_DOLLAR_FIELD_PATTERN, _BRACE_FIELD_PATTERN)
|
||||
for match in pattern.finditer(text)
|
||||
}
|
||||
|
||||
|
||||
def _recipient_values(entry: EntryConfig) -> dict[str, str]:
|
||||
@@ -106,7 +131,7 @@ def _template_values(config: CampaignConfig, entry: EntryConfig) -> dict[str, An
|
||||
values: dict[str, Any] = {}
|
||||
for key, value in config.global_values.items():
|
||||
values[f"global::{key}"] = value
|
||||
for key, value in entry.fields.items():
|
||||
for key, value in effective_entry_field_values(config, entry).items():
|
||||
values[f"local::{key}"] = value
|
||||
if entry.id:
|
||||
values["local::id"] = entry.id
|
||||
@@ -390,6 +415,20 @@ def build_entry_message(
|
||||
issues = _message_issues_from_attachment_resolution(resolution)
|
||||
validation_status = _validation_status_from_attachment_status(resolution.status)
|
||||
|
||||
ignored_field_overrides = ignored_entry_field_overrides(config, entry)
|
||||
if ignored_field_overrides:
|
||||
issues.append(
|
||||
MessageIssue(
|
||||
severity="warning",
|
||||
code="field_override_not_allowed",
|
||||
message="Recipient field value(s) ignored because the campaign field does not allow overrides: " + ", ".join(ignored_field_overrides),
|
||||
behavior="warn",
|
||||
source="fields",
|
||||
)
|
||||
)
|
||||
if validation_status == MessageValidationStatus.READY:
|
||||
validation_status = MessageValidationStatus.WARNING
|
||||
|
||||
if not entry.active:
|
||||
draft = MessageDraft(
|
||||
entry_index=entry_index,
|
||||
|
||||
Reference in New Issue
Block a user