1. 程式人生 > >Django中不返回QuerySets的API -- Django從入門到精通系列教程

Django中不返回QuerySets的API -- Django從入門到精通系列教程

該系列教程繫個人原創,並完整發布在個人官網劉江的部落格和教程

所有轉載本文者,需在頂部顯著位置註明原作者及www.liujiangblog.com官網地址。

以下的方法不會返回QuerySets,但是作用非常強大,尤其是粗體顯示的方法,需要背下來。

方法名 解釋
get() 獲取單個物件
create() 建立物件,無需save()
get_or_create() 查詢物件,如果沒有找到就新建物件
update_or_create() 更新物件,如果沒有找到就建立物件
bulk_create() 批量建立物件
count() 統計物件的個數
in_bulk()
根據主鍵值的列表,批量返回物件
iterator() 獲取包含物件的迭代器
latest() 獲取最近的物件
earliest() 獲取最早的物件
first() 獲取第一個物件
last() 獲取最後一個物件
aggregate() 聚合操作
exists() 判斷queryset中是否有物件
update() 批量更新物件
delete() 批量刪除物件
as_manager() 獲取管理器

1. get()

get(**kwargs)

返回按照查詢引數匹配到的單個物件,引數的格式應該符合Field lookups的要求。

如果匹配到的物件個數不只一個的話,觸發MultipleObjectsReturned異常

如果根據給出的引數匹配不到物件的話,觸發DoesNotExist異常。例如:

Entry.objects.get(id='foo') # raises Entry.DoesNotExist

DoesNotExist異常從django.core.exceptions.ObjectDoesNotExist繼承,可以定位多個DoesNotExist異常。 例如:

from django.core.exceptions import ObjectDoesNotExist
try:
    e = Entry.objects.get(id=3)
    b = Blog.objects.get(id=1)
except ObjectDoesNotExist:
    print("Either the entry or blog doesn't exist.")

如果希望查詢器只返回一行,則可以使用get()而不使用任何引數來返回該行的物件:

entry = Entry.objects.filter(...).exclude(...).get()

2. create()

create(**kwargs)

在一步操作中同時建立並且儲存物件的便捷方法.

p = Person.objects.create(first_name="Bruce", last_name="Springsteen")

等於:

p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)

引數force_insert表示強制建立物件。如果model中有一個你手動設定的主鍵,並且這個值已經存在於資料庫中, 呼叫create()將會失敗並且觸發IntegrityError因為主鍵必須是唯一的。如果你手動設定了主鍵,做好異常處理的準備。

3. get_or_create()

get_or_create(defaults=None, **kwargs)

通過kwargs來查詢物件的便捷方法(如果模型中的所有欄位都有預設值,可以為空),如果該物件不存在則建立一個新物件

該方法返回一個由(object, created)組成的元組,元組中的object 是一個查詢到的或者是被建立的物件, created是一個表示是否建立了新的物件的布林值。

對於下面的程式碼:

try:
    obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
    obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
    obj.save()

如果模型的欄位數量較大的話,這種模式就變的非常不易用了。 上面的示例可以用get_or_create()重寫 :

obj, created = Person.objects.get_or_create(
    first_name='John',
    last_name='Lennon',
    defaults={'birthday': date(1940, 10, 9)},
)

任何傳遞給get_or_create()的關鍵字引數,除了一個可選的defaults,都將傳遞給get()呼叫。 如果查詢到一個物件,返回一個包含匹配到的物件以及False 組成的元組。 如果查詢到的物件超過一個以上,將引發MultipleObjectsReturned。如果查詢不到物件,get_or_create()將會例項化並儲存一個新的物件,返回一個由新的物件以及True組成的元組。新的物件將會按照以下的邏輯建立:

params = {k: v for k, v in kwargs.items() if '__' not in k}
params.update({k: v() if callable(v) else v for k, v in defaults.items()})
obj = self.model(**params)
obj.save()

它表示從非'defaults' 且不包含雙下劃線的關鍵字引數開始。然後將defaults的內容新增進來,覆蓋必要的鍵,並使用結果作為關鍵字引數傳遞給模型類。

如果有一個名為defaults__exact的欄位,並且想在get_or_create()時用它作為精確查詢,只需要使用defaults,像這樣:

Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})

當你使用手動指定的主鍵時,get_or_create()方法與create()方法有相似的錯誤行為 。 如果需要建立一個物件而該物件的主鍵早已存在於資料庫中,IntegrityError異常將會被觸發。

這個方法假設進行的是原子操作,並且正確地配置了資料庫和正確的底層資料庫行為。如果資料庫級別沒有對get_or_create中用到的kwargs強制要求唯一性(unique和unique_together),方法容易導致競態條件,可能會有相同引數的多行同時插入。(簡單理解,kwargs必須指定的是主鍵或者unique屬性的欄位才安全。)

最後建議只在Django檢視的POST請求中使用get_or_create(),因為這是一個具有修改性質的動作,不應該使用在GET請求中,那樣不安全。

可以通過ManyToManyField屬性和反向關聯使用get_or_create()。在這種情況下,應該限制查詢在關聯的上下文內部。 否則,可能導致完整性問題。

例如下面的模型:

class Chapter(models.Model):
    title = models.CharField(max_length=255, unique=True)

class Book(models.Model):
    title = models.CharField(max_length=256)
    chapters = models.ManyToManyField(Chapter)

可以通過Book的chapters欄位使用get_or_create(),但是它只會獲取該Book內部的上下文:

>>> book = Book.objects.create(title="Ulysses")
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, True)
>>> book.chapters.get_or_create(title="Telemachus")
(<Chapter: Telemachus>, False)
>>> Chapter.objects.create(title="Chapter 1")
<Chapter: Chapter 1>
>>> book.chapters.get_or_create(title="Chapter 1")
# Raises IntegrityError

發生這個錯誤是因為嘗試通過Book “Ulysses”獲取或者建立“Chapter 1”,但是它不能,因為它與這個book不關聯,但因為title 欄位是唯一的它仍然不能建立。

在Django1.11在defaults中增加了對可呼叫值的支援。

4. update_or_create()

update_or_create(defaults=None, **kwargs)

類似前面的get_or_create()

通過給出的kwargs來更新物件的便捷方法, 如果沒找到物件,則建立一個新的物件。defaults是一個由 (field, value)對組成的字典,用於更新物件。defaults中的值可以是可呼叫物件(也就是說函式等)。

該方法返回一個由(object, created)組成的元組,元組中的object是一個建立的或者是被更新的物件, created是一個標示是否建立了新的物件的布林值。

update_or_create方法嘗試通過給出的kwargs 去從資料庫中獲取匹配的物件。 如果找到匹配的物件,它將會依據defaults 字典給出的值更新欄位。

像下面的程式碼:

defaults = {'first_name': 'Bob'}
try:
    obj = Person.objects.get(first_name='John', last_name='Lennon')
    for key, value in defaults.items():
        setattr(obj, key, value)
    obj.save()
except Person.DoesNotExist:
    new_values = {'first_name': 'John', 'last_name': 'Lennon'}
    new_values.update(defaults)
    obj = Person(**new_values)
    obj.save()

如果模型的欄位數量較大的話,這種模式就變的非常不易用了。 上面的示例可以用update_or_create() 重寫:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

kwargs中的名稱如何解析的詳細描述可以參見get_or_create()

get_or_create()一樣,這個方法也容易導致競態條件,如果資料庫層級沒有前置唯一性會讓多行同時插入。

在Django1.11在defaults中增加了對可呼叫值的支援。

5. bulk_create()

bulk_create(objs, batch_size=None)

以高效的方式(通常只有1個查詢,無論有多少物件)將提供的物件列表插入到資料庫中:

>>> Entry.objects.bulk_create([
...     Entry(headline='This is a test'),
...     Entry(headline='This is only a test'),
... ])

注意事項:

  • 不會呼叫模型的save()方法,並且不會發送pre_savepost_save訊號。
  • 不適用於多表繼承場景中的子模型。
  • 如果模型的主鍵是AutoField,則不會像save()那樣檢索並設定主鍵屬性,除非資料庫後端支援。
  • 不適用於多對多關係。

batch_size引數控制在單個查詢中建立的物件數。

6. count()

count()

返回在資料庫中對應的QuerySet物件的個數。count()永遠不會引發異常。

例如:

# 返回總個數.
Entry.objects.count()
# 返回包含有'Lennon'的物件的總數
Entry.objects.filter(headline__contains='Lennon').count()

7. in_bulk()

in_bulk(id_list=None)

獲取主鍵值的列表,並返回將每個主鍵值對映到具有給定ID的物件的例項的字典。 如果未提供列表,則會返回查詢集中的所有物件。

例如:

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}
>>> Blog.objects.in_bulk()
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}

如果向in_bulk()傳遞一個空列表,會得到一個空的字典。

在舊版本中,id_list是必需的引數,現在是一個可選引數。

8. iterator()

iterator()

提交資料庫操作,獲取QuerySet,並返回一個迭代器。

QuerySet通常會在內部快取其結果,以便在重複計算時不會導致額外的查詢。而iterator()將直接讀取結果,不在QuerySet級別執行任何快取。對於返回大量只需要訪問一次的物件的QuerySet,這可以帶來更好的效能,顯著減少記憶體使用。

請注意,在已經提交了的iterator()上使用QuerySet會強制它再次提交資料庫操作,進行重複查詢。此外,使用iterator()會導致先前的prefetch_related()呼叫被忽略,因為這兩個一起優化沒有意義。

9. latest()

latest(field_name=None)

使用日期欄位field_name,按日期返回最新物件。

下例根據Entry的'pub_date'欄位返回最新發布的entry:

Entry.objects.latest('pub_date')

如果模型的Meta指定了get_latest_by,則可以將latest()引數留給earliest()或者field_name。 預設情況下,Django將使用get_latest_by中指定的欄位。

earliest()和latest()可能會返回空日期的例項,可能需要過濾掉空值:

Entry.objects.filter(pub_date__isnull=False).latest('pub_date')

10. earliest()

earliest(field_name=None)

類同latest()。

11. first()

first()

返回結果集的第一個物件, 當沒有找到時返回None。如果QuerySet沒有設定排序,則將會自動按主鍵進行排序。例如:

p = Article.objects.order_by('title', 'pub_date').first()

first()是一個簡便方法,下面的例子和上面的程式碼效果是一樣:

try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

12. last()

last()

工作方式類似first(),只是返回的是查詢集中最後一個物件。

13. aggregate()

aggregate(*args, **kwargs)

返回彙總值的字典(平均值,總和等),通過QuerySet進行計算。每個引數指定返回的字典中將要包含的值。

使用關鍵字引數指定的聚合將使用關鍵字引數的名稱作為Annotation 的名稱。 匿名引數的名稱將基於聚合函式的名稱和模型欄位生成。 複雜的聚合不可以使用匿名引數,必須指定一個關鍵字引數作為別名。

例如,想知道Blog Entry 的數目:

>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}

通過使用關鍵字引數來指定聚合函式,可以控制返回的聚合的值的名稱:

>>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))
{'number_of_entries': 16}

14. exists()

exists()

如果QuerySet包含任何結果,則返回True,否則返回False。

查詢具有唯一性欄位(例如primary_key)的模型是否在一個QuerySet中的最高效的方法是:

entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
    print("Entry contained in queryset")

它將比下面的方法快很多,這個方法要求對QuerySet求值並迭代整個QuerySet:

if entry in some_queryset:
   print("Entry contained in QuerySet")

若要查詢一個QuerySet是否包含任何元素:

if some_queryset.exists():
    print("There is at least one object in some_queryset")

將快於:

if some_queryset:
    print("There is at least one object in some_queryset")

15. update()

update(**kwargs)

對指定的欄位執行批量更新操作,並返回匹配的行數(如果某些行已具有新值,則可能不等於已更新的行數)。

例如,要對2010年釋出的所有部落格條目啟用評論,可以執行以下操作:

>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)

可以同時更新多個欄位 (沒有多少欄位的限制)。 例如同時更新comments_on和headline欄位:

>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')

update()方法無需save操作。唯一限制是它只能更新模型主表中的列,而不是關聯的模型,例如不能這樣做:

>>> Entry.objects.update(blog__name='foo') # Won't work!

仍然可以根據相關欄位進行過濾:

>>> Entry.objects.filter(blog__id=1).update(comments_on=True)

update()方法返回受影響的行數:

>>> Entry.objects.filter(id=64).update(comments_on=True)
1
>>> Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True)
0
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
132

如果你只是更新一下物件,不需要為物件做別的事情,最有效的方法是呼叫update(),而不是將模型物件載入到記憶體中。 例如,不要這樣做:

e = Entry.objects.get(id=10)
e.comments_on = False
e.save()

建議如下操作:

Entry.objects.filter(id=10).update(comments_on=False)

用update()還可以防止在載入物件和呼叫save()之間的短時間內資料庫中某些內容可能發生更改的競爭條件。

如果想更新一個具有自定義save()方法的模型的記錄,請迴圈遍歷它們並呼叫save(),如下所示:

for e in Entry.objects.filter(pub_date__year=2010):
    e.comments_on = False
    e.save()

16. delete()

delete()

批量刪除QuerySet中的所有物件,並返回刪除的物件個數和每個物件型別的刪除次數的字典。

delete()動作是立即執行的。

不能在QuerySet上呼叫delete()。

例如,要刪除特定部落格中的所有條目:

>>> b = Blog.objects.get(pk=1)
# Delete all the entries belonging to this Blog.
>>> Entry.objects.filter(blog=b).delete()
(4, {'weblog.Entry': 2, 'weblog.Entry_authors': 2})

預設情況下,Django的ForeignKey使用SQL約束ON DELETE CASCADE,任何具有指向要刪除的物件的外來鍵的物件將與它們一起被刪除。 像這樣:

>>> blogs = Blog.objects.all()
# This will delete all Blogs and all of their Entry objects.
>>> blogs.delete()
(5, {'weblog.Blog': 1, 'weblog.Entry': 2, 'weblog.Entry_authors': 2})

這種級聯的行為可以通過的ForeignKey的on_delete引數自定義。(什麼時候要改變這種行為呢?比如日誌資料,就不能和它關聯的主體一併被刪除!)

delete()會為所有已刪除的物件(包括級聯刪除)發出pre_deletepost_delete訊號。

17. as_manager()

classmethod as_manager()

一個類方法,返回Manager的例項與QuerySet的方法的副本。