4 Routing und URL-Handling

4.1 Route-Definitionen

Das Routing-System von Flask bildet das Herzstück der URL-Verarbeitung und bestimmt, welche Funktionen bei bestimmten URL-Aufrufen ausgeführt werden. Flask verwendet Dekoratoren zur Definition von Routes, die eine direkte Zuordnung zwischen URLs und Python-Funktionen ermöglichen.

4.1.1 Grundlegende Route-Definition

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Startseite der Anwendung'

@app.route('/ueber-uns')
def about():
    return 'Informationen über unser Unternehmen'

@app.route('/kontakt')
def contact():
    return 'Kontaktinformationen'

4.1.2 Route-Parameter und Konverter

Flask unterstützt dynamische URL-Segmente durch Variable Rules. Diese ermöglichen die Übergabe von Parametern direkt über die URL-Struktur.

@app.route('/benutzer/<username>')
def show_user_profile(username):
    return f'Benutzerprofil: {username}'

@app.route('/artikel/<int:artikel_id>')
def show_article(artikel_id):
    return f'Artikel Nummer: {artikel_id}'

@app.route('/kategorie/<string:kategorie_name>')
def show_category(kategorie_name):
    return f'Kategorie: {kategorie_name}'

@app.route('/preis/<float:betrag>')
def show_price(betrag):
    return f'Preis: {betrag:.2f} EUR'

4.1.3 Verfügbare Konverter

string: (Standard) Akzeptiert Text ohne Schrägstriche int: Positive Ganzzahlen float: Positive Fließkommazahlen path: Wie string, aber akzeptiert auch Schrägstriche uuid: UUID-Strings

4.1.4 Benutzerdefinierte Konverter

from werkzeug.routing import BaseConverter

class ListConverter(BaseConverter):
    def to_python(self, value):
        return value.split(',')
    
    def to_url(self, values):
        return ','.join(BaseConverter.to_url(value) for value in values)

app.url_map.converters['list'] = ListConverter

@app.route('/tags/<list:tag_liste>')
def show_tags(tag_liste):
    return f'Tags: {", ".join(tag_liste)}'

4.1.5 URL-Präfixe und Subdomains

@app.route('/api/v1/users')
def api_users():
    return 'API: Benutzerliste'

@app.route('/admin/dashboard')
def admin_dashboard():
    return 'Administrator Dashboard'

# Subdomain-Routing
@app.route('/', subdomain='api')
def api_index():
    return 'API Startseite'

4.1.6 Route-Optionen

@app.route('/artikel/<int:id>', methods=['GET'])
def get_article(id):
    return f'Artikel {id} anzeigen'

@app.route('/suche', methods=['GET', 'POST'], strict_slashes=False)
def search():
    return 'Suchfunktion'

@app.route('/legacy-url', redirect_to='/neue-url')
def legacy_redirect():
    pass  # Wird automatisch weitergeleitet

4.2 HTTP-Methoden (GET, POST, PUT, DELETE)

HTTP-Methoden definieren die Art der Anfrage und bestimmen, welche Aktionen auf Serverseite ausgeführt werden sollen. Flask unterstützt alle Standard-HTTP-Methoden für RESTful-Anwendungen.

4.2.1 GET-Requests

GET-Requests dienen dem Abrufen von Daten ohne Seiteneffekte auf dem Server.

from flask import request, jsonify

@app.route('/artikel', methods=['GET'])
def get_articles():
    # Artikel aus Datenbank abrufen
    return jsonify({
        'artikel': [
            {'id': 1, 'titel': 'Erste Nachricht'},
            {'id': 2, 'titel': 'Zweite Nachricht'}
        ]
    })

@app.route('/artikel/<int:id>', methods=['GET'])
def get_article(id):
    # Einzelnen Artikel abrufen
    return jsonify({'id': id, 'titel': f'Artikel {id}'})

4.2.2 POST-Requests

POST-Requests erstellen neue Ressourcen oder führen Aktionen mit Seiteneffekten aus.

from flask import request, jsonify

@app.route('/artikel', methods=['POST'])
def create_article():
    data = request.get_json()
    
    if not data or 'titel' not in data:
        return jsonify({'fehler': 'Titel ist erforderlich'}), 400
    
    # Artikel in Datenbank speichern
    new_article = {
        'id': 123,
        'titel': data['titel'],
        'inhalt': data.get('inhalt', '')
    }
    
    return jsonify(new_article), 201

@app.route('/benutzer/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    if not username or not password:
        return jsonify({'fehler': 'Benutzername und Passwort erforderlich'}), 400
    
    # Authentifizierung prüfen
    return jsonify({'nachricht': 'Anmeldung erfolgreich'})

4.2.3 PUT-Requests

PUT-Requests aktualisieren bestehende Ressourcen vollständig.

@app.route('/artikel/<int:id>', methods=['PUT'])
def update_article(id):
    data = request.get_json()
    
    if not data:
        return jsonify({'fehler': 'JSON-Daten erforderlich'}), 400
    
    # Artikel vollständig aktualisieren
    updated_article = {
        'id': id,
        'titel': data.get('titel', ''),
        'inhalt': data.get('inhalt', ''),
        'autor': data.get('autor', '')
    }
    
    return jsonify(updated_article)

4.2.4 PATCH-Requests

PATCH-Requests für partielle Updates.

@app.route('/artikel/<int:id>', methods=['PATCH'])
def patch_article(id):
    data = request.get_json()
    
    # Nur übermittelte Felder aktualisieren
    updated_fields = {}
    if 'titel' in data:
        updated_fields['titel'] = data['titel']
    if 'inhalt' in data:
        updated_fields['inhalt'] = data['inhalt']
    
    return jsonify({'id': id, 'aktualisiert': updated_fields})

4.2.5 DELETE-Requests

DELETE-Requests entfernen Ressourcen.

@app.route('/artikel/<int:id>', methods=['DELETE'])
def delete_article(id):
    # Artikel aus Datenbank löschen
    return '', 204  # No Content

@app.route('/benutzer/<int:user_id>/artikel', methods=['DELETE'])
def delete_user_articles(user_id):
    # Alle Artikel eines Benutzers löschen
    deleted_count = 5  # Beispielwert
    return jsonify({'geloescht': deleted_count})

4.2.6 Mehrere Methoden pro Route

@app.route('/artikel/<int:id>', methods=['GET', 'PUT', 'DELETE'])
def handle_article(id):
    if request.method == 'GET':
        return jsonify({'id': id, 'titel': f'Artikel {id}'})
    
    elif request.method == 'PUT':
        data = request.get_json()
        return jsonify({'id': id, 'aktualisiert': True})
    
    elif request.method == 'DELETE':
        return '', 204

# Alternative: Separate Funktionen für bessere Lesbarkeit
@app.route('/benutzer/<int:id>')
def get_user(id):
    return jsonify({'id': id, 'name': f'Benutzer {id}'})

@app.route('/benutzer/<int:id>', methods=['PUT'])
def update_user(id):
    return jsonify({'id': id, 'aktualisiert': True})

@app.route('/benutzer/<int:id>', methods=['DELETE'])
def delete_user(id):
    return '', 204

4.3 URL-Parameter und Query-Parameter

URL-Parameter und Query-Parameter bieten verschiedene Möglichkeiten zur Datenübertragung an den Server. URL-Parameter sind Teil der Route-Definition, während Query-Parameter optional über das request-Objekt zugänglich sind.

4.3.1 URL-Parameter verarbeiten

@app.route('/produkt/<kategorie>/<int:produkt_id>')
def show_product(kategorie, produkt_id):
    return f'Kategorie: {kategorie}, Produkt-ID: {produkt_id}'

@app.route('/archiv/<int:jahr>/<int:monat>')
def show_archive(jahr, monat):
    if monat < 1 or monat > 12:
        return 'Ungültiger Monat', 400
    
    return f'Archiv für {monat:02d}/{jahr}'

@app.route('/datei/<path:dateiname>')
def serve_file(dateiname):
    # Dateiname kann Verzeichnisse enthalten
    return f'Datei: {dateiname}'

4.3.2 Query-Parameter verarbeiten

from flask import request

@app.route('/suche')
def search():
    query = request.args.get('q', '')
    seite = request.args.get('seite', 1, type=int)
    pro_seite = request.args.get('pro_seite', 10, type=int)
    sortierung = request.args.get('sort', 'datum')
    
    if not query:
        return 'Suchbegriff fehlt', 400
    
    results = {
        'suchbegriff': query,
        'seite': seite,
        'pro_seite': pro_seite,
        'sortierung': sortierung,
        'ergebnisse': []
    }
    
    return jsonify(results)

@app.route('/filter')
def filter_items():
    # Mehrere Werte für einen Parameter
    kategorien = request.args.getlist('kategorie')
    min_preis = request.args.get('min_preis', type=float)
    max_preis = request.args.get('max_preis', type=float)
    verfuegbar = request.args.get('verfuegbar', type=bool)
    
    filter_params = {
        'kategorien': kategorien,
        'preis_von': min_preis,
        'preis_bis': max_preis,
        'nur_verfuegbare': verfuegbar
    }
    
    return jsonify(filter_params)

4.3.3 Query-Parameter-Validierung

@app.route('/api/artikel')
def api_articles():
    try:
        seite = request.args.get('seite', 1, type=int)
        if seite < 1:
            raise ValueError('Seite muss größer als 0 sein')
        
        limit = request.args.get('limit', 10, type=int)
        if limit < 1 or limit > 100:
            raise ValueError('Limit muss zwischen 1 und 100 liegen')
        
        sort_by = request.args.get('sort_by', 'created_at')
        allowed_sorts = ['created_at', 'title', 'author']
        if sort_by not in allowed_sorts:
            raise ValueError(f'Ungültige Sortierung. Erlaubt: {", ".join(allowed_sorts)}')
        
        direction = request.args.get('direction', 'desc').lower()
        if direction not in ['asc', 'desc']:
            raise ValueError('Richtung muss "asc" oder "desc" sein')
        
    except ValueError as e:
        return jsonify({'fehler': str(e)}), 400
    
    return jsonify({
        'seite': seite,
        'limit': limit,
        'sortierung': f'{sort_by} {direction}'
    })

4.3.4 Kombinierte Parameter

@app.route('/benutzer/<int:user_id>/artikel')
def user_articles(user_id):
    # URL-Parameter
    if user_id <= 0:
        return 'Ungültige Benutzer-ID', 400
    
    # Query-Parameter
    status = request.args.get('status', 'alle')
    jahr = request.args.get('jahr', type=int)
    
    filter_info = {
        'benutzer_id': user_id,
        'status_filter': status
    }
    
    if jahr:
        filter_info['jahr_filter'] = jahr
    
    return jsonify(filter_info)

4.4 URL-Generierung mit url_for

Die url_for-Funktion generiert URLs basierend auf Endpunkt-Namen und bietet Flexibilität bei URL-Änderungen. Sie ist essentiell für die Wartbarkeit von Flask-Anwendungen.

4.4.1 Grundlegende URL-Generierung

from flask import url_for, redirect, render_template

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/benutzer/<int:id>')
def user_profile(id):
    return f'Benutzerprofil {id}'

@app.route('/artikel/<int:article_id>')
def show_article(article_id):
    # URL zu anderem Endpunkt generieren
    user_url = url_for('user_profile', id=123)
    return f'Artikel {article_id}. <a href="{user_url}">Zum Autor</a>'

@app.route('/weiterleitung')
def redirect_example():
    return redirect(url_for('index'))

4.4.2 URL-Generierung mit Parametern

@app.route('/produkt/<kategorie>/<int:id>')
def product_detail(kategorie, id):
    return f'Produkt {id} in Kategorie {kategorie}'

@app.route('/navigation')
def navigation():
    urls = {
        'startseite': url_for('index'),
        'produkt_1': url_for('product_detail', kategorie='elektronik', id=1),
        'produkt_2': url_for('product_detail', kategorie='buecher', id=42),
        'benutzer': url_for('user_profile', id=123)
    }
    return jsonify(urls)

4.4.3 URL-Generierung mit Query-Parametern

@app.route('/suche')
def search():
    return 'Suchergebnisse'

@app.route('/suchlinks')
def search_links():
    # Query-Parameter als zusätzliche Argumente
    search_electronics = url_for('search', q='laptop', kategorie='elektronik')
    search_books = url_for('search', q='python', kategorie='buecher', seite=2)
    
    return f'''
    <a href="{search_electronics}">Laptop suchen</a><br>
    <a href="{search_books}">Python Bücher (Seite 2)</a>
    '''

4.4.4 URL-Generierung in Templates

<!-- templates/navigation.html -->
<nav>
    <ul>
        <li><a href="{{ url_for('index') }}">Startseite</a></li>
        <li><a href="{{ url_for('user_profile', id=current_user.id) }}">Mein Profil</a></li>
        <li><a href="{{ url_for('search', q='', kategorie='alle') }}">Suche</a></li>
    </ul>
</nav>

<!-- Dynamische URLs mit Variablen -->
{% for artikel in artikel_liste %}
    <div>
        <h3><a href="{{ url_for('show_article', article_id=artikel.id) }}">{{ artikel.titel }}</a></h3>
        <p>Von <a href="{{ url_for('user_profile', id=artikel.autor_id) }}">{{ artikel.autor_name }}</a></p>
    </div>
{% endfor %}

4.4.5 URL-Generierung für statische Dateien

@app.route('/assets')
def asset_links():
    css_url = url_for('static', filename='css/style.css')
    js_url = url_for('static', filename='js/app.js')
    img_url = url_for('static', filename='images/logo.png')
    
    return f'''
    <link rel="stylesheet" href="{css_url}">
    <script src="{js_url}"></script>
    <img src="{img_url}" alt="Logo">
    '''

4.4.6 URL-Generierung mit Blueprint-Endpunkten

from flask import Blueprint

api_bp = Blueprint('api', __name__, url_prefix='/api')

@api_bp.route('/benutzer/<int:id>')
def get_user(id):
    return jsonify({'id': id})

@app.route('/api-links')
def api_links():
    # Blueprint-Endpunkt mit Präfix
    user_api_url = url_for('api.get_user', id=123)
    return f'<a href="{user_api_url}">Benutzer API</a>'

4.4.7 Externe URLs und absolute URLs

@app.route('/absolute-urls')
def absolute_urls():
    # Absolute URL generieren
    abs_url = url_for('index', _external=True)
    
    # URL mit spezifischer Scheme
    https_url = url_for('user_profile', id=123, _external=True, _scheme='https')
    
    return f'''
    Absolute URL: {abs_url}<br>
    HTTPS URL: {https_url}
    '''

4.4.8 URL-Generierung für verschiedene Umgebungen

@app.route('/umgebungs-urls')
def environment_urls():
    # URLs für verschiedene Umgebungen
    if app.config.get('ENV') == 'production':
        base_url = 'https://produktion.firma.de'
    else:
        base_url = 'http://localhost:5000'
    
    # Manuelle URL-Konstruktion wenn nötig
    manual_url = f"{base_url}{url_for('user_profile', id=123)}"
    
    return f'Manuelle URL: {manual_url}'

4.4.9 Fehlerbehandlung bei URL-Generierung

from werkzeug.routing import BuildError

@app.route('/sichere-url-generierung')
def safe_url_generation():
    try:
        # Versuch URL zu generieren
        url = url_for('nicht_existierender_endpunkt')
    except BuildError:
        # Fallback-URL
        url = url_for('index')
    
    return redirect(url)

Das Routing-System bildet die Grundlage für alle weiteren Flask-Funktionalitäten und ermöglicht die strukturierte Entwicklung von Web-APIs und Anwendungen.