Django F()表達式

Django F()表達式

一個F()對象表明一個模型字段的值或註釋列。使用它能夠直接引用模型字段的值並執行數據庫操做而不用把它們導入到python的內存中。
相反,Django使用F()對象生成一個描述數據庫級別所需操做的SQL表達式。
經過一個例子很容易理解。一般,有人會這樣作:python

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field += 1
reporter.save()

這裏咱們從數據庫中取出reporter.stories_field的值放入內存中並使用咱們熟悉的python運算符操做它,而後把對象保存到數據庫中。可是咱們還能夠這樣作:數據庫

from django.db.models import F

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field = F('stories_field') + 1
reporter.save()

雖然reporter.stories_field = F('stories_field') + 1看起來像常規的Python爲實例屬性賦值,但實際上它是一個描述數據庫上操做的SQL結構。
當Django遇到要給F()實例,它會覆蓋標準的Python運算符來建立一個封裝的SQL表達式;在這個例子中,指示數據庫增長由reorter.stories_field表示的數據庫字段。
不管repoter.stories_field的值是或曾是什麼,Python永遠不須要知道--徹底由數據庫來處理。Python經過Django的F()類作的全部事情僅是參考某個字段建立SQL語法來描述操做。express

實例對象必須從新加載後才能獲取以這種方式保存的值:django

reporter = Reporters.objects.get(pk=reporter.pk)
# 或者,更簡潔的操做
reporter.refresh_from_db()

像上面這樣在單個實例中使用,F()能夠經過updage()方法被用在QuerySets對象實例中。這樣能夠省略上面用到的兩個查詢--get()save()app

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_field=F('stories_field') + 1)

咱們也可使用update()來增長多個對象的字段值,這樣比從數據中取出他們,經過循環挨個增長值,而後再保存回數據庫中要更加快速:性能

Reporters.objects.all().update(stories_field=F('stories_field') + 1)

所以,F()能夠經過如下方式提供性能優點:ui

  • 直接在數據庫中操做而不是python
  • 減小一些操做所需的數據庫查詢次數

使用F()避免競爭關係

使用F()的另外一個好處是使用數據庫來更新字段而不是用Python,以此避免競爭關係。
若是有兩個Python線程執行上面第一個示例,A線程可能在B線程從數據庫檢索數據後來檢索、增長並保存某個字段值。那麼B線程的保存將基於他最初檢索到的原始值;而A線程的工做將會丟失。
若是有數據庫自身負責更新字段,這個過程將會更加健壯:在執行sasve()update()時基於數據庫中當前的字段值,而不是基於被檢索出的實例的值。線程

F()操做在Model.save()後會持續存在

分配給模型字段的F()對象在模型實例保存後依然保持不變,並會應用於以後的每一個save()。例如:翻譯

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_field = F('stories_field') + 1
reporter.save()  # F()表達式第一次執行

reporter.name = 'Tintin Jr')
reporter.save()  # F()表達式第二次執行

stories_field將會被更新兩次!若是初始值爲1,那麼最終值將會是3code

在過濾器(filter)中使用F()

F()QuerySet過濾器中也很是有用,經過它能夠實現基於自身字段值來過濾一組對象,而不是經過Python值。
F()實例充當查詢中一個模型字段的引用。這些引用在查詢過濾器能夠被用來比較同一模型實例的兩個不一樣字段的值。
例如,咱們要查找全部Blog記錄中comment多於pingback的記錄,咱們構建一個F()對象來引用pingback的數量:

"""
class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline
"""

from django.db.models import F
Entry.objects.filter(n_commnets__gt=F('n_pingbacks'))

Django支持F()對象使用加、減、乘、除、取模和冪運算等算術操做,兩個操做數能夠是常數或F()對象。要查詢blog記錄中comment大於兩本pingback的記錄,修改查詢爲:

Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

爲了查詢rating 比pingback 和comment 數目總和要小的記錄,咱們將這樣查詢:

Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你還能夠在F()對象中使用雙下劃線標記來跨越關聯關係。 帶有雙下劃線的F()對象將引入任何須要的join 操做以訪問關聯的對象。 例如,如要獲取author 的名字與blog 名字相同的Entry,咱們能夠這樣查詢:

Entry.objects.filter(authors__name=F('blog__name'))

對於date 和date/time 字段,你能夠給它們加上或減去一個timedelta對象。 下面的例子將返回發佈超過3天后被修改的全部Entry:

from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

在annotate中使用F()

F()可使用算術運算組合不一樣字段在模型中建立動態字段。

company = Company.objects.annotate(chairs_needed=F('num_employee') - F('num_chairs'))

若是組合的是不一樣類型的字段,你須要告訴Django返回值是哪一種字段類型。因爲F()不直接支持output_field,因此須要使用ExpressionWrapper來封裝表達式:

from django.db.models import DatetimeField, ExpressionWrapper, F
Ticket.objects.annotate(
    expires=ExpressionWrapper(
        F('active_at') + F('duration'), output_field=DateTimeField()
    )
)

當引用關聯字段(如ForeignKey)時,F()返回的主鍵值而不是一個模型實例:

>>> car = Company.objects.annotate(built_by=F('manufacturrer'))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3

使用F()對null值排序

使用F()和傳遞nulls_firstnulls_last參數給Expression.asc()desc()來控制字段的null值的排序。默認狀況下這個排序取決於你的數據庫。
例如,要將未聯繫過(last_contactednull)的公司排在已聯繫過的公司後面:

from django.db.models import F
Company.objects.order_by(F('last_contacted').desc(nulls_last=True))

翻譯自Django文檔

相關文章
相關標籤/搜索