django模型层
django模型层功能:与数据库打交道,使用ORM(对象关系映射)方便的实现数据的增删改查操作。
上来先提示: django自带的sqlite3数据库对日期格式不是很敏感,处理的时候容易出错,所以最好使用其他数据库,如MySQL。(复习django连接MySQL数据库的基本配置)。
django ORM
ORM,对象关系映射;作用
:能够让一个不用sql语句的小白也能够通过python 面向对象的代码简单快捷的操作数据库;缺点
:封装程度太高,有时候sql语句的效率偏低,需要你自己写SQL语句。
书写类
from django.db import models
class User(models.Model): # 必须继承models.Model
id = models.AutoField(primary_key=True)
# id int primary_key auto_increment
username = models.CharField(max_length=32, verbose_name='用户名')
# username varchar(32)
password = models.IntegerField()
# password int
数据库迁移命令
python3 manage.py makemigrations 将操作记录先保存起来(migrations文件夹)
python3 manage.py migrate 将操作真正的同步到数据库中
注意:只要你修改了models.py
中跟数据库相关的代码,就必须重新执行上述的两条命令,才能真正的修改数据库中的数据。
补充:
-
CharField
必须要指定max_length
参数,不指定会直接报错 -
verbose_name
该参数是所有字段都有的,用来对字段做解释说明 -
当我们自己不定义主键时,orm会自动建一个名为
id
的主键字段
ORM创建表关系
表关系:一对多、多对多、一对一
一对多
models.ForeignKey()
# 图书表与出版社表
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish = models.ForeignKey(to='Publish') # 一对多,外键关联
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
- 一对多时,外键放在多的一方;
- 创建外键时使用
ForeignKey
,需要指定关联的表名字,默认被关联的字段是被关联表的主键字段。 publish
做外键字段,但内部ORM会自动将publish
转变为publish_id
,因此我们在书写该字段时无需再手动加_id
- 外键关联时,django1版本都是默认级联删除和级联更新的。
一对一
models.OneToOneField()
# 作者表和作者详情表
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
author_detail = models.OneToOneField(to='AuthorDetail') # 一对一,
class AuthorDetail(models.Model):
phone = models.BigIntegerField()
addr = models.CharField(max_length=32)
- 一对一的关系时,外键字段建在任意一方都可以,但推荐放在查询频率较高的一方;
- 默认也是直接关联被关联表的主键字段,即
AuthorDetail
表的主键id
字段; OneToOneField
也会自动给字段加_id
后缀,默认也是级联删除和级联更新的。
多对多
models.ManyToManyField()
# 图书表与作者表
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2)
publish = models.ForeignKey(to='Publish') # 一对多,外键关联
authors = models.ManyToManyField(to='Author') # 多对多,
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
-
多对多关系时,外键字段建在任意一方均可,但推荐放在查询频率较高的一方;
-
authors
是一个虚拟字段,主要是用来告诉ORM,书籍表和作者表是多对多关系; -
ORM会自动创建第三张关系表。
-
多对多关系表有三种创建方式,另外两种创建方式参考文末补充部分
单表操作
增加数据:create()
或者对象的save()
# 方法1
res1 = models.User.objects.create(name='xliu', age=19, register_time='2002-11-20')
# 方法2
import datetime
ctime = datetime.datetime.now()
user_obj = models.User(name='the3times', age=20, register_time=ctime)
user_obj.save()
# 补充:res1接收create()方法返回的新增对象
补充:批量增加数据:bulk_create()
先生成一堆对象,存在列表中,再将列表中的对象一次性插入到数据库中,避免多次与数据库打交道,减少数据库的时间操作。
book_list = []
for i in range(100):
book_obj = models.Book(title='第%s本书'%i)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list) # bulk_create一次性插入
删除数据:delete()
# 方式1
res = models.User.objects.filter(pk=2).delete()
print(res) # res是该删除操作影响的条数信息
# 方式2
user_obj = models.User.objects.filter(pk=1).first()
user_obj.delete() # 对象调用delete()
# 补充:pk是主键的缩写,使用pk可以不需要考虑被操作表中的主键字段具体写法:不论是id,uid,sid都可以使用pk来统称。
修改数据:update()
# 方式1
models.User.objects.filter(pk=4).update(name='noone')
# 方式2
obj = models.User.objects.filter(pk=4).first()
obj.name = 'new_name' # 此处是修改属性,再保存
obj.save()
# 补充:update是批量操作的;删除数据方式1中使用的delete()也是批量操作,但方式2不是
查数据:filter()
、get()
# filter
user_obj = models.User.objects.filter(pk=6).first()
# get()
user_obj = models.User.objects.get(pk=4)
# 补充:
- filter()获取的是QuerySet对象,包含一堆数据,需要通过first()或索引取值的方式获取单独对象
- get()可以直接获取指定的对象,但是如果对象不存在或者不唯一则直接报错,
QuerySet对象
QuerySet是一种类似列表一样的,可循环遍历、可索引切片;一个QuerySet对象可以包含一个或多个元素,通常每个元素都是一个Model 类实例对象,也有特殊的QuerySet(列表包含字典、列表包含元组)。QuerySet类型封装了很多方法,用来管理Model 实例对象。
queryset=Book.objects.all() # 返回值就是一个QuerySet对象
print(queryset) # <QuerySet [<Book: python>, <Book: linux>]>
res = models.User.objects.values('name', 'age')
# res = <QuerySet [{'name': 'xliu', 'age': 19}, {'name': 'the3times', 'age': 20}]>
res2 = models.User.objects.values_list('name', 'age')
# res2 = <QuerySet [('xliu', 19), ('the3times', 20)]>
-
QuerySet特性之可切片
-
QuerySet特性之可迭代
-
QuerySet对象可以使用13个API
-
QuerySet支持链式操作
13条基本方法
all():获取所有数据,返回QuerySet对象
res = models.User.objects.all()
# res = <QuerySet [<User: User object>]>
filter():获取满足条件的数据,返回QuerySet对象
res = models.User.objects.filter(age=18)
get():获取具体数据对象,对象不存在则报错
res = models.User.objects.get(PK=1)
# res = User object
first():获取QuerySet对象中的第一个数据
res = models.User.objects.all().first()
# res = User object
last():获取QuerySet对象中的最后一个数据
values():获取指定字段,返回QuerySet对象
res = models.User.objects.values('name', 'age')
# res = <QuerySet [{'name': 'xliu', 'age': 19}, {'name': 'the3times', 'age': 20}]>
# 补充:作用类似SQL语句select name, age from user
values_list():获取指定字段,返回QuerySet对象
res = models.User.objects.values_list('name', 'age')
# res = <QuerySet [('xliu', 19), ('the3times', 20)]>
# 补充:列表套元祖的对象
distinct():去重(注意排除pk
后再去重),返回QuerySet对象
res = models.User.objects.values('age').distinct()
# res = <QuerySet [{'age': 19}, {'age': 20}]>
order_by():排序(默认升序排序,降序再字段前加-
),返回QuerySet对象
res = models.User.objects.values_list('age').order_by('age')
# 降序
res = models.User.objects.values_list('age').order_by('-age')
reverse():反转,前提是已经排序过,否则没有效果,返回QuerySet对象
res = models.User.objects.values_list('age').order_by('age').reverse()
# res <QuerySet [(20,), (19,)]>
# 升序或降序的另一种表达方式
count():统计个数,返回数字
res = models.User.objects.count()
# res = 2
exclude():排除字段,返回QuerySet对象
res = models.User.objects.values_list('name').exclude(name='xliu')
# res = <QuerySet [('the3times',)]>
exists():判断是否有对象,返回布尔值
res = res = models.User.objects.filter(pk=1212).exists()
# True
双下划线查询方法
双下划线方法可以在filter()
里面实现神奇的操作,灵活地现数据的过滤查询操作。
基本语法
filter(字段名__方法)
常用双下划线方法
filter(age__gt=35) # 过滤年龄 > 35的
filter(age__lt=35) # 过滤年龄 < 35的
filter(age__gte=35) # 过滤年龄 >= 35
filter(age__lte=35) # 过滤年龄 <= 35
filter(age__in=[18,35,50]) # 年龄是这三个之中一个的
filter(age__range=[18, 50] # 年龄在18-50之间的,包括首尾
filter(name__contains='s') # 名字包含's'的,区分大小写
filter(name__icontains='s') # 名字包含's'的,不区分大小写
filter(name__startswith='s') # 名字以's'开头的
filter(name__endswith='s') # 名字以's'结尾的
filter(name__isnull=False) # name字段不能为空的
filter(register_time__year='2020') # 注册时间是2020年的
filter(register_time__month='5') # 注册时间是5月的
filter(register_time__day='21') # 注册时间是21日的
一对多-外键增删改
一对多和一对一的操作基本一致。
增加:
# 方式1,实际字段publish_id,赋值id值
models.Book.objects.create(title='人生', price=99, publish_id=1)
# 方式2, 使用虚拟字段,赋值对象
publish_obj = models.Publish.objects.filter(pk=3).first()
models.Book.objects.create(title='三体', price=100, publish=publish_obj)
删除:默认级联删除
models.Publish.objects.filter(pk=1).delete()
修改:同增加一样,实际id或者对象
# 方式1:
models.Book.objects.filter(pk=2).update(publish_id=2)
# 方式2
pub_obj = models.Publish.objects.filter(pk=3).first()
models.Book.objects.filter(pk=2).update(publish=pub_obj)
多对多-外键增删改
多对多 增删改 就是在操作第三张关系表
增加多对多关系:add()
# 方式1,添加id值
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add(1) # 增加 1 1记录,即id为1的书绑定id为1的作者
book_obj.authors.add(2, 3) # 增加两条记录:1 2 和 1 3
# 方式2, 添加对象
book_obj = models.Book.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj1) # 增加1条记录
book_obj.authors.add(author_obj2, author_obj3) # 增加2条
补充总结:
book_obj.authors
表示到了第三种张关系表了。- 通过对象调用多对多关系表的虚拟字段进入第三张关系表
add()
给第三张关系表添加数据,括号内既可以传数字也可以传对象,并且都支持多个同时操作。
删除多对多关系:remove()
# 同理,通过book_obj.authors进入第三张虚拟关系表
# 同理,remove()括号内支持传数字或者对象,并且支持多个同时操作
book_obj.authors.remove(2)
book_obj.authors.remove(1, 3)
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.remove(author_obj2, author_obj3)
修改多对多关系:set()
# 方式1,使用数字
book_obj.authors.set([1, 2]) # 将该书的作者设置为 id为1和2的作者
book_obj.authors.set([3]) # 将该书的作者设置为 id为3的作者
# 方式2,使用对象
author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.set([author_obj2, author_obj3])
# 注意:
- set(),括号内必须传一个可迭代对象(列表或元祖), 支持数字或对象,支持同时多个
- 注意:set是一个覆盖操作,用当前新的关系覆盖之前的关系
补充:清空第三张关系表中的绑定关系
book_obj.authors.clear()
多表查询
正反向关系
# 正向:拥有外键的表 ------> 被关联表
# 反向:被关联表 ------> 拥有外键的表
- 一对一、一对多、多对多都适用正反向关系
正反向查询口诀:
正向查询按字段 # 多个结果时:'字段.all()'
反向查询按表名小写 # 多个结果时:'表名小写_set.all()'
子查询(基于对象的跨表查询)
子查询,就是分步骤查询的意思,先查询得到的结果作为后查选的条件。
#1.查询书籍主键为1的出版社(一对多,正向按外键字段publish)
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res.name)
#2.查询书籍主键为1的作者(多对多,正向按外键字段authors, 查询结果为多个需要.all())
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.authors.all()
print(res) # res是<QuerySet [<Author: Author object>, <Author: Author object>, <Author: Author object>]>
#3.查询作者jason的电话号码(一对一,正向查询按外键字段author_detail)
author_obj = models.Author.objects.filter(name='xliu').first()
res = author_obj.author_detail
print(res.phone)
总结1:子查询时,正向查询按外键字段,查找结果为多时需要在外键字段后再加.all()
;查询结果为一时直接获取数据对象,多个结果时为QuerySet对象
# 4.查询出版社是东方出版社出版的书(一对多,向查询按表明小写,查询结果为多个再加上_set.all())
pub_obj = models.Publish.objects.filter(name='东方出版社').first()
res = pub_obj.book_set.all()
print(res) # res是QuerySet对象
# 5.查询作者是xliu写过的书(多对多,反向查询按表明小写,查询结果为多个再加上_set.all())
author_obj = models.Author.objects.filter(name='xliu').first()
res = author_obj.book_set.all()
print(res) # res是QuerySet对象
# 6.查询手机号是110的作者姓名(一对一,反向查询按表明小写)
au_detail_obj = models.AuthorDetail.objects.filter(phone='110').first()
res = au_detail_obj.author
print(res.name)
总结2:子查询时,反向查询按表名小写,查找结果为多时需要在表名小写后再加_set.all()
;查询结果为一时直接获取数据对象,多个结果时为QuerySet对象
联表查询(基于双下划线的的跨表查询)
联表查询就是像MySQ里面SQL语句的联表查询一样,只不过在django的ORM里面使用基于双下划线联表查询(跨表查询)。
提示:双下划线联表查询也遵循正反向关系。
练习1:查询xliu的手机号和作者姓名:一对一
# 正向:在values()中使用'字段__'跨表取数据
res = models.Author.objects.filter(name='xliu').values('author_detail__phone', 'name')
print(res) # <QuerySet [{'author_detail__phone': 110, 'name': 'xliu'}]>
# 反向,使用'表明小写__'跨表取数据,且在filter也可以使用
res = models.AuthorDetail.objects.filter(author__name='xliu').values('phone', 'author__name')
print(res)
# 反向理解:拿作者姓名是xliu的作者详情
res = models.AuthorDetail.objects.filter(author__name='xliu')
练习2:查询xliu的手机号和作者姓名:一对多
# 正向:
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res) # <QuerySet [{'title': '人生', 'publish__name': '北方出版社'}]>
# 反向
res2 = models.Publish.objects.filter(book__pk=1).values('book__title', 'name')
print(res2) # <QuerySet [{'book__title': '人生', 'name': '北方出版社'}]>
练习3:查询书籍主键为1的作者姓名:多对多
# 正向
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res) # <QuerySet [{'authors__name': 'xliu'}, {'authors__name': 'jack'}, {'authors__name': 'lili'}]>
# 反向
res = models.Author.objects.filter(book__pk=1).values('name')
print(res) # <QuerySet [{'name': 'xliu'}, {'name': 'jack'}, {'name': 'lili'}]>
练习4:查询书籍主键是1的作者的手机号
# 正向
res1 = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
# 半反向
res2 = models.Author.objects.filter(book__pk=1).values('author_detail__phone')
# 全反向
res3 = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
# 结果
# res1 = <QuerySet [{'authors__author_detail__phone': 110}, {'authors__author_detail__phone': 120}, {'authors__author_detail__phone': 119}]>
# res2 = <QuerySet [{'author_detail__phone': 110}, {'author_detail__phone': 120}, {'author_detail__phone': 119}]>
# res3 = <QuerySet [{'phone': 110}, {'phone': 120}, {'phone': 119}]>
总结3:
- 双下划线跨表查询循序正反向口诀
- 正向查询时在
values()
中使用字段加双下划线跨表查询取值 - 反向查询时先在
filter()
中使用表名小写加双下划线跨表再在values()
中使用表名小写加双下划线跨表取值。 - 不论是一对一、多对一、多对多,查询的结果都是QuerySet对象。
聚合查询
聚合查询就是使用一些统计工具,如统计最大值最小值、平均值最大值这些聚合函数。
聚合函数的导入方式为:from django.db.models import Max, Min, Sum, Count, Avg
django的ORM中这些聚合函数需要在aggregate
方法内使用,且一般配合分组一块使用。
from app01 import models
from django.db.models import Max, Min, Sum, Avg, Count
#1 查询价格最低的书
res = models.Book.objects.aggregate(Min('price'))
print(res) # {'price__min': Decimal('72.00')}
#2 查询所有书的平均价格和书的个数
res = models.Book.objects.aggregate(avg_price=Avg('price'), total_counts=Count('pk'))
print(res) # {'avg_price': 117.5, 'total_counts': 4}
总结:
- 1、可以给聚合查询的参数起别名
- 2、
aggreagate
方法内可以同时使用多个聚合函数
分组查询
分组查询使用的方法是annotate()
annotate
的括号内什么也不写,默认按照models后面的表分组,即按照表的主键分组。
res = models.Book.objects.annotate()
print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>]>
annotate
括号内使用聚合函数,在分组字段内统计数据,且支持跨表统计。
# 1.统计每一本书的作者个数
res = models.Book.objects.annotate(author_counts=Count('authors')).values('title', 'author_counts')
# 上述方式等价于
res = models.Book.objects.annotate(author_counts=Count('authors__id')).values('title', 'author_counts')
# 2.统计不止一个作者的图书
res = models.Book.objects.annotate(author_counts=Count('authors')).filter(author_counts__gt=1).values('title', 'author_counts')
print(res) # <QuerySet [{'title': '人生', 'author_counts': 3}]>
# 3.查询每个作者出的书的总价格
res = models.Author.objects.annotate(total_price=Sum('book__price')).values('name', 'total_price')
print(res)
补充:分组后可以使用分组字段和聚合函数,如果还需要再values()内获取其他字段数据,可能或报错。这是因为MySQL数据库的原因,这是需要取消数据库表查询时的严格模式:
ONLY_FULL_GROUP_BY
通过values()
指定分组字段
# 4.查询每个出版社出版图书的个数
res = models.Book.objects.values('publish_id').annotate(book_counts=Count('pk')).values('publish__name', 'book_counts')
print(res) # <QuerySet [{'publish__name': '北方出版社', 'book_counts': 2}, {'publish__name': '南方出版社', 'book_counts': 1}, {'publish__name': '东方出版社', 'book_counts': 1}]>
# 等价于
res = models.Publish.objects.annotate(book_counts=Count('book__pk')).values('name', 'book_counts')
print(res) # <QuerySet [{'name': '北方出版社', 'book_counts': 2}, {'name': '南方出版社', 'book_counts': 1}, {'name': '东方出版社', 'book_counts': 1}]>
F查询&Q查询
F查询
- F查询可以获得表中某个字段的数据值,尤其适合表中两个字段之间的比较运算。
- 在操作字符类型的数据时,F不能直接做字符串的拼接,需要借助
Concat、Value
from django.db.models import F
# 1.查询卖出数大于库存数的书籍
res = models.Book.objects.filter(maichu__gt=F('kucun'))
# 2.将所有书籍的价格提升500块
models.Book.objects.update(price=F('price') + 500)
字符类型字段拼接操作时,需要借助Concat、Value
from django.db.models.functions import Concat
from django.db.models import F, Value
# 3.将所有书的名称后面加上爆款两个字
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
Q查询
- 多个条件筛选过滤时,
filter()
采用逻辑与的操作;设置逻辑或或者逻辑非则要借助Q查询 - Q()将条件包起来,多个Q直接用都好隔开默认依然是逻辑与的关系。
- 管道符
|
分割是逻辑或的操作,波浪号~
分割是逻辑非的操作。
import django.db.models import Q
# 1.查询卖出数大于100或者价格小于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关系
res = models.Book.objects.filter(~Q(maichu__gt=100)|Q(price__lt=600))
Q的高级用法:查询条件的左边也可以是字符串的形式,这样支持前端用户选择查询的指标。
q = Q() # 实例化Q对象
q.connector = 'or' # 修改连接关系
q.children.append(('maichu__gt', 100)) # 字符串的查询条件
q.children.append(('price__lt', 600))
res = models.Book.objects.filter(q) # filter内支持传q对象
print(res)
django开启事务
from django.db import transaction
try:
with transaction.atomic(): # 在with代码快内书写的所有orm操作都属于同一个事务
...
except Exception as e:
print(r)
...
补充:事务的四大特性:原子性、一致性、隔离性、持久性;事务回滚:rollback;事务确认:commit(复习mysql的事务操作及pymysql的事务操作)
数据库查询优化
储备知识:orm查询是惰性查询,当需要数据时才真正的到数据库取数据。仅仅一个查询语句是不会去数据库的,用到这个数据时才去数据库,比如打印操作时再去数据库。
res = models.Book.objects.all() # 此时不执行语句
print(res) # 当打印结果时才不得不去数据库查询数据
only & defer
当使用all()
查数据时,将所有数据一次性全部取出,后续再使用结果里面对象的数据时不会再去数据库。
res = models.Book.objects.all()
for d in res:
print(d.title) # 此时循环打印时无需去数据库
only()
方法则不同,only()
方法内可以指定查询的字段,返回的结果中再使用only
括号内的字段时不会再去数据库,使用其他的字段则再去数据库。
res = models.Book.objects.only('title')
for d in res:
print(d.title) # 无需去数据库
print(d.price) # 需要去数据库
补充:only括号内可以传入多个字段,用逗号隔开。
defer()
方法和only()
方法用法一样,但是查询的方式却完全相反。返回的结果中再使用defer
括号内的字段时需要再去数据库,而使用其他的字段则无需去数据库。
select_related & prefetch_related
这两个方法和跨表操作有关的优化方法。select_related
是联表操作;prefetch_related
是子查询操作;
之前学习的all()
当涉及跨表操作时数据库操作次数较多。
res = models.Book.objects.all()
for i in res:
print(i.publish.name) # 每循环一次就要走一次数据库查询
select_related
是联表操作,内部直接先将book与publish连起来,然后一次性将大表里面的所有数据全部封装成查询出来的对象;这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了。
res = models.Book.objects.select_related('publish')
for i in res:
print(i.publish.name) # 循环时不再走数据库
注意:select_related
括号内只能放一对一和一对多的外键字段、不支持多对多的。
prefetch_related
是子查询操作,将子查询查询出来的所有结果也给你封装到对象中,给你的感觉好像也是一次性搞定的。但其实本质上是子查询的多次操作完成的。
res = models.Book.objects.prefetch_related('publish')
for i in res:
print(i.publish.name)
总结:两种方法各有千秋,不能一概而论,要分情况讨论。
补充创建多对多关系表的三种方式
ORM提供了三种方式用于创建多对多关系表。
方式1:全自动(上文以介绍)
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操作:正反向查询、第三张关系表的操作add\remove\set\clear
# 缺点:第三张关系表的扩展性极差(没有办法额外添加字段...)
方式2:纯手动(不推荐)
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\remove\set\clear
方式3:半自动(推荐)
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)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
# 优点:扩展性强、可以使用ORM的正反向查询(推荐使用)
# 缺点:无法使用第三张关系表操作的:add\remove\set,不过clear方法可以用
through
参数的作用:告知ORM不要再自动创建第三张表,使用指定的关系表。through_fields
参数的作用:告知ORM两张表关联的字段。
# through_fields字段先后顺序
判断的本质:
通过第三张表查询对应的表,需要用到哪个字段就把哪个字段放前面
简单理解
当前表是谁,就把对应的关联字段放前面(因为多对多外键字段可以放在任意表中)