Django ORMで、ForeignKeyやManyToManyで紐づいたモデルのフィールドを条件にしてデータをフィルタリングする方法を解説します。ダブルアンダースコア(__)記法を使うと、関連モデルのフィールドを簡単に参照できます。
基本的な使い方
関連モデルのフィールドでフィルタするには、関連名__フィールド名の形式で指定します。
views.py
from .models import Company, Employee
def index(request):
# Employeeのnameが"test"であるCompanyを取得
companies = Company.objects.filter(
employee__name="test"
)
print(companies)実行結果
<QuerySet [<Company: 株式会社A>]>employee__nameのように、関連モデル名(小文字)にダブルアンダースコアを付けてフィールド名を指定します。
複数条件でのフィルタリング
関連モデルの複数のフィールドを条件に指定することもできます。
views.py
# Employeeのidが1〜10かつnameが"test"であるCompanyを取得
companies = Company.objects.filter(
employee__id__range=(1, 10),
employee__name="test"
)
# 自身の条件と関連モデルの条件を組み合わせる
companies = Company.objects.filter(
id=1,
employee__name="test"
)filter()に複数の条件を渡すと、全てAND条件で結合されます。
select_relatedで効率的に取得
関連モデルのデータもテンプレートで使う場合は、select_related()を使ってN+1問題を回避します。
views.py
# N+1問題を防ぎつつ、関連データでフィルタリング
employees = Employee.objects.select_related('company').filter(
company__name="株式会社A"
)
# 各employeeのcompany.nameにアクセスしても追加クエリが発生しない
for emp in employees:
print(f"{emp.name} - {emp.company.name}")実行結果
山田太郎 - 株式会社A
佐藤花子 - 株式会社Aprefetch_relatedで逆方向の関連を取得
1対多の「1」側から「多」側を取得する場合はprefetch_related()を使います。
views.py
from django.db.models import Prefetch
# 基本的な使い方
companies = Company.objects.prefetch_related('employee_set').filter(
employee__name__contains="田"
).distinct()
# Prefetchオブジェクトで関連データ自体にもフィルタをかける
companies = Company.objects.prefetch_related(
Prefetch(
'employee_set',
queryset=Employee.objects.filter(age__gte=20),
to_attr='adult_employees'
)
)Prefetchオブジェクトを使うと、関連データ自体にもフィルタ条件を適用できます。
ルックアップの種類
関連モデルのフィールドに対しても、通常のルックアップが全て使えます。
views.py
# 部分一致
Company.objects.filter(employee__name__contains="田中")
# 前方一致
Company.objects.filter(employee__name__startswith="山")
# 範囲指定
Company.objects.filter(employee__age__range=(20, 30))
# NULLチェック
Company.objects.filter(employee__email__isnull=False)
# リスト内の値に一致
Company.objects.filter(employee__department__in=["営業部", "技術部"])実践的な使用例
ビューでの実践的な検索機能の例です。
views.py
from django.shortcuts import render
from .models import Company, Employee
def company_search(request):
companies = Company.objects.prefetch_related('employee_set')
# 社員名で会社を検索
employee_name = request.GET.get('employee_name')
if employee_name:
companies = companies.filter(
employee__name__contains=employee_name
).distinct()
# 部署で絞り込み
department = request.GET.get('department')
if department:
companies = companies.filter(
employee__department=department
).distinct()
return render(request, 'companies/search.html', {
'companies': companies
})ポイント
関連モデルでフィルタすると結果にJOINが使われるため、重複が発生することがあります。.distinct()を付けて重複を排除しましょう。
注意
select_related()はForeignKeyとOneToOneFieldに対して使います。ManyToManyFieldや逆参照の場合はprefetch_related()を使ってください。間違えるとパフォーマンスが悪化します。
まとめ
関連名__フィールド名のダブルアンダースコア記法で関連モデルの条件を指定できる- 複数条件を
filter()に渡すとAND条件になる select_related()でForeignKey先のデータを効率的に取得できるprefetch_related()で逆参照やManyToManyのデータを取得できる- 関連モデルでフィルタ時は
.distinct()で重複を排除する