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是子查询操作;

之前学习的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字段先后顺序

判断的本质:
        通过第三张表查询对应的表,需要用到哪个字段就把哪个字段放前面
简单理解
        当前表是谁,就把对应的关联字段放前面(因为多对多外键字段可以放在任意表中)
posted @ 2020-05-29 19:25  the3times  阅读(233)  评论(0编辑  收藏  举报