django之模型层2
一、正反向进阶操作
# 就是之前的正反向是根据已知的条件点出表查询 现在我需要不点已知条件的表而是点其他的来查询结果 '''正反向查询进阶操作''' # 1.查询主键为1的书籍对应的出版社名称及书名 # 如果不让我们点击已知条件的表 而题目当中除了书籍表就剩下一个出版社的表了 # 然后出版社查询书籍的话 那就是反向查询 书名小写 res = models.Publish.objects.filter(book__pk=1).values('book__title','name') print(res) # <QuerySet [{'book__title': 'python', 'name': '北方出版社'}]> '''filter中就是放查询条件的 不用字符串 因为是反向所以表名小写即可 values中获取具体的值需要字符串 因为书名在书籍表中 所以需要去书籍表中获取 又因为是反向所以书名小写即可 因为前面点击的就是出版社表 所以直接写出版社表中的字段名即可''' # 2.查询主键为3的书籍对应的作者姓名及书名 # 题目中就只有书籍表和作者表 我们使用作者表查询 作者表查询书籍表 是反向 res = models.Author.objects.filter(book__pk=3).values('book__title', 'name') print(res) # <QuerySet [{'book__title': 'golang', 'name': 'jason'}, {'book__title': 'golang', 'name': 'tony'}]> # 3.查询jason的作者的电话号码和地址 # 作者详情表查询作者表是反向 res = models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'addr') print(res) # <QuerySet [{'phone': 111, 'addr': '上海'}]> # 4.查询南方出版社出版的书籍名称和价格 # 已知条件是出版社 那么我们可以使用书籍表查询 是正向 res = models.Book.objects.filter(publish__name='南方出版社').values('title', 'price') print(res) # <QuerySet [{'title': 'python', 'price': Decimal('333333.00')}]> '''filter中是放查询条件的 因为是正向 所以只需要写外键字段即可 写外键字段其实就跳到了该表 然后在使用双下划线就可以使用该表中的字段 values中是获取具体的值 如果查询的结果不在前面查询的表 就需要使用外键或表名小写 如果是前面的查询的表 直接写该表的字段名即可 ''' # 5.查询jason写过的书的名称和日期 # 书籍表查询作者表 是正向 res = models.Book.objects.filter(authors__name='jason').values('title', 'publish_time') print(res)
# <QuerySet [{'title': 'java', 'publish_time': datetime.datetime(2022, 7, 5, 17, 33, 42, tzinfo=datetime.timezone.utc)}, {'title': 'golang', 'publish_time': datetime.datetime(2022, 7, 5, 17, 34, 2, tzinfo=datetime.timezone.utc)}]> # 6.查询电话是111的作者姓名和年龄 # 作者表查询作者详情表是方向 res = models.Author.objects.filter(author_detail__phone=111).values('name', 'age') print(res) # <QuerySet [{'name': 'jason', 'age': 18}]> # 7.查询主键为1的书籍对应的作者电话号码和书籍名称 # 如果题目中有过个表我们可以点击任意一个表都可以找到结果 # 1.使用作者详情表 作者详情表与书籍表是没有外键链接的所以需要去作者表中查询然后再去书籍表 # 作者详情查询作者表 反向 作者表查询书籍表 反向 # res = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone', 'author__name','author__book__title') # print(res) # <QuerySet [{'phone': 222, 'author__name': 'kevin', 'author__book__title': 'python'}]> '''filter中只要表有关系都可以一直使用双下划线 然后根据前面查询的表判断时正向还是反向 使用表名小写还是外键字段即可 values中'phone'是在作者详情表中的所以可以直接使用字段名称 作者名不在作者详情表中所以需要使用表名小写 书籍名称是通过作者表名称小写在小写书籍表名称才拿到书籍名称 ''' # 2.使用作者表查询 # res = models.Author.objects.filter(book__pk=1).values('author_detail__phone', 'name', 'book__title') # print(res) # <QuerySet [{'author_detail__phone': 222, 'name': 'kevin', 'book__title': 'python'}]> '''values中因为作者表查询作者详情表是正向 所以只需要写外键字段即可''' # 3.使用书籍表 res = models.Book.objects.filter(pk=1).values('authors__name', 'authors__author_detail__phone', 'title') print(res) # <QuerySet [{'authors__name': 'kevin', 'authors__author_detail__phone': 222, 'title': 'python'}]>
二、聚合查询
# mysql中有个聚合函数 但是mysql的聚合函数需要分组之后搭配使用 max、min、sum、avg、count # 在ORM中叫聚合查询 聚合查询可以直接对一个表使用 不用分组也可以使用 在ORM中需要导入模块 from django.db.models import Max, Min, Sum, Avg, Count # 没有分组之前需要使用关键字 aggregate eg: res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk')) print(res) # {'price__max': Decimal('444444.00'), 'price__min': Decimal('222222.00'), 'price__sum': Decimal('999999.00'), 'price__avg': Decimal('333333.000000'), 'pk__count': 3} # 就是求书籍表汇总价格最高、价格最低、价格总和、平均价格、计算基本书籍
三、分组查询
# 分组查询关键字是:annotate '''因为分组有个特性 就是按照什么分组那么只能获取该字段 其他字段只能使用其他方法 但是我们不想要这个特性 我们想要获取其他字段 我们可以忽略这个特性 直接去mysql中 将sql_mode中only_full_group_by配置移除即可 show variables like '%mode%'; set global sql_mode = 'STRICT_TRANS_TABLES'; 如果有使用上面代码即可 没有不用管''' # 示例1:统计每一本书的作者个数 res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num') print(res) # <QuerySet [{'title': 'python', 'author_num': 1}, {'title': 'java', 'author_num': 3}, {'title': 'golang', 'author_num': 2}]> # 我们按照书籍分组 然后按照作者计算个数 '''1.这是按照整条数据分组 models.Book.objects.annotate() 就是按照一个个书籍记录分组 2.我们也可以按照表中具体的字段分组 models.book.objects.values('title').annotate() 因为values在annotate前面所以现在values不是在拿值了 而是按照values中的字段分组 ''' # 就是每个出版社有几本书 res1 = models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id', 'book_num') print(res1) # <QuerySet [{'publish_id': 1, 'book_num': 1}, {'publish_id': 2, 'book_num': 1}, {'publish_id': 3, 'book_num': 1}]> # 示例2:统计出每个出版社卖的最便宜的书的价格 res = models.Publish.objects.annotate(book_min=Min('book__price')).values('name', 'book_min') print(res) # <QuerySet [{'name': '北方出版社', 'book_min': Decimal('222222.00')}, {'name': '南方出版社', 'book_min': Decimal('333333.00')}, {'name': '东方出版社', 'book_min': Decimal('444444.00')}]> # 就是按照出版社分组 然后到书籍表中计算最便宜的价格 # 示例3:统计不止一个作者的图书 # 就是一本书的作者要大于1个 res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num') print(res) # <QuerySet [{'title': 'java', 'author_num': 3}, {'title': 'golang', 'author_num': 2}]> # 首先按照书籍表分组 然后拿到作者的个数 在使用filter筛选大于1的个数即可 '''filter在annotate前面则是where 在annotate后面则是having''' # 示例4:查询各个作者出的书的总价格 res = models.Author.objects.annotate(author_book_sum=Sum('book__price')).values('name', 'author_book_sum') print(res) # <QuerySet [{'name': 'kevin', 'author_book_sum': Decimal('555555.00')}, {'name': 'jason', 'author_book_sum': Decimal('777777.00')}, {'name': 'tony', 'author_book_sum': Decimal('777777.00')}] # 首先按照作者表分组 然后去书籍表计算作者每本书的总价
四、F查询
# 就比如我们现在需要查询库存比卖出多的书籍 而库存和卖出字段都在书籍表中这个时候我们可以使用F查询 # F查询: 查询条件不是自定义的 而是来自与表中的其他字段 # F查询需要导入模块 from django.db.models import F # 1.查询库存数大于卖出数的书籍 # res = models.Book.objects.filter(kucun__gt=maicu).values('title') # 如果直接使用字段来进行比较的话是会报错的 所以需要F查询 F查询就是把括号中的字段的值拿出来比较 res = models.Book.objects.filter(kucun__gt=F('maicu')).values('title') print(res) # <QuerySet [{'title': 'java'}, {'title': 'golang'}]> # 2.将所有书籍的价格上涨1000块 models.Book.objects.update(price=F('price') + 1000) # F查询就是把括号内的字段的值拿出来再加1000 # 3.将主键值为1书籍名称加上爆款后缀 # models.Book.objects.filter(pk=1).update(title=F('title') + '爆款') # 因为F查询是不能拼接不了字符串的 所以需要使用其他方法一起使用 from django.db.models.functions import Concat from django.db.models import Value # 需要使用Concat和Value方法 models.Book.objects.filter(pk=1).update(title=Concat(F('title'), Value('爆款'))) '''就是filter中写上条件 使用update修改 update中需要写上修改的字段名称 然后使用F查询将指定字段拿出来与 Value中的字符拼接即可 '''
五、Q查询
# 现在我们使用查询filter的时候中是可以写多个条件的 但是这个多个条件是and关系 那么如何修改为or和not关系呢? eg : models.Book.objects.filter(pk=1, publish_id=3) # 上面的条件必须都满足才行 但是如果是or关系怎么修改 '''那么可以使用Q查询: Q查询可以改变filter括号内多个条件的逻辑运算符 还可以将查询条件的字段改为字符串形式''' from django.db.models import Q # Q查询可以改变filter括号内多个条件的逻辑运算符 res = models.Book.objects.filter(Q(pk=1), Q(publish_id=3)) res = models.Book.objects.filter(Q(pk=1) | Q(publish_id=3)) res = models.Book.objects.filter(~Q(pk=1) | Q(publish_id=3)) print(res) '''将查询条件放到Q里面即可 逗号隔开还是and关系 管道符是或关系 在Q前面加上小波浪号是not关系 ''' # Q查询还可以将查询条件的字段改为字符串形式 models.Book.objects.filter(pk=1) models.Book.objects.filter('pk'=1) '''就是现在我们想要将pk按照字符串的形式'pk'筛选数据 但是字符串和变量名的意思是不一样的 如果可以按照字符串的形式筛选条件的话 就可以实现用户的交互 用户输入什么就用这个条件查询 ''' q_obj = Q() # 我们需要先创建一个Q对象 q_obj.connector = 'or' # Q查询默认是and关系 可以修改为or关系 q_obj.children.append(('pk', 1)) # 然后将筛选条件添加到Q对象中 是一个元祖 q_obj.children.append(('publish_id', 3)) # 可以添加多个 res = models.Book.objects.filter(q_obj) # 然后将Q对象放入到filter括号内即可 print(res) # <QuerySet [<Book: 书籍对象:python爆款>, <Book: 书籍对象:golang>]> # 这个时候我们就可以使用字符串来当做筛选条件了
六、ORM查询优化
1.only与defer优化
'''ORM查询默认是惰性查询 当orm语句只用在后续真正使用的时候才会执行''' # eg: res = models.Book.objects.filter(pk=1) # 当我们只写了ORM语句的时候后续也不需要ORM就不会执行 不会走数据库减轻数据库的压力 print(res) # 只有后续代码需要使用ORM的时候 才会执行才会走数据库 # orm还自带分页功能limit # 这样就算得出的结果很多也可以使用分页减轻数据库的压力和服务端的压力 res = models.Book.objects.filter() # 当我们筛选出条件的时候 得到的是一个Queryset数据对象 for obj in res: # 通过for循环我们就拿到了一个个书籍对象 print(obj.title) # 然后点title就可以拿到所有的书籍名称 res = models.Book.objects.values('title', 'price') print(res) # 这个时候是一列表套字典 也可以拿到书籍的名称 for i in res: print(i.get('title')) # 但是只能使用get方法拿到书籍名称 print(i.title) # 通过点的方法是拿不到书籍名称的 因为字典 # 但是现在我们就像通过点的方式拿到数据 不想操作字典可该怎么办呢? # ORM查询优化有四种 '''only和defer''' # 我们可以使用only查询 res = models.Book.objects.only('title', 'price') # 也就是把一个个书籍对象而且只有括号内两个字段 for obj in res: print(obj.title) # 这个时候就可以点的方法拿到书籍名称了 print(obj.price) # 只有括号内的字段名不会再走数据库查询 print(obj.publish_time) # 也可以点括号内没有的字段 '''only方法就是把括号内字段名封装成一个个数据对象 对象在点击的时候不会再走数据库查询 对象只有点存在就只会走一次数据库 但是对象也可以点击括号内没有的字段 只不过每次都会走数据库查询 ''' # defer查询 res = models.Book.objects.defer('title') for obj in res: print(obj.title) '''defer查询刚好与only相反 defer只有点括号内没有的字段才不会再走数据库 对象点不存在的才会走一次数据库 defer点括号内存在的字段就会走数据库 点存在的查一次就要走一次 '''
2.select_related与prefetch_related优化
'''select_related和prefetch_related''' # 现在我们要查询每本书的出版社名称 # res = models.Book.objects.filter() # for i in res: # print(i.publish.name) # 这个时候会频繁的走数据库 这样很不好如果数据量很大的话数据库很容易蹦 # 我们可以使用select_related优化 相当于链表操作 res = models.Book.objects.select_related('publish') print(res) for obj in res: print(obj.publish.name) '''相当于把两张表链表操作 链成一张大表 然后在从这张大表提取数据 select_related只能接收外键字段(一对一和一对多)字段 自动链表 得出的数据对象在点击表中数据的时候都不会再走数据库查询''' """ prefetch_related底层其实是子查询 将查询之后的结果也一次性封装到数据对象中 用户在使用的时候是感觉不出来的 """ res = models.Book.objects.prefetch_related('publish') for obj in res: print(obj.publish.name)
七、事物操作
事务:ACID 事务隔离级别:脏读 幻读 不可重复读 ... 原生SQL: start transaction\rollback\commit\savepoint from django.db import transaction try: with transaction.atomic(): pass # 多条ORM语句 except Exception as e: # 如果报错执行的语句 print(e)
八、模型层常见字段
AutoField() CharField() IntegerField() BigIntegerField() DateField() DateTimeField() DecimalField() EmailField() BooleanField() # 传布尔值存数字0或1 TextField() # 存储大段文本 FileField() # 存储文件数据 自动找指定位置存储 字段存具体路径 ForeignKey() OneToOneField() ManyToManyField() # ORM还支持自定义字段 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): return 'char(%s)' % self.max_length info = MyCharField(max_length=32)
九、ORM常见的字段参数
max_length verboses_name auto_now auto_now_add null default max_digits decimal_places unique=True db_index=True choices # 当字段数据的可能性是可以完全列举出来的时候 应该考虑使用该参数 eg:性别、学历、成绩 ... class UserInfo(models.Model): username = models.CharField(max_length=32) gender_choice = ( # 先写好可能出现的情况 (1, '男性'), (2, '女性'), (3, '其他'), ) gender = models.IntegerField(choices=gender_choice) # 然后真正的字段使用整型字段然后使用choices等于上述字段即可 obj = models.UserInfo.objects.create(username='jason', gender=1) # 创建的时候直接写绑定号的数字 print(obj.username) # jason print(obj.gender) # 1 获取数据是真是数据 想要获取绑定的数据需要关键字 print(obj.get_gender_display()) # 男性 # 创建的时候还可以写没有的数字 就是获取的时候真是数据和绑定的都是数字 # 外键字段 to to_field related_name on_delete ''' 创建外键字段的时候还有下方这些 1、models.CASCADE 级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除 2、models.SET_NULL 当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空 3、models.PROTECT 当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除 4、models.SET_DEFAULT 当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值 5、models.SET() 当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数 6、models.DO_NOTHING 什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似'''
十、多对多创建的三种方式
1.自动创建 authors = models.ManyToManyField(to='Author') 优点:第三张表自动创建 缺点:第三张表扩展性差 2.手动创建 class Book(models.Model): pass class Author(models.Model): pass class Book2Author(models.Model): book_id = models.ForeignKey(to="Book") author_id = models.ForeignKey(to="Author") 优点:第三张表扩展性强 缺点:无法使用正反向查询以及多对多四个方法 3.半自动创建 class Book(models.Model): authors = models.ManyToManyField(to='Author', through='Book2Author' through_fields=('book_id','author_id') ) # 前面的字段是根据当前表是哪个就哪个字段在前面 如果外键再短在Author表中那么author_id就要在前面 class Author(models.Model): pass class Book2Author(models.Model): book_id = models.ForeignKey(to="Book") author_id = models.ForeignKey(to="Author") 优点:扩展性强并且支持正反向查询 缺点:无法使用多对多四个方法
十一、作业
1.路由层
# 首页 path('home/', views.home, name='home_view'), # 图书展示 path('book/', views.book, name='book_view'), # 添加书籍 path('bookadd/', views.book_add, name='book_add_view'), # 修改书籍 path('bookedit/<int:edit_id>', views.book_edit, name='book_edit_view'), # 删除书籍 path('delete/<int:delete_id>', views.delete, name='delete_view'),
2.视图层
def home(request): return render(request, 'home.html') def book(request): # 获取书籍对象 queryset_book = models.Book.objects.filter() return render(request, 'book.html', locals()) def book_add(request): # 1.判断请求格式 if request.method == 'POST': # 2.获取用户输入信息 title = request.POST.get('title') price = request.POST.get('price') publish_time = request.POST.get('publish_time') publish_id = request.POST.get('publish_id') author_list = request.POST.getlist('author_list') if title and price and publish_time and publish_id and author_list: # 3.创建书籍 book_obj = models.Book.objects.create(title=title, price=price, publish_id=publish_id, publish_time=publish_time) # 4.创建书籍与作者的绑定关系 book_obj.authors.add(*author_list) return redirect('book_view') return HttpResponse('请全部填写') # 获取出版社对象和作者对象 queryset_publish = models.Publish.objects.filter() queryset_author = models.Author.objects.filter() return render(request, 'book_add.html', locals()) def book_edit(request, edit_id): # 根据用户的选择获取书籍对象 book_obj = models.Book.objects.filter(pk=edit_id).first() # 1.判断请求格式 if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') publish_time = request.POST.get('publish_time') publish_id = request.POST.get('publish_id') author_list = request.POST.getlist('author_list') if title and price and publish_time and publish_id and author_list: # 3.创建书籍 models.Book.objects.filter(pk=edit_id).update(title=title, price=price, publish_id=publish_id, publish_time=publish_time) # 4.创建书籍与作者的绑定关系 book_obj.authors.set(author_list) return redirect('book_view') return HttpResponse('请全部填写') # 获取作者对象和出版社对象 queryset_author = models.Author.objects.filter() queryset_publish = models.Publish.objects.filter() return render(request, 'edit.html', locals()) def delete(request, delete_id): models.Book.objects.filter(pk=delete_id).delete() return redirect('book_view')
3.book.html
{% extends 'home.html' %} {% block centend %} <div> <h1 class="text-center">数据展示</h1> <a href="{% url 'book_add_view' %}" class="btn btn-primary btn-xs">新增书籍</a> <table class="table table-hover table-striped"> <thead> <tr> <th>id</th> <th>书名</th> <th>价格</th> <th>出版时间</th> <th>出版社名称</th> <th>作者</th> <th class="text-center">操作</th> </tr> </thead> <tbody> {% for book_obj in queryset_book %} <tr> <td>{{ forloop.counter }}</td> <td>{{ book_obj.title }}</td> <td>{{ book_obj.price }}</td> <td>{{ book_obj.publish_time|date:'Y-m-d H:s:i' }}</td> <td>{{ book_obj.publish.name }}</td> <td> {% for author_obj in book_obj.authors.all %} # 如果循环的是最后一个那么就在后面加上逗号 {% if forloop.last %} {{ author_obj.name }} {% else %} {{ author_obj.name }}, {% endif %} {% endfor %} </td> <td class="text-center"> <a href="{% url 'book_edit_view' book_obj.id %}" class="btn btn-success btn-xs">编辑</a> <a href="{% url 'delete_view' book_obj.id %}" class="btn btn-danger btn-xs">删除</a> </td> </tr> {% endfor %} </tbody> </table> </div> {% endblock %}
4.bookadd.html
{% extends 'home.html' %} {% block centend %} <div> <h1 class="text-center">新增书籍</h1> <form action="" method="post"> <p>title: <input type="text" name="title" class="form-control"></p> <p>price: <input type="text" name="price" class="form-control"></p> <p>publish_time: <input type="datetime-local" name="publish_time" class="form-control"></p> <p> <select name="publish_id" id="" class="form-control"> {% for publish_obj in queryset_publish %} <option value="{{ publish_obj.id }}">{{ publish_obj.name }}</option> {% endfor %} </select> </p> <p> <select name="author_list" id="" class="form-control" multiple> {% for author_obj in queryset_author %} # 显示到页面上让用户选择 <option value="{{ author_obj.id }}">{{ author_obj.name }}</option> {% endfor %} </select> </p> <input type="submit" value="提交" class="btn btn-success btn-block"> </form> </div> {% endblock %}
5.bookedit.html
{% extends 'home.html' %} {% block centend %} <div> <h1 class="text-center">修改书籍</h1> <form action="" method="post"> <p>title: <input type="text" name="title" class="form-control" value="{{ book_obj.title }}"></p> <p>price: <input type="text" name="price" class="form-control" value="{{ book_obj.price }}"></p> <p>publish_time: <input type="datetime-local" name="publish_time" class="form-control"></p> <p> <select name="publish_id" id="" class="form-control"> {% for publish_obj in queryset_publish %} {% if publish_obj == book_obj.publish %} <option value="{{ publish_obj.id }}" selected>{{ publish_obj.name }}</option> {% else %} <option value="{{ publish_obj.id }}">{{ publish_obj.name }}</option> {% endif %} {% endfor %} </select> </p> <p> <select name="author_list" id="" class="form-control" multiple> {% for author_obj in queryset_author %} {% if author_obj in book_obj.authors.all %} <option value="{{ author_obj.id }}" selected>{{ author_obj.name }}</option> {% else %} <option value="{{ author_obj.id }}">{{ author_obj.name }}</option> {% endif %} {% endfor %} </select> </p> <input type="submit" value="提交" class="btn btn-success btn-block"> </form> </div> {% endblock %}