Python course Β· Module 5: Flask and Web Apps
Jinja2 Templates - dynamic interfaces
In this lesson11
Welcome back! Darwin here with a new tool - Jinja2 templates!
So far you've been writing HTML as hardcoded strings in Python functions. That works, but it's unreadable, hard to maintain, and impractical for larger applications!
Safari Analogy: Hardcoded HTML is like drawing every safari map by hand from scratch - tiring and time-consuming! Jinja2 templates are like map templates - you prepare the structure once, then just fill in the data!
The problem with hardcoded HTML
Remember the code from the previous lesson:
1@app.route('/species')
2def list_species():
3 species_html = ""
4 for spec in species_db.values():
5 species_html += f"""
6 <div style="background: white; padding: 15px;">
7 <h3>{spec['name']}</h3>
8 <p><strong>Population:</strong> {spec['population']}</p>
9 </div>
10 """
11
12 return f"""
13 <!DOCTYPE html>
14 <html>
15 <head><title>Species list</title></head>
16 <body>
17 <h1>Species list</h1>
18 {species_html}
19 </body>
20 </html>
21 """Problems: HTML mixed with Python logic Hard to read and edit No HTML syntax highlighting Code duplication (navigation, footer) Difficult collaboration with designers No reusability
Solution - Jinja2 Templates
Jinja2 is a template engine - a templating engine that allows separating HTML from Python logic.
How it works?
- You create HTML files in the
templates/folder - You use
{{ variable }}to display data - You use
{% if %},{% for %}for logic - Flask renders template + data β finished HTML
Analogy: Jinja2 is like a fill-in form - you have a ready structure (HTML), you just insert data (Python variables)!
Project structure with templates
1safari_web/
2β
3βββ app.py
4β
5βββ templates/ β Folder for HTML templates
6β βββ base.html
7β βββ home.html
8β βββ species_list.html
9β βββ species_detail.html
10β
11βββ static/ β Folder for static files
12 βββ css/
13 β βββ style.css
14 βββ js/
15 β βββ script.js
16 βββ images/
17 βββ logo.pngFlask automatically looks for files in the templates/ folder!
Jinja2 basics - syntax
1. Variables - {{ }}
Display the value of a variable:
1<h1>{{ species_name }}</h1>
2<p>Population: {{ population }}</p>2. Expressions
1<p>{{ 2 + 3 }}</p> <!-- 5 -->
2<p>{{ "Hello " + name }}</p> <!-- Hello Darwin -->
3<p>{{ species.name }}</p> <!-- Attribute access -->
4<p>{{ species['name'] }}</p> <!-- Dict access -->
5<p>{{ species_list[0] }}</p> <!-- List element access -->3. Control structures - {% %}
If statement:
1{% if population > 1000 %}
2 <span style="color: green;">Safe</span>
3{% elif population > 100 %}
4 <span style="color: orange;">Endangered</span>
5{% else %}
6 <span style="color: red;">Critically endangered</span>
7{% endif %}For loop:
1<ul>
2{% for species in species_list %}
3 <li>{{ species.name }} - {{ species.population }} individuals</li>
4{% endfor %}
5</ul>For loop with index:
1{% for species in species_list %}
2 <p>{{ loop.index }}. {{ species.name }}</p>
3{% endfor %}4. Filters - |
Filters modify variables:
1<p>{{ name | upper }}</p> <!-- LION -->
2<p>{{ name | lower }}</p> <!-- lion -->
3<p>{{ name | title }}</p> <!-- Lion -->
4<p>{{ species_list | length }}</p> <!-- 3 -->
5<p>{{ price | round(2) }}</p> <!-- 19.99 -->
6<p>{{ text | truncate(50) }}</p> <!-- Truncate to 50 chars -->
7<p>{{ published_date | default('None') }}</p> <!-- Default value -->Popular filters:
upper,lower,title- case changelength- list/string lengthdefault(value)- default value if Noneround(n)- round to n placestruncate(n)- truncate textjoin(separator)- join listsafe- disable HTML escaping (use carefully!)
5. Comments - {# #}
1{# This is a comment - won't be visible in HTML #}
2<h1>{{ species_name }}</h1>Rendering templates in Flask
Use the render_template() function:
1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7 # Render template + pass variables
8 return render_template('home.html',
9 title='Safari Database',
10 species_count=3)
11
12@app.route('/species/<int:species_id>')
13def show_species(species_id):
14 species = species_db.get(species_id)
15
16 return render_template('species_detail.html',
17 species=species)Flask:
- Looks for the file
templates/home.html - Renders the template with passed variables
- Returns finished HTML
First template - home.html
Create a templates/ folder and a home.html file:
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>{{ title }}</title>
7 <style>
8 body {
9 font-family: Arial, sans-serif;
10 max-width: 800px;
11 margin: 50px auto;
12 padding: 20px;
13 background-color: #f4f1de;
14 }
15 h1 { color: #2c5f2d; }
16 </style>
17</head>
18<body>
19 <h1>{{ title }}</h1>
20 <p>Welcome to Safari Database!</p>
21 <p>The database contains <strong>{{ species_count }} species</strong>.</p>
22</body>
23</html>app.py:
1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7 return render_template('home.html',
8 title='Safari Database',
9 species_count=3)
10
11if __name__ == '__main__':
12 app.run(debug=True)Run: http://localhost:5000/ - you'll see the rendered template!
Template inheritance
The most important Jinja2 feature! It allows creating a base template (layout) and extending it in other templates.
Analogy: Template inheritance is like DNA inheritance - a descendant species (child template) inherits traits from its parent (base template), but can have its own unique characteristics!
base.html - base template
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>{% block title %}Safari Database{% endblock %}</title>
7 <style>
8 body {
9 font-family: Arial, sans-serif;
10 max-width: 900px;
11 margin: 0 auto;
12 padding: 20px;
13 background-color: #f4f1de;
14 }
15 header {
16 background-color: #2c5f2d;
17 color: white;
18 padding: 20px;
19 border-radius: 8px;
20 margin-bottom: 20px;
21 }
22 nav {
23 margin: 20px 0;
24 padding: 15px;
25 background: white;
26 border-radius: 8px;
27 }
28 nav a {
29 margin-right: 15px;
30 color: #2c5f2d;
31 text-decoration: none;
32 font-weight: bold;
33 }
34 nav a:hover {
35 text-decoration: underline;
36 }
37 main {
38 background: white;
39 padding: 30px;
40 border-radius: 12px;
41 }
42 footer {
43 margin-top: 30px;
44 text-align: center;
45 color: #666;
46 }
47 </style>
48 {% block extra_css %}{% endblock %}
49</head>
50<body>
51 <header>
52 <h1>Safari Database</h1>
53 <p>African species data management system</p>
54 </header>
55
56 <nav>
57 <a href="{{ url_for('home') }}">Home</a>
58 <a href="{{ url_for('list_species') }}">Species list</a>
59 <a href="{{ url_for('search') }}">Search</a>
60 </nav>
61
62 <main>
63 {% block content %}
64 <!-- Content from child templates goes here -->
65 {% endblock %}
66 </main>
67
68 <footer>
69 <p>© 2024 Safari Database by Darwin</p>
70 </footer>
71
72 {% block extra_js %}{% endblock %}
73</body>
74</html>Blocks ({% block %}): Places that can be overridden in child templates.
home.html - extending base.html
1{% extends 'base.html' %}
2
3{% block title %}Home - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>Welcome to Safari Database!</h2>
7 <p>The database contains <strong>{{ species_count }} species</strong>.</p>
8
9 <div style="margin-top: 30px;">
10 <h3>System features:</h3>
11 <ul>
12 <li>Browse species list</li>
13 <li>Search species</li>
14 <li>Population statistics</li>
15 <li>Add new observations</li>
16 </ul>
17 </div>
18
19 <p style="margin-top: 30px;">
20 <a href="{{ url_for('list_species') }}" style="background: #2c5f2d; color: white; padding: 10px 20px; border-radius: 4px; text-decoration: none;">
21 See all species β
22 </a>
23 </p>
24{% endblock %}{% extends 'base.html' %} - inherits from base.html
{% block content %} - overrides the content block
species_list.html - species list
1{% extends 'base.html' %}
2
3{% block title %}Species list - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>All species ({{ species | length }})</h2>
7
8 {% if species %}
9 {% for spec in species %}
10 <div style="background: #f9f9f9; padding: 20px; margin: 15px 0; border-radius: 8px; border-left: 4px solid #2c5f2d;">
11 <h3 style="margin-top: 0;">{{ spec.name }}</h3>
12 <p><strong>Scientific name:</strong> <em>{{ spec.scientific_name }}</em></p>
13 <p><strong>Population:</strong> {{ spec.population }} individuals</p>
14
15 {% if spec.population > 1000 %}
16 <span style="color: green;">Safe</span>
17 {% elif spec.population > 100 %}
18 <span style="color: orange;">Endangered</span>
19 {% else %}
20 <span style="color: red;">Critically endangered</span>
21 {% endif %}
22
23 <p style="margin-top: 15px;">
24 <a href="{{ url_for('show_species', species_id=spec.id) }}" style="color: #2c5f2d; font-weight: bold;">
25 See details β
26 </a>
27 </p>
28 </div>
29 {% endfor %}
30 {% else %}
31 <p style="color: #666;">No species in the database.</p>
32 {% endif %}
33{% endblock %}species_detail.html - species details
1{% extends 'base.html' %}
2
3{% block title %}{{ species.name }} - Safari Database{% endblock %}
4
5{% block content %}
6 {% if species %}
7 <h2>{{ species.name }}</h2>
8
9 <div style="background: #f9f9f9; padding: 30px; border-radius: 12px; border: 3px solid #2c5f2d;">
10 <table style="width: 100%; border-collapse: collapse;">
11 <tr>
12 <td style="padding: 10px; font-weight: bold;">Common name:</td>
13 <td style="padding: 10px;">{{ species.name }}</td>
14 </tr>
15 <tr>
16 <td style="padding: 10px; font-weight: bold;">Scientific name:</td>
17 <td style="padding: 10px;"><em>{{ species.scientific_name }}</em></td>
18 </tr>
19 <tr>
20 <td style="padding: 10px; font-weight: bold;">Population:</td>
21 <td style="padding: 10px;">{{ species.population }} individuals</td>
22 </tr>
23 <tr>
24 <td style="padding: 10px; font-weight: bold;">Status:</td>
25 <td style="padding: 10px;">
26 {% if species.population > 1000 %}
27 <span style="color: green;">Safe</span>
28 {% elif species.population > 100 %}
29 <span style="color: orange;">Endangered</span>
30 {% else %}
31 <span style="color: red;">Critically endangered</span>
32 {% endif %}
33 </td>
34 </tr>
35 <tr>
36 <td style="padding: 10px; font-weight: bold;">System ID:</td>
37 <td style="padding: 10px;">{{ species.id }}</td>
38 </tr>
39 </table>
40 </div>
41
42 <p style="margin-top: 30px;">
43 <a href="{{ url_for('list_species') }}" style="color: #2c5f2d;">β Back to list</a> |
44 <a href="{{ url_for('home') }}" style="color: #2c5f2d;">β Home page</a>
45 </p>
46 {% else %}
47 <h2>Species not found</h2>
48 <p>The species with the given ID does not exist in the database.</p>
49 <p><a href="{{ url_for('list_species') }}">β Back to list</a></p>
50 {% endif %}
51{% endblock %}Static files
CSS, JavaScript, images files are stored in the static/ folder.
Structure:
1static/
2βββ css/
3β βββ style.css
4βββ js/
5β βββ script.js
6βββ images/
7 βββ logo.pngstatic/css/style.css
1/* Safari Database Styles */
2body {
3 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
4 max-width: 900px;
5 margin: 0 auto;
6 padding: 20px;
7 background-color: #f4f1de;
8}
9
10header {
11 background: linear-gradient(135deg, #2c5f2d 0%, #45a049 100%);
12 color: white;
13 padding: 30px;
14 border-radius: 12px;
15 margin-bottom: 20px;
16 box-shadow: 0 4px 6px rgba(0,0,0,0.1);
17}
18
19.species-card {
20 background: white;
21 padding: 20px;
22 margin: 15px 0;
23 border-radius: 8px;
24 border-left: 4px solid #2c5f2d;
25 box-shadow: 0 2px 4px rgba(0,0,0,0.05);
26 transition: transform 0.2s;
27}
28
29.species-card:hover {
30 transform: translateY(-3px);
31 box-shadow: 0 4px 8px rgba(0,0,0,0.1);
32}
33
34.badge {
35 display: inline-block;
36 padding: 4px 12px;
37 border-radius: 4px;
38 font-size: 13px;
39 font-weight: bold;
40}
41
42.badge-safe { background: #4caf50; color: white; }
43.badge-endangered { background: #ff9800; color: white; }
44.badge-critical { background: #f44336; color: white; }Using static files in templates
1<!DOCTYPE html>
2<html>
3<head>
4 <title>Safari Database</title>
5 <!-- Link to CSS -->
6 <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
7</head>
8<body>
9 <!-- Logo -->
10 <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">
11
12 <!-- Page content -->
13
14 <!-- Link to JS -->
15 <script src="{{ url_for('static', filename='js/script.js') }}"></script>
16</body>
17</html>url_for('static', filename='...') generates the URL to a static file.
Complete application with templates
app.py
1# app.py
2from flask import Flask, render_template, request, redirect, url_for
3
4app = Flask(__name__)
5
6# Test data
7species_db = {
8 1: {"id": 1, "name": "Lion", "scientific_name": "Panthera leo", "population": 120},
9 2: {"id": 2, "name": "African Elephant", "scientific_name": "Loxodonta africana", "population": 450},
10 3: {"id": 3, "name": "Cheetah", "scientific_name": "Acinonyx jubatus", "population": 7100},
11 4: {"id": 4, "name": "Giraffe", "scientific_name": "Giraffa camelopardalis", "population": 850},
12}
13next_id = 5
14
15@app.route('/')
16def home():
17 return render_template('home.html',
18 species_count=len(species_db))
19
20@app.route('/species')
21def list_species():
22 return render_template('species_list.html',
23 species=list(species_db.values()))
24
25@app.route('/species/<int:species_id>')
26def show_species(species_id):
27 species = species_db.get(species_id)
28 return render_template('species_detail.html',
29 species=species)
30
31@app.route('/search')
32def search():
33 query = request.args.get('q', '').lower()
34
35 if query:
36 results = [s for s in species_db.values()
37 if query in s['name'].lower() or query in s['scientific_name'].lower()]
38 else:
39 results = []
40
41 return render_template('search.html',
42 query=query,
43 results=results)
44
45if __name__ == '__main__':
46 print("Safari Database starting...")
47 app.run(debug=True)templates/base.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>{% block title %}Safari Database{% endblock %}</title>
7 <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8 {% block extra_css %}{% endblock %}
9</head>
10<body>
11 <header>
12 <h1>Safari Database</h1>
13 <p>African species data management system</p>
14 </header>
15
16 <nav>
17 <a href="{{ url_for('home') }}">Home</a>
18 <a href="{{ url_for('list_species') }}">Species list</a>
19 <a href="{{ url_for('search') }}">Search</a>
20 </nav>
21
22 <main>
23 {% block content %}{% endblock %}
24 </main>
25
26 <footer>
27 <p>© 2024 Safari Database by Darwin | Powered by Flask & Jinja2</p>
28 </footer>
29
30 {% block extra_js %}{% endblock %}
31</body>
32</html>templates/search.html
1{% extends 'base.html' %}
2
3{% block title %}Search - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>Species search</h2>
7
8 <form method="GET" style="margin: 30px 0;">
9 <input type="text"
10 name="q"
11 value="{{ query }}"
12 placeholder="Enter species name..."
13 style="padding: 12px; width: 60%; border: 2px solid #2c5f2d; border-radius: 4px; font-size: 16px;">
14 <button type="submit"
15 style="padding: 12px 24px; background: #2c5f2d; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;">
16 Search
17 </button>
18 </form>
19
20 {% if query %}
21 <h3>Results for: "{{ query }}" ({{ results | length }})</h3>
22
23 {% if results %}
24 {% for species in results %}
25 <div class="species-card">
26 <h3 style="margin-top: 0;">{{ species.name }}</h3>
27 <p><em>{{ species.scientific_name }}</em></p>
28 <p>Population: {{ species.population }} individuals</p>
29 <a href="{{ url_for('show_species', species_id=species.id) }}" style="color: #2c5f2d; font-weight: bold;">
30 See details β
31 </a>
32 </div>
33 {% endfor %}
34 {% else %}
35 <p style="color: #666; text-align: center; padding: 40px;">
36 No species found matching query "{{ query }}"
37 </p>
38 {% endif %}
39 {% else %}
40 <p style="color: #666; text-align: center; padding: 40px;">
41 Enter a species name to start searching.
42 </p>
43 {% endif %}
44{% endblock %}Useful Jinja2 techniques
1. Passing multiple variables
1@app.route('/dashboard')
2def dashboard():
3 return render_template('dashboard.html',
4 title='Dashboard',
5 user='Darwin',
6 species_count=10,
7 observations_count=152,
8 recent_activity=['Lion observation', 'Added cheetah'])2. Passing a dictionary as **kwargs
1context = {
2 'title': 'Dashboard',
3 'user': 'Darwin',
4 'species_count': 10
5}
6
7return render_template('dashboard.html', **context)3. Global variables (available in all templates)
1@app.context_processor
2def inject_globals():
3 return {
4 'app_name': 'Safari Database',
5 'version': '1.0.0',
6 'current_year': 2024
7 }
8
9# Now {{ app_name }}, {{ version }}, {{ current_year }} work everywhere!4. Custom filters
1@app.template_filter('format_population')
2def format_population(value):
3 if value >= 1000:
4 return f"{value / 1000:.1f}k"
5 return str(value)
6
7# In template:
8# {{ species.population | format_population }} β "7.1k"5. Macros (reusable components)
1{# macros.html #}
2{% macro render_species_card(species) %}
3 <div class="species-card">
4 <h3>{{ species.name }}</h3>
5 <p><em>{{ species.scientific_name }}</em></p>
6 <p>Population: {{ species.population }}</p>
7 </div>
8{% endmacro %}
9
10{# In other templates #}
11{% from 'macros.html' import render_species_card %}
12
13{% for species in species_list %}
14 {{ render_species_card(species) }}
15{% endfor %}Summary
In this lesson you learned:
- Why templates are better than hardcoded HTML
- Jinja2 syntax ({{ }}, {% %}, {# #})
- Variables and expressions
- Control structures (if, for)
- Filters (|upper, |length, |default)
- Template inheritance (extends, blocks)
- Static files (CSS, JS, images)
url_for()in templates- Complete Safari Database application with templates
Final Safari Analogy: Jinja2 templates are like genetic DNA templates - you have a base structure (base.html = ancestor DNA), which you extend in descendants (child templates = descendant DNA) with unique characteristics (blocks)! All species (templates) inherit common traits (navigation, footer), but have their own uniqueness (content)!
Next lesson: Darwin will show you Flask-WTF - forms and data validation the professional way!
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you render the template 'species.html' with an 'animals' variable?
2. What is Jinja2 in the context of Flask?
These are 2 of 4 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
What does the syntax for displaying a species variable look like in Jinja2?
- Horizontal ordering
How do you start an if condition in Jinja2?
- Click in order
Arrange a for loop iterating over animals in Jinja2:
- Vertical ordering
Arrange the structure of a Jinja2 template with inheritance:
- Code editor
Write a template with a for loop displaying animal names in <li> elements.