Django-Part4——模型层 [TOC]
测试脚本
当只是想测试django项目中的某一个.py文件内容,那么可以不用书写前后端交互的形式,而是直接写一个测试脚本即可。
脚本代码无论是写在应用下的tests.py,还是单独开设.py文件都可以。
django中的文件默认不会暴露出来,需要准备测试环境才能进行测试。
1 2 3 4 5 6 7 8 9 10 11 import osimport sysif __name__ == "__main__" : os.environ.setdefault("DJANGO_SETTINGS_MODULE" , "djangoProject.settings" ) import django django.setup()
Django ORM 创建模型表
对象关系映射(ORM),能够以面向对象的方式简便快捷地操作数据库。但是封装程度太高,有时候还是需要自己写SQL语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class User (models.Model ): id = models.AutoField(primary_key=True , verbose_name='主键' ) username = models.CharField(max_length=32 , verbose_name='用户名' ) password = models.IntegerField(verbose_name='密码' ) class Author (models.Model ): username = models.CharField(max_length=32 ) password = models.IntegerField()
每次只要修改了models.py中跟数据库相关的代码,就必须重新执行以下两条命令。
如果发现执行之后没有发生变化,则检查app是否已经注册
1 2 3 4 python manage.py makemigrations python manage.py migrate
字段的增/删/改
当数据表拥有记录时,不能增加非空字段:
在终端内直接给出默认值
退出终端程序,并修改代码为可以为空xxx = models.CharField(max_length=32, null=True)
退出终端程序,并修改代码设置默认值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 modelsclass 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) authors = models.ManyToManyField(to='Author' ) 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) 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' : username = request.POST.get('username' ) password = request.POST.get('password' ) from app01 import models result = models.User.objects.filter (username=username) print(type (result)) user_obj = reslt.first() 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 def userlist (request ): user_queryset = models.User.objects.all () 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 ): 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' ) edit_list.update(username=username, password=password) 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 ): delete_id = request.GET.get('user_id' ) res = models.User.objects.filter (id =delete_id).delete() print(res) user_obj = models.User.objects.filter (pk=delete_id).first() user_obj.delete() return redirect('/userlist/' )
一般都会添加一个is_delete字段做软删除,而不会直接删除。
必知必会13条
all():查询所有数据
filter():带有过滤条件的查询
get():直接拿数据对象,但是条件不存在直接报错
first():拿queryset里面第一个元素
last():拿queryset里面最后一个元素
values():可以指定获取的数据字段,相当于select name,age from …
1 2 res = models.User.objects.values('name' ,'age' ) print(res)
values_list():
1 2 res = models.User.objects.values_list('name' ,'age' ) print(res)
和values()仅仅只是封装格式不一样,sql查询语句是一样的。
distinct():去重
1 2 res = models.User.objects.values('name' ,'age' ).distinct() print(res)
去重一定要是(在虚表中)一模一样的数据。
例如:.all()的数据带有主键,因此无法去重
order_by():
1 2 3 res = models.User.objects.order_by('age' ) res = models.User.objects.order_by('-age' ) print(res)
reverse():
1 2 3 res = models.User.objects.all () res1 = models.User.objects.order_by('age' ).reverse() print(res, res1)
count():统计当前数据的个数
1 2 res = models.User.objects.count() print(res)
exclude():排除在外
1 2 res = models.User.objects.exclude(name='jason' ) print(res)
exists():基本用不到因为数据本身就自带布尔值 返回的是布尔值
1 2 res = models.User.objects.filter (pk=10 ).exists() print(res)
查看内部sql语句的方式
只有QuerySet类对象可以使用.query查看内部封装的sql语句
在配置文件中配置logging,查看所有的sql语句
需要在Python控制台中运行才能看见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 res = models.User.objects.values_list('name' , 'age' ) print(res.query) 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 res = models.User.objects.filter (age__gt=35 ) print(res) 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) res = models.User.objects.filter (age__in=[18 , 32 , 40 ]) print(res) res = models.User.objects.filter (age__range=[18 , 40 ]) print(res) res = models.User.objects.filter (name__contains='s' ) print(res) 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) 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 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) models.Publish.objects.filter (pk=1 ).delete() 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 book_obj = models.Book.objects.filter (pk=1 ).first() print(book_obj.authors) book_obj.authors.add(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) 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) 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]) 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 book_obj = models.Book.objects.filter (pk=1 ).first() res = book_obj.publish print(res.name) book_obj = models.Book.objects.filter (pk=1 ).first() res = book_obj.authors print(res) print(res.all ()) print(models.Book.objects.filter (pk=3 ).first().authors.all ()) author_obj = models.Author.objects.filter (name="jason" ).first() res = author_obj.author_detail print(res.phone, res.addr) publish_obj = models.Publish.objects.filter (name='东方出版社' ).first() res = publish_obj.book_set res = publish_obj.book_set.all () print(res) author_obj = models.Author.objects.filter (name='jason' ).first() res = author_obj.book_set res = author_obj.book_set.all () print(res) 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 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' ) res = models.AuthorDetail.objects.filter (author__name='jason' ).values('phone' , 'author__name' ) print(res) 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) res = models.Book.objects.filter (pk=1 ).values('authors__name' ) print(res) res = models.Author.objects.filter (book__id=1 ).values('name' ) print(res) 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 modelsfrom django.db.models import Max,Min,Sum,Count,Avgres = models.Book.objects.aggregate(Max('price' ),Min('price' ),Sum('price' ),Count('pk' ),Avg('price' )) print(res)
分组查询(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 res = models.Book.objects.annotate(author_num=Count("author__pk" )).values("author_num" , 'title' , 'author_num' ) print(res) res = models.Publish.objects.annotate(min_price=Min('book__price' )).values('name' , 'min_price' ) print(res) res = models.Book.objects.annotate(author_num=Count("author_id" )) \ .filter (author__num__gt=1 ) \ .values('title' , 'author_num' ) print(res) res = models.Author.objects.annotate(sum_price=Sum('book__price' )).values('name' , "sum_price" ) print(res) models.Book.objects.values('price' ).annotate()
F与Q查询 F
能够直接获取到表中某个字段对应的数据
F不能够直接操作字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.db.models import F res = models.Book.objects.filter (sale__gt=F('store' )) print(res) models.Book.objects.update(price=F('price' ) + 500 ) from django.db.models.functions import Concat from django.db.models import Value 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 from django.db.models import Qres = models.Book.objects.filter (Q(maichu__gt=100 ), Q(price__lt=600 )) res = models.Book.objects.filter (Q(maichu__gt=100 ) | Q(price__lt=600 )) res = models.Book.objects.filter (~Q(maichu__gt=100 ) | Q(price__lt=600 )) print(res) q = Q() q.connector = 'or' q.children.append(('maichu__gt' , 100 )) q.children.append(('price__lt' , 600 )) res = models.Book.objects.filter (q) print(res)
django中如何开启事务
1 2 3 4 5 6 7 8 9 10 from django.db import transactiontry : with transaction.atomic(): ... except Exception as e: print(e)
orm字段及参数 常用字段及参数
AutoField:主键字段
一般会自动创建,不需要手动设置
CharField:varchar
verbose_name=字段的注释
max_length=长度
null=True可以为空
blank=True可以存空字符串(这两个有区别)
unique=True唯一
IntegerField:int
BigIntegerField:bigint
DecimalField
max_digits=允许的最大位数
decimal_places=小数的最大位数
EmailFiled:varchar(254)
DateField:date
DateTimeField:datetime
auto_now=每次修改数据时更新当前时间
auto_now_add=只在创建数据时记录当前时间
BooleanField:Field
该字段传布尔值(False/True),数据库里面存0/1
TextField:Field
该字段可以用来存大段内容(文章、博客…),没有字数限制
FileField:Field
更多字段,直接参考博客: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 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 )
外键字段及参数
unique=True
ForeignKey(unique=True)等价于OneToOneField()
db_index=True
为此字段设置索引
to
设置要关联的表
to_field
设置要关联的表的字段,默认关联另外一张的主键字段,并且自动添加_id后缀
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
related_name
反向查询使用的字段名,即代替 字段名__set
limit_choices_to
限制选择条件
数据库查询优化 all()
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' )) res = models.Book.objects.only('title' ) print(res) for i in res: print(i.title) print(i.price)
defer()
defer与only刚好相反
defer括号内放的字段不在查询出来的对象里面,查询该字段需要重新走数据库
而如果查询的是非括号内的字段,则不需要走数据库了
1 2 3 res = models.Book.objects.defer('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' ) for i in res: print(i.publish.name)
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 ): for i in range (10000 ): models.Book.objects.create(title='第%s本书' % i) 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 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 ) 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) print(user_obj.get_gender_display()) user_obj = models.User.objects.filter (pk=4 ).first() print(user_obj.get_gender_display())
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 )
纯手动
不建议使用该方式。
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 ) class Book2Author (models.Model ): book = models.ForeignKey(to='Book' ) author = models.ForeignKey(to='Author' )