管理員

class Manager[原始碼]

Manager 是一個介面,透過此介面,資料庫查詢操作會提供給 Django 模型。在 Django 應用程式中,每個模型至少存在一個 Manager

Manager 類別的運作方式記錄在 執行查詢 中;本文檔專門討論自訂 Manager 行為的模型選項。

管理員名稱

預設情況下,Django 會為每個 Django 模型類別新增一個名為 objectsManager。但是,如果您想要使用 objects 作為欄位名稱,或者如果您想要使用 objects 以外的名稱作為 Manager,您可以在每個模型的基礎上重新命名它。若要重新命名給定類別的 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 的原因有兩個:新增額外的 Manager 方法,和/或修改 Manager 傳回的初始 QuerySet

新增額外的管理員方法

新增額外的 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

另一個要注意的事項是,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 的基本 QuerySet,方法是覆寫 Manager.get_queryset() 方法。get_queryset() 應該傳回具有您所需的屬性的 QuerySet

例如,下列模型有兩個 Manager — 一個傳回所有物件,另一個僅傳回羅德·達爾的書籍

# 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() 將只會傳回羅德·達爾撰寫的書籍。

由於 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 來指定自訂預設管理員。

如果您正在撰寫必須處理未知模型的程式碼,例如,在實作泛型檢視的協力廠商應用程式中,請使用此管理員 (或 _base_manager),而不是假設模型具有 objects 管理員。

基本管理員

Model._base_manager

不要在此類型的管理員子類別中篩選掉任何結果

此管理員用於存取從其他模型相關聯的物件。在這些情況下,Django 必須能夠查看它正在擷取的模型的所有物件,以便可以擷取任何被參考的內容

因此,您不應覆寫 get_queryset() 以篩選掉任何列。如果您這樣做,Django 將會傳回不完整的結果。

從管理員呼叫自訂 QuerySet 方法

雖然標準 QuerySet 的大多數方法可以直接從 Manager 存取,但只有當您也在 Manager 上實作它們時,自訂 QuerySet 上定義的額外方法才能這樣做。

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() 來建立具有自訂 QuerySet 方法副本的 Manager 實例。

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

QuerySet.as_manager() 建立的 Manager 實例實際上與先前範例中的 PersonManager 相同。

並非每個 QuerySet 方法都適用於 Manager 層級;例如,我們刻意防止將 QuerySet.delete() 方法複製到 Manager 類別。

方法會根據以下規則複製

  • 預設會複製公開方法。

  • 預設不會複製私有方法(以下底線開頭)。

  • 如果設定為 False 的方法具有 queryset_only 屬性,則會一律複製。

  • 如果設定為 True 的方法具有 queryset_only 屬性,則永遠不會複製。

例如:

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() 來達成此目的,此呼叫會傳回基本 Manager子類別,其中包含自訂 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()

會引發例外狀況。這是因為管理器的目的是封裝用於管理物件集合的邏輯。由於您不能有抽象物件的集合,因此管理它們沒有意義。如果您有適用於抽象模型的功能,您應該將該功能放在抽象模型上的 staticmethodclassmethod 中。

實作考量

無論您將哪些功能新增至您的自訂 Manager,都必須能夠對 Manager 實例進行淺層複製;也就是說,以下程式碼必須正常運作

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

Django 在特定查詢期間會對管理器物件進行淺層複製;如果您的管理器無法複製,這些查詢將會失敗。

對於大多數自訂管理器而言,這不會是問題。如果您只是將簡單的方法新增至您的 Manager,您不太可能在無意中讓您的 Manager 實例無法複製。但是,如果您要覆寫 __getattr__ 或您的 Manager 物件的某些其他控制物件狀態的私有方法,您應該確保您不會影響您的 Manager 被複製的能力。

回到頂端