Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Jinja2 Templates - dynamiczne interfejsy

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! 🗺️✨

Problem z hardcoded HTML

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)

Rozwiązanie - Jinja2 Templates

Jinja2 to template engine - silnik szablonów pozwalający oddzielić HTML od logiki Pythona.

Jak to działa?

  1. Tworzysz pliki HTML w folderze
    templates/
  2. Używasz
    {{ zmienna }}
    do wyświetlania danych
  3. Używasz
    {% if %}
    ,
    {% for %}
    do logiki
  4. Flask renderuje szablon + dane → gotowy HTML

Analogia: Jinja2 to jak formularz do wypełnienia - masz gotową strukturę (HTML), tylko wstawiasz dane (zmienne Pythona)! 📝🦁

Struktura projektu z templates

1safari_web/
23├── app.py
45├── templates/          ← Folder na szablony HTML
6│   ├── base.html
7│   ├── home.html
8│   ├── species_list.html
9│   └── species_detail.html
1011└── static/             ← Folder na pliki statyczne
12    ├── css/
13    │   └── style.css
14    ├── js/
15    │   └── script.js
16    └── images/
17        └── logo.png

Flask automatycznie szuka plików w folderze

templates/
!

Podstawy Jinja2 - składnia

1. Zmienne - {{ }}

Wyświetl wartość zmiennej:

1<h1>{{ species_name }}</h1>
2<p>Populacja: {{ population }}</p>

2. Wyrażenia

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 -->

3. Control structures - {% %}

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 %}

4. Filtry - |

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 liter
  • length
    - długość listy/stringa
  • default(value)
    - wartość domyślna jeśli None
  • round(n)
    - zaokrąglenie do n miejsc
  • truncate(n)
    - skrócenie tekstu
  • join(separator)
    - połącz listę
  • safe
    - wyłącz HTML escaping (ostrożnie!)

5. Komentarze - {# #}

1{# To jest komentarz - nie będzie widoczny w HTML #}
2<h1>{{ species_name }}</h1>

Renderowanie templates w Flask

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:

  1. Szuka pliku
    templates/home.html
  2. Renderuje szablon z przekazanymi zmiennymi
  3. Zwraca gotowy HTML

Pierwszy template - home.html

Stwó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! 🎉

Template inheritance - dziedziczenie szablonów

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! 🧬🦁

base.html - bazowy szablon

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>&copy; 2024 Safari Database by Darwin</p>
70    </footer>
71
72    {% block extra_js %}{% endblock %}
73</body>
74</html>

Bloki (

{% block %}
): Miejsca, które mogą być nadpisane w child templates.

home.html - rozszerzenie base.html

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 %}

{% extends 'base.html' %}
- dziedziczy z base.html
{% block content %}
- nadpisuje blok content

species_list.html - lista gatunków

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 %}

species_detail.html - szczegóły gatunku

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 %}

Static files - pliki statyczne

Pliki CSS, JavaScript, obrazy przechowujemy w folderze

static/
.

Struktura:

1static/
2├── css/
3│   └── style.css
4├── js/
5│   └── script.js
6└── images/
7    └── logo.png

static/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; }

Użycie static files w templates

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>

url_for('static', filename='...')
generuje URL do pliku statycznego.

Kompletna aplikacja z templates

app.py

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)

templates/base.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>{% 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>&copy; 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 %}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 %}

Przydatne techniki Jinja2

1. Przekazywanie wielu zmiennych

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'])

2. Przekazywanie słownika jako **kwargs

1context = {
2    'title': 'Dashboard',
3    'user': 'Darwin',
4    'species_count': 10
5}
6
7return render_template('dashboard.html', **context)

3. Globalne zmienne (dostępne we wszystkich 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# Teraz {{ app_name }}, {{ version }}, {{ current_year }} działają wszędzie!

4. Custom filtry

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"

5. Makra (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>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 %}

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • ✅ Dlaczego templates są lepsze niż hardcoded HTML
  • ✅ Składnia Jinja2 ({{ }}, {% %}, {# #})
  • ✅ Zmienne i wyrażenia
  • ✅ Control structures (if, for)
  • ✅ Filtry (|upper, |length, |default)
  • ✅ Template inheritance (extends, blocks)
  • ✅ Static files (CSS, JS, images)
  • url_for()
    w templates
  • ✅ Kompletna aplikacja Safari Database z templates

Analogia 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! 📝✅

Ir a CodeWorlds