1. 程式人生 > >Django中自定義模型管理器(Manager)及方法

Django中自定義模型管理器(Manager)及方法

1.自定義管理器(Manager)

在語句Book.objects.all()中,objects是一個特殊的屬性,通過它來查詢資料庫,它就是模型的一個Manager.
每個Django模型至少有一個manager,你可以建立自定義manager以定製資料庫的訪問.
這裡有兩個方法建立自定義manager:新增額外的manager;修改manager返回的初始Queryset.

新增額外的manager

增加額外的manager是為模組新增表級功能的首選辦法.(至於行級功能,也就是隻作用於模型例項物件的函式,則通過自定義模型方法實現).
例如,為Book模型新增一個title_count()的manger方法,它接收一個keyword

,並返回標題中包含keyword的書的數量.

models.py

from django.db import models


# 自定義模型管理器類
class BookManager(models.Manager):
    #自定義模型管理器中的方法
    def title_count(self, keyword):
        return self.filter(title_icountains=keyword).count()


class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    ...
    objects = BookManager()

    def __str__(self):
        return self.title

1.我們建立一個BookManager類,繼承自django.db.models.Manager.它只有一個方法title_count(),來進行統計.注意,這個方法使用了self.filter(),這個self指manager本身.
2.將BookManager()賦值給模型的objects屬性.它將取代模型的預設manager(objects).把它命名為objects是為了與預設的manager保持一致.
現在我們可以進行下面的操作:

>>> Books.objects.title_count('django')    #這是我們自定義的manager中的查詢方法
2
>>> Books.objects.filter(title__icontains='django').count()    # 預設的查詢方法依然可用
2

這樣我們可以將經常使用的查詢進行封裝,就不必重複寫程式碼了.

修改初始Manager Queryset

manager的基礎Queryset返回系統中的所有物件.例如,Book.objects.all()返回book資料庫中的所有書籍.你而已通過覆蓋Manager.get_queryset()方法來重寫manager的基礎Queryset.get_queryset()應該按照你的需求返回一個Queryset.
例如,下面的模型有兩個manger--一個返回所有物件,另一個僅返回作者是Roald Dahl的書

from django.db import models

#首先,定義一個Manager的子類
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')


# 然後,將它顯式地插入到Book模型中
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)
    ...
    objects = models.Manager()    # 預設Manager
    dahl_objects = DahlBookManager()    # 自定義的特殊Manager

在這個示例模型中,Book.objects.all()將返回資料庫中的所有書籍,而Book.dahl_objects.all()只返回作者是Roald Dahl的書籍.注意我們明確的將objects設定為預設Manger的一個例項,因為如果我們不這樣做,那麼dahl_objects將成為唯一一個可用的manager.
由於get_queryset()返回一個Queryset物件,所以你可以使用filter(),exclude()和其他所有的Queryset方法.

如果你使用自定義的Manager物件,請注意,Django遇到的第一個Manager(以它在模型中被定義的位置為準)會有一個特殊狀態。 Django將會把第一個Manager 定義為預設Manager ,Django的許多部分(但是不包括admin應用)將會明確地為模型使用這個manager。 結論是,你應該小心地選擇你的預設manager。因為覆蓋get_queryset()了,你可能接受到一個無用的返回對像,你必須避免這種情況.

2.自定義模型方法

為了給你的對像新增一個行級功能,那就定義一個自定義方法.鑑於manager經常被用來用一些整表操作(table-wide).模型方法應該只對特殊模型例項起作用.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        # Returns the person's baby_boomer status
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return 'Pre-boomer'
        elif self.birth_date < datetime.date(1965, 1, 1):
            return 'Baby boomer'
        else:
            return 'Post-boomer'

    def _get_full_name(self):
        # Return the person's full name
        return f'{self.first_name} {self.last_name}'
    full_name = property(_get_full_name)    # 將類方法包裝為屬性

這些方法的使用:

>>> p = Person.objects.get(first_name='Barack', last_name='Obama')
>>> p.birth_date
datetime.date(1961, 8, 4)
>>> p.baby_boomer_status()
'Baby boomer'
>>> p.full_name  # 注意這不是一個方法 -- 它被視為一個屬性
'Barack Obama'

3.重寫預定義的模型方法

還有一組模型方法了封裝了一些你可能想要自定義的資料庫行為.特別是你可能想要修改save()delete()的工作方式.你可以自由的重寫這些方法(以及其他的模型方法)來改變行為.重寫內建方法的經典用例就是你想要在儲存一個物件是做些其他的什麼.例如:

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super(Blog, self).save(*args, **kwargs)    #Call the "real" save() method.
        do_something_else()

你也可以阻止儲存行為:

from django.db import models


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        if self.name == 'Yoko Ono's Blog':
            return    # Yoko shall never have her own blog!
        else:
            super(Blog, self).save(*args, **kwargs)    #Call the "real" save() method

記住,繼承超類的方法非常重要,即super(Blog, self).save(*args, **kwargs),它確保該物件仍被儲存到資料庫中.如果你忘記呼叫超類方法,那麼預設的行為將不會發生,也不會發生資料庫操作.
同樣重要的是,您要傳遞可以傳遞給模型方法的引數——這就是*args, **kwargs所做的事情。Django將不時擴充套件內建模型方法的功能,並新增新的引數。如果您在方法定義中使用了*args, **kwargs,您將保證您的程式碼在新增時將自動支援這些引數。

Model.clean()

應用這個方法來提供自定義的模型驗證,以及修改模型的屬性.例如,你可以使用它來給一個欄位自動提供值,或者用於多個欄位需要一起驗證的情形:

import detetime
from django.core.exceptions import ValidationError
from django.db import models

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date
        if self.status == 'draft' and self.pub_date is not done:
            raise ValidationEroor('Draft entries may not have a publication date')
        #Set the pub_date for published items if it hasn't been set already
        if self.status == 'published' and self.pub_date is None:
            self.pub_date = datetime.date.today()

注意,呼叫模型的save()方法時,不會自動呼叫clean()方法,需要views手動呼叫.
上面的示例中,clean()引發的ValidationError異常通過一個字串例項化,所以它將被儲存在一個特殊的錯誤字典中,鍵為NON_FIELD_ERRORS.這個鍵用於整個模型出現的錯誤而不是一個特定欄位穿線的錯誤:

from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
try:
    article.full_clean()
except ValidationError as e:
    non_field_errors = e.message_dict[NON_FIELD_ERRORS]

若要引發一個特定欄位的異常,可以使用一個字典例項化ValidationError,其中字典的鍵為欄位名.我們可以更新前面的例子,只引發pub_date欄位上的異常:

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None:
            raise ValidationError({'pub_date': 'Draft entries may not have a publication date.'})
        ...

&n