Templates: Dynamic HTML with Jinja2
Welcome to Templates! Think of templates as fill-in-the-blank worksheets - you have a structure with placeholders, and Flask fills them in with real data to create personalized web pages.
What are Templates?
Templates are HTML files with placeholders that Flask fills in with data. Instead of writing HTML strings in Python, you write clean HTML and insert dynamic content where needed.
Your First Template
Let’s create a simple template:
<!-- templates/hello.html -->
<!DOCTYPE html>
<html>
<head>
<title>Hello Page</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
<p>Welcome to my website.</p>
</body>
</html>
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello/<name>')
def hello(name):
return render_template('hello.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
What happens:
render_template()finds the template file{{ name }}gets replaced with the actual name- Flask returns the complete HTML
Template Variables
Pass data to templates:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
user = {
'name': 'Alice',
'age': 25,
'hobbies': ['reading', 'coding', 'gaming']
}
return render_template('profile.html', user=user, title="User Profile")
@app.route('/products')
def products():
items = [
{'name': 'Laptop', 'price': 999.99, 'in_stock': True},
{'name': 'Mouse', 'price': 29.99, 'in_stock': False},
{'name': 'Keyboard', 'price': 79.99, 'in_stock': True}
]
return render_template('products.html', products=items)
if __name__ == '__main__':
app.run(debug=True)
<!-- templates/profile.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ user.name }}'s Profile</h1>
<p>Age: {{ user.age }}</p>
<h2>Hobbies:</h2>
<ul>
{% for hobby in user.hobbies %}
<li>{{ hobby }}</li>
{% endfor %}
</ul>
</body>
</html>
Jinja2 Syntax
Variables
<!-- Variable output -->
<h1>{{ title }}</h1>
<p>{{ user.name }}</p>
<p>{{ item.price | round(2) }}</p>
<!-- Safe HTML (prevents XSS) -->
<p>{{ user.bio | safe }}</p>
<!-- Default values -->
<p>{{ user.location | default('Unknown') }}</p>
Control Structures
<!-- Conditionals -->
{% if user.is_admin %}
<p>Welcome, Administrator!</p>
{% elif user.is_moderator %}
<p>Welcome, Moderator!</p>
{% else %}
<p>Welcome, User!</p>
{% endif %}
<!-- Loops -->
<h2>Products:</h2>
<ul>
{% for product in products %}
<li>
{{ product.name }} - ${{ product.price }}
{% if not product.in_stock %}
<span style="color: red;">Out of stock</span>
{% endif %}
</li>
{% else %}
<li>No products available</li>
{% endfor %}
</ul>
<!-- Loop variables -->
{% for user in users %}
<p>User #{{ loop.index }}: {{ user.name }}</p>
{% if not loop.last %}, {% endif %}
{% endfor %}
Filters
<!-- String filters -->
<p>{{ "hello world" | title }}</p> <!-- Hello World -->
<p>{{ "HELLO" | lower }}</p> <!-- hello -->
<p>{{ name | truncate(10) }}</p> <!-- Alice... -->
<!-- Number filters -->
<p>${{ price | round(2) }}</p> <!-- $29.99 -->
<p>{{ count | default(0) }}</p> <!-- 0 if count is None -->
<!-- List filters -->
<p>{{ items | length }}</p> <!-- Number of items -->
<p>{{ items | first }}</p> <!-- First item -->
<p>{{ items | last }}</p> <!-- Last item -->
<!-- Date filters -->
<p>{{ post.date | strftime('%B %d, %Y') }}</p> <!-- January 01, 2023 -->
Template Inheritance
Create reusable layouts:
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('home') }}">Home</a>
<a href="{{ url_for('about') }}">About</a>
<a href="{{ url_for('contact') }}">Contact</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
<p>© 2023 My Site</p>
{% endblock %}
</footer>
</body>
</html>
<!-- templates/home.html -->
{% extends "base.html" %}
{% block title %}Home - My Site{% endblock %}
{% block content %}
<h1>Welcome Home!</h1>
<p>This is the home page content.</p>
{% endblock %}
<!-- templates/about.html -->
{% extends "base.html" %}
{% block title %}About - My Site{% endblock %}
{% block content %}
<h1>About Us</h1>
<p>We are a great company doing amazing things.</p>
{% endblock %}
{% block footer %}
<p>© 2023 My Site - Special about page footer</p>
{% endblock %}
Include and Macros
Reuse template parts:
<!-- templates/sidebar.html -->
<div class="sidebar">
<h3>Navigation</h3>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<!-- templates/macros.html -->
{% macro render_product(product) %}
<div class="product">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
{% if product.in_stock %}
<button>Add to Cart</button>
{% else %}
<span>Out of Stock</span>
{% endif %}
</div>
{% endmacro %}
{% macro render_user(user) %}
<div class="user-card">
<img src="{{ user.avatar }}" alt="{{ user.name }}">
<h4>{{ user.name }}</h4>
<p>{{ user.bio | truncate(50) }}</p>
</div>
{% endmacro %}
<!-- templates/products.html -->
{% extends "base.html" %}
{% from "macros.html" import render_product %}
{% block content %}
<h1>Our Products</h1>
<div class="products">
{% for product in products %}
{{ render_product(product) }}
{% endfor %}
</div>
{% endblock %}
Static Files
Serve CSS, JavaScript, and images:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('home.html')
# Static files are served automatically from /static/ URL
# Files in static/ directory are accessible at /static/filename
<!-- templates/home.html -->
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
<!-- Link to CSS file -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Welcome!</h1>
<!-- Display image -->
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo">
<!-- Include JavaScript -->
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
/* static/style.css */
body {
font-family: Arial, sans-serif;
margin: 40px;
}
h1 {
color: #333;
}
Context Processors
Make data available to all templates:
from flask import Flask, render_template
app = Flask(__name__)
@app.context_processor
def inject_globals():
return {
'site_name': 'My Awesome Site',
'current_year': 2023,
'navigation': [
{'url': '/', 'label': 'Home'},
{'url': '/about', 'label': 'About'},
{'url': '/contact', 'label': 'Contact'}
]
}
@app.route('/')
def home():
return render_template('home.html')
# Now site_name, current_year, and navigation are available in all templates
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{ site_name }}</title>
</head>
<body>
<nav>
{% for item in navigation %}
<a href="{{ item.url }}">{{ item.label }}</a>
{% endfor %}
</nav>
<footer>
<p>© {{ current_year }} {{ site_name }}</p>
</footer>
</body>
</html>
Practical Examples
Example 1: Blog Layout
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Blog{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='blog.css') }}">
</head>
<body>
<header>
<h1>My Blog</h1>
<nav>
<a href="{{ url_for('home') }}">Home</a>
<a href="{{ url_for('about') }}">About</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2023 My Blog</p>
</footer>
</body>
</html>
<!-- templates/post.html -->
{% extends "base.html" %}
{% block title %}{{ post.title }} - My Blog{% endblock %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<p class="meta">By {{ post.author }} on {{ post.date.strftime('%B %d, %Y') }}</p>
<div class="content">
{{ post.content | safe }}
</div>
{% if post.tags %}
<div class="tags">
Tags:
{% for tag in post.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</article>
<div class="comments">
<h3>Comments ({{ post.comments | length }})</h3>
{% for comment in post.comments %}
<div class="comment">
<strong>{{ comment.author }}</strong> said:
<p>{{ comment.content }}</p>
<small>{{ comment.date.strftime('%B %d, %Y at %I:%M %p') }}</small>
</div>
{% endfor %}
</div>
{% endblock %}
Example 2: E-commerce Product Page
from flask import Flask, render_template
app = Flask(__name__)
# Mock product data
products = {
1: {
'id': 1,
'name': 'Wireless Headphones',
'price': 99.99,
'description': 'High-quality wireless headphones with noise cancellation.',
'features': ['Bluetooth 5.0', '30-hour battery', 'Noise cancelling', 'Comfortable fit'],
'images': ['headphones1.jpg', 'headphones2.jpg'],
'in_stock': True,
'rating': 4.5,
'reviews': 128
}
}
@app.route('/product/<int:product_id>')
def product_page(product_id):
product = products.get(product_id)
if not product:
return "Product not found", 404
return render_template('product.html', product=product)
if __name__ == '__main__':
app.run(debug=True)
<!-- templates/product.html -->
{% extends "base.html" %}
{% block title %}{{ product.name }} - Shop{% endblock %}
{% block content %}
<div class="product-page">
<div class="product-images">
{% for image in product.images %}
<img src="{{ url_for('static', filename='products/' + image) }}" alt="{{ product.name }}">
{% endfor %}
</div>
<div class="product-info">
<h1>{{ product.name }}</h1>
<div class="rating">
{% for i in range(5) %}
{% if i < product.rating %}
⭐
{% else %}
☆
{% endif %}
{% endfor %}
<span>({{ product.reviews }} reviews)</span>
</div>
<p class="price">${{ "%.2f"|format(product.price) }}</p>
<div class="stock-status">
{% if product.in_stock %}
<span class="in-stock">✓ In Stock</span>
<button class="add-to-cart">Add to Cart</button>
{% else %}
<span class="out-of-stock">✗ Out of Stock</span>
{% endif %}
</div>
<div class="description">
<h3>Description</h3>
<p>{{ product.description }}</p>
</div>
<div class="features">
<h3>Features</h3>
<ul>
{% for feature in product.features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}
Example 3: Dashboard with Data Tables
from flask import Flask, render_template
app = Flask(__name__)
# Mock dashboard data
@app.route('/dashboard')
def dashboard():
stats = {
'total_users': 15420,
'active_users': 8934,
'total_revenue': 45678.90,
'conversion_rate': 3.45
}
recent_orders = [
{'id': '#1234', 'customer': 'Alice Johnson', 'amount': 299.99, 'status': 'completed'},
{'id': '#1235', 'customer': 'Bob Smith', 'amount': 149.50, 'status': 'pending'},
{'id': '#1236', 'customer': 'Carol Davis', 'amount': 79.99, 'status': 'shipped'},
]
return render_template('dashboard.html', stats=stats, orders=recent_orders)
if __name__ == '__main__':
app.run(debug=True)
<!-- templates/dashboard.html -->
{% extends "base.html" %}
{% block title %}Dashboard - Admin Panel{% endblock %}
{% block content %}
<div class="dashboard">
<h1>Dashboard</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Users</h3>
<p class="stat-number">{{ "{:,}".format(stats.total_users) }}</p>
</div>
<div class="stat-card">
<h3>Active Users</h3>
<p class="stat-number">{{ "{:,}".format(stats.active_users) }}</p>
</div>
<div class="stat-card">
<h3>Total Revenue</h3>
<p class="stat-number">${{ "%.2f"|format(stats.total_revenue) }}</p>
</div>
<div class="stat-card">
<h3>Conversion Rate</h3>
<p class="stat-number">{{ "%.1f"|format(stats.conversion_rate) }}%</p>
</div>
</div>
<div class="recent-orders">
<h2>Recent Orders</h2>
<table>
<thead>
<tr>
<th>Order ID</th>
<th>Customer</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>{{ order.id }}</td>
<td>{{ order.customer }}</td>
<td>${{ "%.2f"|format(order.amount) }}</td>
<td>
<span class="status status-{{ order.status }}">
{{ order.status | title }}
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
Best Practices
1. Use Template Inheritance
<!-- Good: Base template with blocks -->
{% extends "base.html" %}
{% block content %}...{% endblock %}
2. Organize Templates Logically
templates/
├── base.html # Base layout
├── home.html # Home page
├── about.html # About page
├── products/
│ ├── list.html # Product list
│ └── detail.html # Product detail
└── admin/
├── dashboard.html # Admin dashboard
└── users.html # User management
3. Use Filters for Formatting
<!-- Good -->
{{ price | round(2) }}
{{ date | strftime('%B %d, %Y') }}
<!-- Bad -->
{{ "%.2f"|format(price) }}
{{ date.strftime('%B %d, %Y') }}
4. Escape User Content
<!-- Good: Auto-escaped -->
<p>{{ user_comment }}</p>
<!-- Safe only when you trust the content -->
<p>{{ trusted_html | safe }}</p>
5. Use Context Processors for Global Data
@app.context_processor
def inject_user():
return {'current_user': get_current_user()}
Practice Exercises
Exercise 1: Personal Blog
Create a blog with templates for:
- Base layout with navigation
- Post list page
- Individual post pages
- About page
- Contact form
Exercise 2: E-commerce Store
Build product pages with:
- Product listing with filters
- Individual product pages
- Shopping cart display
- Checkout form
- Order confirmation
Exercise 3: Admin Dashboard
Create an admin interface with:
- Dashboard with statistics
- User management table
- Data visualization charts
- Settings forms
- Navigation sidebar
Exercise 4: Social Media Feed
Build a social media interface with:
- News feed with posts
- User profiles
- Comment sections
- Like/favorite buttons
- Search and filtering
Summary
Templates bring dynamic content to your Flask applications:
Basic Templates:
<h1>Hello, {{ name }}!</h1>
Control Structures:
{% if user.is_admin %}
<p>Welcome, Admin!</p>
{% endif %}
{% for item in items %}
<p>{{ item.name }}</p>
{% endfor %}
Template Inheritance:
<!-- base.html -->
<html>{% block content %}{% endblock %}</html>
<!-- child.html -->
{% extends "base.html" %}
{% block content %}<h1>Content</h1>{% endblock %}
Key Features:
- Variable substitution with
{{ }} - Control structures with
{% %} - Filters for formatting data
- Template inheritance for layouts
- Macros for reusable components
- Static file serving
Next: Forms - processing user input and validation! 📝