【入門】Django Filter でQuerySetを効率的に絞り込む方法

はい、承知いたしました。Django Filterを用いたQuerySetの絞り込み方法について、約5000語の詳細な解説記事を執筆します。


【入門】Django Filter でQuerySetを効率的に絞り込む方法:検索フォーム実装の強力ツール徹底解説

Webアプリケーション開発において、ユーザーが大量のデータの中から必要なものを見つけるための検索・絞り込み機能は非常に重要です。Djangoでアプリケーションを開発している際、データベースからデータを取得し、特定の条件でフィルタリングするにはQuerySetのfilter()exclude()メソッドを使用します。しかし、ユーザーがウェブ画面から様々な条件を指定してデータを絞り込める検索フォームを実装しようとすると、入力された値を検証し、対応するQuerySetのフィルタリング処理を組み立てるという一連の作業は、意外と手間がかかり、コードが複雑になりがちです。

このような課題を解決し、検索・絞り込み機能の実装を効率化してくれる強力なライブラリが「Django Filter」です。Django Filterを使えば、シンプルな定義によって柔軟なフィルタリング機能を持つ検索フォームを簡単に作成し、QuerySetの絞り込み処理と連携させることができます。

本記事は、Django開発初心者の方でもDjango Filterを使いこなせるようになることを目指し、その基本的な使い方から、様々なオプション、カスタムフィルタ、ビューとの連携、パフォーマンスに関する考慮事項まで、詳細かつ網羅的に解説します。この記事を読めば、あなたのDjangoプロジェクトに高度な検索・絞り込み機能を効率的に組み込めるようになるでしょう。

1. なぜDjango Filterを使うのか?

DjangoにはQuerySetという強力な抽象化レイヤーがあり、これを使ってデータベースからデータを取得し、様々な条件でフィルタリングすることができます。例えば、ユーザーモデルからアクティブなユーザーを取得するには次のように書きます。

“`python

素のDjangoでのフィルタリング例

from django.contrib.auth.models import User

active_users = User.objects.filter(is_active=True)
“`

また、特定のキーワードを含むユーザー名や、指定した日付以降に登録されたユーザーを取得することも可能です。

“`python

複数の条件でのフィルタリング例

from django.contrib.auth.models import User
from datetime import date

today = date.today()
recent_active_users = User.objects.filter(
is_active=True,
username__icontains=”john”,
date_joined__gte=today
)
“`

このように、DjangoのQuerySetは単体でも非常に強力です。しかし、ユーザーがWebブラウザ上のフォームから検索条件を入力し、それに応じてデータを絞り込むという機能を実現しようとすると、素のDjangoだけではいくつかの課題が出てきます。

素のDjangoで検索フォームを実装する際の課題:

  1. 入力値の処理と検証: ユーザーがフォームに入力したデータ(GETパラメータなど)を取得し、それが正しい形式であるか(数値か、日付か、存在するかなど)を検証する必要があります。
  2. 動的なQuerySetの構築: 入力された条件に応じて、filter()メソッドに渡す引数を動的に組み立てる必要があります。例えば、ユーザーが名前だけを入力したら名前でフィルタリング、名前とメールアドレスを入力したら両方でフィルタリング、というように、入力された項目に応じてQuerySetを変更する必要があります。これはif文などを使って複雑なコードになりがちです。
  3. フォームとの連携: 検索フォーム(HTML)の生成と、入力値の取得・検証、そしてQuerySetへの変換という一連の流れを、手動で連携させる必要があります。DjangoのFormクラスを使っても、そのフォームの入力値をQuerySetのフィルタリング条件にマッピングする部分は自分で書かなければなりません。
  4. 再利用性: 同じようなフィルタリング条件を別のビューでも使いたい場合、コードをコピー&ペーストするか、共通関数化する必要がありますが、これも構造によっては煩雑になります。
  5. UIの生成: 検索条件入力用のHTMLフォームを生成するコードも必要です。

Django Filterは、これらの課題を解決するために設計されたライブラリです。

Django Filterを使うメリット:

  1. 宣言的なフィルタ定義: どのモデルのどのフィールドを、どのような方法でフィルタリングしたいかを、クラス定義の中で宣言的に記述できます。これにより、フィルタリングロジックが整理され、見通しが良くなります。
  2. 自動的なQuerySet構築: 定義したフィルタに基づいて、ユーザーからの入力(GETパラメータなど)を自動的に解釈し、適切なQuerySetを構築してくれます。手動でfilter()メソッドを組み立てる必要がなくなります。
  3. フォームとの連携: フィルタ定義から、入力用のHTMLフォームを自動的に生成できます。DjangoのFormクラスと密接に連携しており、フォームの表示や検証も容易に行えます。
  4. 再利用性: フィルタ定義はFilterSetクラスとして独立しているため、異なるビューから同じフィルタ定義を簡単に再利用できます。
  5. 多様なフィルタタイプとオプション: 数値、文字列、日付、ブーリアン、関連モデルなど、様々なデータ型に対応したフィルタタイプが用意されており、さらに「以上」「以下」「含む」「~で始まる」といった多様な検索条件を指定するためのlookup_exprオプションが豊富に用意されています。
  6. カスタムフィルタ: 標準の機能だけでは実現できない複雑なフィルタリングロジックも、カスタムメソッドを定義することで柔軟に対応できます。

つまり、Django Filterは、ユーザーインターフェース(検索フォーム)とデータベース(QuerySet)の間の複雑な連携部分を肩代わりしてくれる強力なツールなのです。これにより、開発者はビジネスロジック(どのようなデータをどのように表示するか)に集中できるようになります。

2. Django Filterのインストールとセットアップ

Django Filterを使い始めるのは簡単です。まずはpipを使ってライブラリをインストールします。

bash
pip install django-filter

インストールが完了したら、Djangoプロジェクトのsettings.py'django_filters'INSTALLED_APPSに追加します。

“`python

settings.py

INSTALLED_APPS = [
# … 既存のアプリケーション
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘yourapp’, # あなたのアプリケーション名
‘django_filters’, # これを追加
]

… 他の設定

“`

これで、Django Filterを使う準備が整いました。

3. Django Filterの基本的な使い方

Django Filterの基本的な使い方は、以下のステップで行います。

  1. フィルタ定義クラス(FilterSet)を作成する。
  2. ビューでそのFilterSetクラスを利用してQuerySetをフィルタリングする。
  3. テンプレートでフィルタリング結果とフィルタ入力フォームを表示する。

具体的な例として、簡単な書籍リストアプリケーションを考えましょう。

3.1. モデルの準備

まず、フィルタリング対象となるモデルを定義します。booksというアプリケーションを作成し、以下のようなBookモデルを定義します。

“`python

books/models.py

from django.db import models

class Author(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
    return self.name

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
published_date = models.DateField()
price = models.DecimalField(max_digits=6, decimal_places=2)
is_published = models.BooleanField(default=True)

def __str__(self):
    return self.title

“`

モデルを定義したら、マイグレーションを実行してデータベースにテーブルを作成しておきましょう。

bash
python manage.py makemigrations books
python manage.py migrate

必要であれば、管理サイトからデータをいくつか投入しておくと、動作確認がしやすくなります。

3.2. フィルタ定義クラス (FilterSet) の作成

次に、どのモデルを、どのような条件でフィルタリングしたいかを定義するクラスを作成します。慣習として、アプリケーションディレクトリ内にfilters.pyというファイルを作成し、その中に記述します。

“`python

books/filters.py

import django_filters
from .models import Book, Author

class BookFilter(django_filters.FilterSet):
class Meta:
model = Book
fields = [
‘title’,
‘author’,
‘published_date’,
‘price’,
‘is_published’,
]
“`

このコードでは、django_filters.FilterSetを継承したBookFilterクラスを定義しています。
内部のMetaクラスで、フィルタリングの対象となるモデルをmodel = Bookとして指定し、どのフィールドでフィルタリングを可能にするかをfieldsリストで指定しています。

fieldsに指定したフィールドは、デフォルトではそのフィールドの型に応じた標準的なフィルタ(例えばCharFieldなら文字列フィルタ、DateFieldなら日付フィルタ)が、exactという検索条件(厳密一致)で利用可能になります。

3.3. ビューでの利用

作成したFilterSetクラスをビューで使用し、QuerySetをフィルタリングします。ここでは簡単な関数ベースビューを作成します。

“`python

books/views.py

from django.shortcuts import render
from .models import Book
from .filters import BookFilter # 作成したフィルタクラスをインポート

def book_list(request):
# まず、フィルタリング対象となる元のQuerySetを用意する
# ここでは全てのBookオブジェクトを取得
queryset = Book.objects.all()

# リクエストのGETパラメータとQuerySetを使ってFilterSetをインスタンス化
# request.GET にユーザーがフォームに入力した値が入っています
# queryset にはフィルタリング前のQuerySetを渡します
book_filter = BookFilter(request.GET, queryset=queryset)

# filter.qs プロパティから、フィルタリング後のQuerySetを取得
# ユーザーが何も検索条件を指定しなかった場合、queryset がそのまま返されます
# 検索条件を指定した場合、その条件でフィルタリングされたQuerySetが返されます
filtered_books = book_filter.qs

# テンプレートに渡すコンテキストデータを作成
context = {
    'filter': book_filter, # テンプレートでフォーム表示に使うため、FilterSetインスタンスを渡す
    'books': filtered_books # テンプレートでフィルタリング結果を表示に使うため、フィルタリング後のQuerySetを渡す
}

# テンプレートをレンダリングしてレスポンスを返す
return render(request, 'books/book_list.html', context)

“`

このビューの肝は以下の2行です。

python
book_filter = BookFilter(request.GET, queryset=queryset)
filtered_books = book_filter.qs

  1. BookFilter(request.GET, queryset=queryset): BookFilterクラスをインスタンス化しています。
    • 第一引数request.GETには、ユーザーが検索フォームから送信したデータ(URLの?title=...&price=...のようなGETパラメータ)が入っています。Django Filterはこのデータを使って、どのフィルタ条件が指定されたかを自動的に解釈します。
    • queryset引数には、フィルタリングを行う対象となるQuerySetを渡します。通常はModel.objects.all()や、事前に別の条件で絞り込んだQuerySetなどを渡します。
  2. book_filter.qs: インスタンス化されたbook_filterオブジェクトの.qsプロパティに、フィルタリングが適用されたQuerySetが格納されています。このQuerySetをテンプレートに渡して表示します。

3.4. URL設定

作成したビューにアクセスするためのURLを設定します。

“`python

books/urls.py

from django.urls import path
from . import views

urlpatterns = [
path(‘books/’, views.book_list, name=’book_list’),
]

プロジェクトの urls.py に include するのを忘れずに

project/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path(‘admin/’, admin.site.urls),
path(”, include(‘books.urls’)), # これを追加
]
“`

3.5. テンプレートの作成

最後に、ビューから渡されたデータを受け取って表示するテンプレートを作成します。

“`html





Book List

Book List

{{ filter.form.as_p }} {# FilterSetインスタンスから自動生成されるフォームを表示 #}

{# フィルタをクリアするためのリンク(任意) #}
Clear

Results

{% if books %}

    {% for book in books %}

  • {{ book.title }} by {{ book.author.name }} (Published: {{ book.published_date }}, Price: {{ book.price }})
  • {% endfor %}

{{ books.count }} book(s) found.

{% else %}

No books found matching the criteria.

{% endif %}


“`

このテンプレートでは、{{ filter.form.as_p }}という記述で、ビューから渡されたbook_filterインスタンスが持つフォームを、段落タグ<p>で囲んだ形式で自動的に描画しています。このフォームには、BookFilterで指定したfieldsに対応する入力フィールドが自動的に生成されます。

ユーザーがフォームに条件を入力して「Search」ボタンをクリックすると、入力値がGETパラメータとしてビューに送信され、それに基づいてbook_filter.qsがフィルタリングされたQuerySetを返す、という一連の流れが実現されます。

これで、基本的なフィルタリング機能を持つ書籍リストページが完成しました。

4. フィールドの種類とオプション

前述の例では、Meta.fieldsにモデルのフィールド名をリストで指定するだけでフィルタを定義しました。これは最もシンプルな方法ですが、実際にはもっときめ細かいフィルタリング条件や、異なる入力フォームを使いたい場合があります。

Django Filterでは、様々なデータ型に対応したフィルタフィールドクラスが用意されており、それぞれに多様なオプションを指定できます。filters.pyFilterSetクラスを定義する際に、Meta.fieldsを使う代わりに、クラスの属性として個別にフィルタフィールドを定義します。

“`python

books/filters.py (修正版)

import django_filters
from .models import Book, Author

class BookFilter(django_filters.FilterSet):
# 個別にフィルタフィールドを定義
title = django_filters.CharFilter(lookup_expr=’icontains’, label=’タイトル’)
author__name = django_filters.CharFilter(lookup_expr=’icontains’, label=’著者名’)
published_date__year = django_filters.NumberFilter(lookup_expr=’exact’, label=’出版年’)
price__gte = django_filters.NumberFilter(lookup_expr=’gte’, label=’価格(以上)’)
price__lte = django_filters.NumberFilter(lookup_expr=’lte’, label=’価格(以下)’)
is_published = django_filters.BooleanFilter(label=’公開済み’)

class Meta:
    model = Book
    # 個別に定義した場合は fields は不要、または
    # Meta.fields で指定しつつ、個別に定義したフィールドは
    # Meta.fields よりも優先される
    # fields = [] # または削除

“`

このようにクラス属性としてフィルタフィールドを定義すると、より細かく制御できます。

4.1. 主なフィルタフィールドクラス

よく使われるフィルタフィールドクラスには以下のようなものがあります。

  • django_filters.CharFilter: 文字列フィールド用のフィルタ。CharField, TextFieldなどに対応。
  • django_filters.NumberFilter: 数値フィールド用のフィルタ。IntegerField, FloatField, DecimalFieldなどに対応。
  • django_filters.BooleanFilter: ブーリアンフィールド用のフィルタ。BooleanFieldに対応。チェックボックスなどで表現されることが多いです。
  • django_filters.DateFilter: 日付フィールド用のフィルタ。DateFieldに対応。
  • django_filters.DateTimeFilter: 日時フィールド用のフィルタ。DateTimeFieldに対応。
  • django_filters.ModelChoiceFilter: 関連モデルの選択肢を提供するフィルタ。ForeignKeyに対応。ドロップダウンリストなどで表現されます。
  • django_filters.ModelMultipleChoiceFilter: 関連モデルの複数の選択肢を提供するフィルタ。ManyToManyFieldや、ForeignKeyで複数選択を許可したい場合に対応。複数選択可能なリストなどで表現されます。
  • django_filters.RangeFilter: 数値や日付/日時に対して、「~から~まで」という範囲指定を可能にするフィルタ。内部的には開始値と終了値の2つの入力フィールドを持ちます。
  • django_filters.MultipleChoiceFilter: 特定の選択肢(Choices)から複数を選択してフィルタリングできるフィルタ。モデルフィールドにchoicesオプションが指定されている場合などに便利です。

4.2. lookup_expr オプション

各フィルタフィールドクラスは、lookup_exprオプションを指定することで、データベースでの比較方法を変更できます。これはDjangoのQuerySetで__(ダブルアンダースコア)を使って指定するルックアップと同じものです。

よく使われるlookup_exprの例:

  • exact: 厳密一致 (field=value) – デフォルト
  • iexact: 大文字・小文字を区別しない厳密一致 (field__iexact=value)
  • contains: 部分一致 (field__contains=value) – 大文字・小文字を区別
  • icontains: 大文字・小文字を区別しない部分一致 (field__icontains=value)
  • startswith: ~で始まる (field__startswith=value) – 大文字・小文字を区別
  • istartswith: 大文字・小文字を区別しない ~で始まる (field__istartswith=value)
  • endswith: ~で終わる (field__endswith=value) – 大文字・小文字を区別
  • iendswith: 大文字・小文字を区別しない ~で終わる (field__iendswith=value)
  • gt: より大きい (field__gt=value)
  • gte: より大きいか等しい (field__gte=value)
  • lt: より小さい (field__lt=value)
  • lte: より小さいか等しい (field__lte=value)
  • in: リスト中のいずれかに一致 (field__in=value_list) – MultipleChoiceFilterやカンマ区切りの入力と組み合わせて使われます。
  • range: 範囲内 (field__range=(start, end)) – RangeFilterと組み合わせて使われます。
  • isnull: NULLかどうか (field__isnull=True_or_False)
  • date: 日時フィールドを日付として比較 (datetime_field__date=date_value)
  • year, month, day, week, week_day, quarter: 日付/日時フィールドの特定部分で比較
  • time, hour, minute, second: 日時/時刻フィールドの特定部分で比較

例:

“`python

books/filters.py (lookup_expr の例)

import django_filters
from .models import Book

class BookFilter(django_filters.FilterSet):
# タイトルに含まれるキーワードで検索 (大文字・小文字区別なし)
title = django_filters.CharFilter(lookup_expr=’icontains’, label=’タイトルに含まれる’)

# 価格が指定した値以上
price__gte = django_filters.NumberFilter(lookup_expr='gte', label='価格(以上)')

# 出版年で検索
published_date__year = django_filters.NumberFilter(lookup_expr='exact', label='出版年')

# 著者 (関連モデル) の名前で部分一致検索
author__name = django_filters.CharFilter(lookup_expr='icontains', label='著者名に含まれる')

class Meta:
    model = Book
    # 個別に定義したフィールドは Meta.fields に含めなくても良い
    # ここでは author モデルそのものでフィルタリングするドロップダウンも追加
    fields = ['author'] # これにより author モデルの ModelChoiceFilter が自動生成される

“`

この例のように、個別にフィルタフィールドを定義することで、デフォルトのexact以外のlookup_exprを指定したり、ユーザーに分かりやすいlabelを付けたりできます。フィールド名にはlookup_exprをダブルアンダースコアで繋げたもの(例:price__gte)を使うと、フィルタの目的が明確になり、Django Filterも自動的にそれを解釈して適切なルックアップを適用してくれます。ただし、price__gteという名前自体はただの変数名であり、自動的な解釈に必須なのはlookup_expr='gte'の部分です。変数名はmin_priceなど分かりやすいものにすることも可能です。

“`python

変数名をわかりやすくする例

class BookFilter(django_filters.FilterSet):
# 変数名を min_price としても、lookup_expr が ‘gte’ であればOK
min_price = django_filters.NumberFilter(field_name=’price’, lookup_expr=’gte’, label=’価格(以上)’)
max_price = django_filters.NumberFilter(field_name=’price’, lookup_expr=’lte’, label=’価格(以下)’)

class Meta:
    model = Book
    fields = ['author'] # author モデルでのフィルタは Meta で定義したまま

``
ここで登場した
field_nameオプションは、フィルタフィールドの名前(例:min_price)と実際にフィルタリングを適用したいモデルのフィールド名(例:price)が異なる場合に、モデルフィールド名を指定するために使用します。特に、関連モデルのフィールドをフィルタリングする場合(例:author__name)や、同じモデルフィールドに対して複数のフィルタ(例:min_price,max_priceがどちらもprice`フィールドを対象とする)を定義する場合に便利です。

4.3. field_name オプション (関連モデルのフィルタリング)

関連モデルのフィールドでフィルタリングしたい場合は、field_nameオプションにリレーションシップを辿るパスをドット表記で指定します。これはQuerySetで関連モデルをフィルタリングする際と同じ記法です。

例:Bookモデルから、関連するAuthorモデルのnameフィールドでフィルタリングしたい場合。

“`python

books/filters.py

import django_filters
from .models import Book, Author

class BookFilter(django_filters.FilterSet):
# Author モデルの name フィールドで部分一致検索
author_name = django_filters.CharFilter(field_name=’author__name’, lookup_expr=’icontains’, label=’著者名に含まれる’)

class Meta:
    model = Book
    # 他のフィールドはここでは省略
    fields = ['title', 'published_date'] # 例

“`

この例では、フィルタフィールド名をauthor_nameとしていますが、実際にフィルタリングされるのはBookモデルのauthorというForeignKeyを辿った先のAuthorモデルのnameフィールドです。field_name='author__name'によって、この関連モデルのフィールドを指定しています。

4.4. exclude=True オプション

特定の条件に一致しないデータを取得したい(exclude()に相当するフィルタ)場合は、フィルタフィールドにexclude=Trueオプションを指定します。

“`python

books/filters.py

import django_filters
from .models import Book

class BookFilter(django_filters.FilterSet):
# 指定したタイトルと厳密に一致しないものを取得
title_exclude = django_filters.CharFilter(field_name=’title’, exclude=True, lookup_expr=’exact’, label=’タイトルが一致しない’)

class Meta:
    model = Book
    fields = [] # 他のフィールドはここでは定義しない

“`

ユーザーがこのフィルタに「Django」と入力すると、タイトルが「Django」である本を除いた全ての書籍が取得されます。

4.5. distinct=True オプション

フィルタリングの結果、重複するレコードが含まれる可能性がある場合(特にリレーションシップを跨いだフィルタリングなどで)、重複を排除したい場合はdistinct=Trueオプションを指定します。これはQuerySetの.distinct()メソッドと同じ効果を持ちます。

“`python

books/filters.py

import django_filters
from .models import Book

class BookFilter(django_filters.FilterSet):
class Meta:
model = Book
fields = [‘author__name’] # 例: 著者名でフィルタリング
distinct = True # フィルタリング結果の重複を排除
“`

もしある著者が複数の本を書いていて、その著者名でフィルタリングした場合、デフォルトではその著者の本が全て結果に含まれます。distinct=Trueを指定することで、フィルタリング後のQuerySetに対してdistinct()が適用され、重複が排除されます。

4.6. label, help_text, widget オプション

これらのオプションは、生成されるフォームの表示や動作をカスタマイズするために使用します。これらはDjangoのFormフィールドのオプションと同じように機能します。

  • label: フォームフィールドのラベルとして表示されるテキストを指定します。指定しない場合は、フィルタフィールド名から自動的に生成されます。
  • help_text: フォームフィールドの下に表示されるヘルプテキストを指定します。
  • widget: フォームフィールドで使用するウィジェット(HTMLタグ)を指定します。デフォルトのウィジェットを別のもの(例:テキスト入力フィールドをテキストエリアに変更、ラジオボタンをセレクトボックスに変更など)に置き換えたい場合に利用します。

例:

“`python

books/filters.py

import django_filters
from .models import Book
from django.forms import SelectDateWidget # 日付選択用のウィジェット

class BookFilter(django_filters.FilterSet):
# ラベルとヘルプテキストを指定
title = django_filters.CharFilter(lookup_expr=’icontains’,
label=’書籍タイトル’,
help_text=’部分一致で検索します’)

# widget を指定して日付選択をドロップダウンにする
published_date = django_filters.DateFilter(widget=SelectDateWidget, label='出版日')

class Meta:
    model = Book
    fields = ['is_published'] # 他のフィールドはここでは省略

“`

これらのオプションを使うことで、ユーザーにとってより使いやすい検索フォームを作成できます。

5. より高度な使い方

基本的なフィルタフィールドとオプションだけでも多くのケースに対応できますが、さらに複雑なフィルタリングロジックや、より柔軟なフォーム制御が必要になることもあります。

5.1. カスタムフィルタメソッド (method オプション)

標準のlookup_exprでは実現できない複雑なフィルタリングロジックは、カスタムメソッドを作成し、フィルタフィールドのmethodオプションでそのメソッドを指定することで実現できます。

カスタムメソッドは、以下のシグネチャを持つ必要があります。

“`python
def filter_method_name(self, queryset, name, value):
# value はユーザーがフィルタフィールドに入力した値
# name はフィルタフィールド名 (field_name が指定されていれば field_name)
# queryset はフィルタリング前の QuerySet

# value を使って queryset をフィルタリングする処理を記述
# フィルタリング後の QuerySet を return する

if value: # 値が入力された場合のみ処理
    # 例:特定の条件に基づいて QuerySet を変更
    # filtered_queryset = queryset.filter(...) or queryset.exclude(...)
    pass # ここに実際のフィルタリングロジック

    return filtered_queryset
return queryset # 値が入力されなかった場合は元の QuerySet を返す

“`

例:タイトルまたは著者名のどちらかに特定のキーワードが含まれる本を検索したい場合(OR条件)。標準のfieldsや個別のフィルタ定義ではAND条件にしかならないため、カスタムメソッドを使います。

“`python

books/filters.py

import django_filters
from django.db.models import Q # OR検索に使う Q オブジェクト

class BookFilter(django_filters.FilterSet):
# キーワード検索用のフィールドを定義し、カスタムメソッドを指定
search_keyword = django_filters.CharFilter(method=’filter_by_keyword’, label=’タイトルまたは著者名で検索’)

def filter_by_keyword(self, queryset, name, value):
    if not value:
        return queryset # キーワードがなければ元のQuerySetを返す

    # タイトルにキーワードを含む OR 著者名にキーワードを含む
    return queryset.filter(
        Q(title__icontains=value) | Q(author__name__icontains=value)
    )

class Meta:
    model = Book
    # カスタムフィルタフィールドは fields に含める必要はない
    fields = [] # Meta.fields は空にするか、他の個別フィルタを定義する

“`

この例では、search_keywordというフィルタフィールドを定義し、method='filter_by_keyword'としています。filter_by_keywordメソッド内で、入力されたvalue(キーワード)を使って、タイトルまたは著者名のどちらかにキーワードが含まれるようにQオブジェクトを用いてOR条件でフィルタリングしています。

カスタムメソッドは非常に強力で、ほとんどどんな複雑なフィルタリングロジックも実現できます。ただし、そのロジックはメソッド内で手動で記述する必要があります。

5.2. フォームウィジェットのカスタマイズ

前述のwidgetオプションの補足です。デフォルトで生成されるフォームフィールドのHTML要素や属性をより細かく制御したい場合に利用します。DjangoのFormクラスで使えるあらゆるウィジェットを指定できます。

例:タイトル検索の入力フィールドにCSSクラスを追加したい場合。

“`python

books/filters.py

import django_filters
from django import forms # forms モジュールをインポート

class BookFilter(django_filters.FilterSet):
title = django_filters.CharFilter(
lookup_expr=’icontains’,
label=’書籍タイトル’,
# TextInput ウィジェットを指定し、attrs に CSS クラスを追加
widget=forms.TextInput(attrs={‘class’: ‘form-control’, ‘placeholder’: ‘タイトルを入力’})
)

class Meta:
    model = Book
    fields = ['published_date'] # 例

“`

このようにforms.TextInputなどのウィジェットクラスをインポートし、attrs引数にHTML属性を辞書形式で渡すことで、生成される<input type="text">タグにclass="form-control"placeholder="タイトルを入力"といった属性を追加できます。これは、BootstrapなどのCSSフレームワークを使っている場合にフォームのスタイルを適用するのに非常に便利です。

5.3. 初期値の設定 (default オプション)

フィルタフォームにデフォルトで特定の値を設定しておきたい場合は、フィルタフィールドにdefaultオプションを指定します。

“`python

books/filters.py

import django_filters

class BookFilter(django_filters.FilterSet):
# is_published フィールドをデフォルトで True に設定
is_published = django_filters.BooleanFilter(label=’公開済み’, default=True)

class Meta:
    model = Book
    fields = ['title'] # 例

“`

このフィルタフォームを開いた際、is_publishedに対応するチェックボックスは最初からチェックされた状態になります。

5.4. 必須・非必須の設定

Django Filterによって生成されるフォームフィールドは、デフォルトでは必須ではありません。つまり、ユーザーが何も入力しなくてもフィルタリングは実行されます(入力されなかったフィルタ条件は無視されます)。

もし特定のフィルタフィールドを必須にしたい場合は、カスタムフィルタフィールドを定義する際に、DjangoのFormフィールドと同様にrequired=Trueを指定します。

“`python

books/filters.py

import django_filters

class BookFilter(django_filters.FilterSet):
# title フィールドを必須にする
title = django_filters.CharFilter(lookup_expr=’icontains’, label=’書籍タイトル’, required=True)

class Meta:
    model = Book
    fields = ['published_date'] # 例

“`

ただし、フィルタフィールドを必須にすると、そのフォームが送信されたときにそのフィールドに値が含まれていないとフォームのバリデーションエラーが発生します。これは一般的なフォームの動作としては正しいですが、検索フォームにおいては「何も指定しない=絞り込み条件なし」として扱いたい場合が多いため、必須にするケースは限定的かもしれません。

5.5. 複数のフィールドでのフィルタリング

Meta.fieldsに複数のフィールド名を指定したり、クラス属性として複数のフィルタフィールドを定義したりすることで、複数の条件でAND検索を行うことができます。

“`python

books/filters.py

import django_filters

class BookFilter(django_filters.FilterSet):
# タイトルに含まれる OR 著者名に含まれる (カスタムメソッドで OR)
search_keyword = django_filters.CharFilter(method=’filter_by_keyword’, label=’タイトルまたは著者名で検索’)

# かつ、価格が指定範囲内 (gte/lte で AND)
min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte', label='価格(以上)')
max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte', label='価格(以下)')

# かつ、公開済みかどうか (BooleanField で AND)
is_published = django_filters.BooleanFilter(label='公開済み')

def filter_by_keyword(self, queryset, name, value):
    # ... (前述と同じ OR 処理)
    pass # 実際のコードでは OR 処理を記述

class Meta:
    model = Book
    # Meta.fields は必要に応じて他のフィールドを追加
    fields = ['published_date'] # 例えば出版日で exact 検索も可能に

“`

この例では、search_keywordmin_pricemax_priceis_published、そしてpublished_dateという複数のフィルタ条件を同時に指定できます。ユーザーがこれらのフォームフィールドに値を入力して送信すると、Django Filterは入力された全ての条件を使ってQuerySetをフィルタリングします。カスタムメソッド内でのロジック(search_keyword)を除けば、指定された条件はデフォルトでAND結合されます。

6. ビューとの連携 (FilterView)

Django Filterは、ジェネリックビューであるListViewと組み合わせて使うとさらに効率的です。django_filtersには、ListViewを継承し、フィルタリング機能を組み込んだFilterViewという便利なクラスが用意されています。

6.1. FilterViewの基本的な使い方

FilterViewを使うと、ビューでのFilterSetのインスタンス化やQuerySetのフィルタリング、テンプレートへのコンテキストデータ追加といった定型的な処理を省略できます。

“`python

books/views.py (FilterView を使う場合)

from django_filters.views import FilterView
from .models import Book
from .filters import BookFilter

FilterView を継承するだけで OK

class BookListView(FilterView):
model = Book # 対象モデルを指定
filterset_class = BookFilter # 使用する FilterSet クラスを指定
template_name = ‘books/book_list.html’ # 使用するテンプレートを指定
context_object_name = ‘books’ # テンプレートで QuerySet にアクセスする際の変数名 (ListView と同じ)
# Paginate if necessary
# paginate_by = 10 # オプションでページネーションも可能
“`

FilterViewを継承したクラスビューでは、以下の属性を指定するだけでほとんどの準備が完了します。

  • model: フィルタリング対象のモデル。
  • filterset_class: 使用するFilterSetクラス。
  • template_name: レンダリングするテンプレート。
  • context_object_name: テンプレート内でフィルタリング後のQuerySetにアクセスするための変数名。デフォルトはモデル名の小文字複数形(例: book_list)ですが、明示的に指定しておくと分かりやすいです。

URL設定もクラスベースビュー向けに変更します。

“`python

books/urls.py (FilterView を使う場合)

from django.urls import path
from .views import BookListView # クラスベースビューをインポート

urlpatterns = [
path(‘books/’, BookListView.as_view(), name=’book_list’), # .as_view() を呼び出す
]
“`

テンプレートは関数ベースビューの場合とほぼ同じまま使用できます。FilterViewは自動的に以下のことを行ってくれるためです。

  • request.GETを使ってfilterset_classをインスタンス化します。
  • インスタンス化したFilterSetオブジェクトをfilterというコンテキスト名でテンプレートに渡します。
  • FilterSetの.qsで取得したフィルタリング後のQuerySetを、context_object_nameで指定した名前(またはデフォルト名)でテンプレートに渡します。

これにより、ビューのコードが大幅に簡潔になります。多くのケースではFilterViewを使うのが最も効率的な方法です。

6.2. FilterViewでのカスタマイズ

FilterViewも、ジェネリックビューと同様に様々なメソッドをオーバーライドしてカスタマイズできます。

  • get_queryset(): フィルタリング対象となる元のQuerySetをカスタマイズしたい場合にオーバーライドします。例えば、特定のユーザーに関連するデータのみをフィルタリング対象としたい場合などです。
    “`python
    # books/views.py (get_queryset の例)
    class BookListView(FilterView):
    # … (他の属性)
    filterset_class = BookFilter

    def get_queryset(self):
        # デフォルトの QuerySet (Book.objects.all()) に加えて、
        # 例えばログインユーザーが所有する書籍のみを対象とする
        queryset = super().get_queryset() # まず親クラスの get_queryset を呼ぶ
        if self.request.user.is_authenticated:
            # 例: 書籍に owner が設定されている場合
            # queryset = queryset.filter(owner=self.request.user)
            pass # ここにカスタムの QuerySet 取得ロジック
    
        # ここで返した QuerySet がフィルタリングの対象となります
        return queryset
    

    * **`get_context_data()`**: テンプレートに渡すコンテキストデータを追加したい場合にオーバーライドします。例えば、フィルタリング結果の件数を表示したい場合などです。python

    books/views.py (get_context_data の例)

    class BookListView(FilterView):
    # … (他の属性)

    def get_context_data(self, **kwargs):
        # まず親クラスの get_context_data を呼んでデフォルトのコンテキストを取得
        context = super().get_context_data(**kwargs)
    
        # ここでカスタムのコンテキストデータを追加
        context['some_extra_data'] = 'This is extra data'
        # context['result_count'] = context[self.context_object_name].count() # 結果件数など
    
        return context
    

    “`

FilterViewを使えば、Django Filterの機能とDjangoのジェネリックビューの強力な機能を組み合わせて、効率的に開発を進めることができます。

7. テンプレートでの表示

Django Filterによって生成されるフォームは、テンプレートで様々な方法で表示できます。

7.1. フォーム全体の自動表示

最も簡単な方法は、FilterSetインスタンスの.formプロパティにアクセスし、DjangoのFormオブジェクトとしてレンダリングすることです。

“`html

{{ filter.form.as_p }} {# 各フィールドが

タグで囲まれる #}
{# または {{ filter.form.as_ul }} や {{ filter.form.as_table }} #}

“`

as_p, as_ul, as_tableは、フォーム全体を指定されたHTML構造でレンダリングする便利なメソッドです。手軽ですが、レイアウトの自由度は低くなります。

7.2. 個々のフィールドの手動表示

より柔軟なレイアウトやCSSの適用が必要な場合は、フォームの各フィールドを手動で表示します。

“`html

{{ filter.form.title.label_tag }} {# フィールドのラベルを表示 #}
{{ filter.form.title }} {# フィールドの入力ウィジェットを表示 #}
{% if filter.form.title.errors %} {# フィールドのエラーメッセージを表示 #}

    {% for error in filter.form.title.errors %}

  • {{ error }}
  • {% endfor %}

{% endif %}
{% if filter.form.title.help_text %} {# フィールドのヘルプテキストを表示 #}

{{ filter.form.title.help_text|safe }}

{% endif %}

{{ filter.form.author.label_tag }}
{{ filter.form.author }}

{# … 他のフィールドも同様に記述 … #}


“`

このように、{{ filter.form.field_name }}として各フィールドにアクセスし、.label_tag, .errors, .help_textといったプロパティやメソッドを使って、ラベル、入力ウィジェット、エラーメッセージ、ヘルプテキストを個別に表示できます。これにより、HTML構造を自由に制御し、デザインを細かく調整できます。

ウィジェットにCSSクラスを追加したい場合は、フィルタ定義クラスのwidgetオプションを使うのが最もクリーンな方法です(前述)。

7.3. フィルタリング状態の維持

ユーザーが検索条件を指定してページを再読み込みした際に、フォームに以前入力した値が初期値として表示されていると使い勝手が良いです。Django Filterはこれを自動的に行ってくれます。BookFilter(request.GET, ...)のように、FilterSetをインスタンス化する際にrequest.GETを渡しているため、フォームはGETパラメータから値を取得し、それらを初期値として使用します。

7.4. 検索結果件数の表示

フィルタリング結果の件数を表示したい場合は、ビューから渡されたQuerySetオブジェクトの.count()メソッドをテンプレートで使用します。

“`html

{% if books %}

{{ books.count }} book(s) found.

{% else %}

No books found matching the criteria.

{% endif %}
“`

8. パフォーマンスに関する考慮事項

大量のデータを扱う場合、フィルタリングのパフォーマンスは非常に重要です。Django Filter自体は効率的なQuerySetを生成しますが、いくつか注意すべき点があります。

  1. データベースインデックスの利用: フィルタリングによく使用するフィールド(例: title, published_date, price, authorの外部キーフィールドなど)には、データベースインデックスを設定することを強く推奨します。これにより、データベースが高速に該当レコードを検索できるようになります。Djangoモデルのフィールド定義でdb_index=Trueを指定するか、Metaクラスのindexesオプションを使用します。
    “`python
    # books/models.py (インデックスの例)
    from django.db import models

    class Book(models.Model):
    title = models.CharField(max_length=200, db_index=True) # シンプルなフィールドのインデックス
    author = models.ForeignKey(Author, on_delete=models.CASCADE) # ForeignKey には自動的にインデックスが作成されることが多いですが、確認が必要です
    published_date = models.DateField(db_index=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)
    # …

    class Meta:
         # より高度なインデックス(例:複合インデックス)
         indexes = [
             models.Index(fields=['author', 'published_date']),
         ]
    

    2. **リレーションシップを辿るフィルタリング:** `author__name`のように関連モデルのフィールドでフィルタリングする場合、DjangoはJOINクエリを生成します。大量のリレーションを持つデータに対して頻繁にJOINを行うフィルタリングは、パフォーマンスに影響を与える可能性があります。関連モデルのフィールドにも適切にインデックスを設定することが重要です。
    3. **`select_related` / `prefetch_related` との組み合わせ:** フィルタリング自体に直接関係ありませんが、フィルタリング後に取得したQuerySetをテンプレートで表示する際に、関連データを効率的に取得するために`select_related`や`prefetch_related`を使用することを検討してください。これにより、テンプレートでの関連データへのアクセスによるN+1問題を回避できます。これは`FilterView`の`get_queryset`メソッド内で元のQuerySetに対して適用するのが良い方法です。
    python

    books/views.py (select_related の例 in FilterView)

    class BookListView(FilterView):
    # … (他の属性)

    def get_queryset(self):
        # 関連する author を事前に取得しておく
        queryset = super().get_queryset().select_related('author')
        return queryset
    

    ``
    4. **複雑なカスタムフィルタ:** カスタムフィルタメソッド内で非効率なデータベースクエリを実行していないか注意が必要です。メソッド内で複数のQuerySetを生成したり、ループの中でデータベースアクセスを行ったりするとパフォーマンスが低下します。可能な限り、メソッド内で行う処理は単一の効率的なQuerySet操作にまとめるように努めてください。
    5. **デバッグツール:**
    django-debug-toolbar`のようなツールを使うと、各ページで実行されたSQLクエリを確認できます。生成されたクエリが意図通りか、非効率な部分がないかを確認するのに非常に役立ちます。

これらの点を考慮することで、Django Filterを使った検索・絞り込み機能を、大量のデータに対しても快適に動作させることができます。

9. よくある質問 (FAQ)

Q1: 素のfilter()とDjango Filterはどう使い分ければ良いですか?

A1:
* 素のfilter(): ビューやモデルメソッド内で、アプリケーションのロジックとして固定的な条件でQuerySetを絞り込む場合に使用します。例えば、「公開済みの記事だけを取得する」「今日作成されたユーザーだけを取得する」といった、ユーザーの入力に関係なく常に適用される条件です。
* Django Filter: ユーザーがウェブ画面から入力する、動的で可変的な検索条件に基づいてQuerySetを絞り込む場合に使用します。検索フォームの実装が主な目的です。

Q2: 複数の条件でAND検索フォームを作るにはどうすれば良いですか?

A2:
FilterSetクラスのMeta.fieldsに複数のフィールド名をリストで指定するか、クラス属性として複数のフィルタフィールドを個別に定義すれば、それらは自動的にAND条件で結合されます。これがDjango Filterのデフォルトの動作です。

“`python

複数のフィールドで AND 検索

class BookFilter(django_filters.FilterSet):
class Meta:
model = Book
# ここにリストアップされたフィールドは AND 条件で結合されます
fields = [‘title’, ‘author’, ‘published_date__year’, ‘is_published’]

または個別に定義

class BookFilter(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr=’icontains’)
author = django_filters.ModelChoiceFilter(queryset=Author.objects.all())
# … 他のフィールドも個別に定義
class Meta:
model = Book
fields = [] # 個別に定義する場合は Meta.fields は不要
“`

Q3: デフォルトでは全てのデータが表示されるようにするには?

A3:
通常、Django Filterは検索条件が何も指定されていない場合、元のQuerySet(FilterSetのインスタンス化時にqueryset引数として渡されたQuerySet)をそのまま返します。FilterViewを使用する場合も同様です。

したがって、ビューのget_querysetや関数ベースビューの冒頭でModel.objects.all()のように全てのデータを取得するQuerySetを用意しておけば、デフォルトで全てのデータが表示されます。これはDjango Filterの標準的な挙動です。

Q4: フィルタリング条件をURLパラメータではなくPOSTで送信したいのですが?

A4:
Django FilterはデフォルトでGETパラメータ(request.GET)を読み取ってフィルタリングを行います。これは、検索条件をURLに含めることで、そのURLをブックマークしたり、他のユーザーと共有したりできるという利点があるためです。

もしPOSTで送信されたデータを使いたい場合は、ビューでFilterSetをインスタンス化する際にrequest.POSTを渡すことも可能です。

“`python

ビューで POST データを使う例

def book_list_post(request):
queryset = Book.objects.all()
# POST データを使って FilterSet をインスタンス化
book_filter = BookFilter(request.POST, queryset=queryset)
filtered_books = book_filter.qs

context = {
    'filter': book_filter,
    'books': filtered_books
}
return render(request, 'books/book_list_post.html', context)

テンプレートのフォームも`method="post"`に変更し、CSRFトークンを含める必要があります。html

{% csrf_token %} {# POST の場合は CSRF トークンが必要 #}
{{ filter.form.as_p }}

“`
ただし、POSTは一般的にデータ作成・更新・削除に使われるべきメソッドであり、冪等性(複数回実行しても同じ結果になること)が保証されません。検索はデータの取得であり冪等であるべき操作なので、通常はGETを使用することが推奨されます。POSTを使う特別な理由がない限り、GETを使用しましょう。

Q5: フィルタリング条件をセッションに保存して、ページ遷移しても維持したいのですが?

A5:
Django Filter自体にはセッション管理の機能は含まれていません。フィルタリング条件をセッションに保存したい場合は、ビュー側で手動で実装する必要があります。

例えば、GETリクエストでフィルタリング条件が送信された場合に、その条件をセッションに保存し、以降のGETリクエストでセッションに条件があればそれを使う、というロジックをビューに記述します。

“`python

ビューでセッションを使う例

from django.shortcuts import render
from django.urls import reverse # リダイレクト先URL生成に使う
from django.http import HttpResponseRedirect

FILTER_SESSION_KEY = ‘book_filter_params’

def book_list_with_session(request):
queryset = Book.objects.all()
filter_params = request.GET # 現在のリクエストのGETパラメータ

# もしGETパラメータが送信されていて、それがフィルタリング目的であればセッションに保存
# (例: 'search' ボタンが押されたと判断できるパラメータがあるかなど)
# ここではシンプルに、GETパラメータがあれば常にセッションに保存
if filter_params:
     request.session[FILTER_SESSION_KEY] = filter_params.dict()
     # 保存後、セッションを使ってリダイレクトすることで、URLからパラメータを消す(任意)
     # return HttpResponseRedirect(reverse('book_list_with_session')) # リダイレクトは UX 次第

# セッションにフィルタ条件が保存されていれば、それを使う
elif FILTER_SESSION_KEY in request.session:
    filter_params = request.session[FILTER_SESSION_KEY]
else:
    filter_params = {} # セッションにもGETにもなければ空の辞書

book_filter = BookFilter(filter_params, queryset=queryset)
filtered_books = book_filter.qs

context = {
    'filter': book_filter,
    'books': filtered_books,
}
return render(request, 'books/book_list.html', context) # 同じテンプレートを使える

“`
ただし、セッションを使うとURLで条件を共有できなくなる、サーバーサイドに負荷がかかるなどのデメリットもあります。要件に応じてGETパラメータとセッションを使い分けるか検討が必要です。

10. まとめ

本記事では、Django Filterを使ってDjangoアプリケーションに効率的かつ柔軟な検索・絞り込み機能を実装する方法を詳細に解説しました。

  • Django Filterは、検索フォームの入力とQuerySetのフィルタリング処理を連携させる複雑さを解消し、開発効率を大幅に向上させます。
  • FilterSetクラスを定義することで、どのモデルのどのフィールドを、どのような条件でフィルタリング可能にするかを宣言的に記述できます。
  • CharFilter, NumberFilter, DateFilter, ModelChoiceFilterなど、様々なフィルタフィールドクラスと、lookup_exprオプションを組み合わせて、多様な検索条件に対応できます。
  • field_nameオプションを使えば、関連モデルのフィールドによるフィルタリングも簡単に行えます。
  • methodオプションを使えば、カスタムメソッドによって複雑なフィルタリングロジック(OR条件など)も実現できます。
  • FilterViewジェネリックビューを使えば、ビューのコードを大幅に削減し、より効率的に開発を進めることができます。
  • テンプレートでは、filter.formを使ってフォーム全体を自動表示することも、個々のフィールドを手動で表示することも可能です。
  • 大量データを扱う場合は、データベースインデックスの利用や関連データの効率的な取得(select_related, prefetch_related)といったパフォーマンスに関する考慮も重要です。

Django Filterは、ユーザーがデータを簡単に絞り込める高機能な検索インターフェースを実装するための強力なツールです。ぜひあなたのプロジェクトに導入し、開発効率とユーザーエクスペリエンスの向上に役立ててください。

今後さらに深く学びたい場合は、Django Filterの公式ドキュメントを参照することをお勧めします。この記事で解説した内容以外にも、さらに多くの高度な機能やオプションが紹介されています。

参考資料:

これで、Django Filterの入門レベルから一歩進んだ活用方法まで、十分に理解できたことでしょう。Happy Coding!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール