Django-Part4——模型层

[TOC]

测试脚本

  • 当只是想测试django项目中的某一个.py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。
  • 脚本代码无论是写在应用下的tests.py,还是单独开设.py文件都可以。
  • django中的文件默认不会暴露出来,需要准备测试环境才能进行测试。
1
2
3
4
5
6
7
8
9
10
11
# 测试环境的准备:1. 在manage.py中拷贝前四行代码;2. 额外增加两行代码
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoProject.settings") # 这里要改成相应的项目名
import django
django.setup()

# 测试代码

  • from app01 import models写在main之外时,可以通过勾选”使用Python控制台运行”来解决 https://blog.csdn.net/weixin_44393803/article/details/89739066

  • 当不准备环境配置时,可以在运行配置中勾选 Python控制台启动 。这样会忽略脚本中的配置环境代码。(推荐)

Django ORM

创建模型表

  • 对象关系映射(ORM),能够以面向对象的方式简便快捷地操作数据库。但是封装程度太高,有时候还是需要自己写SQL语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -> app -> models.py
# 1. 首先在models.py中书写一个类
class User(models.Model):
# id int primary_key auto_increment
id = models.AutoField(primary_key=True, verbose_name='主键') # verbose_name用来对字段解释
# username varchar(32)
username = models.CharField(max_length=32, verbose_name='用户名') # CharField必须要指定max_length参数,否则报错
# password int
password = models.IntegerField(verbose_name='密码')


class Author(models.Model):
# 由于一张表中必须要有一个主键字段,并且一般情况下都叫id字段
# 所以当不定义主键字段时,orm会自动创建一个名为id主键字段
# 也就意味着:后续在创建表时,如果主键字段名没有额外的叫法,那么主键字段可以省略不写
# username varchar(32)
username = models.CharField(max_length=32)
# password int
password = models.IntegerField()

  • 每次只要修改了models.py中跟数据库相关的代码,就必须重新执行以下两条命令。
  • 如果发现执行之后没有发生变化,则检查app是否已经注册
1
2
3
4
# -> 控制台
# 2. 数据库迁移命令
python manage.py makemigrations # 将操作记录记录migrations文件夹中
python manage.py migrate # 将操作真正的同步到数据库中
  • 除自建表之外,还有Django需要使用的表

image-20210616153539184

字段的增/删/改

  • 当数据表拥有记录时,不能增加非空字段:

    1. 在终端内直接给出默认值
    2. 退出终端程序,并修改代码为可以为空xxx = models.CharField(max_length=32, null=True)
    3. 退出终端程序,并修改代码设置默认值xxx = models.CharField(max_length=32, default='xxx')
  • 修改字段则是修改对应代码

  • 删除字段则是注释/删除对应代码

修改model文件之后,不要忘了执行数据库迁移的两条命令

不要轻易地删除/注释字段,删除也最好使用软删除!

在对于数据库操作时一定要注意隐私安全,一定要锁屏!

创建表关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from django.db import models


# Create your models here.

# 创建表关系。先将基表创建出来,然后再添加外键字段
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
price = models.DecimalField(max_digits=8, decimal_places=2) # 总共八位 小数点后面占两位

# 图书和出版社是一对多,并且书是多的一方,所以外键字段放在书表里面
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# to_field=默认与出版社表的主键字段做外键关联
# 如果该字段是ForeignKey,则orm会自动在字段的后面加 _id -> publish_id
# 因此在定义ForeignKey的时候就不要加 _id

# 图书和作者是多对多的关系,外键字段建在任意一方均可,但是推荐建在查询频率较高的一方
authors = models.ManyToManyField(to='Author')
# authors是一个虚拟字段 主要是用来告诉orm 书籍表和作者表是多对多关系
# orm将自动创建第三张关系表


class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)


class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()

# 作者与作者详情是一对一的关系 外键字段建在任意一方都可以 但是推荐你建在查询频率较高的表中
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE) # django2.x之后要手动添加级联删除
# OneToOneField()也会自动给字段 author_detail 加 _id 后缀
# on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET(value)五个可选择的值


class AuthorDetail(models.Model):
phone = models.BigIntegerField() # 直接用字符类型更好
addr = models.CharField(max_length=32)

  • A表外键字段 = models.ForeignKey(to=B表)
  • 关系表外键字段 = models.ManyToManyField(to=B表)
  • A表外键字段 = models.OneToOneField(to=B表)

外键都会自动加_id后缀

单表操作

增删改查

  • 在视图函数中,from app01 import models首先导入对应的app下的model
  • django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错。
  • pk会自动查找到当前表的主键字段,指代的就是当前表的主键字段,避免区分当前表的主键字段名uid/pid/sid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def reg(request):
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
from app01 import models
# 第一种方式(推荐使用)
reslt = models.User.objects.create(username=username, password=password)
print(res, res.username, res.password) # 返回值就是当前被创建的对象本身
# 第二种方式
user_obj = models.User(username=username, password=password)
user_obj.save() # 保存数据

# 先给用户返回一个注册页面
return render(request, 'reg.html')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def login(request):
if request.method == 'POST':
# 获取用户的用户名和密码 然后利用orm操作数据 校验数据是否正确
username = request.POST.get('username')
password = request.POST.get('password')

# 去数据库中查询数据
from app01 import models
# select * from user where username='jason';
# filter相当于where,括号内可以携带多个参数,参数与参数之间是and关系
result = models.User.objects.filter(username=username)
print(type(result)) # <QuerySet [<User: User object>]>
# 支持索引取值、切片操作,但是不支持负数索引
user_obj = reslt.first() # 从queryset拿出一个个数据对象
print(user_obj.username) # 直接调用字段

# 比对密码是否一致
if user_obj:
if password == user_obj.password:
return HttpResponse("登陆成功")
else:
return HttpResponse("密码错误")
else:
return HttpResponse("用户不存在")
return render(request, 'login.html')
  • models.User.objects.filter()等效于models.User.objects.all()
  • .first()等效于[0]

数据展示:

1
2
3
4
5
6
# -> views.py
def userlist(request):
# 查询出用户表里面所有的数据
user_queryset = models.User.objects.all()
# return render(request,'userlist.html',{'user_queryset':user_queryset})
return render(request,'userlist.html', locals()) # 返回当前的命名空间

模板语法for:

1
2
3
4
5
6
7
8
9
10
11
12
{% for user_obj in user_queryset %}
<tr>
<td>{{ user_obj.id }}</td>
<td>{{ user_obj.username }}</td>
<td>{{ user_obj.password }}</td>
<td>
{# 利用url问号后面携带参数的方式,将编辑按钮所在行的主键值发送给后端#}
<a href="/edit_user/?user_id={{ user_obj.id }}" class="btn btn-primary btn-xs">编辑</a>
<a href="/delete_user/?user_id={{ user_obj.id }}" class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def edit_user(request):
# 获取url问号后面的参数
edit_id = request.GET.get('user_id')
# 查询当前用户想要编辑的数据对象
edit_list = models.User.objects.filter(id=edit_id)
edit_obj = edit_list.first()

if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
# 改 1 批量更新filter查询出来的列表中所有对象。只修改被修改的字段
edit_list.update(username=username, password=password)

# 改 2 单条数据更新。无论该字段是否被修改,重写所有字段(当字段特别多时,效率很低)
edit_obj.username = username
edit_obj.password = password
edit_obj.save()

return redirect('/userlist/') # 跳转到数据的展示页面
return render(request, 'edit_user.html', locals()) # 将数据对象展示到页面上

1
2
3
4
5
6
7
8
9
10
11
def delete_user(request):
# 获取用户想要删除的数据id值
delete_id = request.GET.get('user_id')
# 删 1 批量删除数据库中找到对应的数据
res = models.User.objects.filter(id=delete_id).delete()
print(res) # (1, {'app01.User': 1})
# 删 2
user_obj = models.User.objects.filter(pk=delete_id).first()
user_obj.delete()

return redirect('/userlist/')

一般都会添加一个is_delete字段做软删除,而不会直接删除。

必知必会13条

  1. all():查询所有数据

  2. filter():带有过滤条件的查询

  3. get():直接拿数据对象,但是条件不存在直接报错

  4. first():拿queryset里面第一个元素

  5. last():拿queryset里面最后一个元素

  6. values():可以指定获取的数据字段,相当于select name,age from …

    1
    2
    res = models.User.objects.values('name','age')
    print(res) # 列表套字典 <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>
  7. values_list():

    1
    2
    res = models.User.objects.values_list('name','age')
    print(res) # 列表套元组 <QuerySet [('jason', 18), ('egonPPP', 84)]>

    和values()仅仅只是封装格式不一样,sql查询语句是一样的。

  8. distinct():去重

    1
    2
    res = models.User.objects.values('name','age').distinct()
    print(res)
    • 去重一定要是(在虚表中)一模一样的数据。

      例如:.all()的数据带有主键,因此无法去重

  9. order_by():

    1
    2
    3
    res = models.User.objects.order_by('age')  # 默认升序
    res = models.User.objects.order_by('-age') # 降序
    print(res)
  10. reverse():

    1
    2
    3
    res = models.User.objects.all()  # 反转
    res1 = models.User.objects.order_by('age').reverse() # 必须先排序
    print(res, res1)
  11. count():统计当前数据的个数

    1
    2
    res = models.User.objects.count()  # 统计当前数据的个数
    print(res)
  12. exclude():排除在外

    1
    2
    res = models.User.objects.exclude(name='jason')
    print(res)
  13. exists():基本用不到因为数据本身就自带布尔值 返回的是布尔值

    1
    2
    res = models.User.objects.filter(pk=10).exists()
    print(res)

    查看内部sql语句的方式

  14. 只有QuerySet类对象可以使用.query查看内部封装的sql语句

  15. 在配置文件中配置logging,查看所有的sql语句

  16. 需要在Python控制台中运行才能看见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 方式1
res = models.User.objects.values_list('name', 'age')
print(res.query) # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`

# 方式2
# -> settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}

双下划线查询

  • __gt: 大于
  • __gte: 大于等于
  • __lt: 小于
  • __lte: 小于等于
  • __in: 在目标集合中,左右闭区间
  • __range: 在目标范围中
  • __contains: 模糊查询,包含(区分大小写)
  • __icontains: 包含(不区分大小写)
  • __startswith: 以某字符开头
  • __endswith: 以某字符结尾
  • __month: 在目标月份
  • __year: 在目标年份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 年龄大于35岁的数据
res = models.User.objects.filter(age__gt=35)
print(res)
# 年龄小于35岁的数据
res = models.User.objects.filter(age__lt=35)
print(res)
# 大于等于 小于等于
res = models.User.objects.filter(age__gte=32)
print(res)
res = models.User.objects.filter(age__lte=32)
print(res)
# 年龄是 18 或者 32 或者 40
res = models.User.objects.filter(age__in=[18, 32, 40])
print(res)
# 年龄在18到40岁之间的 首尾都要
res = models.User.objects.filter(age__range=[18, 40])
print(res)
# 查询出名字里面含有s的数据 模糊查询
res = models.User.objects.filter(name__contains='s')
print(res)
# 是否区分大小写 查询出名字里面含有p的数据 区分大小写
res = models.User.objects.filter(name__contains='p')
print(res)
# 忽略大小写
res = models.User.objects.filter(name__icontains='p')
print(res)
# 开头结尾
res = models.User.objects.filter(name__startswith='j')
res1 = models.User.objects.filter(name__endswith='j')
print(res, res1)
# 查询出注册时间是 2020 1月
res = models.User.objects.filter(register_time__month='1', register_time__year='2020')
res = res.filter(register_time__year='2020')

一对多外键增删改查

  • 增 create(): publish_id=num或者publish=publish_obj

  • 删 filter(pk=pk).delete(): 会按照on_delete处理对应外键

  • 改 filter(pk=pk).update(): publish_id=num或者publish=publish_obj

    这是批量修改,在first()返回的对象中没有update这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
# 一对多增删改查
# 1. 增
# 直接写实际字段 id
models.Book.objects.create(title='三国演义', price=123.23, publish_id=1)
# 投放虚拟字段 对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title="红楼梦", price=666.23, publish=publish_obj)
# 2. 删
models.Publish.objects.filter(pk=1).delete() # 会按照on_delete处理对应外键
# 3. 改
models.Book.objects.filter(pk=1).update(publish_id=2)
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)

多对多外键增删改查

  • models.表名.objects.filter(pk=条件).first().多对多关系.add()

    方法支持传递主键,也可以传递对象

  • models.表名.objects.filter(pk=条件).first().多对多关系.remove()

    这只是在第三张关系表中删除记录

  • models.表名.objects.filter(pk=条件).first().多对多关系.set()

    括号内必须给一个可迭代对象

    如果不符合要求,则先删除,后新增

  • models.表名.objects.filter(pk=条件).first().多对多关系.clear()

    在第三张关系表中清空某个书籍与作者的绑定关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1. 增加关系
# 给书籍添加作者
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.authors) # 就类似于你已经到了Book和Authors的多对多关系表了
book_obj.authors.add(1) # 书籍id为1的书籍绑定一个主键为1的作者
book_obj.authors.add(2, 3)

author_obj = models.Author.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj)
book_obj.authors.add(author_obj1, author_obj2)

# 2. 删
book_obj.authors.remove(2)
book_obj.authors.remove(1, 3)

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj, author_obj1)

# 3. 改
book_obj.authors.set([1, 2])
book_obj.authors.set([3]) # 括号内必须给一个可迭代对象

author_obj = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj, author_obj1]) # 支持多个

# 4. 清空
book_obj.authors.clear()
book_obj.authors.set([])

多表查询

正反向

  • 当a表中拥有b表的外键字段,则a查b为正向,否则反向。
  • 一对一和多对多正反向的判断也是如此

外键字段要放在查询频率高的表中。

  • 正向查询按 字段
  • 反向查询
    • 查询一对多、多对多时按 表名小写+_set
    • 查询一对一时按 表名小写

子查询(基于对象的跨表查询)

在书写orm语句的时候跟写sql语句一样的:

  • 不要企图一次性将orm语句写完,如果比较复杂,就写一点看一点

正向什么时候需要加.all():

  • 查询 多对多 和 一对多 关系时需要加
  • 查询 一对一 和 多对一 关系时不需要
  • 当为需要all()的情况时,即使结果只有一个,也会返回集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 1. 查询主键为1的书籍的出版社
# 有字段 -> 正向;查询多对一关系中一的一方 -> 不用.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res.name)

# 2. 查询主键为2的书籍的作者
# 有字段 -> 正向;多对多关系 -> 要.all()
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.authors
print(res)
# app01.Author.None
print(res.all())
# <QuerySet [<Author: Author object (1)>, <Author: Author object (3)>]>
print(models.Book.objects.filter(pk=3).first().authors.all())
# <QuerySet [<Author: Author object (1)>]>

# 3. 查询作者jason的电话号码
# 有字段 -> 正向;一对一关系 -> 不用.all()
author_obj = models.Author.objects.filter(name="jason").first()
res = author_obj.author_detail
print(res.phone, res.addr)

# 4. 查询出版社是东方出版社出版的书
# 无字段 -> 反向;查询一对多关系中多的一方 -> 要.all()
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
res = publish_obj.book_set # app01.Book.None
res = publish_obj.book_set.all()
print(res)

# 5. 查询作者jason写过的书
# 无字段 -> 反向;多对多关系 -> 要.all()
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.book_set # app01.Book.None
res = author_obj.book_set.all()
print(res)

# 6.查询手机号是110的作者姓名
# 无字段 -> 反向;一对一关系 -> 不用.all(),不用_set
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_detail_obj.author
print(res.name)

联表查询(基于双下划线的跨表查询)

  • QuerySet要先用int取字典,再用key取对应的数据
  • 可以放多个查询的键
  • __就是用来获得表下的一个字段
  • 而写到__之前则表示已经进入了另外一个表
  • 这里非对象时都不需要_set
  • 可以 __ id 或者 __pk,是等价的
  • 可以无限制的跨表,正向、反向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1. 查询jason的手机号和作者姓名
# 有字段 -> 正向;
res = models.Author.objects.filter(name='jason').values("author_detail__phone", "name")
print(res[0]["author_detail__phone"])
# 反向
res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是jason的作者详情
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'author__name')
print(res)

# 2.查询书籍主键为1的出版社名称和书的名称
# 正向,写到publish时就已经到了publish表,主需要__来取这个表下的任意字段
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)
# 反向
res = models.Publish.objects.filter(book__pk=1).values('name', 'book__title')
print(res)

# 3.查询书籍主键为1的作者姓名
# 正向
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)
# 反向
res = models.Author.objects.filter(book__id=1).values('name')
print(res)

# 4. 查询书籍主键是1的作者的手机号
res = models.Book.objects.filter(pk=1).values("authors__author_detail__phone")
print(res)

聚合查询(aggregate)

  • 只要是跟数据库相关的模块,基本上都在django.db.models里面,如果没有则应该在django.db里面。
1
2
3
4
5
from app01 import models
from django.db.models import Max,Min,Sum,Count,Avg
res = models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
print(res)
# {'price__max': Decimal('899.23'), 'price__min': Decimal('123.23'), 'price__sum': Decimal('2466.58'), 'pk__count': 5, 'price__avg': Decimal('493.316000')}

分组查询(annotate)

  • MySQL分组查询特点:分组之后默认只能获取到分组的依据,组内其他字段都无法直接获取了(严格模式,ONLY_FULL_GROUP_BY)
  • author和author__pk是等价的,个人认为写全更好
  • models后面点什么,就是按什么分组
  • author_num是自己定义的临时字段,用来存储统计出来的每本书对应的作者个数
  • 在使用values创建虚表时,依然可以放入多个字段
  • 只要orm语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法
  • queryset对象就代表一个虚表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1. 统计每一本书的作者个数
# 只需要写author就可以了
# models后面点什么,就是按什么分组
# author_num是我们自己定义的字段,用来存储统计出来的每本书对应的作者个数
# 在使用values创建虚表时,依然可以放入多个字段
res = models.Book.objects.annotate(author_num=Count("author__pk")).values("author_num", 'title', 'author_num')
print(res)

# 2. 统计每个出版社卖的最便宜的书的价格
# 按publish分组,反向找book表中price的最小值,作为publish表的min_price临时字段
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)

# 3. 统计不止一个作者的图书
# 先按照图书分组,求每一本书对应的作者个数
# 过滤出不止一个作者的图书
res = models.Book.objects.annotate(author_num=Count("author_id")) \
.filter(author__num__gt=1) \
.values('title', 'author_num')
# 只要orm语句得出的结果还是一个queryset对象,那么就可以继续无限制的点queryset对象封装的方法
# queryset对象就代表一个虚表
print(res)

# 4. 查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', "sum_price")
print(res)

# 5. 按照指定的字段分组,本质也是创造虚表
models.Book.objects.values('price').annotate()

F与Q查询

F

  • 能够直接获取到表中某个字段对应的数据
  • F不能够直接操作字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 查询卖出数大于库存数的书籍
from django.db.models import F
res = models.Book.objects.filter(sale__gt=F('store'))
print(res)

# 2. 将所有书籍的价格提升500块
models.Book.objects.update(price=F('price') + 500)

# 3. 将所有书的名称后面加上爆款两个字
# 在操作字符类型的数据的时候,F不能够直接做到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
# models.Book.objects.update(title=F('title') + '爆款') # 所有的名称会全部变成空白
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

Q

  • 扩展搜索表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1.查询卖出数大于100或者价格小于600的书籍
# res = models.Book.objects.filter(maichu__gt=100,price__lt=600) # filter括号内多个参数是and关系
from django.db.models import Q
res = models.Book.objects.filter(Q(maichu__gt=100), Q(price__lt=600)) # Q包裹逗号分割 还是and关系
res = models.Book.objects.filter(Q(maichu__gt=100) | Q(price__lt=600)) # | or关系
res = models.Book.objects.filter(~Q(maichu__gt=100) | Q(price__lt=600)) # ~ not关系
print(res) # <QuerySet []>

# 2. Q的高阶用法,将查询条件的左边变成字符串形式
q = Q()
q.connector = 'or' # 默认是and关系
q.children.append(('maichu__gt', 100))
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q)
print(res)

django中如何开启事务

  • ACID
    • 原子性
      • 不可分割的最小单位
    • 一致性
      • 跟原子性是相辅相成
    • 隔离性
      • 事务之间互相不干扰
    • 持久性
      • 事务一旦确认永久生效
  • 事务的回滚
    • rollback
  • 事务的确认
    • commit
1
2
3
4
5
6
7
8
9
10
# Django中如何简单地开启事务
from django.db import transaction
try:
with transaction.atomic():
# sql1
# sql2
...
# 在with代码快内书写的所有orm操作都是属于同一个事务
except Exception as e:
print(e)

orm字段及参数

常用字段及参数

  1. AutoField:主键字段

    • primary_key=True

    一般会自动创建,不需要手动设置

  2. CharField:varchar

    • verbose_name=字段的注释
    • max_length=长度
    • null=True可以为空
    • blank=True可以存空字符串(这两个有区别)
    • unique=True唯一
  3. IntegerField:int

  4. BigIntegerField:bigint

  5. DecimalField

    • max_digits=允许的最大位数
    • decimal_places=小数的最大位数
  6. EmailFiled:varchar(254)

  7. DateField:date

    DateTimeField:datetime

    • auto_now=每次修改数据时更新当前时间
    • auto_now_add=只在创建数据时记录当前时间
  8. BooleanField:Field

    该字段传布尔值(False/True),数据库里面存0/1

  9. TextField:Field

    该字段可以用来存大段内容(文章、博客…),没有字数限制

  10. FileField:Field

    • upload_to = “/data/{file}”

      给该字段传一个文件对象,会自动将文件保存到/data目录下,并将文件路径保存到数据库中

更多字段,直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html,后续补全

自定义字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义字段
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
# 调用父类的init方法
super().__init__(max_length=max_length, *args, **kwargs) # 一定要是关键字的形式传入

def db_type(self, connection):
"""
返回真正的数据类型及各种约束条件
:param connection:
:return:
"""
return 'char(%s)' % self.max_length


# 自定义字段使用
myfield = MyCharField(max_length=16, null=True)

外键字段及参数

  1. unique=True

    ForeignKey(unique=True)等价于OneToOneField()

  2. db_index=True

    为此字段设置索引

  3. to

    设置要关联的表

  4. to_field

    设置要关联的表的字段,默认关联另外一张的主键字段,并且自动添加_id后缀

  5. on_delete

    当删除关联表中的数据时,当前表与其关联的行的行为。

  6. related_name

    反向查询使用的字段名,即代替 字段名__set

  7. limit_choices_to

    限制选择条件

数据库查询优化

all()

  • orm语句的特点:惰性查询

    如果仅仅只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别,直接不执行。

1
2
res = models.Book.objects.all()  # 此时并没有查询数据库
print(res) # 要用数据了才会走数据库

only()

只取目标字段

  • 拿到数据对象,这些对象本身只有only中提到的字段参数
  • 如果调用没有包含的字段,则会重新回到数据库查询
1
2
3
4
5
6
7
8
9
10
# 获取书籍表中所有书的名字
res = models.Book.objects.values('title')
for d in res:
print(d.get('title'))
# 实现获取数据对象,点title就能够拿到书名,并且没有其他字段
res = models.Book.objects.only('title')
print(res) # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]>
for i in res:
print(i.title) # 点only括号内的字段,不会走数据库
print(i.price) # 点only括号内没有的字段,会重新走数据库查询而all()不需要走了

defer()

  • defer与only刚好相反
  • defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据库
  • 而如果查询的是非括号内的字段,则不需要走数据库了
1
2
3
res = models.Book.objects.defer('title')  # 对象除了没有title属性之外其他的都有
for i in res:
print(i.price)

跨表操作

  • select_related内部先将book与publish连起来,然后一次性将大虚表的所有数据封装给查询出来的对象

    这个时候对象无论是点击book表的数据还是publish表的数据都无需再走数据库查询了

  • select_related括号内只能放外键字段:一对多、一对一

    不可以多对多

1
2
3
4
5
6
7
   res = models.Book.objects.all()
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询

res = models.Book.objects.select_related('authors') # INNER JOIN
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询
  • prefetch_related内部其实就是子查询

    指把内部查询的结果作为外层查询的比较条件

  • 两次查询,但不一定就比select_related差,要看实际情况

1
2
3
4
res = models.Book.objects.prefetch_related('publish')  # 子查询

for i in res:
print(i.publish.name)

批量插入

当批量插入数据的时候,使用orm的bulk_create方法能够大幅减少操作时间

1
2
3
4
5
6
7
8
9
10
11
12
13
def ab_pl(request):
# 1. 逐条插入
for i in range(10000):
models.Book.objects.create(title='第%s本书' % i)

# 2. 批量插入
book_list = []
for i in range(100000):
book_list.append(models.Book(title='第%s本书' % i))
models.Book.objects.bulk_create(book_list)

book_queryset = models.Book.objects.all()
return render(request, 'ab_pl.html', locals())

分页器

减少同一时间的数据展示量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def ab_pl(request):
# 分页
try:
cur_page = int(request.GET.get('cur_page', default=1)) # 如果获取不到当前页码,就展示第一页
per_page = int(request.GET.get('per_page', default=10)) # 每页展示多少条
start_page = int((cur_page - 1) * per_page) # 起始位置
end_page = int(cur_page * per_page) # 终止位置
except Exception:
start_page = 0
end_page = 10

book_list = models.Book.objects.all()

page_queryset = book_list[start_page:end_page]
response = serializers.serialize('json', page_queryset)
return HttpResponse(response)

choices参数

  • 只要某个字段的内容是可以枚举完全的,则可以采用choices参数:
    • 学历
    • 客户来源
    • ……
  • 元组套元组
  • 需要保证数据库表中字段类型跟key的数据类型一致
  • 在数据库中存的是key,value在orm中
  • 不存在的key直接输出key,存在的key可以通过get_字段_display()转换成value输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -> models.py
class User(models.Model):
username = models.CharField(max_length=32)
age = models.IntegerField()
# 性别
gender_choices = ((1, '男'), (2, '女'))
gender = models.SmallIntegerField(choices=gender_choices)

score = models.CharField(choices=(
('A', '优秀'),
('B', '良好'),
('C', '及格'),
('D', '不合格'),
), null=True) # 保证字段类型跟列举出来的元组第一个数据类型一致

# -> test.py
# 存的时候,没有列举出来的数字也能存,范围还是按照字段类型决定
models.User.objects.create(username='jason', age=18, gender=1)
models.User.objects.create(username='egon', age=85, gender=2)
models.User.objects.create(username='tank', age=40, gender=3)
models.User.objects.create(username='tony', age=45, gender=4)

# 取
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender) # 数据库中存储的key
print(user_obj.get_gender_display()) # 对于choices参数的字段,通过get_字段名_display()获取对应的值
user_obj = models.User.objects.filter(pk=4).first()
print(user_obj.get_gender_display()) # 如果没有对应关系,那么返回字段存储的原key

MTV与MVC模型

  • MTV:Django号称是MTV模型
    • M:models
    • T:templates
    • V:views
  • MVC:其实django本质也是MVC
    • M:models
    • V:views
    • C:controller
  • vue框架:MVVM模型

多对多三种创建方式

全自动

  • 利用orm自动帮我们创建第三张关系表
  • 优点:
    • 不需要写代码,非常方便
    • 支持orm提供操作第三张关系表的方法
  • 不足之处:
    • 第三张关系表的扩展性极差:没有办法额外添加字段
1
2
3
4
5
6
7
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')

class Author(models.Model):
name = models.CharField(max_length=32)

纯手动

  • 优点:
    • 第三张表完全取决于你自己进行额外的扩展
  • 不足之处:
    • 需要写的代码较多
    • 不能够使用orm提供的简单方法

不建议使用该方式。

1
2
3
4
5
6
7
8
9
10
class Book(models.Model):
name = models.CharField(max_length=32)

class Author(models.Model):
name = models.CharField(max_length=32)

class Book2Author(models.Model):
book_id = models.ForeignKey(to='Book')
author_id = models.ForeignKey(to='Author')

半自动

  • 可以使用orm的正反向查询
  • 但是没法使用add、set、remove、clear这四个方法
  • 多对多表关系可以放在任何一方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author',
through='Book2Author',
through_fields=('book','author')
) # 将当前表名放在元组第一位

class Author(models.Model):
name = models.CharField(max_length=32)
# books = models.ManyToManyField(to='Book',
# through='Book2Author',
# through_fields=('author', 'book')
# )

class Book2Author(models.Model):
book = models.ForeignKey(to='Book') # 外键默认加id,这里不要加,包括through_fields
author = models.ForeignKey(to='Author')