Witaj z powrotem, @name! Darwin tutaj z nowym narzędziem - Jinja2 templates! 🎨📄
Do tej pory pisałeś/aś HTML jako hardcoded strings w funkcjach Pythona. To działa, ale jest nieczytelne, trudne w utrzymaniu, i niepraktyczne dla większych aplikacji! 😓
Analogia Safari: Hardcoded HTML to jak ręczne rysowanie każdej mapy safari od zera - męczące i czasochłonne! Jinja2 templates to jak szablony map - raz przygotowujesz strukturę, potem tylko wypełniasz danymi! 🗺️✨
Przypomnij sobie kod z poprzedniej lekcji:
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>Populacja:</strong> {spec['population']}</p>
9 </div>
10 """
11
12 return f"""
13 <!DOCTYPE html>
14 <html>
15 <head><title>Lista gatunków</title></head>
16 <body>
17 <h1>Lista gatunków</h1>
18 {species_html}
19 </body>
20 </html>
21 """Problemy: ❌ HTML zmieszany z logiką Pythona ❌ Trudno czytać i edytować ❌ Brak podświetlania składni HTML ❌ Duplikacja kodu (nawigacja, footer) ❌ Trudna współpraca z designerami ❌ Brak ponownego użycia (reusability)
Jinja2 to template engine - silnik szablonów pozwalający oddzielić HTML od logiki Pythona.
Jak to działa?
templates/{{ zmienna }} do wyświetlania danych{% if %}, {% for %} do logikiAnalogia: Jinja2 to jak formularz do wypełnienia - masz gotową strukturę (HTML), tylko wstawiasz dane (zmienne Pythona)! 📝🦁
1safari_web/
2│
3├── app.py
4│
5├── templates/ ← Folder na szablony HTML
6│ ├── base.html
7│ ├── home.html
8│ ├── species_list.html
9│ └── species_detail.html
10│
11└── static/ ← Folder na pliki statyczne
12 ├── css/
13 │ └── style.css
14 ├── js/
15 │ └── script.js
16 └── images/
17 └── logo.pngFlask automatycznie szuka plików w folderze
templates/!Wyświetl wartość zmiennej:
1<h1>{{ species_name }}</h1>
2<p>Populacja: {{ population }}</p>1<p>{{ 2 + 3 }}</p> <!-- 5 -->
2<p>{{ "Hello " + name }}</p> <!-- Hello Darwin -->
3<p>{{ species.name }}</p> <!-- Dostęp do atrybutu -->
4<p>{{ species['name'] }}</p> <!-- Dostęp jak w dict -->
5<p>{{ species_list[0] }}</p> <!-- Dostęp do elementu listy -->If statement:
1{% if population > 1000 %}
2 <span style="color: green;">Bezpieczny</span>
3{% elif population > 100 %}
4 <span style="color: orange;">Zagrożony</span>
5{% else %}
6 <span style="color: red;">Krytycznie zagrożony</span>
7{% endif %}For loop:
1<ul>
2{% for species in species_list %}
3 <li>{{ species.name }} - {{ species.population }} osobników</li>
4{% endfor %}
5</ul>For loop z indeksem:
1{% for species in species_list %}
2 <p>{{ loop.index }}. {{ species.name }}</p>
3{% endfor %}Filtry modyfikują zmienne:
1<p>{{ name | upper }}</p> <!-- LEW -->
2<p>{{ name | lower }}</p> <!-- lew -->
3<p>{{ name | title }}</p> <!-- Lew -->
4<p>{{ species_list | length }}</p> <!-- 3 -->
5<p>{{ price | round(2) }}</p> <!-- 19.99 -->
6<p>{{ text | truncate(50) }}</p> <!-- Skróć do 50 znaków -->
7<p>{{ published_date | default('Brak') }}</p> <!-- Wartość domyślna -->Popularne filtry:
upper, lower, title - zmiana wielkości literlength - długość listy/stringadefault(value) - wartość domyślna jeśli Noneround(n) - zaokrąglenie do n miejsctruncate(n) - skrócenie tekstujoin(separator) - połącz listęsafe - wyłącz HTML escaping (ostrożnie!)1{# To jest komentarz - nie będzie widoczny w HTML #}
2<h1>{{ species_name }}</h1>Użyj funkcji
render_template():1from flask import Flask, render_template
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7 # Renderuj szablon + przekaż zmienne
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:
templates/home.htmlStwórz folder
templates/ i plik home.html:1<!DOCTYPE html>
2<html lang="pl">
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>Witaj w Safari Database!</p>
21 <p>Baza zawiera <strong>{{ species_count }} gatunków</strong> afrykańskich.</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)Uruchom: http://localhost:5000/ - zobaczysz wyrenderowany template! 🎉
Najważniejsza funkcja Jinja2! Pozwala tworzyć bazowy szablon (layout) i rozszerzać go w innych szablonach.
Analogia: Template inheritance to jak DNA dziedziczenie - gatunek potomny (child template) dziedziczy cechy rodzica (base template), ale może mieć własne unikalne cechy! 🧬🦁
1<!DOCTYPE html>
2<html lang="pl">
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>System zarządzania danymi o gatunkach afrykańskich</p>
54 </header>
55
56 <nav>
57 <a href="{{ url_for('home') }}">Home</a>
58 <a href="{{ url_for('list_species') }}">Lista gatunków</a>
59 <a href="{{ url_for('search') }}">Szukaj</a>
60 </nav>
61
62 <main>
63 {% block content %}
64 <!-- Tutaj będzie treść z child templates -->
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>Bloki (
): Miejsca, które mogą być nadpisane w child templates.{% block %}
1{% extends 'base.html' %}
2
3{% block title %}Strona główna - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>Witaj w Safari Database!</h2>
7 <p>Baza danych zawiera <strong>{{ species_count }} gatunków</strong> afrykańskich.</p>
8
9 <div style="margin-top: 30px;">
10 <h3>Funkcje systemu:</h3>
11 <ul>
12 <li>📋 Przeglądanie listy gatunków</li>
13 <li>🔍 Wyszukiwanie gatunków</li>
14 <li>📊 Statystyki populacji</li>
15 <li>➕ Dodawanie nowych obserwacji</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 Zobacz wszystkie gatunki →
22 </a>
23 </p>
24{% endblock %}
- dziedziczy z base.html
{% extends 'base.html' %}
- nadpisuje blok content{% block content %}
1{% extends 'base.html' %}
2
3{% block title %}Lista gatunków - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>🦁 Lista wszystkich gatunków ({{ 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>Nazwa naukowa:</strong> <em>{{ spec.scientific_name }}</em></p>
13 <p><strong>Populacja:</strong> {{ spec.population }} osobników</p>
14
15 {% if spec.population > 1000 %}
16 <span style="color: green;">✅ Bezpieczny</span>
17 {% elif spec.population > 100 %}
18 <span style="color: orange;">⚠️ Zagrożony</span>
19 {% else %}
20 <span style="color: red;">🚨 Krytycznie zagrożony</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 Zobacz szczegóły →
26 </a>
27 </p>
28 </div>
29 {% endfor %}
30 {% else %}
31 <p style="color: #666;">Brak gatunków w bazie danych.</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;">Nazwa pospolita:</td>
13 <td style="padding: 10px;">{{ species.name }}</td>
14 </tr>
15 <tr>
16 <td style="padding: 10px; font-weight: bold;">Nazwa naukowa:</td>
17 <td style="padding: 10px;"><em>{{ species.scientific_name }}</em></td>
18 </tr>
19 <tr>
20 <td style="padding: 10px; font-weight: bold;">Populacja:</td>
21 <td style="padding: 10px;">{{ species.population }} osobników</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;">✅ Bezpieczny</span>
28 {% elif species.population > 100 %}
29 <span style="color: orange;">⚠️ Zagrożony</span>
30 {% else %}
31 <span style="color: red;">🚨 Krytycznie zagrożony</span>
32 {% endif %}
33 </td>
34 </tr>
35 <tr>
36 <td style="padding: 10px; font-weight: bold;">ID w systemie:</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;">← Powrót do listy</a> |
44 <a href="{{ url_for('home') }}" style="color: #2c5f2d;">← Strona główna</a>
45 </p>
46 {% else %}
47 <h2>❌ Gatunek nie znaleziony</h2>
48 <p>Gatunek o podanym ID nie istnieje w bazie danych.</p>
49 <p><a href="{{ url_for('list_species') }}">← Powrót do listy</a></p>
50 {% endif %}
51{% endblock %}Pliki CSS, JavaScript, obrazy przechowujemy w folderze
static/.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 do 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 <!-- Treść strony -->
13
14 <!-- Link do JS -->
15 <script src="{{ url_for('static', filename='js/script.js') }}"></script>
16</body>
17</html>
generuje URL do pliku statycznego.url_for('static', filename='...')
1# app.py
2from flask import Flask, render_template, request, redirect, url_for
3
4app = Flask(__name__)
5
6# Dane testowe
7species_db = {
8 1: {"id": 1, "name": "Lew", "scientific_name": "Panthera leo", "population": 120},
9 2: {"id": 2, "name": "Słoń afrykański", "scientific_name": "Loxodonta africana", "population": 450},
10 3: {"id": 3, "name": "Gepard", "scientific_name": "Acinonyx jubatus", "population": 7100},
11 4: {"id": 4, "name": "Żyrafa", "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 startuje...")
47 app.run(debug=True)1<!DOCTYPE html>
2<html lang="pl">
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>System zarządzania danymi o gatunkach afrykańskich</p>
14 </header>
15
16 <nav>
17 <a href="{{ url_for('home') }}">Home</a>
18 <a href="{{ url_for('list_species') }}">Lista gatunków</a>
19 <a href="{{ url_for('search') }}">Szukaj</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 %}Szukaj - Safari Database{% endblock %}
4
5{% block content %}
6 <h2>🔍 Wyszukiwarka gatunków</h2>
7
8 <form method="GET" style="margin: 30px 0;">
9 <input type="text"
10 name="q"
11 value="{{ query }}"
12 placeholder="Wpisz nazwę gatunku..."
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 Szukaj
17 </button>
18 </form>
19
20 {% if query %}
21 <h3>Wyniki dla: "{{ 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>Populacja: {{ species.population }} osobników</p>
29 <a href="{{ url_for('show_species', species_id=species.id) }}" style="color: #2c5f2d; font-weight: bold;">
30 Zobacz szczegóły →
31 </a>
32 </div>
33 {% endfor %}
34 {% else %}
35 <p style="color: #666; text-align: center; padding: 40px;">
36 ❌ Nie znaleziono gatunków pasujących do zapytania "{{ query }}"
37 </p>
38 {% endif %}
39 {% else %}
40 <p style="color: #666; text-align: center; padding: 40px;">
41 Wpisz nazwę gatunku, aby rozpocząć wyszukiwanie.
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=['Obserwacja lwa', 'Dodano geparda'])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# Teraz {{ app_name }}, {{ version }}, {{ current_year }} działają wszędzie!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# W 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>Populacja: {{ species.population }}</p>
7 </div>
8{% endmacro %}
9
10{# W innych templates #}
11{% from 'macros.html' import render_species_card %}
12
13{% for species in species_list %}
14 {{ render_species_card(species) }}
15{% endfor %}W tej lekcji nauczyłeś/aś się:
url_for() w templatesAnalogia Safari finalna: Jinja2 templates to jak genetyczne szablony DNA - masz bazową strukturę (base.html = DNA przodka), którą rozszerzasz w potomkach (child templates = DNA potomka) z unikalnymi cechami (blocks)! Wszystkie gatunki (templates) dziedziczą wspólne cechy (nawigacja, footer), ale mają własne unikalności (treść)! 🧬🦁
Następna lekcja: Darwin pokaże Ci Flask-WTF - formularze i walidację danych w profesjonalny sposób! 📝✅