マネージャー—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.0.x/topics/db/managers
移動先:案内検索

マネージャー

class Manager

Managerは、データベースクエリ操作をDjangoモデルに提供するためのインターフェイスです。 Djangoアプリケーションのすべてのモデルに少なくとも1つのManagerが存在します。

Managerクラスの動作方法は、クエリの作成に記載されています。 このドキュメントでは、Managerの動作をカスタマイズするモデルオプションについて具体的に触れています。

マネージャー名

デフォルトでは、DjangoはすべてのDjangoモデルクラスにobjectsという名前のManagerを追加します。 ただし、フィールド名としてobjectsを使用する場合、またはManagerobjects以外の名前を使用する場合は、名前を変更できます。 -モデルベース。 特定のクラスの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つあります。それは、Managerメソッドを追加するため、および/または最初のQuerySetを変更するためです。Managerが返します。

マネージャーメソッドの追加

モデルに「テーブルレベル」の機能を追加するには、Managerメソッドを追加することをお勧めします。 (「行レベル」の機能、つまりモデルオブジェクトの単一インスタンスに作用する関数の場合は、カスタムManagerメソッドではなく、モデルメソッドを使用します。)

カスタムManagerメソッドは、必要なものをすべて返すことができます。 QuerySetを返す必要はありません。

たとえば、このカスタムManagerは、メソッドwith_counts()を提供します。このメソッドは、すべてのOpinionPollオブジェクトのリストを返し、それぞれに追加のnum_responses属性があります。集計クエリの:

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        with connection.cursor() as 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 p.id, p.question, p.poll_date
                ORDER BY p.poll_date 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(OpinionPoll, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

この例では、OpinionPoll.objects.with_counts()を使用して、num_responses属性を持つOpinionPollオブジェクトのリストを返します。

この例についてもう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は誤った結果を返します。 そうしないでください。 結果をget_queryset()でフィルタリングするマネージャーは、ベースマネージャーとしての使用には適していません。


マネージャーからカスタム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メソッドを使用したマネージャーの作成

QuerySetManagerの両方でメソッドを複製する必要がある上記のアプローチの代わりに、 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がカスタムマネージャーとモデル継承を処理する方法は次のとおりです。

  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に配置する必要があります。


実装上の懸念

カスタムManagerに追加する機能が何であれ、Managerインスタンスの浅いコピーを作成できる必要があります。 つまり、次のコードが機能する必要があります。

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

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

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