Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Routing i view functions - zaawansowane techniki

Witaj ponownie, @name! Darwin tutaj, gotowy pokazać Ci zaawansowane techniki routingu w Flask! 🛤️🔀

W poprzedniej lekcji stworzyłeś/aś prostą aplikację Flask z podstawowymi routes. Teraz nauczysz się profesjonalnych technik używanych w produkcyjnych aplikacjach! 🚀

Analogia Safari: Routing to jak sieć szlaków safari - musisz umieć przekierowywać turystów (użytkowników), obsługiwać różne trasy, reagować na błędy (zgubienie się), i budować mapy (URL building)! 🗺️🧭

Przypomnienie - podstawy routingu

1from flask import Flask
2
3app = Flask(__name__)
4
5@app.route('/')
6def home():
7    return "Strona główna"
8
9@app.route('/species/<int:species_id>')
10def show_species(species_id):
11    return f"Gatunek ID: {species_id}"

Route = dekorator

@app.route()
mapujący URL → funkcję widoku.

Teraz nauczysz się zaawansowanych technik! 🎯

Wiele routes dla jednej funkcji

Możesz przypisać wiele URL do tej samej funkcji:

1@app.route('/')
2@app.route('/home')
3@app.route('/index')
4def home():
5    return "<h1>Strona główna Safari Database</h1>"

Użytkownik może wejść:

  • /
    → wykonuje się
    home()
  • /home
    → wykonuje się
    home()
  • /index
    → wykonuje się
    home()

Po co? Wiele możliwych wejść do tej samej zawartości - wygodniejsze dla użytkowników! 🚪🚪🚪

Analogia: To jak wiele wejść do tego samego siedliska - turyści mogą wejść od północy, południa lub wschodu, ale wszyscy widzą te same lwy! 🦁

Trailing slash - różnica między / a bez /

Flask rozróżnia URL z

/
na końcu i bez:

1# Z trailing slash (domyślne)
2@app.route('/species/')
3def list_species():
4    return "Lista gatunków"
5
6# Bez trailing slash
7@app.route('/about')
8def about():
9    return "O systemie"

Jak to działa?

Z trailing slash (

/species/
):

  • /species/
    → ✅ działa
  • /species
    → ✅ przekierowanie 301 do
    /species/

Bez trailing slash (

/about
):

  • /about
    → ✅ działa
  • /about/
    → ❌ 404 Not Found

Najlepsza praktyka: Używaj z trailing slash dla "folderów" (collections), bez slash dla "plików" (pojedynczych zasobów):

1@app.route('/species/')          # Lista (folder)
2@app.route('/species/<int:id>')  # Pojedynczy (plik)

HTTP Methods - szczegóły

Flask pozwala na obsługę różnych metod HTTP w tym samym route:

1from flask import request
2
3@app.route('/species', methods=['GET', 'POST', 'PUT', 'DELETE'])
4def species_handler():
5    if request.method == 'GET':
6        return "Pobierz listę gatunków"
7
8    elif request.method == 'POST':
9        return "Utwórz nowy gatunek"
10
11    elif request.method == 'PUT':
12        return "Aktualizuj gatunek"
13
14    elif request.method == 'DELETE':
15        return "Usuń gatunek"

Lepszy sposób - osobne funkcje:

1@app.route('/species', methods=['GET'])
2def list_species():
3    return "Lista gatunków"
4
5@app.route('/species', methods=['POST'])
6def create_species():
7    return "Utworzono gatunek"

Lub jeszcze lepiej - użyj dekoratorów skrótowych:

1@app.get('/species')
2def list_species():
3    return "Lista gatunków (GET)"
4
5@app.post('/species')
6def create_species():
7    return "Utworzono gatunek (POST)"
8
9@app.put('/species/<int:id>')
10def update_species(id):
11    return f"Zaktualizowano gatunek {id} (PUT)"
12
13@app.delete('/species/<int:id>')
14def delete_species(id):
15    return f"Usunięto gatunek {id} (DELETE)"

Analogia: Różne metody HTTP to jak różne czynności w safari - GET to obserwacja (bezpieczne), POST to dodanie nowego gatunku do bazy (zmiana stanu), DELETE to usunięcie wpisu! 🔍➕❌

Request object - dostęp do danych

Obiekt

request
zawiera wszystkie informacje o żądaniu HTTP:

1from flask import request
2
3@app.route('/search')
4def search():
5    # Query parameters (?name=lion&habitat=savanna)
6    species_name = request.args.get('name')
7    habitat = request.args.get('habitat')
8    page = request.args.get('page', 1, type=int)  # Domyślnie 1
9
10    return f"""
11    <h1>Wyszukiwanie</h1>
12    <p>Gatunek: {species_name}</p>
13    <p>Siedlisko: {habitat}</p>
14    <p>Strona: {page}</p>
15    """
16
17@app.route('/add-species', methods=['POST'])
18def add_species():
19    # Form data (z formularza HTML)
20    name = request.form.get('name')
21    population = request.form.get('population', type=int)
22
23    return f"Dodano: {name} (populacja: {population})"
24
25@app.route('/api/species', methods=['POST'])
26def api_create_species():
27    # JSON data (z fetch/API)
28    data = request.get_json()
29    name = data.get('name')
30    population = data.get('population')
31
32    return {"message": f"Utworzono {name}", "id": 123}
33
34@app.route('/info')
35def request_info():
36    return f"""
37    <h1>Informacje o żądaniu</h1>
38    <p><strong>URL:</strong> {request.url}</p>
39    <p><strong>Metoda:</strong> {request.method}</p>
40    <p><strong>Path:</strong> {request.path}</p>
41    <p><strong>User-Agent:</strong> {request.user_agent}</p>
42    <p><strong>IP:</strong> {request.remote_addr}</p>
43    <p><strong>Cookies:</strong> {request.cookies}</p>
44    <p><strong>Headers:</strong> {dict(request.headers)}</p>
45    """

Przydatne atrybuty

request
:

  • request.args
    - query parameters (?key=value)
  • request.form
    - dane z formularza (POST)
  • request.get_json()
    - JSON data
  • request.files
    - przesłane pliki
  • request.cookies
    - ciasteczka
  • request.headers
    - nagłówki HTTP
  • request.method
    - metoda HTTP (GET, POST, etc.)
  • request.url
    - pełny URL
  • request.path
    - ścieżka URL
  • request.remote_addr
    - IP klienta

Przykład - wyszukiwarka Safari:

1@app.route('/search')
2def search():
3    query = request.args.get('q', '')
4    filter_type = request.args.get('type', 'all')
5    sort = request.args.get('sort', 'name')
6
7    # Symulacja wyszukiwania
8    results = [
9        {"name": "Lew", "type": "mammal"},
10        {"name": "Gepard", "type": "mammal"},
11    ]
12
13    # Filtruj według typu
14    if filter_type != 'all':
15        results = [r for r in results if r['type'] == filter_type]
16
17    html = f"<h1>Wyniki dla: {query}</h1>"
18    html += f"<p>Typ: {filter_type}, Sortowanie: {sort}</p>"
19
20    for result in results:
21        html += f"<p>🦁 {result['name']} ({result['type']})</p>"
22
23    return html

Test:

  • /search?q=lew
  • /search?q=lew&type=mammal&sort=population

Response object - zwracanie odpowiedzi

Flask automatycznie konwertuje zwracane wartości na obiekty Response:

1from flask import make_response, jsonify
2
3# Prosty string (status 200)
4@app.route('/hello')
5def hello():
6    return "Hello Safari!"
7
8# String + status code
9@app.route('/created')
10def created():
11    return "Gatunek utworzony!", 201
12
13# String + status + headers
14@app.route('/custom')
15def custom():
16    return "Custom response", 200, {'X-Custom-Header': 'Safari'}
17
18# JSON response
19@app.route('/api/species/<int:id>')
20def api_species(id):
21    species = {"id": id, "name": "Lew", "population": 120}
22    return jsonify(species)
23
24# make_response() - bardziej kontroli
25@app.route('/cookie')
26def set_cookie():
27    response = make_response("Cookie ustawiony!")
28    response.set_cookie('user_id', '123', max_age=3600)
29    return response

Format zwracania:

  • return "text"
    → 200 OK
  • return "text", 201
    → 201 Created
  • return "text", 404
    → 404 Not Found
  • return jsonify(data)
    → JSON response
  • return make_response(...)
    → custom Response

Redirects - przekierowania

Użyj

redirect()
do przekierowania użytkownika na inny URL:

1from flask import redirect, url_for
2
3@app.route('/old-species')
4def old_species():
5    # Przekieruj na nowy URL
6    return redirect('/species')
7
8@app.route('/species/<int:id>')
9def show_species(id):
10    # Jeśli gatunek nie istnieje, przekieruj do listy
11    species = get_species_by_id(id)
12
13    if species is None:
14        return redirect('/species')
15
16    return f"<h1>{species['name']}</h1>"
17
18@app.route('/login', methods=['POST'])
19def login():
20    username = request.form.get('username')
21    password = request.form.get('password')
22
23    if authenticate(username, password):
24        # Sukces - przekieruj do dashboardu
25        return redirect('/dashboard')
26    else:
27        # Błąd - przekieruj z powrotem do logowania
28        return redirect('/login?error=invalid')

Analogia: Redirect to jak zmiana trasy safari - jeśli szlak jest zamknięty (gatunek nie istnieje), przewodnik przekierowuje grupę na inną trasę (listę gatunków)! 🔀🛤️

url_for() - budowanie URL

url_for()
buduje URL na podstawie nazwy funkcji zamiast hardcoded URL:

1from flask import url_for
2
3@app.route('/')
4def home():
5    # Zamiast hardcoded '/species'
6    species_url = url_for('list_species')
7
8    # Zamiast hardcoded '/species/1'
9    lion_url = url_for('show_species', species_id=1)
10
11    return f"""
12    <h1>Safari Database</h1>
13    <a href="{species_url}">Lista gatunków</a> |
14    <a href="{lion_url}">Zobacz lwa</a>
15    """
16
17@app.route('/species')
18def list_species():
19    return "<h1>Lista gatunków</h1>"
20
21@app.route('/species/<int:species_id>')
22def show_species(species_id):
23    return f"<h1>Gatunek ID: {species_id}</h1>"

Po co

url_for()
?Centralny routing - zmiana URL w jednym miejscu ✅ Bezpieczniejsze - Flask automatycznie escape'uje parametry ✅ Czytelniejsze - używasz nazw funkcji zamiast URL

Przykład z query parameters:

1@app.route('/search')
2def search():
3    return "Wyniki wyszukiwania"
4
5# Buduj URL z query params
6url = url_for('search', q='lew', type='mammal')
7# Wynik: /search?q=lew&type=mammal

Przekierowanie z

url_for()
:

1@app.route('/old-url')
2def old_url():
3    return redirect(url_for('list_species'))

Error Handlers - obsługa błędów

Stwórz custom error pages dla 404, 500, etc.:

1# 404 - Not Found
2@app.errorhandler(404)
3def page_not_found(error):
4    return """
5    <!DOCTYPE html>
6    <html>
7    <head>
8        <title>404 - Safari Database</title>
9        <style>
10            body {
11                font-family: Arial, sans-serif;
12                text-align: center;
13                padding: 100px;
14                background-color: #f4f1de;
15            }
16            h1 { color: #d62828; font-size: 72px; }
17        </style>
18    </head>
19    <body>
20        <h1>404</h1>
21        <h2>🦁 Gatunek nie znaleziony!</h2>
22        <p>Szukany zasób nie istnieje w bazie Safari Database.</p>
23        <a href="/">← Powrót do strony głównej</a>
24    </body>
25    </html>
26    """, 404
27
28# 500 - Internal Server Error
29@app.errorhandler(500)
30def internal_error(error):
31    return """
32    <h1>500 - Błąd serwera</h1>
33    <p>Coś poszło nie tak. Nasze lwy pracują nad naprawą!</p>
34    <a href="/">← Powrót do strony głównej</a>
35    """, 500
36
37# Custom exception
38class SpeciesNotFoundError(Exception):
39    pass
40
41@app.errorhandler(SpeciesNotFoundError)
42def handle_species_not_found(error):
43    return f"<h1>Gatunek nie znaleziony</h1><p>{error}</p>", 404
44
45@app.route('/species/<int:id>')
46def show_species(id):
47    species = get_species_by_id(id)
48
49    if species is None:
50        raise SpeciesNotFoundError(f"Gatunek o ID {id} nie istnieje")
51
52    return f"<h1>{species['name']}</h1>"

Analogia: Error handlers to jak procedury awaryjne w safari - gdy turysta zgubi się (404) lub wystąpi awaria pojazdu (500), przewodnik wie, jak zareagować! 🚨🦁

Before/After Request Hooks

Flask pozwala wykonywać kod przed i po każdym żądaniu:

1import time
2
3# Przed każdym żądaniem
4@app.before_request
5def before_request():
6    # Zapisz czas rozpoczęcia
7    request.start_time = time.time()
8
9    # Loguj żądanie
10    print(f"🔵 {request.method} {request.path}")
11
12# Po każdym żądaniu
13@app.after_request
14def after_request(response):
15    # Oblicz czas wykonania
16    if hasattr(request, 'start_time'):
17        duration = time.time() - request.start_time
18        print(f"✅ Czas wykonania: {duration:.3f}s")
19
20    # Dodaj custom header
21    response.headers['X-Safari-Database'] = 'v1.0'
22
23    return response
24
25# Po każdym żądaniu (nawet gdy wystąpi błąd)
26@app.teardown_request
27def teardown_request(exception=None):
28    if exception:
29        print(f"❌ Wystąpił błąd: {exception}")
30
31    # Zamknij połączenie z bazą danych
32    # db.close()

Przypadki użycia:

  • 🔐 Autentykacja (sprawdź, czy użytkownik zalogowany)
  • 📊 Logowanie requestów
  • ⏱️ Mierzenie czasu wykonania
  • 🗄️ Połączenie/zamknięcie bazy danych
  • 🔒 Nagłówki bezpieczeństwa

Analogia: Hooki to jak procedury przed/po safari - przed: sprawdź sprzęt, zlicz turystów; po: zapisz obserwacje, sprawdź, czy wszyscy wrócili! 📋✅

Praktyczny przykład - Safari Database v2

Stworzymy ulepszoną wersję Safari Database używając wszystkich poznanych technik! 🦁

1# app.py
2from flask import Flask, request, redirect, url_for, make_response, jsonify
3import time
4
5app = Flask(__name__)
6
7# Dane testowe
8species_db = {
9    1: {"id": 1, "name": "Lew", "scientific_name": "Panthera leo", "population": 120},
10    2: {"id": 2, "name": "Słoń", "scientific_name": "Loxodonta africana", "population": 450},
11    3: {"id": 3, "name": "Gepard", "scientific_name": "Acinonyx jubatus", "population": 7100},
12}
13next_id = 4
14
15# --- Hooks ---
16@app.before_request
17def log_request():
18    request.start_time = time.time()
19    print(f"🔵 [{request.method}] {request.path}")
20
21@app.after_request
22def log_response(response):
23    if hasattr(request, 'start_time'):
24        duration = time.time() - request.start_time
25        print(f"✅ Status: {response.status_code} | Czas: {duration:.3f}s")
26    return response
27
28# --- Routes ---
29@app.route('/')
30@app.route('/home')
31def home():
32    return f"""
33    <!DOCTYPE html>
34    <html>
35    <head>
36        <title>Safari Database</title>
37        <style>
38            body {{ font-family: Arial; max-width: 800px; margin: 50px auto; background: #f4f1de; }}
39            h1 {{ color: #2c5f2d; }}
40            nav {{ margin: 20px 0; }}
41            nav a {{ margin-right: 15px; color: #2c5f2d; text-decoration: none; font-weight: bold; }}
42            nav a:hover {{ text-decoration: underline; }}
43        </style>
44    </head>
45    <body>
46        <h1>🦁 Safari Database</h1>
47        <nav>
48            <a href="{url_for('home')}">Home</a>
49            <a href="{url_for('list_species')}">Lista gatunków</a>
50            <a href="{url_for('add_species_form')}">Dodaj gatunek</a>
51            <a href="{url_for('search')}">Szukaj</a>
52        </nav>
53        <h2>Witaj w Safari Database!</h2>
54        <p>Baza zawiera <strong>{len(species_db)} gatunków</strong>.</p>
55        <p><a href="{url_for('list_species')}">Zobacz wszystkie →</a></p>
56    </body>
57    </html>
58    """
59
60@app.get('/species')
61def list_species():
62    species_html = ""
63    for spec in species_db.values():
64        species_html += f"""
65        <div style="background: white; padding: 15px; margin: 10px 0; border-radius: 8px;">
66            <h3>{spec['name']}</h3>
67            <p><strong>Populacja:</strong> {spec['population']}</p>
68            <a href="{url_for('show_species', species_id=spec['id'])}">Szczegóły</a> |
69            <a href="{url_for('delete_species', species_id=spec['id'])}" style="color: red;">Usuń</a>
70        </div>
71        """
72
73    return f"""
74    <!DOCTYPE html>
75    <html>
76    <head><title>Lista gatunków</title></head>
77    <body style="font-family: Arial; max-width: 800px; margin: 50px auto; background: #f4f1de;">
78        <h1>🦁 Lista gatunków ({len(species_db)})</h1>
79        {species_html}
80        <p><a href="{url_for('home')}">← Powrót</a></p>
81    </body>
82    </html>
83    """
84
85@app.route('/species/<int:species_id>')
86def show_species(species_id):
87    spec = species_db.get(species_id)
88
89    if spec is None:
90        # Użyj custom error handler
91        return f"<h1>404</h1><p>Gatunek {species_id} nie istnieje</p>", 404
92
93    return f"""
94    <!DOCTYPE html>
95    <html>
96    <head><title>{spec['name']}</title></head>
97    <body style="font-family: Arial; max-width: 800px; margin: 50px auto; background: #f4f1de;">
98        <h1>🦁 {spec['name']}</h1>
99        <div style="background: white; padding: 30px; border-radius: 12px;">
100            <p><strong>Nazwa naukowa:</strong> {spec['scientific_name']}</p>
101            <p><strong>Populacja:</strong> {spec['population']} osobników</p>
102            <p><strong>ID:</strong> {spec['id']}</p>
103        </div>
104        <p style="margin-top: 20px;">
105            <a href="{url_for('list_species')}">← Lista</a> |
106            <a href="{url_for('home')}">← Home</a>
107        </p>
108    </body>
109    </html>
110    """
111
112@app.route('/species/add', methods=['GET'])
113def add_species_form():
114    return f"""
115    <!DOCTYPE html>
116    <html>
117    <head><title>Dodaj gatunek</title></head>
118    <body style="font-family: Arial; max-width: 800px; margin: 50px auto; background: #f4f1de;">
119        <h1>➕ Dodaj nowy gatunek</h1>
120        <form method="POST" action="{url_for('add_species')}" style="background: white; padding: 30px; border-radius: 12px;">
121            <p><label>Nazwa: <input type="text" name="name" required></label></p>
122            <p><label>Nazwa naukowa: <input type="text" name="scientific_name" required></label></p>
123            <p><label>Populacja: <input type="number" name="population" required></label></p>
124            <button type="submit" style="padding: 10px 20px; background: #2c5f2d; color: white; border: none; border-radius: 4px;">Dodaj</button>
125        </form>
126        <p><a href="{url_for('home')}">← Powrót</a></p>
127    </body>
128    </html>
129    """
130
131@app.post('/species/add')
132def add_species():
133    global next_id
134
135    name = request.form.get('name')
136    scientific_name = request.form.get('scientific_name')
137    population = request.form.get('population', type=int)
138
139    # Dodaj do bazy
140    new_species = {
141        "id": next_id,
142        "name": name,
143        "scientific_name": scientific_name,
144        "population": population
145    }
146    species_db[next_id] = new_species
147    next_id += 1
148
149    # Przekieruj do szczegółów nowego gatunku
150    return redirect(url_for('show_species', species_id=new_species['id']))
151
152@app.route('/species/<int:species_id>/delete')
153def delete_species(species_id):
154    if species_id in species_db:
155        del species_db[species_id]
156
157    # Przekieruj do listy
158    return redirect(url_for('list_species'))
159
160@app.route('/search')
161def search():
162    query = request.args.get('q', '').lower()
163
164    if not query:
165        return f"""
166        <h1>🔍 Wyszukiwarka</h1>
167        <form method="GET">
168            <input type="text" name="q" placeholder="Wpisz nazwę gatunku">
169            <button type="submit">Szukaj</button>
170        </form>
171        <a href="{url_for('home')}">← Powrót</a>
172        """
173
174    # Wyszukaj
175    results = [s for s in species_db.values() if query in s['name'].lower()]
176
177    results_html = f"<h2>Znaleziono: {len(results)}</h2>"
178    for spec in results:
179        results_html += f"<p>🦁 <a href='{url_for('show_species', species_id=spec['id'])}'>{spec['name']}</a></p>"
180
181    return f"""
182    <h1>🔍 Wyniki dla: "{query}"</h1>
183    {results_html}
184    <p><a href="{url_for('search')}">← Nowe wyszukiwanie</a></p>
185    """
186
187# --- API Endpoints ---
188@app.get('/api/species')
189def api_list_species():
190    return jsonify(list(species_db.values()))
191
192@app.get('/api/species/<int:species_id>')
193def api_get_species(species_id):
194    spec = species_db.get(species_id)
195    if spec is None:
196        return jsonify({"error": "Not found"}), 404
197    return jsonify(spec)
198
199# --- Error Handlers ---
200@app.errorhandler(404)
201def page_not_found(error):
202    return f"""
203    <div style="text-align: center; padding: 100px; background: #f4f1de;">
204        <h1 style="font-size: 72px; color: #d62828;">404</h1>
205        <h2>🦁 Nie znaleziono!</h2>
206        <p><a href="{url_for('home')}">← Powrót do strony głównej</a></p>
207    </div>
208    """, 404
209
210if __name__ == '__main__':
211    print("🦁 Safari Database v2 startuje...")
212    app.run(debug=True)

Testuj aplikację:

  • http://localhost:5000/ - strona główna
  • http://localhost:5000/species - lista gatunków
  • http://localhost:5000/species/1 - szczegóły lwa
  • http://localhost:5000/species/add - dodaj gatunek
  • http://localhost:5000/search?q=lew - wyszukaj
  • http://localhost:5000/api/species - API JSON

Podsumowanie

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

  • ✅ Wiele routes dla jednej funkcji
  • ✅ Trailing slash (/ vs bez /)
  • ✅ HTTP methods (GET, POST, PUT, DELETE)
  • ✅ Request object (args, form, json, headers)
  • ✅ Response object (make_response, jsonify)
  • ✅ Redirects (redirect, url_for)
  • ✅ URL building z
    url_for()
  • ✅ Error handlers (404, 500, custom)
  • ✅ Before/After request hooks
  • ✅ Praktyczna aplikacja Safari Database v2

Analogia Safari finalna: Zaawansowany routing to jak profesjonalny system szlaków safari - możesz przekierowywać turystów (

redirect
), obsługiwać zgubienia się (
404
), mierzyć czas trasy (hooki), i budować mapy z nazw zamiast współrzędnych (
url_for
)! 🗺️🧭

Następna lekcja: Darwin pokaże Ci Jinja2 templates - kończymy z hardcoded HTML strings i zaczynamy dynamiczne szablony! 🎨📄

Vai a CodeWorlds