Django ORMのカスタムマネージャー入門
スコープを定義する方法
Django ORMのカスタムマネージャーとカスタムQuerySetを使って、よく使うクエリ条件をメソッドとして定義する方法を解説します。
こんな人向けの記事です
- よく使うクエリ条件を再利用可能にしたい人
- カスタムマネージャーの作り方を知りたい人
- QuerySetメソッドのチェーンを実現したい人
Step 1カスタムマネージャーとは
Djangoのマネージャー(Manager)はモデルに対するデータベースクエリのインターフェースです。objectsがデフォルトのマネージャーで、all()やfilter()を提供しています。カスタムマネージャーを作ると、よく使うクエリ条件をメソッドとして定義できます。
Python
# カスタムマネージャーなしの場合(毎回同じfilterを書く)
# views.py のあちこちで...
active_products = Product.objects.filter(is_active=True)
active_products = Product.objects.filter(is_active=True) # 重複!
# カスタムマネージャーありの場合(1行で呼べる)
active_products = Product.objects.active() # スッキリ!Step 2基本的なカスタムマネージャー
models.Managerを継承してカスタムマネージャーを作成します。
Python
# models.py
from django.db import models
from django.utils import timezone
class ProductManager(models.Manager):
"""Productモデル用のカスタムマネージャー"""
def active(self):
"""アクティブな商品のみ取得"""
return self.filter(is_active=True)
def expensive(self, threshold=10000):
"""指定価格以上の商品を取得"""
return self.filter(price__gte=threshold)
def recently_added(self, days=7):
"""直近n日以内に追加された商品を取得"""
since = timezone.now() - timezone.timedelta(days=days)
return self.filter(created_at__gte=since)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
objects = ProductManager() # カスタムマネージャーを設定
def __str__(self):
return self.namePython
# 使い方
active = Product.objects.active()
print(f"アクティブ: {active.count()}件")
expensive = Product.objects.expensive(5000)
print(f"5000円以上: {expensive.count()}件")
recent = Product.objects.recently_added(30)
print(f"直近30日: {recent.count()}件")実行結果
アクティブ: 15件
5000円以上: 8件
直近30日: 3件Step 3カスタムQuerySetの定義
カスタムマネージャーのメソッドはチェーンできません。チェーン可能にするにはカスタムQuerySetを定義します。
Python
# models.py
from django.db import models
class ProductQuerySet(models.QuerySet):
"""チェーン可能なカスタムQuerySet"""
def active(self):
return self.filter(is_active=True)
def expensive(self, threshold=10000):
return self.filter(price__gte=threshold)
def in_stock(self):
return self.filter(stock__gt=0)
def by_category(self, category_name):
return self.filter(category__name=category_name)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
stock = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
category = models.ForeignKey("Category", on_delete=models.CASCADE)
# QuerySetをマネージャーとして使う
objects = ProductQuerySet.as_manager()
def __str__(self):
return self.namePython
# チェーンが可能!
products = Product.objects.active().expensive(5000).in_stock()
print(f"アクティブで5000円以上かつ在庫あり: {products.count()}件")
# filter()やorder_by()とも組み合わせ可能
products = Product.objects.active().by_category(
"電子機器"
).order_by("-price")[:5]as_manager()の仕組み
QuerySet.as_manager()は、カスタムQuerySetのメソッドをマネージャーから直接呼べるようにします。これによりProduct.objects.active()のようにマネージャーからもチェーンからも同じメソッドが使えます。Step 4QuerySetのチェーン
カスタムマネージャーとカスタムQuerySetを両方使いたい場合の書き方です。
Python
# models.py
class ProductQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def in_stock(self):
return self.filter(stock__gt=0)
class ProductManager(models.Manager):
def get_queryset(self):
return ProductQuerySet(self.model, using=self._db)
def active(self):
return self.get_queryset().active()
def in_stock(self):
return self.get_queryset().in_stock()
def sales_report(self):
"""マネージャー専用の集計メソッド"""
from django.db.models import Sum, Avg
return self.get_queryset().active().aggregate(
total_price=Sum("price"),
avg_price=Avg("price"),
)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
stock = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
objects = ProductManager()
def __str__(self):
return self.namePython
# マネージャーからもQuerySetからもメソッドが使える
products = Product.objects.active().in_stock()
# マネージャー専用メソッド
report = Product.objects.sales_report()
print(f"合計: {report['total_price']}円, 平均: {report['avg_price']}円")Step 5実践的な活用例
Python
# models.py - 実務でよく使うパターン
class ArticleQuerySet(models.QuerySet):
def published(self):
"""公開済みの記事"""
return self.filter(
status="published",
published_at__lte=timezone.now()
)
def draft(self):
"""下書きの記事"""
return self.filter(status="draft")
def by_author(self, user):
"""特定ユーザーの記事"""
return self.filter(author=user)
def popular(self, min_views=100):
"""人気記事"""
return self.filter(view_count__gte=min_views)
def search(self, keyword):
"""タイトルまたは本文でキーワード検索"""
from django.db.models import Q
return self.filter(
Q(title__icontains=keyword) |
Q(content__icontains=keyword)
)
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
status = models.CharField(max_length=20, default="draft")
author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
view_count = models.IntegerField(default=0)
published_at = models.DateTimeField(null=True, blank=True)
objects = ArticleQuerySet.as_manager()
# views.pyでの使用例
def article_list(request):
articles = Article.objects.published().popular().order_by("-published_at")
keyword = request.GET.get("q")
if keyword:
articles = articles.search(keyword)
return render(request, "article/list.html", {"articles": articles})まとめ
- カスタムマネージャーでよく使うクエリ条件をメソッドとして定義できる
- カスタムQuerySetを使えばメソッドチェーンが可能になる
QuerySet.as_manager()で簡単にマネージャーとして登録できる- ビューのコードがシンプルになり、クエリ条件の再利用性が向上する
- ビジネスロジックに合った命名で可読性も上がる