🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Python Template Engines — Jinja2 & String Templates

Python AdvancedTemplates🟢 Free Lesson

Advertisement

Python Template Engines — Jinja2 & String Templates

Templating generates dynamic text from templates with variable substitution and logic. Essential for HTML generation, email templates, configuration files, and report generation. This tutorial covers string.Template, Jinja2 fundamentals, template inheritance, filters, macros, and real-world use cases.

Learning Objectives

  • Use Python's string.Template for simple substitution
  • Master Jinja2 for complex templating
  • Apply template inheritance for reusable layouts
  • Create custom filters and macros
  • Generate HTML, emails, and reports
  • Build a dynamic report generator

string.Template (Standard Library)

from string import Template

# Simple substitution
template = Template("Hello, $name! Your balance is $${amount}.")
result = template.substitute(name="Alice", amount=1000)
print(result)  # Hello, Alice! Your balance is $1000.

# Safe substitution (no error on missing keys)
result = template.safe_substitute(name="Bob")
print(result)  # Hello, Bob! Your balance is $${amount}.

# From a file
template = Template(open("email.txt").read())
result = template.substitute(name="Charlie", date="2024-01-15")

string.Template vs Jinja2

Featurestring.TemplateJinja2
Syntax$name, ${name}{{ name }}
DependenciesNone (stdlib)External package
Control flowNoYes (if/for)
FiltersNoYes
InheritanceNoYes
Auto-escapingNoYes (HTML)
Best forSimple substitutionComplex templates

Jinja2 Basics

Variable Substitution

from jinja2 import Template

# Simple variable
template = Template("Hello, {{ name }}!")
print(template.render(name="Alice"))

# Dot notation for objects
template = Template("{{ user.name }} ({{ user.email }})")
print(template.render(user={"name": "Alice", "email": "alice@example.com"}))

# Square bracket notation
template = Template("{{ user['name'] }}")
print(template.render(user={"name": "Alice"}))

# Undefined handling
from jinja2 import Undefined
template = Template("{{ name | default('Anonymous') }}")
print(template.render())  # Anonymous

Filters

Filters transform variables using the pipe | syntax.

from jinja2 import Template

# String filters
template = Template("{{ name | upper }}")  # ALICE
template = Template("{{ name | lower }}")  # alice
template = Template("{{ name | title }}")  # Alice
template = Template("{{ text | truncate(10) }}")  # First te...
template = Template("{{ text | replace('old', 'new') }}")
template = Template("{{ text | center(20) }}")
template = Template("{{ text | striptags }}")  # Remove HTML

# Number filters
template = Template("{{ price | round(2) }}")
template = Template("{{ price | int }}")
template = Template("{{ price | float }}")

# List filters
template = Template("{{ items | length }}")
template = Template("{{ items | first }}")
template = Template("{{ items | last }}")
template = Template("{{ items | join(', ') }}")
template = Template("{{ items | sort | reverse | list }}")

# Default filter
template = Template("{{ name | default('Anonymous') }}")
print(template.render())  # Anonymous

# Formatting filter
template = Template("{{ '{:.2f}'.format(price) }}")
template = Template("{{ price | round(2) }}")

# Multiple filters
template = Template("{{ name | upper | center(20) }}")

Control Flow

from jinja2 import Template

# For loops
template = Template("""
{% for item in items %}
  {{ loop.index }}. {{ item }}
{% endfor %}
""")
print(template.render(items=["Apple", "Banana", "Cherry"]))
# 1. Apple
# 2. Banana
# 3. Cherry

# Loop variables
template = Template("""
{% for user in users %}
  {{ loop.index }}/{{ loop.length }}: {{ user.name }}{% if loop.first %} (first){% endif %}{% if loop.last %} (last){% endif %}
{% endfor %}
""")

# If/elif/else
template = Template("""
{% if score >= 90 %}
  Grade: A
{% elif score >= 80 %}
  Grade: B
{% elif score >= 70 %}
  Grade: C
{% else %}
  Grade: F
{% endif %}
""")

# If in
template = Template("""
{% if 'admin' in user.roles %}
  Admin panel available
{% endif %}
""")

# Macros (reusable components)
template = Template("""
{% macro input_field(name, type='text', placeholder='') %}
  <input type="{{ type }}" name="{{ name }}" placeholder="{{ placeholder }}">
{% endmacro %}

{{ input_field('username', placeholder='Enter username') }}
{{ input_field('password', type='password') }}
""")

Template Inheritance

Template inheritance lets you create a base layout and extend it across multiple pages.

Base Template (layout.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}My App{% endblock %}</title>
    
    {% block extra_css %}{% endblock %}
</head>
<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/contact">Contact</a>
    </nav>

    <div class="container">
        {% block content %}{% endblock %}
    </div>

    <footer>
        {% block footer %}© 2024 My App{% endblock %}
    </footer>

    {% block extra_js %}{% endblock %}
</body>
</html>

Child Template (page.html)

{% extends "layout.html" %}

{% block title %}{{ page_title }} - My App{% endblock %}

{% block extra_css %}

{% endblock %}

{% block content %}
<div class="hero">
    <h1>{{ heading }}</h1>
    <p>{{ subheading }}</p>
</div>

<div class="articles">
    {% for article in articles %}
    <article>
        <h2>{{ article.title }}</h2>
        <p>{{ article.summary | truncate(200) }}</p>
        <a href="/articles/{{ article.id }}">Read more</a>
    </article>
    {% endfor %}
</div>
{% endblock %}

{% block extra_js %}
<script>
    console.log("Page loaded");
</script>
{% endblock %}

Using Inheritance

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader('templates'))

template = env.get_template('page.html')
html = template.render(
    page_title="Blog",
    heading="Latest Articles",
    subheading="Read our latest posts",
    articles=[
        {"id": 1, "title": "First Post", "summary": "This is the first post..."},
        {"id": 2, "title": "Second Post", "summary": "This is the second post..."},
    ]
)

Generating Emails with Templates

from jinja2 import Template

# Welcome email
welcome_template = Template("""
Subject: Welcome to {{ company }}, {{ name }}!

Hi {{ name }},

Welcome to {{ company }}! We're excited to have you on board.

Your account details:
- Username: {{ username }}
- Email: {{ email }}

{% if referral_code %}
Your referral code: {{ referral_code }}
Share it with friends for {{ referral_bonus }} credits!
{% endif %}

To get started, visit: {{ getting_started_url }}

Best regards,
The {{ company }} Team
""")

email = welcome_template.render(
    company="Acme Inc",
    name="Alice",
    username="alice123",
    email="alice@example.com",
    referral_code="ALICE2024",
    referral_bonus=50,
    getting_started_url="https://acme.com/getting-started"
)

# Invoice email
invoice_template = Template("""
Subject: Invoice #{{ invoice_number }}

Dear {{ customer_name }},

Invoice #{{ invoice_number }}
Date: {{ invoice_date }}

Items:
{% for item in items %}
- {{ item.description }}: ${{ "%.2f" | format(item.price) }} x {{ item.quantity }} = ${{ "%.2f" | format(item.price * item.quantity) }}
{% endfor %}

{% if discount %}
Discount: -${{ "%.2f" | format(discount) }}
{% endif %}

Total: ${{ "%.2f" | format(total) }}

Payment due by: {{ due_date }}

Thank you for your business!
""")

Generating HTML Reports

from jinja2 import Template

report_template = Template("""
<!DOCTYPE html>
<html>
<head>
    <title>Sales Report — {{ period }}</title>
    
</head>
<body>
    <h1>Sales Report — {{ period }}</h1>
    <p>Generated: {{ generated_at }}</p>

    <h2>Summary</h2>
    <table>
        <tr><td>Total Revenue</td><td>${{ "%.2f" | format(total_revenue) }}</td></tr>
        <tr><td>Total Orders</td><td>{{ total_orders }}</td></tr>
        <tr><td>Average Order Value</td><td>${{ "%.2f" | format(avg_order_value) }}</td></tr>
    </table>

    <h2>Top Products</h2>
    <table>
        <tr><th>Product</th><th>Units Sold</th><th>Revenue</th></tr>
        {% for product in products %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ product.units }}</td>
            <td>${{ "%.2f" | format(product.revenue) }}</td>
        </tr>
        {% endfor %}
    </table>

    {% if top_regions %}
    <h2>Top Regions</h2>
    <ul>
        {% for region in top_regions %}
        <li>{{ region.name }}: ${{ "%.2f" | format(region.revenue) }}</li>
        {% endfor %}
    </ul>
    {% endif %}

    <div class="total">
        Total Revenue: ${{ "%.2f" | format(total_revenue) }}
    </div>
</body>
</html>
""")

html = report_template.render(
    period="January 2024",
    generated_at="2024-02-01 10:00:00",
    products=[
        {"name": "Widget A", "units": 150, "revenue": 4500.00},
        {"name": "Widget B", "units": 80, "revenue": 3200.00},
        {"name": "Widget C", "units": 45, "revenue": 1350.00},
    ],
    total_revenue=9050.00,
    total_orders=275,
    avg_order_value=32.91,
    top_regions=[
        {"name": "North America", "revenue": 5000.00},
        {"name": "Europe", "revenue": 3000.00},
        {"name": "Asia Pacific", "revenue": 1050.00},
    ]
)

with open("report.html", "w") as f:
    f.write(html)

Auto-Escaping for Security

from jinja2 import Environment, FileSystemLoader

# Auto-escape HTML (prevents XSS)
env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=True  # Enable auto-escaping
)

# Safe rendering for trusted HTML
template = env.from_string("{{ user_input | safe }}")
template = env.from_string("{{ trusted_html }}")  # Auto-escaped
template = env.from_string("{% autoescape true %}{{ content }}{% endautoescape %}")

# Disable escaping for specific variables
template = env.from_string("{{ html_content | safe }}")  # Not escaped

Custom Filters

from jinja2 import Environment

def format_currency(value, symbol="$"):
    return f"{symbol}{value:,.2f}"

def slugify(value):
    return value.lower().replace(" ", "-").replace("_", "-")

def time_ago(value):
    from datetime import datetime
    diff = datetime.now() - value
    if diff.days > 365:
        return f"{diff.days // 365} years ago"
    elif diff.days > 30:
        return f"{diff.days // 30} months ago"
    elif diff.days > 0:
        return f"{diff.days} days ago"
    elif diff.seconds > 3600:
        return f"{diff.seconds // 3600} hours ago"
    elif diff.seconds > 60:
        return f"{diff.seconds // 60} minutes ago"
    else:
        return "just now"

env = Environment()
env.filters["currency"] = format_currency
env.filters["slugify"] = slugify
env.filters["time_ago"] = time_ago

template = env.from_string("{{ price | currency }}")
print(template.render(price=1234.5))  # $1,234.50

template = env.from_string("{{ title | slugify }}")
print(template.render(title="Hello World"))  # hello-world

Common Mistakes

MistakeProblemSolution
No auto-escapingXSS vulnerabilitiesEnable autoescape=True
Forgetting {% endblock %}Template parse errorAlways close blocks
Using &#123;&#123; | safe &#125;&#125; on untrusted inputSecurity riskOnly use on trusted HTML
Not using default filterUndefined variable errorAlways provide defaults
Hardcoding pathsNot portableUse FileSystemLoader
No whitespace controlMessy HTML outputUse {%- -%} syntax

Key Takeaways

  1. Use string.Template for simple substitution (no dependencies)
  2. Use Jinja2 for complex HTML/document generation
  3. Template inheritance reduces duplication
  4. Auto-escaping prevents XSS in HTML
  5. Use |safe to disable escaping for trusted HTML only
  6. Filters transform variables in templates
  7. Macros are reusable template components
  8. Use default filter to handle missing variables
  9. Custom filters extend Jinja2's capabilities
  10. Always enable auto-escaping for user-generated content

Premium Content

Python Template Engines — Jinja2 & String Templates

Unlock this lesson and 900+ advanced tutorials with a Premium plan.

🎯End-to-end Projects
💼Interview Prep
📜Certificates
🤝Community Access

Already a member? Log in

Need Expert Python Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement