マネージャ

class Manager

マネージャ (Manager) とは、Django のモデルに対するデータベースクエリの操作を提供するインターフェイスです。Django アプリケーション内の1つのモデルに対して、Manager は最低でも1つは存在します。

Manager クラスの詳細については、クエリを作成する に書かれています。ここでは、特に、Manager の動作をカスタマイズするモデルのオプションについて説明しています。

マネージャの名前

デフォルトでは、Django は objects という名前の Manager を各 Django モデルクラスに対して追加します。しかし、もし objects をフィールド名として使いたい場合や、あるいは、Manager に対して objects 以外の名前を使いたい場合には、各モデル単位で好きな名前を付けることができます。あるクラスの Manager の名前を変えるには、そのクラスの上で models.Manager() と書いて、クラス変数を定義します。たとえば、次のように書きます。

from django.db import models


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

このモデル例を使うと、Person.objectsAttributeError 例外を起こしますが、Person.people.all() と書けば、すべての Person オブジェクトのリストが得られます。

マネージャのカスタマイズ

Manager ベースクラスを拡張し、カスタマイズした Manager をモデル内でインスタンス化すれば、特定のモデル用にカスタマイズした Manager を使うことができます。

Manager をカスタマイズしたくなるシチュエーションとしては、たとえば次の2つのような場合が考えられます。1つ目は、Manager に新しいメソッドを追加したい場合、もう1つは、Manager が最初に返す QuerySet を修正したい場合です。

マネージャに新しいメソッドを追加する

Manager に新しいメソッドを追加するのがふさわしいのは、モデルに対する「テーブルレベル」での操作を追加したい場合です。(「低レベル」の機能、たとえば、あるモデルオブジェクトの1つのインスタンスに作用するような関数の場合には、Manager をカスタマイズするのではなく、モデルメソッド を使ってください。

たとえば、このカスタム Managerwith_counts() メソッドを追加します:

from django.db import models
from django.db.models.functions import Coalesce


class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(num_responses=Coalesce(models.Count("response"), 0))


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


class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    # ...

この例では、 OpinionPoll.objects.with_counts() を使うことで、 num_responses 属性が付加された OpinionPoll オブジェクトの QuerySet を得ることができます。

Manager のカスタマイズメソッドは、どんなオブジェクトを返しても構いませんが、QuerySet だけは返してはいけません。

注目すべきもう1つの点は、 Manager 内のメソッドではself.modelとすることで、 Manager に紐づいているモデルクラスを取得できることです。

マネージャが初めに返す QuerySet を修正する

Manager のベース QuerySet は、すべてのオブジェクトを返します。 たとえば、このモデルの例では...:

from django.db import models


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

Book.objects.all() は、データベース内の全ての本を返します。

Manager.get_queryset() メソッドをオーバーライドすることで、 Manager のベース QuerySet を上書きできます。 get_queryset() は、必要な属性を含む QuerySet を返す必要があります。

例えば、次のモデルには 2つManager があります。片方はすべてのオブジェクトを返し、もう片方はRoald Dahlの本のみを返します:

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author="Roald Dahl")


# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager()  # The default manager.
    dahl_objects = DahlBookManager()  # The Dahl-specific manager.

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

get_queryset()QuerySet オブジェクトを返すので、 filter()exclude() 、その他の QuerySet オブジェクトのメソッドを全て使えます。したがって、以下のような文も実行できます。

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

この例では、同じモデルで複数のマネージャを使用するという、追加の面白いテクニックも紹介しました。一つのモデルに対して、好きなだけ Manager() インスタンスを割り当てることができます。これは、何度も同じコードを書かずにモデルに共通の "フィルタ" を定義するための方法です。

例:

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role="A")


class EditorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role="E")


class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices={"A": _("Author"), "E": _("Editor")})
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

この例では、Person.authors.all()Person.editors.all()Person.people.all() を指定でき、期待通りの結果が得られます。

デフォルトマネージャ

Model._default_manager

カスタムの Manager オブジェクトを使う場合、 Django が (モデル内で定義された順番で) 最初に見つけた Manager には特別なステータスがあることに注意してください。Django はクラスで定義された最初の Manager を「デフォルト」の Manager と解釈し、Django の一部の機能 (dumpdata を含む) はそのモデル専用にその Manager を使用します。その結果、get_queryset() をオーバーライドした結果、利用したいオブジェクトを取得できなくなるという事態を避けるために、デフォルトマネージャの選択には注意した方が良いでしょう。

カスタムのデフォルトマネージャは Meta.default_manager_name で指定できます。

たとえば、ジェネリックビューを実装しているサードパーティのアプリで、未知のモデルを扱わなければならないコードを書いている場合、モデルが objects マネージャを持っていると仮定するのではなく、このマネージャ (または _base_manager) を使用してください。

ベースマネージャ

Model._base_manager

このタイプのマネージャのサブクラスでは、結果をフィルタリングしないでください。

このマネージャは、他のモデルからリレーション先オブジェクトにアクセスするために 使用されます。このような状況では、 Django は、参照されるオブジェクトを すべて 取得できるように、取得するモデルのすべてのオブジェクトを見ることができなければなりません。

したがって、 get_queryset() をオーバーライドして行をフィルタリングしてはいけません。もしそうすれば、 Django は不完全な結果を返します。

カスタマイズした QuerySet のメソッド をマネージャから呼び出す

標準の QuerySet のほとんどのメソッドは Manager から直接アクセスできますが、カスタムの QuerySet で定義したメソッドは Manager でも実装する必要があります:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role="A")

    def editors(self):
        return self.filter(role="E")


class PersonManager(models.Manager):
    def get_queryset(self):
        return PersonQuerySet(self.model, using=self._db)

    def authors(self):
        return self.get_queryset().authors()

    def editors(self):
        return self.get_queryset().editors()


class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices={"A": _("Author"), "E": _("Editor")})
    people = PersonManager()

この例では authors()editors() の両方をマネージャ Person.people から直接呼び出すことができます。

QuerySet のメソッドで、マネージャを生成する

QuerySetManager の両方でメソッドを複製する必要がある上記の方法の代わりに、 QuerySet.as_manager() を使用すると、カスタムした QuerySet のメソッドをコピーした Manager のインスタンスを作成できます:

class Person(models.Model):
    ...
    people = PersonQuerySet.as_manager()

QuerySet.as_manager() によって生成される Manager インスタンスは、前の例の PersonManager とほぼ同じになります。

すべての QuerySet メソッドが Manager レベルで意味を持つわけではありません。例えば、 QuerySet.delete() メソッドが Manager クラスにコピーされることは意図的に防いであります。

メソッドは以下の規則に従ってコピーされます:

  • パブリックメソッドはデフォルトでコピーされます。
  • プライベートメソッド (アンダースコアで始まるメソッド) は、デフォルトではコピーされません。
  • queryset_only 属性が False に設定されているメソッドは常にコピーされます。
  • queryset_only 属性が True に設定されているメソッドはコピーされません。

例:

class CustomQuerySet(models.QuerySet):
    # Available on both Manager and QuerySet.
    def public_method(self):
        return

    # Available only on QuerySet.
    def _private_method(self):
        return

    # Available only on QuerySet.
    def opted_out_public_method(self):
        return

    opted_out_public_method.queryset_only = True

    # Available on both Manager and QuerySet.
    def _opted_in_private_method(self):
        return

    _opted_in_private_method.queryset_only = False

from_queryset()

classmethod from_queryset(queryset_class)

高度な使い方をする場合には、カスタムの Manager とカスタムの QuerySet の両方が必要になるかもしれません。この場合、 Manager.from_queryset() を呼び出すことで、カスタム QuerySet メソッドをコピーしたベース Managerサブクラス を返すことができます:

class CustomManager(models.Manager):
    def manager_only_method(self):
        return


class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return


class MyModel(models.Model):
    objects = CustomManager.from_queryset(CustomQuerySet)()

生成されたクラスを変数に格納することもできます:

MyManager = CustomManager.from_queryset(CustomQuerySet)


class MyModel(models.Model):
    objects = MyManager()

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

Django がカスタムマネージャと モデルの継承 をどのように扱うかを以下に示します:

  1. Python の通常の名前解決順序を使って (子クラスの名前は他を全て上書きし、次に最初の親クラスの名前、 というように) 、基底クラスのマネージャは常に子クラスに継承されます。
  2. モデルやその親クラスでマネージャが宣言されていない場合、Django は自動的に objects マネージャを作成します。
  3. クラスのデフォルトマネージャは Meta.default_manager_name で選択されたマネージャか、モデル上で最初に宣言されたマネージャか、最初の親モデルのデフォルトマネージャです。

これらのルールは、抽象的な基底クラスを経由して、カスタムマネージャのコレクションをモデルのグループにインストールしたい場合でも、デフォルトのマネージャをカスタマイズするために必要な柔軟性を提供します。たとえば、次のような基底クラスがあるとします:

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

    class Meta:
        abstract = True

これを子クラスで直接使用した場合、子クラスでマネージャを宣言していなければ objects がデフォルトのマネージャになります:

class ChildA(AbstractBase):
    # ...
    # This class has CustomManager as the default manager.
    pass

もし AbstractBase を継承して別のデフォルトマネージャを指定したい場合は、子クラスにそのデフォルトマネージャを指定できます:

class ChildB(AbstractBase):
    # ...
    # An explicit default manager.
    default_manager = OtherManager()

上記では default_manager がデフォルトです。 objects マネージャは継承されているのでまだ使用可能ですが、デフォルトでは使用されません。

最後に、この例では AbstractBase のデフォルトを使用しつつ、子クラスに追加のマネージャを追加したいとします。 新しいマネージャを子クラスに直接追加することはできません。デフォルトをオーバーライドすることになり、抽象基底クラスのすべてのマネージャを明示的にインクルードする必要があるからです。解決策としては、追加のマネージャを別の基底クラスに置き、それをデフォルトクラスの 後の 継承階層に含めることです:

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

    class Meta:
        abstract = True


class ChildC(AbstractBase, ExtraManager):
    # ...
    # Default manager is CustomManager, but OtherManager is
    # also available via the "extra_manager" attribute.
    pass

抽象モデル上でカスタムマネージャを 定義 することはできますが、抽象モデルを使ってメソッドを 呼び出す ことはできないことに注意してください。 つまり:

ClassA.objects.do_something()

上記は有効なコードですが、:

AbstractBase.objects.do_something()

上記では例外が発生します。 これはマネージャがオブジェクトのコレクションを管理するためのロジックをカプセル化することを想定しているためです。抽象オブジェクトのコレクションを持つことはできないので、それらを管理することは意味がありません。抽象モデルに適用される機能がある場合、その機能は抽象モデルの staticmethod か classmethod に置くべきです。

実装する際の注意

カスタムマネージャにどのような機能を追加するにしても、マネージャインスタンスの浅いコピーを作成できることが必須です:

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

Django は特定のクエリ中にマネージャオブジェクトの浅いコピーを作成します。マネージャがコピーできない場合、これらのクエリは失敗します。

ほとんどのカスタムマネージャでは問題にならないでしょう。単純なメソッドを Manager に追加するだけであれば、 Manager のインスタンスをコピー不可能にすることはまずありません。しかし、オブジェクトの状態をコントロールする Manager オブジェクトの __getattr__ やその他のプライベートメソッドをオーバーライドする場合は、 Manager のコピー機能に影響を与えないようにする必要があります。

Back to Top