DjangoのモデルのManagerクラスのメソッドをメソッドチェインさせる
※追記:django-model-utils というパッケージの PassThroughManager が同じようなことしてくれるみたいです
Model Managers — django-model-utils 2.3a1 documentation
例えば下記のような、書籍名・本棚登録済みかのフラグ・出版日を持ったモデルクラスがあるとする
from django.db import models class Book(models.Model): name = models.CharField(max_length=255) registered_flg = models.BooleanField(default=False) published_date = models.DateTimeField()
ここで、「本棚登録済みの本のみを抽出」とか「出版済みの本のみを抽出」とかしたい場合、マネージャクラスを使う。
class BookManager(models.Manager): def registered(self): return self.filter(registered_flg=True) def published(self): return self.filter(published_date__lte=datetime.datetime.now()) class Book(models.Model): ...(中略) objects = BookManager()
さてこれで目的は果たせたわけだけど、ここでさらに「すでに出版されていて尚且つ本棚登録済みのもの」のみを抽出したい場合どうすればいいだろう。
Managerのメソッドの戻り値に対して別のManagerメソッドをチェイン出来ない
>>> Book.objects.published().registered()
これはAttributeErrorになる。publishedメソッドが返すのはQuerySet。当然registeredなんてメソッドは持っていない。もちろんQuerySetのfilterメソッドをチェインすることは出来るけど...
>>> Book.objects.published().filter(registered_flg=True)
だからと言ってBookManagerに
def registered_and_published(self): return self.filter(published_date__lte=datetime.datetime.now(), registered_flg=True)
QuerySetのカスタマイズ
QuerySetをカスタマイズすることで解決できる
class BookQuerySet(models.query.QuerySet): def registered(self): return self.filter(registered_flg=True) def published(self): return self.filter(published_date__lte=datetime.datetime.now()) class BookManager(models.Manager): def get_query_set(self): return BookQuerySet(self.model) def registered(self): return self.get_query_set().registered() def published(self): return self.get_query_set().published() class Book(models.Model): name = models.CharField(max_length=255) registered_flg = models.BooleanField(default=False) published_date = models.DateTimeField() objects = BookManager()
コレでほぼOKなんだけど、このままだとBookQuerySetにメソッドを追加するたびにBookManagerにもメソッド追加しないといけない。それは冗長なので、BookMangerに__getattr__を持たせるとコードがすっきりする。これでBookQuerySetにいくらメソッドを増やそうが、__getattr__が上手いことやってくれる。
bpythonとかで補完が効かなくなるけど、まあそこはしょうがないか。
最終的なコードはこんな感じ
import datetime from django.db import models class BookQuerySet(models.query.QuerySet): def registered(self): return self.filter(registered_flg=True) def published(self): return self.filter(published_date__lte=datetime.datetime.now()) class BookManager(models.Manager): def get_query_set(self): return BookQuerySet(self.model) def __getattr__(self, name): return getattr(self.get_query_set(), name) class Book(models.Model): name = models.CharField(max_length=255) registered_flg = models.BooleanField(default=False) published_date = models.DateTimeField() objects = BookManager()