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文档

相关文章
相关标签/搜索