Simplify templates to pure Jinja2 + CodeMirror editor + variable reference
Some checks failed
Validate / Hassfest (push) Has been cancelled
Some checks failed
Validate / Hassfest (push) Has been cancelled
Major template system overhaul:
- TemplateConfig simplified from 21 fields to 9: removed all sub-templates
(asset_image, asset_video, assets_format, people_format, etc.)
Users write full Jinja2 with {% for %}, {% if %} inline.
- Default EN/RU templates seeded on first startup (user_id=0, system-owned)
with proper Jinja2 loops over added_assets, people, albums.
- build_full_context() simplified: passes raw data directly to Jinja2
instead of pre-rendering sub-templates.
- CodeMirror editor for template slots (HTML syntax highlighting,
line wrapping, dark theme support via oneDark).
- Variable reference API: GET /api/template-configs/variables returns
per-slot variable descriptions + asset_fields for loop contexts.
- Variable reference modal in UI: click "{{ }} Variables" next to any
slot to see available variables with Jinja2 syntax examples.
- Route ordering fix: /variables registered before /{config_id}.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,101 +39,21 @@ def build_full_context(
|
||||
event_data: dict[str, Any],
|
||||
template_config: TemplateConfig | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the full template context with all variables from the blueprint.
|
||||
"""Build template context by passing raw data directly to Jinja2.
|
||||
|
||||
This assembles the ~40 variables the blueprint supports:
|
||||
- Direct event fields (album_name, added_count, etc.)
|
||||
- Computed fields (common_date, common_location, people, assets, video_warning)
|
||||
- Formatted sub-templates (asset items, people format, etc.)
|
||||
The templates use {% for %}, {% if %} etc. to handle formatting,
|
||||
so no pre-rendering of sub-templates is needed.
|
||||
"""
|
||||
tc = template_config
|
||||
ctx = dict(event_data)
|
||||
|
||||
# People formatting
|
||||
people_list = ctx.get("people", [])
|
||||
if isinstance(people_list, list) and people_list and tc:
|
||||
people_str = ", ".join(str(p) for p in people_list)
|
||||
ctx["people"] = _render(tc.message_people_format, {"people": people_str})
|
||||
elif isinstance(people_list, list):
|
||||
ctx["people"] = ", ".join(str(p) for p in people_list) if people_list else ""
|
||||
else:
|
||||
ctx["people"] = str(people_list) if people_list else ""
|
||||
|
||||
# Asset list formatting
|
||||
added_assets = ctx.get("added_assets", [])
|
||||
if added_assets and tc:
|
||||
date_fmt = tc.date_format or "%d.%m.%Y, %H:%M UTC"
|
||||
|
||||
# Detect common date/location
|
||||
dates = set()
|
||||
locations = set()
|
||||
for a in added_assets:
|
||||
if a.get("created_at"):
|
||||
try:
|
||||
dt = datetime.fromisoformat(str(a["created_at"]).replace("Z", "+00:00"))
|
||||
dates.add(dt.strftime(date_fmt))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
loc_parts = [a.get("city"), a.get("country")]
|
||||
loc = ", ".join(p for p in loc_parts if p)
|
||||
if loc:
|
||||
locations.add(loc)
|
||||
|
||||
common_date = ""
|
||||
if len(dates) == 1:
|
||||
common_date = _render(tc.common_date_template, {"date": next(iter(dates))})
|
||||
ctx["common_date"] = common_date
|
||||
|
||||
common_location = ""
|
||||
if len(locations) == 1:
|
||||
common_location = _render(tc.common_location_template, {"location": next(iter(locations))})
|
||||
ctx["common_location"] = common_location
|
||||
|
||||
# Format individual assets
|
||||
asset_lines = []
|
||||
for a in added_assets:
|
||||
asset_type = a.get("type", "IMAGE")
|
||||
tmpl = tc.message_asset_image if asset_type == "IMAGE" else tc.message_asset_video
|
||||
|
||||
# Per-asset date (only if dates differ)
|
||||
created_if_unique = ""
|
||||
if len(dates) > 1 and a.get("created_at"):
|
||||
try:
|
||||
dt = datetime.fromisoformat(str(a["created_at"]).replace("Z", "+00:00"))
|
||||
created_if_unique = _render(tc.date_if_unique_template, {"date": dt.strftime(date_fmt)})
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Per-asset location
|
||||
location_if_unique = ""
|
||||
loc_parts = [a.get("city"), a.get("country")]
|
||||
loc = ", ".join(p for p in loc_parts if p)
|
||||
if loc and len(locations) > 1:
|
||||
location_if_unique = _render(tc.location_if_unique_template, {"location": loc})
|
||||
|
||||
# Favorite indicator
|
||||
fav = tc.favorite_indicator if a.get("is_favorite") else ""
|
||||
|
||||
asset_ctx = {
|
||||
**a,
|
||||
"created_if_unique": created_if_unique,
|
||||
"location_if_unique": location_if_unique,
|
||||
"location": loc,
|
||||
"is_favorite": fav,
|
||||
}
|
||||
asset_lines.append(_render(tmpl, asset_ctx))
|
||||
|
||||
# Assemble assets list
|
||||
assets_str = "".join(asset_lines)
|
||||
ctx["assets"] = _render(tc.message_assets_format, {"assets": assets_str})
|
||||
else:
|
||||
ctx.setdefault("assets", "")
|
||||
ctx.setdefault("common_date", "")
|
||||
ctx.setdefault("common_location", "")
|
||||
# Ensure lists are actual lists (not strings)
|
||||
if isinstance(ctx.get("people"), str):
|
||||
ctx["people"] = [ctx["people"]] if ctx["people"] else []
|
||||
|
||||
# Video warning
|
||||
added_assets = ctx.get("added_assets", [])
|
||||
has_videos = any(a.get("type") == "VIDEO" for a in added_assets) if added_assets else False
|
||||
ctx["video_warning"] = (tc.video_warning if tc and has_videos else "")
|
||||
ctx["video_warning"] = (template_config.video_warning if template_config and has_videos else "")
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
Reference in New Issue
Block a user