マネジャ

revision-up-to:17812 (1.4) unfinished
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 はクラス内で最初に見つけたマネジャを「デフォ ルトの」マネジャにし、このデフォルトマネジャを ( dumpdata を含 む) あちこちでモデルのマネジャとして使います。ですから、 get_query_set() のオーバライドによって、扱いたいオブジェクトを取り出せ なくなるような羽目に陥らないように、デフォルトマネジャの選択には細心の注意 を払ってください。

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

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

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

これらのルールはモデルのグループに対してカスタムマネジャのコレクションを インストールしたいときに十分なフレキシビリティを提供しています。 例えば、このようなベースクラスがあるとします

class AbstractBase(models.Model):
    ...
    objects = CustomManager()

    class Meta:
        abstract = True

サブクラスでこれを直接使うのであれば、 ベースクラスでマネジャを定義して いないので objects がデフォルトのマネジャ になります。

class ChildA(AbstractBase):
    ...
    # このクラスは CustomManager をデフォルトマネジャとして持っています

AbstractBase から継承するけれども別のマネジャをデフォルトで用意する のであれば、子クラスにデフォルトマネジャを提供することが出来ます:

class ChildB(AbstractBase):
    ...
    # 明示的なデフォルトマネジャ
    default_manager = OtherManager()

ここで、 default_manager がデフォルトです。 objects マネジャは 引き続き利用できます。というのは継承されているからです。デフォルトとして 使われないというだけです。

この例の最後として、子クラスにマネジャを追加したいけれども、 AbstractBase のデフォルトを引き続き利用したいとします。子クラスに 新しいマネジャを直接追加することができません。 というのはそうするとデフォルトを上書いてしまい、抽象基底クラスの全ての マネジャを明示的に含めなければならなくなるからです。 解決法は他の基底クラスにマネジャを追加して、デフォルトの後に継承階層に 含めることです

class ExtraManager(models.Model):
    extra_manager = OtherManager()

    class Meta:
        abstract = True

class ChildC(AbstractBase, ExtraManager):
    ...
    # デフォツオマネージャは CustomManagerですが、
    # OtherManagerも"extra_manager"属性として利用可能です。

実装する際に考慮すること

カスタム Manager にどんな機能を追加したにせよ、 Manager インスタンスのシャロウコピー (shallow copy)を可能にしなければなりませ; すなわち、以下のコードは正しく動く必要があります

>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)

Django はある種のクエリの最中にマネジャをシャロウコピーさせます; もしも Manager がコピーされないのであればそのようなクエリは失敗します。

これはほとんどのカスタムマネジャにとっては問題とはならないでしょう。 自分の Manager に簡単なメソッドを追加するだけであれば、 うっかり Manager インスタンスをコピーさせないようにしてしまう、という ことはあり得ないでしょう。しかし、 オブジェクトの状態を管理するために Manager オブジェクトの __getattr__ やその他のプライベートメソッド をオーバーライドするのであれば、 Manager がコピーされる能力に影響を 与えないようにするべきです。

自動マネジャタイプの制御

このドキュメントはDjangoがマネジャクラスを作る際にすでに何度も触れられて きました: 関連オブジェクトにアクセスする ための デフォルトマネジャ と “プレーン” マネジャ、です。 Djangoの実装においては、一時的なプレーン マネジャが必要な箇所がいくつかあります。これらの自動的に作成されるマネジャ は通常は django.db.models.Manager のインスタンスです。

この章では “自動マネジャ( automatic manager )” という用語を、 Django が モデルマネジャを自動的に作成してくれる、という意味で使います。 – マネジャ無しのモデルのデフォルトマネジャの場合もありますし、関連オブジェクト にアクセスする際に一時的に使われたりもします。

このデフォルトクラスが正しい選択では無い場合もあります。 Django自体では 提供されていない django.contrib.gis アプリケーションに1つの例があります。 データベースと円滑に相互作用するためには、特殊なクエリセット (GeoQuerySet)を使わなくてはならないので、 全ての gis モデルは特殊な マネジャクラス(GeoManager)を使わなくてはいけません。 このような特殊なマネジャを必要とするモデルは自動マネジャが作成されるときには 必ず同じマネジャクラスを使う必要があります。

Django にはカスタムマネジャ開発者に対して、モデルのデフォルトマネジャが作成 される際には必ず自分の作成したマネジャクラスが自動マネジャとして使われるよう に指定できるような方法が用意されています。これは、マネジャクラスに use_for_related_fields 属性を設定することで実現されます

class MyManager(models.Manager):
    use_for_related_fields = True

    ...

この属性がモデルの デフォルト マネジャに設定される(デフォルトマネジャだけ がこのような状況になり得ます)と、Django はクラスに対して自動的にマネジャを 作成する必要になると必ずこのクラスを使うようになります。 それ以外の場合は、 django.db.models.Manager が使われます。

歴史的ノート

使われる目的を考えると、この属性名(use_for_related_fields)は 少し変です。元々は、この属性は関連フィールドのアクセスだけに使われる ようなマネジャを制御していましたのでこういう名前になったのです。 コンセプトが明確になるにつれてますます広い用途で使われるようになり ましたが、名前は変更されていません。これは第一に既存のコードが 将来の Django のバージョンに置いても 継続し動作する ようにするためです。

自動マネジャインスタンスに使えるように正しくマネジャを書く

上記の django.contrib.gis の例ですでに触れたように、 use_for_related_fields は主にカスタム QuerySet サブクラス を返さなければならないマネージャのための機能です。 あなたのマネジャでこの機能を提供する場合、いくつか覚えておかなければ ならないことがありますので、このセクションで示します。

この種のマネジャのサブクラスでは結果をフィルタしてはいけない

自動マネジャが使われる一つの理由は、他のモデルから関連したオブジェクトに アクセスする場合です。この場合、Djangoはフェッチしようとしているモデル の全てのオブジェクトを見る必要があるので、 参照される 全て が取得 される必要があるのです。

もしも get_query_set() メソッドをオーバーライドして、列をフィルター すれば Django は正しくない結果を返します。これをやってはいけません。 get_query_set() の結果をフィルタするマネジャは自動マネジャとしては ふさわしくありません。