カテゴリ絞り込みとキーワード検索
この章では、URLクエリパラメータの処理方法を学び、カテゴリ絞り込みナビゲーションとキーワード検索機能を実装します。
URLクエリパラメータ
クエリパラメータはURLの疑問符(?)以降の部分です。例:/?category=django&q=入门。
Djangoのビュー関数では、次を使用してrequest.GETこれらのパラメータを取得します。
例
def index(request):
# request.GET は辞書に似たオブジェクトです
# .get('key', 'デフォルト値') で安全にパラメータを取得し、存在しない場合はデフォルト値を返します
category_slug = request.GET.get('category', '') # カテゴリ絞り込み
keyword = request.GET.get('q', '') # 検索キーワード
print(f'カテゴリ:{category_slug}, 検索:{keyword}')
# request.GET は辞書に似たオブジェクトです
# .get('key', 'デフォルト値') で安全にパラメータを取得し、存在しない場合はデフォルト値を返します
category_slug = request.GET.get('category', '') # カテゴリ絞り込み
keyword = request.GET.get('q', '') # 検索キーワード
print(f'カテゴリ:{category_slug}, 検索:{keyword}')
ORMチェーン絞り込みとQオブジェクト
絞り込みと検索では、クエリ条件を動的に組み立てる必要があります。
例
# ファイルパス: blog/views.py の index 関数を更新
from django.shortcuts import render
from django.db.models import Q # Qオブジェクトは複雑なクエリ条件を組み合わせるために使用します
from .models import Post, Category
def index(request):
"""ブログホームページ:記事リストを表示し、カテゴリ絞り込み + キーワード検索をサポート"""
posts = Post.objects.all().order_by('-created_at')
# 1. クエリパラメータを取得
category_slug = request.GET.get('category', '')
keyword = request.GET.get('q', '')
# 2. カテゴリで絞り込み
if category_slug:
# category__slug: 外部キーを介してカテゴリテーブルの slug フィールドにアクセス(ダブルアンダースコア)
posts = posts.filter(category__slug=category_slug)
# 3. キーワードで検索(タイトルまたは概要)
if keyword:
# Q(title__icontains=...) | Q(summary__icontains=...): OR 組み合わせ
# icontains: 大文字小文字を区別しない部分一致クエリ
posts = posts.filter(
Q(title__icontains=keyword) | Q(summary__icontains=keyword)
)
# 4. すべてのカテゴリを取得(絞り込みナビゲーションの描画に使用)
categories = Category.objects.all()
context = {
'posts': posts,
'categories': categories,
'category_slug': category_slug, # テンプレートに渡し、現在選択中の項目をハイライトするために使用
'keyword': keyword, # 検索キーワードをエコーバック(再表示)
'title': 'サンプル ブログ - ホームページ',
}
return render(request, 'blog/index.html', context)
from django.shortcuts import render
from django.db.models import Q # Qオブジェクトは複雑なクエリ条件を組み合わせるために使用します
from .models import Post, Category
def index(request):
"""ブログホームページ:記事リストを表示し、カテゴリ絞り込み + キーワード検索をサポート"""
posts = Post.objects.all().order_by('-created_at')
# 1. クエリパラメータを取得
category_slug = request.GET.get('category', '')
keyword = request.GET.get('q', '')
# 2. カテゴリで絞り込み
if category_slug:
# category__slug: 外部キーを介してカテゴリテーブルの slug フィールドにアクセス(ダブルアンダースコア)
posts = posts.filter(category__slug=category_slug)
# 3. キーワードで検索(タイトルまたは概要)
if keyword:
# Q(title__icontains=...) | Q(summary__icontains=...): OR 組み合わせ
# icontains: 大文字小文字を区別しない部分一致クエリ
posts = posts.filter(
Q(title__icontains=keyword) | Q(summary__icontains=keyword)
)
# 4. すべてのカテゴリを取得(絞り込みナビゲーションの描画に使用)
categories = Category.objects.all()
context = {
'posts': posts,
'categories': categories,
'category_slug': category_slug, # テンプレートに渡し、現在選択中の項目をハイライトするために使用
'keyword': keyword, # 検索キーワードをエコーバック(再表示)
'title': 'サンプル ブログ - ホームページ',
}
return render(request, 'blog/index.html', context)
ORMクエリメソッドの説明
| メソッド | 意味 | 例 |
|---|---|---|
field__exact | 完全一致 | title__exact='Django' |
field__iexact | 大文字小文字を区別しない完全一致 | title__iexact='django' |
field__contains | 部分一致(大文字小文字を区別) | title__contains='入门' |
field__icontains | 部分一致(大文字小文字を区別しない) | title__icontains='django' |
field__gte / lte | 以上 / 以下 | created_at__gte='2024-01-01' |
field__in=[...] | リスト内 | category__in=[1, 2, 3] |
ダブルアンダースコア
__はDjango ORMの中心的な規約です。関連モデルのフィールドにアクセスしたり、フィールドのクエリ方法を指定するために使用します。例:category__slug外部キーを越えてCategoryテーブルのslugフィールドにアクセスすることを示します。
ホームページテンプレートを更新
記事リストの上にカテゴリ絞り込みナビゲーションと検索ボックスを追加します。
例
<!-- ファイルパス: blog/templates/blog/index.html -->
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2 class="section-title">最新記事</h2>
<!-- 検索ボックス -->
<div class="search-bar">
<!-- フォーム method="get": 送信後、パラメータはURLに表示されます(/search/?q=xxx) -->
<form method="get" action="{% url 'index' %}">
<input
type="text"
name="q"
value="{{ keyword }}"
placeholder="記事のタイトルまたは概要を検索..."
class="search-input"
/>
{% if keyword %}
<a href="{% url 'index' %}" class="clear-btn">✕</a>
{% endif %}
</form>
</div>
<!-- カテゴリ絞り込みナビゲーション -->
<div class="category-bar">
<!-- 「すべて」ボタン: category パラメータなし -->
<a href="{% url 'index' %}"
class="{% if not category_slug %}active{% endif %}">すべて</a>
{% for cat in categories %}
<a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
<!-- 絞り込み結果の統計 -->
<p class="result-info">
全 {{ posts.count }} 件
{% if keyword %}、「{{ keyword }}」の検索{% endif %}
</p>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="{% url 'post_detail' post.pk %}" 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|truncatechars:80 }}</p>
<span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">一致する記事が見つかりません</p>
{% endif %}
{% endblock %}
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2 class="section-title">最新記事</h2>
<!-- 検索ボックス -->
<div class="search-bar">
<!-- フォーム method="get": 送信後、パラメータはURLに表示されます(/search/?q=xxx) -->
<form method="get" action="{% url 'index' %}">
<input
type="text"
name="q"
value="{{ keyword }}"
placeholder="記事のタイトルまたは概要を検索..."
class="search-input"
/>
{% if keyword %}
<a href="{% url 'index' %}" class="clear-btn">✕</a>
{% endif %}
</form>
</div>
<!-- カテゴリ絞り込みナビゲーション -->
<div class="category-bar">
<!-- 「すべて」ボタン: category パラメータなし -->
<a href="{% url 'index' %}"
class="{% if not category_slug %}active{% endif %}">すべて</a>
{% for cat in categories %}
<a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
<!-- 絞り込み結果の統計 -->
<p class="result-info">
全 {{ posts.count }} 件
{% if keyword %}、「{{ keyword }}」の検索{% endif %}
</p>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="{% url 'post_detail' post.pk %}" 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|truncatechars:80 }}</p>
<span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">一致する記事が見つかりません</p>
{% endif %}
{% endblock %}
絞り込みナビゲーションと検索ボックスのスタイル
例
/* base.html の style に追加 */
.search-bar {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: #2c3e50;
}
.clear-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
text-decoration: none;
font-size: 18px;
}
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar a {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: all 0.2s;
}
.category-bar a.active {
background: #2c3e50;
color: #fff;
border-color: #2c3e50;
}
.category-bar a:hover {
border-color: #2c3e50;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
.card-link {
text-decoration: none;
color: inherit;
}
.search-bar {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: #2c3e50;
}
.clear-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
text-decoration: none;
font-size: 18px;
}
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar a {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: all 0.2s;
}
.category-bar a.active {
background: #2c3e50;
color: #fff;
border-color: #2c3e50;
}
.category-bar a:hover {
border-color: #2c3e50;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
.card-link {
text-decoration: none;
color: inherit;
}
カテゴリボタンのリンクは同時に保持されます
categoryとkeywordパラメータ:?category={{ cat.slug }}&q={{ keyword }}。これにより、ユーザーが先に検索してからカテゴリをクリックしても、検索条件は失われず、2つの条件が同時に有効になります。
実践:絞り込みと検索をテストする
サーバーを起動し、以下の点を確認します:
- カテゴリボタンをクリックすると、対応するカテゴリの記事のみが表示されます
- キーワードを入力すると、タイトルと概要がリアルタイムに絞り込まれます
- カテゴリ + 検索の組み合わせ:絞り込み結果は2つの条件を同時に満たします
- 「すべて」ボタンでカテゴリ絞り込みを解除し、すべての記事を再表示します
章のまとめ
この章では、DjangoでURLクエリパラメータを処理する方法を習得しました:request.GET.get()でパラメータを取得、ORMチェーン絞り込み + Qオブジェクトで複雑なクエリを実装、テンプレートでURLパラメータを動的に連結して絞り込みリンクを実現。
カテゴリ絞り込みと検索機能により、ユーザーは興味のある記事をすばやく見つけることができます。
その他の拡張