Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to the exciting world of Flask templates with Jinja2! ๐ Ever wondered how websites display dynamic content like your username, shopping cart items, or personalized recommendations? Thatโs the magic of templating engines, and Jinja2 is one of the best!
In this tutorial, weโll explore how Jinja2 transforms static HTML into dynamic, data-driven web pages. Whether youโre building a blog ๐, an e-commerce site ๐, or a social media platform ๐, understanding Jinja2 is essential for creating engaging web applications with Flask.
By the end of this tutorial, youโll be creating beautiful, dynamic web pages like a pro! Letโs dive in! ๐โโ๏ธ
๐ Understanding Jinja2 Templates
๐ค What is Jinja2?
Jinja2 is like a super-powered HTML printer ๐จ๏ธ. Imagine youโre creating personalized birthday invitations - instead of writing each one by hand, you create a template with placeholders like [NAME] and [DATE], then fill them in automatically. Thatโs exactly what Jinja2 does for web pages!
In Python terms, Jinja2 is a modern templating engine that lets you:
- โจ Insert Python variables into HTML
- ๐ Use loops and conditions in templates
- ๐ก๏ธ Automatically escape dangerous content for security
- ๐ฆ Create reusable template components
๐ก Why Use Jinja2 with Flask?
Hereโs why developers love Jinja2:
- Clean Separation ๐จ: Keep your Python logic separate from HTML design
- Powerful Features ๐ช: Loops, conditions, filters, and more
- Template Inheritance ๐๏ธ: Build consistent layouts with base templates
- Auto-escaping ๐ก๏ธ: Protection against XSS attacks by default
Real-world example: Imagine building an online bookstore ๐. With Jinja2, you can display thousands of books using a single template that adapts to show each bookโs title, author, price, and cover image!
๐ง Basic Syntax and Usage
๐ Your First Jinja2 Template
Letโs start with a friendly example:
# ๐ Hello, Flask and Jinja2!
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
# ๐จ Pass data to template
user_name = "Alice"
favorite_emoji = "๐"
return render_template('home.html',
name=user_name,
emoji=favorite_emoji)
@app.route('/products')
def products():
# ๐๏ธ List of products
items = [
{'name': 'Python Book', 'price': 29.99, 'emoji': '๐'},
{'name': 'Coffee Mug', 'price': 12.99, 'emoji': 'โ'},
{'name': 'Rubber Duck', 'price': 5.99, 'emoji': '๐ฆ'}
]
return render_template('products.html', products=items)
And hereโs the template (templates/home.html):
<!DOCTYPE html>
<html>
<head>
<title>Welcome {{ name }}! {{ emoji }}</title>
</head>
<body>
<h1>Hello {{ name }}! {{ emoji }}</h1>
<p>Welcome to our awesome Flask app! ๐</p>
</body>
</html>
๐ก Explanation: The {{ }}
syntax is Jinja2โs way of saying โput a variable here!โ Flask automatically looks for templates in a templates
folder.
๐ฏ Common Jinja2 Patterns
Here are patterns youโll use daily:
<!-- ๐๏ธ Pattern 1: Variables -->
<h1>Welcome {{ username }}!</h1>
<p>Your score: {{ score }} points ๐</p>
<!-- ๐จ Pattern 2: Loops -->
<ul>
{% for item in shopping_cart %}
<li>{{ item.name }} - ${{ item.price }} {{ item.emoji }}</li>
{% endfor %}
</ul>
<!-- ๐ Pattern 3: Conditions -->
{% if user.is_logged_in %}
<p>Welcome back, {{ user.name }}! ๐</p>
{% else %}
<p>Please <a href="/login">log in</a> ๐</p>
{% endif %}
<!-- ๐ฏ Pattern 4: Filters -->
<p>Price: ${{ price|round(2) }}</p>
<p>Created: {{ date|date('Y-m-d') }}</p>
<p>Title: {{ title|upper }}</p>
๐ก Practical Examples
๐ Example 1: Dynamic Shopping Cart
Letโs build a real shopping cart page:
# ๐๏ธ Flask route for shopping cart
from flask import Flask, render_template, session
from datetime import datetime
app = Flask(__name__)
app.secret_key = 'your-secret-key-here'
@app.route('/cart')
def shopping_cart():
# ๐ Get cart items from session
cart_items = session.get('cart', [])
# ๐ฐ Calculate totals
subtotal = sum(item['price'] * item['quantity'] for item in cart_items)
tax = subtotal * 0.08 # 8% tax
total = subtotal + tax
return render_template('cart.html',
items=cart_items,
subtotal=subtotal,
tax=tax,
total=total,
current_time=datetime.now())
And the template (templates/cart.html):
<!DOCTYPE html>
<html>
<head>
<title>๐ Your Shopping Cart</title>
<style>
.cart-table { width: 100%; border-collapse: collapse; }
.cart-table th, .cart-table td { padding: 10px; border: 1px solid #ddd; }
.total { font-weight: bold; color: #2ecc71; }
</style>
</head>
<body>
<h1>๐ Your Shopping Cart</h1>
{% if items %}
<table class="cart-table">
<thead>
<tr>
<th>Item</th>
<th>Price</th>
<th>Quantity</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ item.emoji }} {{ item.name }}</td>
<td>${{ "%.2f"|format(item.price) }}</td>
<td>{{ item.quantity }}</td>
<td>${{ "%.2f"|format(item.price * item.quantity) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3">Subtotal:</td>
<td>${{ "%.2f"|format(subtotal) }}</td>
</tr>
<tr>
<td colspan="3">Tax (8%):</td>
<td>${{ "%.2f"|format(tax) }}</td>
</tr>
<tr class="total">
<td colspan="3">Total:</td>
<td>${{ "%.2f"|format(total) }}</td>
</tr>
</tfoot>
</table>
<p>๐ Cart updated: {{ current_time.strftime('%B %d, %Y at %I:%M %p') }}</p>
{% else %}
<p>Your cart is empty! ๐ข <a href="/shop">Start shopping</a> ๐๏ธ</p>
{% endif %}
</body>
</html>
๐ฏ Try it yourself: Add a โRemove Itemโ button and quantity update feature!
๐ฎ Example 2: User Dashboard with Template Inheritance
Letโs create a reusable layout:
Base template (templates/base.html):
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Awesome App{% endblock %} ๐</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; }
.navbar { background: #3498db; color: white; padding: 1rem; }
.navbar a { color: white; text-decoration: none; margin: 0 10px; }
.content { padding: 20px; }
.footer { background: #34495e; color: white; text-align: center; padding: 10px; }
</style>
</head>
<body>
<nav class="navbar">
<a href="/">๐ Home</a>
<a href="/dashboard">๐ Dashboard</a>
<a href="/profile">๐ค Profile</a>
{% if user %}
<span style="float: right;">Welcome, {{ user.name }}! ๐</span>
{% endif %}
</nav>
<div class="content">
{% block content %}
<!-- Page content goes here -->
{% endblock %}
</div>
<footer class="footer">
<p>Made with โค๏ธ and Flask | ยฉ 2024</p>
</footer>
</body>
</html>
Dashboard template (templates/dashboard.html):
{% extends "base.html" %}
{% block title %}Dashboard - {{ user.name }}{% endblock %}
{% block content %}
<h1>๐ Your Dashboard</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>๐ Total Points</h3>
<p class="big-number">{{ user.points }}</p>
</div>
<div class="stat-card">
<h3>๐ Level</h3>
<p class="big-number">{{ user.level }}</p>
</div>
<div class="stat-card">
<h3>๐ฏ Achievements</h3>
<ul>
{% for achievement in user.achievements %}
<li>{{ achievement.emoji }} {{ achievement.name }}</li>
{% endfor %}
</ul>
</div>
</div>
<h2>๐
Recent Activity</h2>
<ul>
{% for activity in recent_activities %}
<li>
{{ activity.timestamp|date }} -
{{ activity.description }}
{% if activity.points_earned > 0 %}
<span class="points">+{{ activity.points_earned }} ๐</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endblock %}
๐ Advanced Concepts
๐งโโ๏ธ Custom Filters and Functions
When youโre ready to level up, create custom Jinja2 features:
# ๐ฏ Custom filters for Jinja2
from flask import Flask
import markdown
app = Flask(__name__)
# ๐ช Custom filter to convert markdown to HTML
@app.template_filter('markdown')
def markdown_filter(text):
return markdown.markdown(text)
# โจ Custom filter for emoji mood
@app.template_filter('mood_emoji')
def mood_emoji_filter(mood):
moods = {
'happy': '๐',
'sad': '๐ข',
'excited': '๐',
'angry': '๐ ',
'confused': '๐ค'
}
return moods.get(mood, '๐')
# ๐จ Custom global function
@app.template_global()
def get_current_year():
from datetime import datetime
return datetime.now().year
Using custom filters in templates:
<!-- ๐ Markdown content -->
<div class="blog-post">
{{ post.content|markdown|safe }}
</div>
<!-- ๐ Mood display -->
<p>Current mood: {{ user.mood|mood_emoji }} {{ user.mood }}</p>
<!-- ๐
Footer with current year -->
<footer>
ยฉ {{ get_current_year() }} My Awesome Site
</footer>
๐๏ธ Macros: Reusable Template Functions
For the brave developers, use macros for DRY templates:
<!-- ๐ Define a macro for product cards -->
{% macro product_card(product) %}
<div class="product-card">
<div class="product-emoji">{{ product.emoji }}</div>
<h3>{{ product.name }}</h3>
<p class="price">${{ "%.2f"|format(product.price) }}</p>
{% if product.in_stock %}
<button class="buy-btn">๐ Add to Cart</button>
{% else %}
<button class="buy-btn" disabled>โ Out of Stock</button>
{% endif %}
</div>
{% endmacro %}
<!-- ๐ฎ Use the macro -->
<div class="products-grid">
{% for item in products %}
{{ product_card(item) }}
{% endfor %}
</div>
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting to Escape User Input
<!-- โ Wrong way - XSS vulnerability! -->
<div>
{{ user_comment|safe }} <!-- ๐ฅ Dangerous if user_comment contains <script> -->
</div>
<!-- โ
Correct way - Auto-escaped by default -->
<div>
{{ user_comment }} <!-- ๐ก๏ธ Safe! HTML is escaped -->
</div>
<!-- โ
Or explicitly escape -->
<div>
{{ user_comment|e }} <!-- ๐ Explicitly escaped -->
</div>
๐คฏ Pitfall 2: Logic in Templates
<!-- โ Too much logic in template -->
{% set total = 0 %}
{% for item in cart %}
{% set total = total + (item.price * item.quantity * (1 - item.discount)) %}
{% endfor %}
<p>Total: ${{ total }}</p>
<!-- โ
Better - calculate in Python -->
<!-- In your Flask route: -->
<!-- total = calculate_cart_total(cart) -->
<p>Total: ${{ "%.2f"|format(total) }}</p>
๐ ๏ธ Best Practices
- ๐ฏ Keep Templates Simple: Business logic belongs in Python, not templates
- ๐ Use Template Inheritance: Create a base template for consistent layouts
- ๐ก๏ธ Trust Auto-escaping: Donโt use
|safe
unless youโre 100% sure - ๐จ Organize Templates: Use folders for different sections (admin/, user/, etc.)
- โจ Use Macros: For repeated HTML patterns, create reusable macros
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Recipe Website
Create a dynamic recipe website with these features:
๐ Requirements:
- โ Recipe list page showing all recipes
- ๐ท๏ธ Individual recipe pages with ingredients and steps
- ๐ค Chef profiles with their recipes
- โญ Rating system (1-5 stars)
- ๐จ Each recipe needs an emoji category!
๐ Bonus Points:
- Add search functionality
- Implement recipe categories (breakfast ๐ฅ, lunch ๐ฅ, dinner ๐)
- Create a โRecipe of the Dayโ feature
๐ก Solution
๐ Click to see solution
# ๐ฏ Flask application for recipes
from flask import Flask, render_template
from datetime import datetime
app = Flask(__name__)
# ๐ณ Sample recipe data
recipes = [
{
'id': 1,
'name': 'Perfect Pancakes',
'emoji': '๐ฅ',
'category': 'breakfast',
'chef': 'Chef Maria',
'rating': 4.8,
'prep_time': 15,
'ingredients': ['2 cups flour', '2 eggs', '1.5 cups milk', 'butter'],
'steps': [
'Mix dry ingredients ๐ฅ',
'Whisk eggs and milk ๐ฅ',
'Combine and mix until smooth ๐',
'Cook on hot griddle ๐ณ'
]
},
{
'id': 2,
'name': 'Garden Salad',
'emoji': '๐ฅ',
'category': 'lunch',
'chef': 'Chef Alex',
'rating': 4.5,
'prep_time': 10,
'ingredients': ['lettuce', 'tomatoes', 'cucumber', 'dressing'],
'steps': [
'Wash vegetables ๐ง',
'Chop into bite sizes ๐ช',
'Toss together ๐ฅ',
'Add dressing and enjoy! ๐'
]
}
]
@app.route('/')
def recipe_list():
return render_template('recipes.html',
recipes=recipes,
recipe_of_day=recipes[0])
@app.route('/recipe/<int:recipe_id>')
def recipe_detail(recipe_id):
recipe = next((r for r in recipes if r['id'] == recipe_id), None)
return render_template('recipe_detail.html', recipe=recipe)
# ๐ Custom filter for star rating
@app.template_filter('stars')
def stars_filter(rating):
full_stars = int(rating)
half_star = 1 if rating - full_stars >= 0.5 else 0
empty_stars = 5 - full_stars - half_star
return 'โญ' * full_stars + 'โจ' * half_star + 'โ' * empty_stars
Base template (templates/recipe_base.html):
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Recipe Book ๐{% endblock %}</title>
<style>
body { font-family: Arial, sans-serif; }
.recipe-card {
border: 1px solid #ddd;
padding: 15px;
margin: 10px;
border-radius: 8px;
}
.rating { color: #f39c12; }
</style>
</head>
<body>
<header>
<h1>๐จโ๐ณ My Recipe Book</h1>
<nav>
<a href="/">๐ Home</a>
<a href="/breakfast">๐ฅ Breakfast</a>
<a href="/lunch">๐ฅ Lunch</a>
<a href="/dinner">๐ Dinner</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>Made with โค๏ธ and lots of ๐ง butter</p>
</footer>
</body>
</html>
Recipe list template (templates/recipes.html):
{% extends "recipe_base.html" %}
{% block content %}
<h2>๐ Recipe of the Day</h2>
<div class="recipe-card featured">
<h3>{{ recipe_of_day.emoji }} {{ recipe_of_day.name }}</h3>
<p>By {{ recipe_of_day.chef }} | {{ recipe_of_day.rating|stars }}</p>
</div>
<h2>๐ All Recipes</h2>
<div class="recipe-grid">
{% for recipe in recipes %}
<div class="recipe-card">
<h3>
<a href="/recipe/{{ recipe.id }}">
{{ recipe.emoji }} {{ recipe.name }}
</a>
</h3>
<p>โฑ๏ธ {{ recipe.prep_time }} mins | {{ recipe.rating|stars }}</p>
<p>๐จโ๐ณ {{ recipe.chef }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
๐ Key Takeaways
Youโve learned so much about Flask templates and Jinja2! Hereโs what you can now do:
- โ Create dynamic web pages with Flask and Jinja2 ๐ช
- โ Use template inheritance for consistent layouts ๐๏ธ
- โ Apply filters and control structures in templates ๐ฏ
- โ Avoid common security pitfalls with auto-escaping ๐ก๏ธ
- โ Build real web applications with dynamic content! ๐
Remember: Jinja2 makes your web pages come alive with data. Keep your templates clean, your logic in Python, and let Jinja2 handle the presentation! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered Flask templates with Jinja2!
Hereโs what to do next:
- ๐ป Practice with the recipe website exercise
- ๐๏ธ Build a personal blog using template inheritance
- ๐ Move on to our next tutorial: Flask Forms and User Input
- ๐ Share your awesome Flask creations with the world!
Remember: Every web developer started with their first template. Keep building, keep learning, and most importantly, have fun creating amazing web applications! ๐
Happy templating! ๐๐โจ