Welcome back, @name! 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! πΊοΈβ¨
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
Jinja2 is a template engine - a templating engine that allows separating HTML from Python logic.
How it works?
Analogy: Jinja2 is like a fill-in form - you have a ready structure (HTML), you just insert data (Python variables)! ππ¦
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!
Display the value of a variable:
1<h1>{{ species_name }}</h1>
2<p>Population: {{ population }}</p>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 -->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 %}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:
1{# This is a comment - won't be visible in HTML #}
2<h1>{{ species_name }}</h1>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:
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! π
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! π§¬π¦
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.
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
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 %}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 %}CSS, JavaScript, images files are stored in the `static/` folder.
1static/
2βββ css/
3β βββ style.css
4βββ js/
5β βββ script.js
6βββ images/
7 βββ logo.png1/* 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; }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.
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)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>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 %}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'])1context = {
2 'title': 'Dashboard',
3 'user': 'Darwin',
4 'species_count': 10
5}
6
7return render_template('dashboard.html', **context)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!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"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 %}In this lesson you learned:
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! πβ