When server.templates.path is configured in pycsw.yml, render_j2_template() in pycsw/ogc/api/util.py creates a custom Jinja2 Environment and correctly registers the to_json filter on it. However, when a requested template is not found in the custom path, the fallback Environment (pointing to the built-in templates) is created without registering to_json. Any built-in template that uses {{ ... | to_json }} then fails with a TemplateAssertionError at render time.
To reproduce
- Set server.templates.path in pycsw.yml to a custom directory
- Place only the templates you want to override in that directory (e.g. item.html) — intentionally omit others (e.g. items.html)
- Request an endpoint whose template is not in the custom directory (e.g. GET /collections/{collectionId}/items?f=html)
Expected behavior
The fallback should transparently use the built-in template, including all registered filters — identical to the behavior when no custom templates.path is set. Only the templates present in the custom directory should be overridden; all others should fall back to the built-in defaults without error.
Actual behavior
HTTP 500. The fallback Environment renders the built-in items.html, which calls {{ data['features'] | to_json }}, but to_json is not registered on the fallback environment:
jinja2.exceptions.TemplateAssertionError: No filter named 'to_json'.
Root cause
In pycsw/ogc/api/util.py, render_j2_template():
env = Environment(loader=FileSystemLoader(templates_path))
custom_templates = True
env.filters['to_json'] = to_json # ✅ registered on custom env
env.globals.update(to_json=to_json)
try:
template = env.get_template(template)
except TemplateNotFound as err:
if custom_templates:
env = Environment(loader=FileSystemLoader(TEMPLATES)) # ❌ new env, no to_json
template = env.get_template(template)
return template.render(...) # crashes on {{ ... | to_json }}
Suggested fix
Register the filter on the fallback environment as well:
except TemplateNotFound as err:
if custom_templates:
env = Environment(loader=FileSystemLoader(TEMPLATES))
env.filters['to_json'] = to_json # ← add this
env.globals.update(to_json=to_json)
template = env.get_template(template)
Or extract filter registration into a helper to avoid duplication:
def _make_env(path):
env = Environment(loader=FileSystemLoader(path))
env.filters['to_json'] = to_json
env.globals.update(to_json=to_json)
return env
Impact
Without this fix, templates.path effectively requires users to copy all built-in templates into their custom directory — including ones they do not want to modify — to avoid hitting the broken fallback. This defeats the purpose of being able to override individual templates.
Environment
- pycsw version: 3.0.dev0
- Python: 3.12
- Deployment: Docker (geopython/pycsw:latest)
When server.templates.path is configured in pycsw.yml, render_j2_template() in pycsw/ogc/api/util.py creates a custom Jinja2 Environment and correctly registers the to_json filter on it. However, when a requested template is not found in the custom path, the fallback Environment (pointing to the built-in templates) is created without registering to_json. Any built-in template that uses {{ ... | to_json }} then fails with a TemplateAssertionError at render time.
To reproduce
Expected behavior
The fallback should transparently use the built-in template, including all registered filters — identical to the behavior when no custom templates.path is set. Only the templates present in the custom directory should be overridden; all others should fall back to the built-in defaults without error.
Actual behavior
HTTP 500. The fallback Environment renders the built-in items.html, which calls {{ data['features'] | to_json }}, but to_json is not registered on the fallback environment:
jinja2.exceptions.TemplateAssertionError: No filter named 'to_json'.
Root cause
In pycsw/ogc/api/util.py, render_j2_template():
Suggested fix
Register the filter on the fallback environment as well:
Impact
Without this fix, templates.path effectively requires users to copy all built-in templates into their custom directory — including ones they do not want to modify — to avoid hitting the broken fallback. This defeats the purpose of being able to override individual templates.
Environment