class Manager
管理器是一個(gè)接口,數(shù)據(jù)庫(kù)查詢(xún)操作通過(guò)它提供給django的模型。django應(yīng)用的每個(gè)模型至少擁有一個(gè) 管理器。
管理器類(lèi)的工作方式在 執(zhí)行查詢(xún)文檔中闡述,而這篇文檔涉及了自定義管理器行為的模型選項(xiàng)。
通常,django為每個(gè)模型類(lèi)添加一個(gè)名為objects的管理器。然而,如果你想將objects用于字段名稱(chēng),或者你想使用其它名稱(chēng)而不是objects訪(fǎng)問(wèn)管理器,你可以在每個(gè)模型類(lèi)中重命名它。在模型中定義一個(gè)值為models.Manager()的屬性,來(lái)重命名管理器。例如:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
使用例子中的模型, Person.objects會(huì)拋出AttributeError異常,而Person.people.all()會(huì)返回一個(gè)包含所有Person對(duì)象的列表。
在一個(gè)特定的模型中,你可以通過(guò)繼承管理器類(lèi)來(lái)構(gòu)建一個(gè)自定義的管理器,以及實(shí)例化你的自定義管理器。
你有兩個(gè)原因可能會(huì)自己定義管理器:向器類(lèi)中添加額外的方法,或者修改管理器最初返回的查詢(xún)集。
為你的模型添加表級(jí)(table-level)功能時(shí),采用添加額外的管理器方法是更好的處理方式。如果要添加行級(jí)功能--就是說(shuō)該功能只對(duì)某個(gè)模型的實(shí)例對(duì)象起作用。在這種情況下,使用 模型方法 比使用自定義的管理器方法要更好。)
自定義的管理器 方法可以返回你想要的任何數(shù)據(jù),而不只是查詢(xún)集。
例如,下面這個(gè)自定義的 管理器提供了一個(gè) with_counts() 方法,它返回所有 OpinionPoll 對(duì)象的列表,而且列表中的每個(gè)對(duì)象都多了一個(gè)名為 num_responses的屬性,這個(gè)屬性保存一個(gè)聚合查詢(xún)(COUNT*)的結(jié)果:
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
cursor = connection.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)
person_name = models.CharField(max_length=50)
response = models.TextField()
在這個(gè)例子中,你已經(jīng)可以使用 OpinionPoll.objects.with_counts() 得到所有含有 num_responses屬性的 OpinionPoll對(duì)象。
這個(gè)例子要注意的一點(diǎn)是: 管理器方法可以訪(fǎng)問(wèn) self.model來(lái)得到它所用到的模型類(lèi)。
管理器自帶的 查詢(xún)集返回系統(tǒng)中所有的對(duì)象。例如,使用下面這個(gè)模型:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
... Book.objects.all() 語(yǔ)句將返回?cái)?shù)據(jù)庫(kù)中所有的 Book 對(duì)象。
你可以通過(guò)重寫(xiě) Manager.get_queryset() 的方法來(lái)覆蓋 管理器自帶的 查詢(xún)集。get_queryset() 會(huì)根據(jù)你所需要的屬性返回 查詢(xún)集。
例如,下面的模型有兩個(gè) 管理器,一個(gè)返回所有的對(duì)象,另一個(gè)則只返回作者是 Roald Dahl 的對(duì)象:
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super(DahlBookManager, self).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.
在這個(gè)簡(jiǎn)單的例子中,Book.objects.all()將返回?cái)?shù)據(jù)庫(kù)中所有的圖書(shū)。而 Book.dahl_objects.all() 只返回作者是 Roald Dahl 的圖書(shū)。
由于 get_queryset() 返回的是一個(gè) 查詢(xún)集 對(duì)象,所以你仍可以對(duì)它使用 filter(), exclude()和其他 查詢(xún)集的方法。所以下面這些例子都是可用的:
Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()
這個(gè)例子還展示了另外一個(gè)很有意思的技巧:在同一個(gè)模型中使用多個(gè)管理器。你可以隨你所意在一個(gè)模型里面添加多個(gè) Manager() 實(shí)例。下面就用很簡(jiǎn)單的方法,給模型添加通用過(guò)濾器:
例如:
class AuthorManager(models.Manager):
def get_queryset(self):
return super(AuthorManager, self).get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super(EditorManager, self).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()
在這個(gè)例子中,你使用 Person.authors.all(), Person.editors.all(),以及 Person.people.all(), 都會(huì)得到和名稱(chēng)相符的結(jié)果。
如果你使用了自定義 管理器對(duì)象,要注意 Django 中的第一個(gè) 管理器 (按照模型中出現(xiàn)的順序而定) 擁有特殊的地位。Django 會(huì)將模型中定義的管理器解釋為默認(rèn)的 管理器,并且 Django 中的一部分應(yīng)用(包括數(shù)據(jù)備份)會(huì)使用默認(rèn)的管理器,除了前面那個(gè)模型。因此,要決定默認(rèn)的管理器時(shí),要小心謹(jǐn)慎,仔細(xì)考量,這樣才能避免重寫(xiě) get_queryset() 導(dǎo)致無(wú)法正確地獲得數(shù)據(jù)。
默認(rèn)情況下,在訪(fǎng)問(wèn)相關(guān)對(duì)象時(shí)(例如choice.poll),Django 并不使用相關(guān)對(duì)象的默認(rèn)管理器,而是使用一個(gè)"樸素"管理器類(lèi)的實(shí)例來(lái)訪(fǎng)問(wèn)。這是因?yàn)?Django 要能從關(guān)聯(lián)對(duì)象中獲得數(shù)據(jù),但這些數(shù)據(jù)有可能被默認(rèn)管理器過(guò)濾掉,或是無(wú)法進(jìn)行訪(fǎng)問(wèn)。
如果普通的樸素管理器類(lèi)(django.db.models.Manager)并不適用于你的應(yīng)用,那么你可以通過(guò)在管理器類(lèi)中設(shè)置 use_for_related_fields ,強(qiáng)制 Django 在你的模型中使用默認(rèn)的管理器。這部分內(nèi)容在 下面有 詳細(xì)介紹。
雖然大多數(shù)標(biāo)準(zhǔn)查詢(xún)集的方法可以從管理器中直接訪(fǎng)問(wèn)到,但是這是一個(gè)例子,訪(fǎng)問(wèn)了定義在自定義 查詢(xún)集上的額外方法,如果你也在管理器上面實(shí)現(xiàn)了它們:
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()
這個(gè)例子展示了如何直接從管理器 Person.people調(diào)用authors() 和 editors()。
django 1.7 中新增
對(duì)于上面的例子,同一個(gè)方法需要在查詢(xún)集 和 管理器上創(chuàng)建兩份副本,作為替代,QuerySet.as_manager()可以創(chuàng)建一個(gè)管理器的實(shí)例,它擁有自定義查詢(xún)集的方法:
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
通過(guò)QuerySet.as_manager()創(chuàng)建的管理器 實(shí)例,實(shí)際上等價(jià)于上面例子中的PersonManager。
并不是每個(gè)查詢(xún)集的方法都在管理器層面上有意義。比如 QuerySet.delete(),我們有意防止它復(fù)制到管理器 中。
方法按照以下規(guī)則進(jìn)行復(fù)制:
例如:
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
classmethod from_queryset(queryset_class)
在進(jìn)一步的使用中,你可能想創(chuàng)建一個(gè)自定義管理器和一個(gè)自定義查詢(xún)集。你可以調(diào)用Manager.from_queryset(),它會(huì)返回管理器的一個(gè)子類(lèi),帶有自定義查詢(xún)集所有方法的副本:
class BaseManager(models.Manager):
def manager_only_method(self):
return
class CustomQuerySet(models.QuerySet):
def manager_and_queryset_method(self):
return
class MyModel(models.Model):
objects = BaseManager.from_queryset(CustomQueryset)()
你也可以在一個(gè)變量中儲(chǔ)存生成的類(lèi):
CustomManager = BaseManager.from_queryset(CustomQueryset)
class MyModel(models.Model):
objects = CustomManager()
類(lèi)繼承和模型管理器兩者之間配合得并不是很好。 管理器一般只對(duì)其定義所在的類(lèi)起作用,在子類(lèi)中對(duì)其繼承絕對(duì)不是一個(gè)好主意。 而且,因?yàn)榈谝粋€(gè) 管理器會(huì)被 Djange 聲明為默認(rèn)的管理器,所以對(duì)默認(rèn)的管理器 進(jìn)行控制是非常必要的。下面就是 Django 如何處理自定義管理器和模型繼承(model inheritance)的:
如果你想在一組模型上安裝一系列自定義管理器,上面提到的這些規(guī)則就已經(jīng)為你的實(shí)現(xiàn)提供了必要的靈活性。你可以繼承一個(gè)抽象基類(lèi),但仍要自定義默認(rèn)的管理器。例如,假設(shè)你的基類(lèi)是這樣的:
class AbstractBase(models.Model):
# ...
objects = CustomManager()
class Meta:
abstract = True
如果你在基類(lèi)中沒(méi)有定義管理器,直接使用上面的代碼,默認(rèn)管理器就是從基類(lèi)中繼承的 objects:
class ChildA(AbstractBase):
# ...
# This class has CustomManager as the default manager.
pass
如果你想從 AbstractBase繼承,卻又想提供另一個(gè)默認(rèn)管理器,那么你可以在子類(lèi)中定義默認(rèn)管理器:
class ChildB(AbstractBase):
# ...
# An explicit default manager.
default_manager = OtherManager()
在這個(gè)例子中, default_manager就是默認(rèn)的 管理器。從基類(lèi)中繼承的 objects 管理器仍是可用的。只不過(guò)它不再是默認(rèn)管理器罷了。
最后再舉個(gè)例子,假設(shè)你想在子類(lèi)中再添加一個(gè)額外的管理器,但是很想使用從 AbstractBase繼承的管理器做為默認(rèn)管理器。那么,你不在直接在子類(lèi)中添加新的管理器,否則就會(huì)覆蓋掉默認(rèn)管理器,而且你必須對(duì)派生自這個(gè)基類(lèi)的所有子類(lèi)都顯示指定管理器。 解決辦法就是在另一個(gè)基類(lèi)中添加新的管理器,然后繼承時(shí)將其放在默認(rèn)管理器所在的基類(lèi) 之后。例如:
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
注意在抽象模型上面定義一個(gè)自定義管理器的時(shí)候,不能調(diào)用任何使用這個(gè)抽象模型的方法。就像:
ClassA.objects.do_something()
是可以的,但是:
AbstractBase.objects.do_something()
會(huì)拋出一個(gè)異常。這是因?yàn)椋芾砥鞅辉O(shè)計(jì)用來(lái)封裝對(duì)象集合管理的邏輯。由于抽象的對(duì)象中并沒(méi)有一個(gè)集合,管理它們是毫無(wú)意義的。如果你寫(xiě)了應(yīng)用在抽象模型上的功能,你應(yīng)該把功能放到抽象模型的靜態(tài)方法,或者類(lèi)的方法中。
無(wú)論你向自定義管理器中添加了什么功能,都必須可以得到 管理器實(shí)例的一個(gè)淺表副本:例如,下面的代碼必須正常運(yùn)行:
>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)
Django 在一些查詢(xún)中會(huì)創(chuàng)建管理器的淺表副本;如果你的管理器不能被復(fù)制,查詢(xún)就會(huì)失敗。
這對(duì)于大多數(shù)自定義管理器不是什么大問(wèn)題。如果你只是添加一些簡(jiǎn)單的方法到你的管理器中,不太可能會(huì)把你的管理器實(shí)例變?yōu)椴豢蓮?fù)制的。但是,如果你覆蓋了__getattr__,或者其它管理器中控制對(duì)象狀態(tài)的私有方法,你應(yīng)該確保不會(huì)影響到管理器的復(fù)制。
這篇文檔已經(jīng)提到了Django創(chuàng)建管理器類(lèi)的一些位置:默認(rèn)管理器和用于訪(fǎng)問(wèn)關(guān)聯(lián)對(duì)象的“樸素” 管理器。在 Django 的實(shí)現(xiàn)中也有很多地方用到了臨時(shí)的樸素管理器。 正常情況下,django.db.models.Manager 類(lèi)的實(shí)例會(huì)自動(dòng)創(chuàng)建管理器。
在整個(gè)這一節(jié)中,我們將那種由 Django 為你創(chuàng)建的管理器稱(chēng)之為 "自動(dòng)管理器",既有因?yàn)闆](méi)有管理器而被 Django 自動(dòng)添加的默認(rèn)管理器, 也包括在訪(fǎng)問(wèn)關(guān)聯(lián)模型時(shí)使用的臨時(shí)管理器。
有時(shí),默認(rèn)管理器也并非是自動(dòng)管理器。 一個(gè)例子就是 Django 自帶的django.contrib.gis 應(yīng)用,所有 gis模型都必須使用一個(gè)特殊的管理器類(lèi)(GeoManager),因?yàn)樗鼈冃枰\(yùn)行特殊的查詢(xún)集(GeoQuerySet)與數(shù)據(jù)庫(kù)進(jìn)行交互。這表明無(wú)論自動(dòng)管理器是否被創(chuàng)建,那些要使用特殊的管理器的模型仍要使用這個(gè)特殊的管理器類(lèi)。
Django 為自定義管理器的開(kāi)發(fā)者提供了一種方式:無(wú)論開(kāi)發(fā)的管理器類(lèi)是不是默認(rèn)的管理器,它都應(yīng)該可以用做自動(dòng)管理器。 可以通過(guò)在管理器類(lèi)中設(shè)置 use_for_related_fields 屬性來(lái)做到這點(diǎn):
class MyManager(models.Manager):
use_for_related_fields = True
# ...
如果在模型中的默認(rèn) 管理器(在這些情況中僅考慮默認(rèn)管理器)中設(shè)置了這個(gè)屬性,那么無(wú)論它是否需要被自動(dòng)創(chuàng)建,Django 都會(huì)自動(dòng)使用它。否則 Django 就會(huì)使用 django.db.models.Manager.
歷史回顧
從它使用目的來(lái)看,這個(gè)屬性的名稱(chēng)(use_for_related_fields)好象有點(diǎn)古怪。原本,這個(gè)屬性?xún)H僅是用來(lái)控制訪(fǎng)問(wèn)關(guān)聯(lián)字段的管理器的類(lèi)型,這就是它名字的由來(lái)。 后來(lái)它的作用更加拓寬了,但是名稱(chēng)一直未變。 因?yàn)橐WC現(xiàn)在的代碼在 Django 以后的版本中仍可以正常工作(continue to work),這就是它名稱(chēng)不變的原因。
在上面的django.contrib.gis 已經(jīng)提到了, use_for_related_fields這個(gè)特性是在需要返回一個(gè)自定義查詢(xún)集子類(lèi)的管理器中使用的。要在你的管理器中提供這個(gè)功能,要注意以下幾點(diǎn)。
一個(gè)原因是自動(dòng)管理器是用來(lái)訪(fǎng)問(wèn)關(guān)聯(lián)模型 的對(duì)象。 在這種情況下,Django 必須要能看到相關(guān)模型的所有對(duì)象,所以才能根據(jù)關(guān)聯(lián)關(guān)系得到任何數(shù)據(jù) 。
如果你重寫(xiě)了 get_queryset() 方法并且過(guò)濾掉了一些行數(shù)據(jù),Django 將返回不正確的結(jié)果。不要這么做! 在 get_queryset()方法中過(guò)濾掉數(shù)據(jù),會(huì)使得它所在的管理器不適于用做自動(dòng)管理器。
use_for_related_fields屬性必須在管理器類(lèi)中設(shè)置,而不是在類(lèi)的 實(shí)例中設(shè)置。上面已經(jīng)有例子展示如何正確地設(shè)置,下面這個(gè)例子就是一個(gè)錯(cuò)誤的示范:
# BAD: Incorrect code
class MyManager(models.Manager):
# ...
pass
# Sets the attribute on an instance of MyManager. Django will
# ignore this setting.
mgr = MyManager()
mgr.use_for_related_fields = True
class MyModel(models.Model):
# ...
objects = mgr
# End of incorrect code.
你也不應(yīng)該在模型中使用這個(gè)屬性之后,在類(lèi)上改變它。這是因?yàn)樵谀P皖?lèi)被創(chuàng)建時(shí),這個(gè)屬性值馬上就會(huì)被處理,而且隨后不會(huì)再讀取這個(gè)屬性值。 這節(jié)的第一個(gè)例子就是在第一次定義的時(shí)候在管理器上設(shè)置use_for_related_fields屬性,所有的代碼就工作得很好。