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