管理員¶
Manager
是一個介面,透過此介面,資料庫查詢操作會提供給 Django 模型。在 Django 應用程式中,每個模型至少存在一個 Manager
。
Manager
類別的運作方式記錄在 執行查詢 中;本文檔專門討論自訂 Manager
行為的模型選項。
管理員名稱¶
預設情況下,Django 會為每個 Django 模型類別新增一個名為 objects
的 Manager
。但是,如果您想要使用 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
方法的管理器¶
為了取代上述需要在 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
類別。
方法會根據以下規則複製
預設會複製公開方法。
預設不會複製私有方法(以下底線開頭)。
如果設定為
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 如何處理自訂管理器和模型繼承
基底類別中的管理器一律會由子類別繼承,使用 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
實例無法複製。但是,如果您要覆寫 __getattr__
或您的 Manager
物件的某些其他控制物件狀態的私有方法,您應該確保您不會影響您的 Manager
被複製的能力。