Schlagwortsuche und Paginierung

In diesem Kapitel lernst du, SQLAlchemy für die kombinierte Suche zu verwenden und die Listenseite mit der in Flask-SQLAlchemy integrierten Paginierung zu optimieren.


ilike — Suche ohne Beachtung der Groß-/Kleinschreibung

SQLAlchemy'silike-Methode implementiert ein Fuzzy-Matching ohne Beachtung der Groß-/Kleinschreibung.

Es entspricht dem SQL-LIKEOperator, ignoriert jedoch die Groß-/Kleinschreibung.

Beispiel

# Einzelfeldsuche
posts = Post.query.filter(Post.title.ilike('%flask%')).all()

# Kombinierte Suche über mehrere Felder: or_() kennzeichnet eine ODER-Beziehung
from sqlalchemy import or_
posts = Post.query.filter(
    or_(
        Post.title.ilike('%flask%'),
        Post.summary.ilike('%flask%')
    )
).all()
MethodenSQL-OperatorenGroß-/Kleinschreibung beachten
like()LIKEUnterscheiden
ilike()LIKE (Groß-/Kleinschreibung ignorieren)Nicht unterscheiden
contains()LIKE '%x%'Unterscheiden
startswith()LIKE 'x%'Unterscheiden
in_()INExakte Übereinstimmung

paginate — Paginierung

Flask-SQLAlchemy bietetpaginate()Die Methode gibt aktuelle Seitendaten + Paginierungsmetadaten in einem einzigen Aufruf zurück.

Beispiel

# Die Index-Ansicht aus main.py refaktorieren
page = request.args.get('page', 1, type=int)  # Aktuelle Seitenzahl (Standard: Seite 1)
per_page = 6                                    # 6 Artikel pro Seite anzeigen

# paginate(page, per_page, error_out)
# error_out=False: Wenn die Seitenzahl den Bereich überschreitet, wird statt 404 eine leere Liste zurückgegeben
pagination = posts_query.paginate(page=page, per_page=per_page, error_out=False)
posts = pagination.items     # Artikelliste für die aktuelle Seite

Attribute des pagination-Objekts

EigenschaftTypeBeschreibung
itemsListeDaten der aktuellen Seite
pageintNummer der aktuellen Seite
pagesintGesamtzahl der Seiten
totalintGesamtzahl der Datensätze
has_prevboolGibt an, ob es eine vorherige Seite gibt
has_nextboolGibt an, ob es eine nächste Seite gibt
prev_numintNummer der vorherigen Seite
next_numintNächste Seitennummer

Vollständige Suche + Paginierungsansicht

Beispiel

# Dateipfad: app/blueprints/main.py
from flask import Blueprint, render_template, request
from sqlalchemy import or_
from app.models import Post, Category

main_bp = Blueprint('main', __name__)

@main_bp.route("/")
def index():
    category_slug = request.args.get('category', '')
    keyword = request.args.get('q', '').strip()
    page = request.args.get('page', 1, type=int)
    per_page = 6

    # Basisabfrage
    posts_query = Post.query.order_by(Post.created_at.desc())

    # Kategoriefilter
    if category_slug:
        posts_query = posts_query.join(Category).filter(Category.slug == category_slug)

    # Stichwortsuche (Titel oder Zusammenfassung)
    if keyword:
        posts_query = posts_query.filter(
            or_(
                Post.title.ilike(f'%{keyword}%'),
                Post.summary.ilike(f'%{keyword}%')
            )
        )

    # Paginierung
    pagination = posts_query.paginate(
        page=page, per_page=per_page, error_out=False)
    posts = pagination.items

    return render_template('index.html',
        posts=posts, pagination=pagination,
        categories=Category.query.all(),
        category_slug=category_slug, keyword=keyword)

Abfrageparameter werden über die URL übergeben (z. B.,/?q=flask&page=2), die Navigationslinks der Paginierung müssen die aktuellen Such- und Kategorieparameter enthalten, andernfalls gehen die Bedingungen nach dem Blättern verloren.


Suchfeld und Paginierungsnavigation im Template

Beispiel

<!-- Dateipfad: app/templates/index.html -->
{% extends 'base.html' %}

{% block title %}EXAMPLE Blog - Startseite{% endblock %}

{% block content %}
<h2 class="section-title">Neueste Artikel</h2>

<!-- Suchfeld -->
<div class="search-bar">
    <form method="get" action="{{ url_for('main.index') }}">
        <input type="text" name="q" value="{{ keyword }}"
              placeholder=Artikeltitel oder Zusammenfassungen durchsuchen... class="search-input">
        {% if keyword %}
        <a href="{{ url_for('main.index', category=category_slug) }}" class="clear-btn">✕</a>
        {% endif %}
    </Form>
</div>

<!-- Kategoriefilter -->
<div class="category-bar">
    <a href="{{ url_for('main.index', q=keyword) }}"
      class="{% if not category_slug %}active{% endif %}">Alle</a>
    {% for cat in categories %}
    <a href="{{ url_for('main.index', category=cat.slug, q=keyword) }}"
      class="{% if category_slug == cat.slug %}active{% endif %}">
        {{ cat.name }}
    </a>
    {% endfor %}
</div>

<p class="result-info">
Insgesamt {{ pagination.total }} Artikel
{% if keyword %}, Suche nach "{{ keyword }}"{% endif %}
</p>

{% if posts %}
<div class="article-grid">
    {% for post in posts %}
    <a href="{{ url_for('posts.post_detail', post_id=post.id) }}" class="card-link">
        <div class="article-card">
            <div class="card-content">
                <span class="card-category">{{ post.category.name }}</span>
                <h3>{{ post.title }}</h3>
                <p>{{ post.summary|truncate(80) }}</p>
                <span class="card-date">{{ post.created_at.strftime('%Y-%m-%d') }}</span>
            </div>
        </div>
    </a>
    {% endfor %}
</div>

<!-- Paginierungsnavigation -->
{% if pagination.pages > 1 %}
<div class="pagination">
    {% if pagination.has_prev %}
    <a href="{{ url_for('main.index', page=pagination.prev_num, category=category_slug, q=keyword) }}">← Vorherige Seite</a>
    {% endif %}

    {% for page_num in range(1, pagination.pages + 1) %}
        {% if page_num == pagination.page %}
        <span class="current">{{ page_num }}</span>
        {% else %}
        <a href="{{ url_for('main.index', page=page_num, category=category_slug, q=keyword) }}">{{ page_num }}</a>
        {% endif %}
    {% endfor %}

    {% if pagination.has_next %}
    <a href="{{ url_for('main.index', page=pagination.next_num, category=category_slug, q=keyword) }}">Nächste Seite →</a>
    {% endif %}
</div>
{% endif %}

{% else %}
<p class="empty-tip">Keine passenden Artikel gefunden.</p>
{% endif %}
{% endblock %}

Kapitelzusammenfassung

In diesem Kapitel hast du die praktischen Funktionen der Listenseite gemeistert: die ilike-Suche ohne Berücksichtigung der Groß-/Kleinschreibung, die kombinierte Suche über mehrere Felder mit or_(), die paginate()-Abfrage zur Seitennummerierung und das Rendern der Paginierungsnavigation in Templates unter Beibehaltung der Filterparameter.

Suche + Kategoriefilterung + Paginierung lassen sich beliebig kombinieren, und die Bedingungen gehen nicht verloren.

Weitere Erweiterungen