feat: per-theme Jinja env enables theme-level macro override

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-22 17:33:05 +08:00
parent b1b4d12a19
commit 67b469d56d
2 changed files with 33 additions and 2 deletions
+16 -2
View File
@@ -16,7 +16,21 @@ from markupsafe import Markup
from .core import sanitize, route_path, THEMES from .core import sanitize, route_path, THEMES
ROOT=Path(__file__).resolve().parent.parent ROOT=Path(__file__).resolve().parent.parent
LOADER=Environment(loader=FileSystemLoader(str(ROOT/'themes')),autoescape=select_autoescape(['html','xml'])) _ENVS={}
def env_for(theme):
"""Per-theme Jinja env: theme dir first, shared themes/ root as fallback.
Lets a theme override shared macros (_professional_components.html) while
inheriting everything else. Cached: one env per theme across large builds.
"""
env=_ENVS.get(theme)
if env is None:
theme_dir=ROOT/'themes'/theme
search=[str(theme_dir),str(ROOT/'themes')] if theme_dir.is_dir() else [str(ROOT/'themes')]
env=Environment(loader=FileSystemLoader(search),autoescape=select_autoescape(['html','xml']))
_ENVS[theme]=env
return env
TITLES={'product':'产品中心','vehicle':'车型中心','post':'新闻资讯','case':'项目案例','brand':'品牌中心','service':'服务与解决方案'} TITLES={'product':'产品中心','vehicle':'车型中心','post':'新闻资讯','case':'项目案例','brand':'品牌中心','service':'服务与解决方案'}
@@ -186,4 +200,4 @@ def render(theme,site,page,records,mode='detail',listing_items=None,blocks_html=
if theme not in THEMES:raise ValueError('不是内置专业主题') if theme not in THEMES:raise ValueError('不是内置专业主题')
ctx=make_context(site,page,records,mode,listing_items,prepared=prepared) ctx=make_context(site,page,records,mode,listing_items,prepared=prepared)
ctx.update(custom_blocks=Markup(blocks_html or ''),has_custom=bool(blocks_html),noindex=(ctx['kind']=='notfound'),designer_replace=meta(page).get('layout_mode')=='replace') ctx.update(custom_blocks=Markup(blocks_html or ''),has_custom=bool(blocks_html),noindex=(ctx['kind']=='notfound'),designer_replace=meta(page).get('layout_mode')=='replace')
return LOADER.get_template(theme+'/pro.html').render(**ctx) return env_for(theme).get_template('pro.html').render(**ctx)
+17
View File
@@ -37,3 +37,20 @@ def test_discover_fails_fast_when_themes_dir_missing(tmp_path, monkeypatch):
import pytest import pytest
with pytest.raises(RuntimeError, match='themes directory missing'): with pytest.raises(RuntimeError, match='themes directory missing'):
core.discover_themes() core.discover_themes()
def test_env_for_theme_prefers_theme_dir_then_shared_root():
from app import theme_pro
env = theme_pro.env_for('industrial')
paths = [str(p).replace('\\', '/') for p in env.loader.searchpath]
assert paths[0].endswith('themes/industrial')
assert paths[1].endswith('themes')
# 共享宏库必须仍可解析(回落路径)
assert env.get_template('_professional_components.html') is not None
def test_env_for_is_cached_per_theme():
from app import theme_pro
assert theme_pro.env_for('industrial') is theme_pro.env_for('industrial')
def test_env_for_pro_template_loads_unprefixed():
from app import theme_pro
assert theme_pro.env_for('industrial').get_template('pro.html') is not None