function render_template
Renders a template string by replacing placeholders with data values and processing conditional blocks (if/endif tags).
/tf/active/vicechatdev/CDocs/utils/notifications.py
704 - 850
moderate
Purpose
This function provides a lightweight template rendering engine that supports variable substitution using {{variable}} syntax and conditional rendering using {% if condition %}...{% endif %} blocks. It's designed for generating dynamic content like emails or documents, with special handling for document URLs. The function supports various conditional operators (not, and, or, ==) and handles nested conditionals up to 10 levels deep.
Source Code
def render_template(template: str, data: Dict[str, Any]) -> str:
"""
Render template with data using placeholder replacement and conditional tags.
Args:
template: Template string
data: Data to populate template
Returns:
Rendered template
"""
# Make a copy of data to avoid modifying the original
template_data = data.copy()
# Ensure we have template_data as a dict
if not isinstance(template_data, dict):
logger.error(f"Template data is not a dict: {type(template_data)}")
template_data = {}
logger.debug(f"render_template called with {len(template_data)} data items")
logger.debug(f"Template data keys: {list(template_data.keys())}")
# Check if we need to create a document URL
if 'doc_uid' in template_data and 'app_url' in template_data:
try:
# Import the document controller function only when needed
from CDocs.controllers.document_controller import get_document_edit_url
# Try to get the document URL for this document
doc_uid = template_data.get('doc_uid')
# If we have a DocUser object in the data, use it
user = template_data.get('user')
if not user:
# Create a placeholder user or use system user
from CDocs.models.user_extensions import DocUser
user = DocUser(uid="system")
# Get document URL through the proper function
try:
# First try to get edit URL which will work for editable documents
url_result = get_document_edit_url(user=user, document_uid=doc_uid)
if url_result and url_result.get('success') and 'edit_url' in url_result:
template_data['document_url'] = url_result['edit_url']
else:
# Fallback to standard app URL format
template_data['document_url'] = f"{template_data['app_url']}/document/{doc_uid}"
except Exception:
# Fallback to standard app URL format
template_data['document_url'] = f"{template_data['app_url']}/document/{doc_uid}"
except ImportError:
# Fallback to standard app URL format if controller not available
template_data['document_url'] = f"{template_data['app_url']}/document/{doc_uid}"
# Process conditionals in template
result = template
# Process if/endif blocks
import re
pattern = re.compile(r'{%\s*if\s+([^%]+?)\s*%}(.*?){%\s*endif\s*%}', re.DOTALL)
def replace_conditional(match):
condition = match.group(1).strip()
content = match.group(2)
# Evaluate the condition
try:
# Handle different condition types
if condition.startswith('not '):
# Handle negation
var_name = condition[4:].strip()
condition_value = not template_data.get(var_name)
elif ' and ' in condition:
# Handle AND condition
parts = condition.split(' and ')
condition_value = all(template_data.get(p.strip()) for p in parts)
elif ' or ' in condition:
# Handle OR condition
parts = condition.split(' or ')
condition_value = any(template_data.get(p.strip()) for p in parts)
elif '==' in condition:
# Handle equality condition
var_name, value = condition.split('==')
var_name = var_name.strip()
value = value.strip().strip('"\'')
condition_value = str(template_data.get(var_name, '')) == value
else:
# Simple variable check
condition_value = bool(template_data.get(condition))
# Return content if condition is true, otherwise empty string
return content if condition_value else ''
except Exception as e:
logger.error(f"Error evaluating template condition '{condition}': {e}")
return ''
# Process all conditionals (may be nested, so apply multiple times)
previous_result = None
max_iterations = 10 # Prevent infinite loops
iteration = 0
while previous_result != result and iteration < max_iterations:
previous_result = result
result = pattern.sub(replace_conditional, result)
iteration += 1
# Process variable placeholders
replaced_count = 0
for key, value in template_data.items():
# Convert None values to empty string
if value is None:
value = ''
# Convert non-string values to string
str_value = str(value)
# Try both placeholder formats
placeholder1 = '{{' + key + '}}' # No spaces
placeholder2 = '{{ ' + key + ' }}' # With spaces
# Count replacements for debugging
count1 = result.count(placeholder1)
count2 = result.count(placeholder2)
if count1 > 0 or count2 > 0:
logger.debug(f"Replacing {key}: {count1 + count2} occurrences")
replaced_count += count1 + count2
result = result.replace(placeholder1, str_value)
result = result.replace(placeholder2, str_value)
# Add debug logging to help troubleshoot template rendering
logger.debug(f"Template rendering completed. Replaced {replaced_count} placeholders.")
# Check for any remaining placeholders and log them
remaining = re.findall(r'{{[^}]*}}', result)
if remaining:
logger.warning(f"Template rendering left unresolved placeholders: {remaining}")
logger.debug(f"Available template data keys were: {list(template_data.keys())}")
# Log first few occurrences for debugging
for placeholder in remaining[:5]: # Show max 5 unresolved placeholders
logger.debug(f"Unresolved placeholder: {placeholder}")
else:
logger.debug("All template placeholders were successfully resolved")
return result
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
template |
str | - | positional_or_keyword |
data |
Dict[str, Any] | - | positional_or_keyword |
Parameter Details
template: A string containing the template to be rendered. Can include placeholders in the format {{variable}} or {{ variable }} (with or without spaces) and conditional blocks using {% if condition %}content{% endif %} syntax. Conditionals support: simple variable checks, negation (not var), logical operators (and, or), and equality checks (var == 'value').
data: A dictionary containing key-value pairs where keys correspond to placeholder names in the template. Values can be of any type (will be converted to strings). Special keys include 'doc_uid' and 'app_url' which trigger automatic document URL generation, and 'user' (DocUser object) for authenticated URL generation. None values are converted to empty strings.
Return Value
Type: str
Returns a string with all placeholders replaced by their corresponding values from the data dictionary and conditional blocks evaluated. Unresolved placeholders (those without matching data keys) remain in the output and are logged as warnings. The function logs debug information about replacement counts and any remaining unresolved placeholders.
Dependencies
reloggingtypingCDocs.controllers.document_controllerCDocs.models.user_extensions
Required Imports
import re
from typing import Dict, Any
Conditional/Optional Imports
These imports are only needed under specific conditions:
from CDocs.controllers.document_controller import get_document_edit_url
Condition: only when 'doc_uid' and 'app_url' are present in the data dictionary
Optionalfrom CDocs.models.user_extensions import DocUser
Condition: only when 'doc_uid' is present in data but 'user' is not provided
OptionalUsage Example
# Basic usage with simple placeholders
template = "Hello {{name}}, your order {{order_id}} is ready!"
data = {'name': 'John', 'order_id': '12345'}
result = render_template(template, data)
# result: "Hello John, your order 12345 is ready!"
# Using conditionals
template = "{% if premium %}Premium user: {{name}}{% endif %}{% if not premium %}Regular user: {{name}}{% endif %}"
data = {'name': 'Alice', 'premium': True}
result = render_template(template, data)
# result: "Premium user: Alice"
# Using equality and logical operators
template = "{% if status == 'active' and verified %}Welcome {{name}}!{% endif %}"
data = {'name': 'Bob', 'status': 'active', 'verified': True}
result = render_template(template, data)
# result: "Welcome Bob!"
# Document URL generation (requires CDocs context)
template = "View document: {{document_url}}"
data = {'doc_uid': 'abc123', 'app_url': 'https://example.com'}
result = render_template(template, data)
# result: "View document: https://example.com/document/abc123"
Best Practices
- Always pass a dictionary to the data parameter; the function validates this but will use an empty dict if validation fails
- Use consistent placeholder formatting (either {{var}} or {{ var }}) throughout your template for readability
- Be aware that the function modifies a copy of the data dictionary, so the original is not affected
- Monitor logs for unresolved placeholders to catch missing data keys during development
- Limit nesting of conditional blocks to avoid hitting the 10-iteration maximum for nested conditionals
- When using document URL generation, provide a 'user' object in data for proper authentication and permissions
- Handle None values in your data dictionary explicitly if you need different behavior than empty strings
- Test complex conditional logic separately as evaluation errors are caught and logged but return empty strings
- Be cautious with equality checks in conditionals as all values are converted to strings for comparison
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function test_template_with_data 64.6% similar
-
function generate_document_from_template 54.1% similar
-
function gen_send_email 50.6% similar
-
function main_v30 48.5% similar
-
function get_email_templates 46.5% similar