マネージャ¶
マネージャ (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.objects
は AttributeError
例外を起こしますが、Person.people.all()
と書けば、すべての Person
オブジェクトのリストが得られます。
マネージャのカスタマイズ¶
Manager
ベースクラスを拡張し、カスタマイズした Manager
をモデル内でインスタンス化すれば、特定のモデル用にカスタマイズした Manager
を使うことができます。
Manager
をカスタマイズしたくなるシチュエーションとしては、たとえば次の2つのような場合が考えられます。1つ目は、Manager
に新しいメソッドを追加したい場合、もう1つは、Manager
が最初に返す QuerySet
を修正したい場合です。
マネージャに新しいメソッドを追加する¶
Manager
に新しいメソッドを追加するのがふさわしいのは、モデルに対する「テーブルレベル」での操作を追加したい場合です。(「低レベル」の機能、たとえば、あるモデルオブジェクトの1つのインスタンスに作用するような関数の場合には、Manager
をカスタマイズするのではなく、モデルメソッド を使ってください。
たとえば、このカスタム Manager
は with_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
のメソッドで、マネージャを生成する¶
QuerySet
と Manager
の両方でメソッドを複製する必要がある上記の方法の代わりに、 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 がカスタムマネージャと モデルの継承 をどのように扱うかを以下に示します:
- Python の通常の名前解決順序を使って (子クラスの名前は他を全て上書きし、次に最初の親クラスの名前、 というように) 、基底クラスのマネージャは常に子クラスに継承されます。
- モデルやその親クラスでマネージャが宣言されていない場合、Django は自動的に
objects
マネージャを作成します。 - クラスのデフォルトマネージャは
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
のコピー機能に影響を与えないようにする必要があります。