Django学习day09随堂笔记
阅读原文时间:2022年05月12日阅读:1

每日测验

"""
今日考题:
1.choices参数的应用场景有哪些,如何获取该字段的值
2.django是什么模型的框架,简述MTV与MVC模型
3.多对多表关系有几种创建方式,各有什么特点?
4.什么是ajax,请手写出ajax的基本语法结构及重要参数含义
"""

昨日内容回顾

在django部分,如果你写代码的时候报错了

"""
1.pycharm窗口提示,前端console界面
2.仔细核对代码(单词写错写多)
3.浏览器缓存没有清除
4.端口号可能冲突了,一直跑的是之前的项目
5.重启你的计算机
(自己学会百度搜索问题,出现bug千万不要慌 要冷静分析 自己想出一个排查思路)
"""
  • 图书管理系统图书的增删改查

    """
    将你之前所学的知识点尽量全部都用上(每个人都应该自己能够独立的完成)
    """
  • choices参数

    """
    在设计表的时候 针对可以列举完全的可能性字段
    一般都是用choices参数
    """
    gender_choices = ((1,'male'),(2,'female'),(3,'others'))
    gender = models.IntegerField()
    
    # 针对具有choices参数的字段 存储数据的时候还是按照字段本身的数据类型存储没有其他的约束,但是如果你存的字段在你列举的范围内 那么可以自动获取对应关系
    user_obj.gender  # 数字
    user_obj.get_gender_display()  # 固定格式 get_choices参数字段名_display()
    """有对应关系就拿对应关系,没有则还是数据本身不会报错"""
    
    # 自己看看一些模型表的设计 体会choices参数的使用
  • MTV与MVC模型

    """
    django自称是MTV框架,但是它的本质其实还是MVC
    """
  • 多对多三种创建方式

    # 全自动(本地测试用这个比较方便)
        authors = models.ManyToManyField(to="Author")
        # 自动创建第三张关系表 扩展性差  add,remove,clear,set
    
    # 纯手动(基本不用)
        # 自己创建第三张表 自己建外键
      # 扩展性高 但是无法利用orm简便的查询方法了
    
    # 半自动(实际项目用这个,扩展性好)
    class Book(models.Model):
        authors = models.ManyToManyField(to="Author",
                                       through='Book2Author',
                                       through_fields=('book','author')
                                      )
    class Author(models.Model):
        books = models.ManyToManyField(to="Author",
                                       through='Book2Author',
                                       through_fields=('author','book')
                                      )
    class Book2Author(models.Model):
      book = 外键()
      author = 外键()
      """
      第三张关系表 还是需要你自己手动建
      但是你可以告诉django orm可以使用orm简便查询方法
      """
  • ajax

    """
    异步提交
    局部刷新
    
    参考案例:github注册实时获取用户名发送给后端确认并动态展示校验结果(页面不刷新)
    复习:input框实时监测事件    input事件
    
    我们学的是jQuery版本的ajax,所以你必须要确保html页面已经提前加载了jQuery
    """
    # ajax基本语法
    $.ajax({
      url:'',  # 朝后端哪个地址发送 跟action三种书写方式一致
      type:'get/post',  # 提交方式 默认get 跟form表单method参数一致
      data:{'username':'jason','password':123},  # 要发送的数据
      success:function(args){
        # 异步回调处理机制
      }
    })
    """
    当你在利用ajax进行前后端交互的时候
    后端无论返回什么都只会被回调函数接受 而不再影响这个浏览器页面了
    """
    
    # 扩展 参数  代码发布项目还会涉及
    dataType:'JSON'
    """
    当后端是以HttpResponse返回的json格式的数据
    默认是不会自动反序列化的
        1.自己手动JSON.parse()
        2.配置dataType参数
    """
    
    # 结论:写ajax的时候 你可以直接将dataType参数加上 以防万一 或者后端就用JsonResonse
    $.ajax({
      url:'',  # 朝后端哪个地址发送 跟action三种书写方式一致
      type:'get/post',  # 提交方式 默认get 跟form表单method参数一致
      dataType:'JSON',
      data:{'username':'jason','password':123},  # 要发送的数据
      success:function(args){
        # 异步回调处理机制
      }
    })

今日内容概要

  • 前后端传输数据的编码格式(contentType)
  • ajax发送json格式数据
  • ajax发送文件数据
  • ajax结合sweetalert实现删除按钮的二次确认
  • django自带的序列化组件(drf做铺垫)
  • 批量插入
  • 自己写一个分页器(只需要掌握分页器的推导思路即可)
  • 自定义分页器的拷贝及使用

今日内容详细

前后端传输数据的编码格式(contentType)

# 我们主要研究post请求数据的编码格式
"""
get请求数据就是直接放在url后面的
url?username=jason&password=123
"""

# 可以朝后端发送post请求的方式
    """
    1.form表单
    2.ajax请求
    """

"""
前后端传输数据的编码格式
    urlencoded

    formdata

    json
"""
# 研究form表单
    默认的数据编码格式是urlencoded
  数据格式:username=jason&password=123
  django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
      username=jason&password=123 >>> request.POST

  如果你把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中而将文件解析到request.FILES中

  form表单是没有办法发送json格式数据的

# 研究ajax
    默认的编码格式也是urlencoded
  数据格式:username=jason&age=20
    django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中
      username=jason&age=20   >>> request.POST

ajax发送json格式数据

"""
前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的
不要骗人家!!!

{"username":"jason","age":25}
    在request.POST里面肯定找不到

    django针对json格式的数据 不会做任何的处理 

request对象方法补充
    request.is_ajax()
        判断当前请求是否是ajax请求 返回布尔值

"""
#前端
<script>
    $('#d1').click(function (){
        $.ajax({
            url:'',
            type:'post',
            {#data:{'username':'kk',age:22},#}
            data:JSON.stringify({'username':'kk',age:22}),
            contentType:'application/json',
            success:function (){

            }
        })
    })
</script>

#后端
#ajax发送json格式数据
def ab_json(request):
    #print(request.is_ajax())#判断当前请求是否为ajax
    if request.is_ajax():
      print(request.body) #b'{"username":"kk","age":22}' 拿到前端发送的二进制json格式的数据
      #针对json格式数据需要你自己手动处理
      json_bytes = request.body

      #json_str = json_bytes.decode('utf-8') #先将二进制json数据转换成字符串
      #json_dict = json.loads(json_str) #loads()是把其他类型的对象转为Python对象
      #print(json_dict, type(json_dict))  # {'username': 'kk', 'age': 22} <class 'dict'>

      #json.loads括号内如果传入了一个二进制格式的数据,那么内部可以自动解码然后再反序列化
      json_dict = json.loads(json_bytes)
      print(json_dict, type(json_dict)) #{'username': 'kk', 'age': 22} <class 'dict'>
    return render(request,'ab_json.html')

"""
ajax发送json格式数据需要注意点
    1.前端ajax中contentType参数指定成:application/json
    2.数据是真正的json格式数据
    3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
"""

ajax发送文件

"""
ajax发送文件需要借助于js内置对象FormData
"""
#前端
<script>
    //点击按钮朝后端发送普通键值对和文件数据
    $('#d4').click(function (){
        // 1.需要先利用FormData生成一个内置对象
        let formDateObj = new FormData();
        //2.添加普通的键值对
        formDateObj.append('username',$('#d1').val())
        formDateObj.append('password',$('#d2').val())
        //3.添加文件对象
        formDateObj.append('myfile',$('#d3')[0].files[0])
        //4.将对象基于ajax发送给后端
        $.ajax({
            url:'',
            type:'post',
            data:formDateObj,//直接将对象放入data后面即可

            //ajax发送文件必须要指定两个参数
            contentType:false, //不需要使用任何编码 django后端能够自动识别formdata对象
            processData: false, //告诉浏览器不要对你的数据进行任何处理

            success:function (args){

            }
        })
    })

</script>

#后端
#ajax发送文件
def ab_file(request):
    if request.is_ajax():
        if request.method == 'POST':
            print(request.POST) #<QueryDict: {'username': ['kk'], 'password': ['123']}>
            print(request.FILES) #<MultiValueDict: {'myfile': [<InMemoryUploadedFile: 01234.jpg (image/jpeg)>]}>
    return  render(request,'ab_file.html')

"""
总结:
    1.需要利用内置对象FormData
                // 2 添加普通的键值对
        formDateObj.append('username',$('#d1').val());
        formDateObj.append('password',$('#d2').val());
        // 3 添加文件对象
        formDateObj.append('myfile',$('#d3')[0].files[0])
    2.需要指定两个关键性的参数
                contentType:false,  // 不需使用任何编码 django后端能够自动识别formdata对象
        processData:false,  // 告诉你的浏览器不要对你的数据进行任何处理
    3.django后端能够直接识别到formdata对象并且能够将内部的普通键值自动解析并封装到request.POST中 文件数据自动解析并封装到request.FILES中
"""

django自带的序列化组件(drf做铺垫)

#序列化组件相关
import json
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):
    user_queryset = models.User.objects.all()
    # [{},{},{},{}]
    # user_list = []
    # for user_obj in user_queryset:
    #     tmp = {
    #         'pk':user_obj.pk,
    #         'username':user_obj.username,
    #         'age':user_obj.age,
    #         'gender':user_obj.get_gender_display()
    #     }
    #     user_list.append(tmp)
    # return JsonResponse(user_list,safe=False)
    """
    [
    {"pk": 1,"username": "melo","age": 25,"gender": "male"},
    {"pk": 2,"username": "masiwei","age": 26,"gender": "male"},
    {"pk": 3,"username": "knowknow","age": 25,"gender": "male"},
    {"pk": 4,"username": "周淑怡","age": 23,"gender": "female"},
    {"pk": 5,"username": "咔咔","age": 24,"gender": "female"}
    ]
    前后端分离的项目
        作为后端开发的你只需要写代码将数据处理好
        能够序列化返回给前端即可
            再写一个接口文档 告诉前端每个字段代表的意思即可
    """

    #如果对象数量过多 那么一个个的写 效率太过低 这个时候 serializers 就来了哦

    #序列化
    res = serializers.serialize('json',user_queryset)
    """会自动帮你将数据变成json格式的字符串 并且内部非常的全面"""
    return HttpResponse(res)
    """"
    [
{
"model": "app01.user",
"pk": 1,
"fields": {
"username": "melo",
"age": 25,
"gender": 1
}
},
{
"model": "app01.user",
"pk": 2,
"fields": {
"username": "masiwei",
"age": 26,
"gender": 1
}
},
{
"model": "app01.user",
"pk": 3,
"fields": {
"username": "knowknow",
"age": 25,
"gender": 1
}
},
{
"model": "app01.user",
"pk": 4,
"fields": {
"username": "周淑怡",
"age": 23,
"gender": 2
}
},
{
"model": "app01.user",
"pk": 5,
"fields": {
"username": "咔咔",
"age": 24,
"gender": 2
}
}
]
写接口就是利用序列化组件渲染数据然后写一个接口文档 该交代交代一下就完事
"""

ajax结合sweetalert

自己要学会如何拷贝
学会基于别人的基础之上做修改
研究各个参数表示的意思 然后找葫芦画瓢

//前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{#    动态引入第三方弹窗库#}
    {% load static %}
    <link rel="stylesheet" href="{% static '/dist/sweetalert.css' %}">
    <script src="{% static 'dist/sweetalert.js' %}"></script>
    <style>
        {#当引用第三方弹窗库的时候 将警告文字换成中文的时候会有一个小bug #}
        {#利用谷歌浏览器检查定位到出问题的元素 进行自定义位置的修改#}
        div.sweet-alert h2{
            padding-top:10px;
        }

    </style>
</head>
<body>
<div class="container-fluid">
    <h1 class="text-center">用户数据展示</h1>
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <table class="table-striped table table-hover">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>用户名</th>
                        <th>年龄</th>
                        <th>性别</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for user_obj in user_queryset %}
                        <tr>
                            <td>{{ user_obj.id }}</td>
                            <td>{{ user_obj.username }}</td>
                            <td>{{ user_obj.age }}</td>
                            <td>{{ user_obj.get_gender_display }}</td>
                            <td>
                                <button class="btn btn-primary btn-xs">编辑</button>
                                <button class="btn btn-danger btn-xs del" delete_id = "{{ user_obj.pk }}">删除</button>

                            </td>

                        </tr>
                    {% endfor %}

                </tbody>

            </table>

        </div>
    </div>
</div>
<script>
    $('.del').click(function (){
        //先将当前标签对象存储起来
        let currentBtn = $(this);

        //二次确认弹窗  先在上面引入第三方弹窗库 然后直接在官网上拷贝使用代码
        // 下载第三方库放在自己的static文件夹中后 需要在setting里面设置一下 把这个文件夹暴露出去
        swal({
          title: "你确定要删吗?",
          text: "你可要考虑清除,可能需要拎包跑路哦!",
          type: "warning",
          showCancelButton: true,
          confirmButtonClass: "btn-danger",
          confirmButtonText: "是的,老子就要删",
          cancelButtonText: "算了算了,不删了",
          closeOnConfirm: false,
          closeOnCancel: false,
          showLoaderOnConfirm: true {# 删除的等待时间 显示转圈圈 #}
        },
        function(isConfirm) {
          if (isConfirm) {
            //朝后端发送ajax请求删除数据后,再弹下面的提示框
            $.ajax({
                //url:"/delete/user/" + currentBtn.attr('delete_id'), //传递主键值方式1
                url:'/delete/user/', //传递主键值方式2 放在请求体里
                type: 'post',
                data:{'delete_id':currentBtn.attr('delete_id')},
                success:function (args){  //args={'code':'','msg':''}
                    //判断响应状态码 然后做不同的处理
                    if(args.code === 1000){
                        swal("删了!",args.msg,'success');
                        //1.lowb版本 直接刷新当前页面
                        //window.location.reload()
                        //2.利用DOM操作 动态刷新
                        currentBtn.parent().parent().remove()
                    }else {
                        swal('完了','出现未知的错误','info')
                    }
                }

            })

          } else {
            swal("怂比", "不要说我认识你", "error");
          }
        });
    })
</script>
</body>
</html>


#后端逻辑
#用户展示页面
def user_list(request):
    user_queryset = models.User.objects.all()
    return render(request,'user_list.html',locals())

#删除用户
import time
def delete_user(request):
    """
    前后端在用ajax进行交互的时候,后端通常给ajax的回调函数返回一个字典格式的数据
    """
    if request.is_ajax():
        if request.method == 'POST':
            back_dic = {"code":1000,'msg':''}
            time.sleep(3) #模拟操作数据的延迟
            delete_id = request.POST.get('delete_id')
            models.User.objects.filter(pk=delete_id).delete()
            back_dic['msg'] = '数据已经删了,你赶紧跑路吧!'
            #我们需要告诉前端我们操作的结果
            return JsonResponse(back_dic)

批量插入

def ab_pl(request):
    #先给顾客表插入50条数据
    #速度太慢 前端加载的慢 用户体验感不好
    # for i in range(50):
    #     models.Book.objects.create(title="第%s本书"%i)
    # #再将所有的数据查询并展示到前端页面
    # book_queryset = models.Book.objects.all()
    # return render(request,'ab_pl.html',locals())

    #批量插入  速度明显要比前面这种快的太多了 推荐使用
    book_list = []
    for i in range(50):
        book_obj = models.Book(title='第%s本书'%i)
        book_list.append(book_obj)
    models.Book.objects.bulk_create(book_list)
    book_queryset = models.Book.objects.all()
    return render(request, 'ab_pl.html', locals())
    """
    当你想要批量插入数据的时候 使用orm给你提供的bulk_create能够大大的减少操作时间
    """

分页器

"""
总数据100 每页展示10 需要10
总数据101 每页展示10 需要11
总数据99 每页展示10  需要10

如何通过代码动态的计算出到底需要多少页?

在制作页码个数的时候 一般情况下都是奇数个        符合中国人对称美的标准
"""
# 分页
    book_list = models.Book.objects.all()

    # 想访问哪一页
    current_page = request.GET.get('page',1)  # 如果获取不到当前页码 就展示第一页
    # 数据类型转换
    try:
        current_page = int(current_page)
    except Exception:
        current_page = 1
    # 每页展示多少条
    per_page_num = 10
    # 起始位置
    start_page = (current_page - 1) * per_page_num
    # 终止位置
    end_page = current_page * per_page_num

    # 计算出到底需要多少页
    all_count = book_list.count()

    page_count, more = divmod(all_count, per_page_num)
    if more:
        page_count += 1

    page_html = ''
    xxx = current_page
    if current_page < 6:
        current_page = 6
    for i in range(current_page-5,current_page+6):
        if xxx == i:
            page_html += '<li class="active"><a href="?page=%s">%s</a></li>'%(i,i)
        else:
            page_html += '<li><a href="?page=%s">%s</a></li>'%(i,i)

    book_queryset =  book_list[start_page:end_page]

"""
django中有自带的分页器模块 但是书写起来很麻烦并且功能太简单
所以我们自己想法和设法的写自定义分页器

上述推导代码你无需掌握 只需要知道内部逻辑即可

我们基于上述的思路 已经封装好了我们自己的自定义分页器
之后需要使用直接拷贝即可
"""

自定义分页器的拷贝及使用

 #已经封装好了的分页器代码 直接cv即可
 class Pagination(object):
   def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
       """
       封装分页相关数据
       :param current_page: 当前页
       :param all_count:    数据库中的数据总条数
       :param per_page_num: 每页显示的数据条数
       :param pager_count:  最多显示的页码个数
       """
       try:
           current_page = int(current_page)
       except Exception as e:
           current_page = 1

       if current_page < 1:
           current_page = 1

       self.current_page = current_page

       self.all_count = all_count
       self.per_page_num = per_page_num

       # 总页码
       all_pager, tmp = divmod(all_count, per_page_num)
       if tmp:
           all_pager += 1
       self.all_pager = all_pager

       self.pager_count = pager_count
       self.pager_count_half = int((pager_count - 1) / 2)

   @property
   def start(self):
       return (self.current_page - 1) * self.per_page_num

   @property
   def end(self):
       return self.current_page * self.per_page_num

   def page_html(self):
       # 如果总页码 < 11个:
       if self.all_pager <= self.pager_count:
           pager_start = 1
           pager_end = self.all_pager + 1
       # 总页码  > 11
       else:
           # 当前页如果<=页面上最多显示11/2个页码
           if self.current_page <= self.pager_count_half:
               pager_start = 1
               pager_end = self.pager_count + 1

           # 当前页大于5
           else:
               # 页码翻到最后
               if (self.current_page + self.pager_count_half) > self.all_pager:
                   pager_end = self.all_pager + 1
                   pager_start = self.all_pager - self.pager_count + 1
               else:
                   pager_start = self.current_page - self.pager_count_half
                   pager_end = self.current_page + self.pager_count_half + 1

       page_html_list = []
       # 添加前面的nav和ul标签
       page_html_list.append('''
                   <nav aria-label='Page navigation>'
                   <ul class='pagination'>
               ''')
       first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
       page_html_list.append(first_page)

       if self.current_page <= 1:
           prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
       else:
           prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)

       page_html_list.append(prev_page)

       for i in range(pager_start, pager_end):
           if i == self.current_page:
               temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
           else:
               temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
           page_html_list.append(temp)

       if self.current_page >= self.all_pager:
           next_page = '<li class="disabled"><a href="#">下一页</a></li>'
       else:
           next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
       page_html_list.append(next_page)

       last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
       page_html_list.append(last_page)
       # 尾部添加标签
       page_html_list.append('''
                                          </nav>
                                          </ul>
                                      ''')
       return ''.join(page_html_list)



"""
当我们需要使用到非django内置的第三方功能或者组件代码的时候
我们一般情况下会创建一个名为utils文件夹 在该文件夹内对模块进行功能性划分
    utils可以在每个应用下创建 具体结合实际情况

我们到了后期封装代码的时候 不再局限于函数
还是尽量朝面向对象去封装

我们自定义的分页器是基于bootstrap样式来的 所以你需要提前导入bootstrap
    bootstrap 版本 v3
    jQuery      版本 v3
"""
# 后端
book_queryset = models.Book.objects.all()
current_page = request.GET.get('page',1)
all_count = book_queryset.count()
# 1 传值生成对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2 直接对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 3 将page_queryset传递到页面 替换之前的book_queryset

# 前端
{% for book_obj in page_queryset %}
    <p>{{ book_obj.title }}</p>
    <nav aria-label="Page navigation">
</nav>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}

"""
你们只需要掌握如何拷贝使用 以及大致的推导思路即可
"""
### 作业

```python
"""
必做
1.整理今日内容到博客
2.熟练掌握ajax发送json数据和文件数据代码
3.自己完成按钮的二次确认操作(ajax+sweetalert)
4.尝试理解自定义分页器逻辑,自己推导出简易版本代码
选做
1.阅读博客,预习自定义分页器封装版本使用方式
"""