掌握Django(四)

本文介绍Django 对象数据库映射的有关知识,包括管理器与查询集,以及数据检索、数据过滤、Q对象、F对象、排序、限制结果、选择字段、延迟字段等内容。


Django ORM

对象与关系型数据库的映射,称为ORM。

在Django中使用ORM,可以:

  • 减少代码的复杂性。
  • 使代码易于理解。
  • 节省时间。

前面介绍的Django的Migrate是Django ORM的一部分,本节我们继续深入。


重置数据库

为了与后续章节介绍的一致,建议下载附件,我们都从一致的地方开始。

附件地址:

https://box.zjenergy.com.cn/l/Y0TMvl

解压后,我们看到有个storefront文件夹和seed.sql文件,用vscode打开storefront文件夹。

找到settings.py,查找DATABASES节,修改成实际的mysql用户密码等配置信息。

执行:pipenv install

安装所有依赖项。

用DataGrip打开MySQL,删除storefront数据库。

再打开一个Query Console,键入SQL语句:

CREATE DATABASE storefront;
执行迁移:
python manage.py migrate
确认mysql数据库已生成了相关数据表。

关闭DataGrip所有的session(底部点services–再右键localhost–close all session),然后打开一个Query Console,将附件的seed.sql拖入,注意确认顶部数据库是storefront,选中所有的语句(command+a),然后点执行按钮。

检查我们的几个表如要store_customer、store_product、store_order、store_orderitem都已填充了数据了。

python manage.py runserver

确认正常。


管理器与查询集

键入一些代码:playgroud–views.py:

def say_hello(request):

    Product.objects.all()

每一个模型都有一个管理器objects,这类似有很多功能按钮的遥控器,用来与数据库对接,除了all()方法,还有filter(),get()等等。

管理器的大部分方法,比如all()都返回一个查询集。

运行all()方法不会从数据库获取数据及返回 products 这样的对象列表。

query_set = Product.objects.all()

query_set是一个查询集,它封装了查询的对象,但它不会生成SQL语句发往数据库执行。

在某些时间点,django将评估查询集并生成正确的SQL语句发往数据库,主要是以下几种情况:

  • 当我们迭代查询集时。for product in query_set:    print(product)键入代码保存,刷新页面,在网页右侧调试栏–SQL,可以看到实际发往数据库的SQL。具体如图。
  • 将查询集转换成列表时。list(query_set)
  • 查询集切片查询时,比如取前5个数据。query_set[0:5]

所以,查询集是惰性的。那为什么在调用all()等方法时,不直接向数据库获取数据呢?

原因是:通过查询集可以组成更复杂的查询,比如:

query_set = Product.objects.all()

query_set.filter(XXX=XXX).filter(XXX=XXX).order_by(XXX)….

我们实际只需要很小的一个子集,如果all()直接返回,内存将有大量的对象,这是很浪费的。

有些方法是直接执行数据库SQL语句返回数据的,比如count(),很明显,不会再有围绕这个结果构建更复杂的语句了。

Product.objects.count()


数据检索

前面提到的all()用以取得全部对象,get方法则用来取得单个对象。

product = Product.objects.get(id=1)

id=1 也可以用 pk=1 代替。

pk表示主键,Django会自动查找主键并比较检索。

get方法返回空的话将抛出异常,需要捕捉,如下:

from django.core.exceptions import ObjectDoesNotExist

try:

    product = Product.objects.get(pk=0)

except ObjectDoesNotExist:

    pass

这种方法比较笨重,每次调用 get 都需要try…catch,可以改成:

product = Product.objects.filter(pk=0).first()

这样即使找不到对象也不会出错,而是返回null。

exists = Product.objects.filter(pk=0).exists()

exists方法返回布尔值,检测是否存在。


过滤对象

过滤用filter方法。

query_set = Product.objects.filter(unit_price=20)

表示过滤出所有价格为20 的商品。

但不能用unit_prick>20,如:

query_set = Product.objects.filter(unit_price>20)

因为 unit_price>20是表达式,结果是布尔值,作为参数传给filter是出错的。filter方法期望得到的参数是(XXX=XXX)。

为了解决大于小于过滤的问题,Django用字段后加两个下划线__再加逻辑查找类型的格式,比如查找价格大于20的:

query_set = Product.objects.filter(unit_price__gt=20)

通过谷歌查找 “queryset api”—>Field lookups ,可以查到有很多方法,如:…filter(unit_price__range=(20,30)),表示价格在20和30之间的符合记录。

双下划线也可以查找到关系,比如外键,一对多、一对一、多对多都可以。

queryset = Product.objects.filter(collection__id__range=(1,3))

这=collectiong是Product的关系,这里返回的查询集是collection的IDd在1-3范围的Product对象。当然不仅是ID,关系collection的其他字段也都是支持的。

为了直观,我们修改下hello.html加入一些查看代码:

<ul>

{% for product in products %}

<li>

{{ product.title }}

</li>

{% endfor %}

</ul>

playground–>views.py的say_hello中传递的字典也加入products。

def say_hello(request):

    queryset = Product.objects.filter(collection__id__range=(1, 3))

    return render(request, ‘hello.html’, {

        ‘name’: ‘kelemi’,

        ‘products’: list(queryset)

    })

前面是数字的过滤,下面我们来看看涉及字符串的过滤查询

Product.objects.filter(title__contains=’coffee’)

包含用 __contains,但我们发现页面未显示找到相关结果,原因是过滤是大小写敏感的。如果想忽略大小写,加上 i 即可。

Product.object.filter(title__icontains=’coffee’)

对于字符串,除了contains,还有

  • __startswith,以什么开头
  • __endswith,以什么结尾

再看看日期过滤

日期过滤有一堆参数:

Product.objects.filter(last_update__year=2022)

除了__year,还有__month,__day以及分、秒等。

也可以直接用日期值进行比较,__date

Product.objects.filter(last_update__date=datetime.date(2022,8,22))

还有一个可以检查是否是null

Product.objects.filter(description__isnull=True)


复杂查询使用Q对象

我们看一下这个:

queryset = Product.objects.filter(inventory__lt=10,unit_price__lt=20)

这个过滤语句用了多关键字参数。当然也可以改造成多filter链式语句:

queryset = Product.objects.filter(inventory__lt=10).filter(unit_price__lt=20)

我们看到,这些都是 and 运算 组合语句,如果我们要用 or运算,就需要用到Q对象

from django.db.models import Q

def say_hello(request):

    queryset = Product.objects.filter(

    Q(inventory__lt=10) | Q(unit_price__lt=20)

)

Q是队列的缩写,需要导入Q类:from django.db.models import Q

上面例子每个Q类之间用 | 分隔,表示 或 。

也可以用 & 、~ 等表示而且、非。

比如:

 …filter(Q(inventory__lt=10)&~Q(unit_price__lt=20))

表示过滤库存小于10而且价格不小于20的记录。


引用字段使用F对象

有某些查询中,可能需要比较两个字段。比如我们要查询Product中的inventory=unit_price的记录,该如何做呢?用F对象!

from django.db.models import F

Product.objects.filter(inventory=F(‘unit_price’))

F对象也可以引用其他对象中的字段,比如:

Product.objects.filter(inventory=F(‘collection__id’))

查找Product中 invenroty等于 Collection的id的记录。


排序

排序用的是order_by方法,如:

queryset = Product.objects.order_by(‘title’)

表示按title升序排列

queryset = Product.objects.order_by(‘-title’)

表示按title降序排列。

queryset = Product.objects.order_by(‘unit_price’,’-title’)

可以进行多字段排序。

django的查询集也有一个反转方法 reverse(),它把各排序字段都作相反处理。

queryset = Product.objects.order_by(‘unit_price’,’-title’).reverse()

加了reverse()方法,实际排序字段变成了 …order_by(‘-unit_price’,’title’)

可以搜索queryset API 查所有方法。

有时候只需取单个元素,可以:

product = Product.objects.order_by(‘unit_price’)[0]

取前后单个记录也可以用earliest 和 latest 方法。earliest()方法将参数的字段按升序排列并只取第1个;latest()方法则将字段按降序排列并取1个记录。

product = Product.objects.earliest(‘unit_price’)

product = Product.objects.latest(‘unit_price’)


限制结果

queryset = Product.objects.all()[0:5]

列表切片,取前5个。

queryset = Product.objects.all()[5:10]

第二页,每页5个记录。


选择字段查询

前面的查询集都返回记录的所有字段,有时候只需返回某几个字段就好,可以使用 values()方法。

queryset = Product.objects.values(‘id’,’title’)

也可以查询关系对象的字段

queryset = Product.objects.values(‘id’,’title’,’collection__title’)

用values方法得到的是一堆字典对象,像这样:

{ ‘id’:1, ‘title’:’XXX’, ‘collection__title’:’XXX’ }

另外还有一个选择字段查询的方法是 values_list,该方法将得到一堆元组:

queryset = Product.objects.values_list(‘id’, ‘title’, ‘collection__title’)

结果是:

( 1, ‘XXX’, ‘XXX’ )


小练习

做个小练习,对前面介绍内容做个回顾。

要求:选择已在订单里的产品,并且按标题排序。

答:

queryset = Product.objects

    .filter(id__in=OrderItem.objects.values(‘product__id’).distinct())

    .order_by(‘title’)

注意 distinct() 是为了保留单个id。


延迟字段

对应 values方法,有 only方法。

Product.objects.only(‘id’, ‘title’)

only 与 values 的区别是:

only 获取类的实例,而 values 获得的是一堆字典对象。

使用 only 方法要非常小心,如果不确定获取哪些字段,将会产生大量的的查询。比如:

queryset = Product.objects.only(‘id’, ‘title’)

再调用,比如类似以下的迭代获取原未列出的字段 unit_price:

for product in list(queryset):

    product.unit_price

由于之前只取字段 id和title,现在要使用 unit_price,每一次迭代都会产生SQL语句访问数据库,严重影响性能。我们来实验一下:

修改 hello.html,添加红色部分显示unit_price:

<li> {{ product.title }} $ {{ product.unit_price }} </li>

我们刷新页面:http://127.0.0.1:8000/playground/hello/

发现有些卡住,因为查询产生了1000多条SQL语句。

而values方法则不会有这个问题,如果不存在,直接返回空。

values返回的是字典,不会产生SQL语句向数据库查询。

与only方法相反的是defer方法。

比如确定用不到description,可以用defer排除掉。

Product.objects.defer(‘description’)

这样会获得除description之外的其他字段。

同样的,需要小心,如果后面需要用到 description 的话,也会向数据库发送查询命令,尤其在迭代中,将严重影响性能。


小结

本文介绍了Django ORM相关的管理器与查询集,以及数据检索、数据过滤、Q对象、F对象、排序、限制结果、选择字段、延迟字段等内容,下一篇继续介绍 Django ORM的相关知识。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注