マネージャー
- class Manager
Manager
は、データベースクエリ操作をDjangoモデルに提供するためのインターフェイスです。 Djangoアプリケーションのすべてのモデルに少なくとも1つのManager
が存在します。
Manager
クラスの動作方法は、クエリの作成に記載されています。 このドキュメントでは、Manager
の動作をカスタマイズするモデルオプションについて具体的に触れています。
マネージャー名
デフォルトでは、DjangoはすべてのDjangoモデルクラスにobjects
という名前のManager
を追加します。 ただし、フィールド名として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つあります。それは、Manager
メソッドを追加するため、および/または最初のQuerySet
を変更するためです。Manager
が返します。
マネージャーメソッドの追加
モデルに「テーブルレベル」の機能を追加するには、Manager
メソッドを追加することをお勧めします。 (「行レベル」の機能、つまりモデルオブジェクトの単一インスタンスに作用する関数の場合は、カスタム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
にアクセスして、それらがアタッチされているモデルクラスを取得できることです。
マネージャーのイニシャルを変更する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
を返す必要があります。
たとえば、次のモデルには two Manager
があります。1つはすべてのオブジェクトを返し、もう1つはRoaldDahlの本だけを返します。
# 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()
はRoaldDahlによって書かれた本のみを返します。
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
オブジェクトを使用する場合、最初に遭遇するManager
Djangoが(モデルで定義されている順序で)特別なステータスを持つことに注意してください。 Djangoは、クラスで定義された最初のManager
を「デフォルト」Manager
として解釈し、Djangoのいくつかの部分(:djadmin: `dumpdata` を含む)はそれを使用します[そのモデル専用のX168X] 。 そのため、get_queryset()
をオーバーライドすると、操作するオブジェクトを取得できなくなる状況を回避するために、デフォルトマネージャーの選択に注意することをお勧めします。
Meta.default_manager_name を使用して、カスタムのデフォルトマネージャーを指定できます。
たとえば、ジェネリックビューを実装するサードパーティのアプリで、不明なモデルを処理する必要のあるコードを記述している場合は、モデルに[ X212X] マネージャー。
ベースマネージャー
- 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()
この例では、マネージャーPerson.people
からauthors()
とeditors()
の両方を直接呼び出すことができます。
QuerySetメソッドを使用したマネージャーの作成
QuerySet
とManager
の両方でメソッドを複製する必要がある上記のアプローチの代わりに、 QuerySet.as_manager()を使用してManager
カスタムQuerySet
のメソッドのコピー:
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
の両方が必要になる場合があります。 これを行うには、カスタムQuerySet
メソッドのコピーを使用してベースManager
のサブクラスを返すManager.from_queryset()
を呼び出します。
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
に配置する必要があります。
実装上の懸念
カスタムManager
に追加する機能が何であれ、Manager
インスタンスの浅いコピーを作成できる必要があります。 つまり、次のコードが機能する必要があります。
>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)
Djangoは、特定のクエリ中にマネージャーオブジェクトの浅いコピーを作成します。 Managerをコピーできない場合、これらのクエリは失敗します。
これは、ほとんどのカスタムマネージャーにとって問題にはなりません。 Manager
に単純なメソッドを追加するだけの場合、Manager
のインスタンスを誤ってコピー不能にする可能性はほとんどありません。 ただし、__getattr__
またはオブジェクトの状態を制御するManager
オブジェクトのその他のプライベートメソッドをオーバーライドする場合は、Manager
の機能に影響を与えないようにする必要があります。 ]コピーされます。