A common complaint about Chameleon is that a template mistake gets reported far from where it is. The usual cause is an unclosed tag, and chameleon-rs can catch it where it happens with one setting: strict_tags.
The problem
When Chameleon finds a start tag with no matching end tag, it treats the tag as an empty element and carries on. Any tal:, metal: or i18n: attribute on that tag then applies to the tag alone, not to the content you meant it to cover. The template compiles without complaint, and one of two things happens.
It renders the wrong thing, with no error. Here the missing </div> means tal:condition hides an empty <div>, and the “secret” paragraph shows anyway:
from chameleon_rs import PageTemplate
page = PageTemplate("""
<section>
<div tal:condition="user.is_admin">
<p>Admin tools</p>
</section>
""")
class User:
is_admin = False
print(page(user=User()))
<section>
<p>Admin tools</p>
</section>
Or it fails somewhere else. Here the missing </ul> is on line 3, but the error names a variable on line 4. In a real page the symptom can be dozens of lines from the cause.
page = PageTemplate("""
<main>
<ul tal:repeat="episode episodes">
<li>${episode}</li>
</main>
""")
try:
page(episodes=['Rust', 'Python'])
except NameError as e:
print('NameError:', '\n'.join(str(e).splitlines()[:4]))
NameError: episode
- Expression: "episode"
- Filename: <string>
Turning it on
It’s off by default, because Chameleon accepts these templates and chameleon-rs matches Chameleon unless you ask otherwise. Turn it on in whichever way suits you:
- For the whole process:
chameleon_rs.configure(template_folder='templates', strict_tags=True). That covers render_partial and every framework integration, including the frameworks’ own configure() calls made afterwards.
- Without changing code: set the environment variable
CHAMELEON_STRICT_TAGS=1. This is handy in development and in tests. Together with CHAMELEON_EAGER=1, a test that loads every template catches these mistakes before they ship.
- Per template or loader:
PageTemplate(source, strict_tags=True), PageTemplateFile(path, strict_tags=True) or PageTemplateLoader(folder, strict_tags=True).
It’s also safe to leave on in production. It only adds a check at compile time, so rendering speed doesn’t change. All 229 templates of two production sites compile with it on.
What it checks
An element is reported when it has no end tag, isn’t self-closed with />, and either:
- carries a
tal:, metal: or i18n: attribute (or a data-tal-* one with enable_data_attributes), or
- is itself in one of those namespaces, such as
<tal:block>.
These are still allowed:
- HTML’s void elements, which never have an end tag:
<input tal:attributes="...">, <img>, <br>, <meta>, <link> and the rest.
- Unclosed elements without template attributes, such as HTML’s optional
</p> and </li>. They render the same either way.
${...} in an unclosed element’s attributes. An interpolation affects only its own attribute, so nothing is lost.