Django v1.0 documentation

マネジャ

revision-up-to:8961 (1.0)
class Manager

マネジャ (Manager) とは、データベースクエリ操作を Django モデルに提供し ているインタフェースです。 Django アプリケーション内のモデルは全て少なくと も一つマネジャを備えています。

マネジャクラス Manager の動作はデータベース API ドキュメントの クエリを生成する で説明していますが、この節ではマネジャの挙動をカス タマイズするためのモデルオプションについて具体的に触れます。

マネジャの名前

デフォルトでは、 Django は全ての Django モデルクラスに object という名 前で Manager オブジェクトを追加します。ただし、 objects をフィール ド名として使いたい場合や、マネジャに objects 以外の名前をつけたい場合に は、モデルごとに名前を変えてやる必要があります。クラス中でマネジャの名前を 変更するには、 models.Manager() 型のクラス属性を定義します。例えば:

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

このようにすると、 Person.people.all()Person オブジェクトのリス トを提供し、 Person.objects の参照は AttributeError 例外を送出します。

カスタムマネジャ

ベースクラスの Manager クラスを拡張して、モデル中でカスタムのマネジャを インスタンス化すれば、モデルでカスタムのマネジャを使えます。

マネジャをカスタマイズする理由は大きく分けて二つあります。一つはマネジャに 追加のメソッドを持たせたい場合、もう一つはマネジャの返す初期 QuerySet を変更したい場合です。

追加のマネジャメソッド定義

モデルに「テーブルレベル」の機能を持たせたい場合、マネジャへのメソッドは良 い方法です。 (「行レベル」の機能を持たせたい、例えばモデルオブジェクトの個々 のインスタンスに影響する関数を実装したい場合には、カスタムのマネジャメソッ ドではなく モデルのメソッド を使って下さい。)

カスタムのマネジャメソッドは何を返してもかまいません。 QuerySet を返さ なくてもよいのです。

例えば、以下のカスタムマネジャでは、 with_counts() というメソッドを提供 しています。このメソッドは全ての OpinionPoll オブジェクトからなるリスト を返しますが、その際に集約クエリの結果である num_responses という追加の 属性を追加します:

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("""
            SELECT p.id, p.question, p.poll_date, COUNT(*)
            FROM polls_opinionpoll p, polls_response r
            WHERE p.id = r.poll_id
            GROUP BY 1, 2, 3
            ORDER BY 3 DESC""")
        result_list = []
        for row in cursor.fetchall():
            p = self.model(id=row[0], question=row[1], poll_date=row[2])
            p.num_responses = row[3]
            result_list.append(p)
        return result_list

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    poll_date = models.DateField()
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(Poll)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

この例では、 OpinionPoll.objects.with_counts() を使うと、 num_responses 属性を備えた OpinionPoll オブジェクトのリストを返しま す。

この例でもう一つ注意して欲しいのは、マネジャメソッドが自分の属しているモデ ルクラスを取り出すために self.model にアクセスできるという点です。

初期 QuerySet の変更

マネジャのベースの QuerySet は、システム上の全てのオブジェクトを返しま す。例えば、以下のモデル:

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

では、 Book.objects.all() とすると、データベース上の全ての books を返し ます。

Manager.get_query_set() メソッドをオーバライドすれば、 Manager のベー スの QuerySet をオーバライドできます。 get_query_set() は必要なプロ パティを備えた QuerySet を返さねばなりません。

例えば、以下のモデルには 二つの マネジャがあります。片方は全てのオブジェ クトを返し、もう一方は Roald Dahl の書いた本だけを返します:

# まず Manager のサブクラスを定義します。
class DahlBookManager(models.Manager):
    def get_query_set(self):
        return super(DahlBookManager, self).get_query_set().filter(author='Roald Dahl')

# 次に Book モデルに明示的にフックします。
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # デフォルトマネジャ。
    dahl_objects = DahlBookManager() # Dahl 縛りのマネジャ。

このモデル例では、 Book.objects.all() はデータベース上の Book を全て返 しますが、 Book.dahl_objects.all() は Roald Dahl の書いた本だけを返しま す。

get_query_set()QuerySet オブジェクトを返すので、もちろん filter()exclude() をはじめ全ての QuerySet メソッドを使えま す。従って、以下のような文を実行できます:

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

この例はもう一つ興味深いテクニックの存在を示しています。それは同じモデルで 複数のマネジャを使えるということです。モデルには、好きなだけマネジャのイン スタンスをアタッチできます。この手法を使うと、よく利用するフィルタをモデル に簡単に実装できます。

例えば:

class MaleManager(models.Manager):
    def get_query_set(self):
        return super(MaleManager, self).get_query_set().filter(sex='M')

class FemaleManager(models.Manager):
    def get_query_set(self):
        return super(FemaleManager, self).get_query_set().filter(sex='F')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female')))
    people = models.Manager()
    men = MaleManager()
    women = FemaleManager()

この例では、 Person.men.all(), Person.women.all(), Person.people.all() といったクエリを発行できるようになっており、期待通 りの結果を得られます。

カスタムのマネジャオブジェクトを使う場合、 Django がモデル内に最初に見つけ たマネジャ (モデルに定義した順番で最初のマネジャ) は特別扱いされるというこ とに注意してください。 Django はクラス内で最初に見つけたマネジャを「デフォ ルトの」マネジャにし、このデフォルトマネジャを (admin アプリケーション以外 でも) あちこちでモデルのマネジャとして使います。ですから、 get_query_set() のオーバライドによって、扱いたいオブジェクトを取り出せ なくなるような羽目に陥らないように、デフォルトマネジャの選択には細心の注意 を払ってください。

マネジャを使ってリレーション先のオブジェクトにアクセスする

デフォルトでは、 Django は「素の」マネジャを使って (choice.poll のよう な)リレーション先のオブジェクトにアクセスします。リレーションを扱うときに 別のマネジャを使いたいのなら、 use_for_related_fields プロパティの設定 されたカスタムマネジャを定義してください:

class MyManager(models.Manager)::
    use_for_related_fields = True
    ...


    ...

カスタムマネジャとモデルの継承

クラスの継承とモデルのマネジャの関係は、お互い完全にしっくりきているわけで はありません。マネジャはたいていあるクラス固有のものなので、サブクラスでマ ネジャを継承するのは必ずしもよいアイデアとはいえないからです。また、最初に 宣言されたマネジャが*デフォルトマネジャ* になることから、デフォルトマネジャ の制御が重要になってきます。そこで、ここでは、 Django がカスタムマネジャと モデル継承 をどのように扱うか説明します:

  1. 抽象ベースクラスでないクラスで定義されたマネジャは、他のクラスに継承 されません 。非抽象ベースクラスのマネジャを再利用したければ、 子クラスで明示的に宣言してください。この種のマネジャは、たいていマネ ジャを定義しているクラス固有のもので、(デフォルトマネジャとして) 継 承すると思わぬ結果を招くことがあるからです。そのため、子クラスに自動 的に継承されないのです。
  2. 抽象ベースクラスのマネジャは、通常の Python の名前解決規則 (name resolution order, 子クラスの名前が他の名前をオーバライドする、 親クラスリストの最初の親クラスの名前から順に参照する、など) に基づい て、常に子クラスに継承されます。抽象ベースクラスは、子クラスに共通し て持たせたい情報やふるまいを保持するためのクラスだからです。共通のマ ネジャの定義は、共通の情報として親クラスに置くのが適切です。
  3. デフォルトマネジャは、そのクラスで最初に宣言されたマネジャクラスか、 最初に見付かった抽象ベースクラスのデフォルトマネジャです。明示的なデ フォルトマネジャの設定がなければ、 Django の通常のデフォルトマネジャ を使います。