🔍 Code Extractor

function render_template

Maturity: 60

Renders a template string by replacing placeholders with data values and processing conditional blocks (if/endif tags).

File:
/tf/active/vicechatdev/CDocs/utils/notifications.py
Lines:
704 - 850
Complexity:
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

  • re
  • logging
  • typing
  • CDocs.controllers.document_controller
  • CDocs.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

Optional
from CDocs.models.user_extensions import DocUser

Condition: only when 'doc_uid' is present in data but 'user' is not provided

Optional

Usage 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

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function test_template_with_data 64.6% similar

    Tests a template file by replacing placeholders with test data and validates that all required placeholders have been filled, excluding known conditional placeholders.

    From: /tf/active/vicechatdev/test_comprehensive_templates.py
  • function generate_document_from_template 54.1% similar

    Generates a document from a template by populating it with provided data, returning the document content as bytes or None if generation fails.

    From: /tf/active/vicechatdev/CDocs/utils/document_processor.py
  • function gen_send_email 50.6% similar

    Sends templated emails using either MS365 or SMTP provider, with support for multiple recipients, attachments, and HTML/text rendering.

    From: /tf/active/vicechatdev/CDocs/utils/notifications.py
  • function main_v30 48.5% similar

    A test function that validates email template rendering by testing multiple HTML email templates with sample data structures for document review and approval workflows.

    From: /tf/active/vicechatdev/test_comprehensive_templates.py
  • function get_email_templates 46.5% similar

    Loads HTML email templates from a predefined directory structure and returns them as a dictionary mapping template names to their content.

    From: /tf/active/vicechatdev/CDocs/utils/notifications.py
← Back to Browse