django自带的序列化组件 批量数据操作 分页器 校验性组件之from组件

day58

 

 

ajax简介

# 异步提交 局部刷新
主要用于页面局部数据的交互!!!

我们学习的是jQuery封装之后的版本 以后还会遇到其他类库封装的版本
但是无论怎么封装 整体结构是一样的

ajax发送普通键值对

$.ajax({
  url:'',
  type:'',
  data:{'name':'jason','pwd':123},
  success:function(args){
  }
})

ajax携带文件数据

let myFormData = new FormData;
myFormDate.append('name','jason')
myFormData.append('file','文件对象')
$.ajax({
	url:'',
  type:'post',
  data:myFormData,
  contentType:false,
  processData:false,
  success:function(args){   
  }
})

ajax发送json格式数据

$.ajax({
	url:'',
  type:'post',
  data:JSON.stringify({'name':'jason','pwd':123}),
  contentType:'application/json',
  success:function(args){   
  }
})
# django针对json格式的数据不会处理 直接在request.body

 

作业讲解

# 1.用户名动态校验
<p>username:
    <input type="text" id="i1">
    <span style="color: red" id="error"></span>
</p>

<script>
    // 这个功能可以使用前端代码完成 但是安全性不高 最好还是经过后端(前端的校验是弱不禁风的!!!)
    $('#i1').on('input',function () {
        let iVal = $(this).val()
        $.ajax({
            url:'',
            type:'post',
            data:{'i1':iVal},
            success:function (args) {
                // 找到span标签设置文本内容
                $('#error').text(args)
            }
        })
    })
</script>
  
# 2.删除二次确认
<div class="container">
    <div class="row">
    <div class="col-md-8 col-md-offset-2"><h2 class="text-center">数据展示</h2>
        <table class="table table-hover table-striped">
            <thead>
                <tr>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {% for d in data.values %}
                    <tr>
                        <td>{{ d.name }}</td>
                        <td>{{ d.pwd }}</td>
                        <td>
                            <a href="#" class="btn btn-primary btn-xs">编辑</a>
                            <a href="#" class="btn btn-danger btn-xs delBtn" delete_id="{{ d.pk }}">删除</a>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
        </table></div>
    </div>
</div>
<script>
    $('.delBtn').click(function () {
        let choiceMsg = confirm('你确定要删吗???')
        let currentEle = $(this);
        // 根据choiceMsg接收到的布尔值 决定是否需要发送ajax请求
        if(choiceMsg){
            $.ajax({
                url:'',
                type:'post',
                data:{'delete_id':currentEle.attr('delete_id')},
                success:function (args) {
                    // 页面刷新(页面应该反映出删除的效果)
                    // 直接刷新页面(老师用的是数据类型 无法刷新查看  应该使用数据库数据就可以刷新查看变化)
                    {#window.location.reload()#}
                    // 通用的方式(通过DOM操作 删除一行标签内容)
                    // 好处在于页面有多页数据的情况下 删除的不是第一页数据 页面不刷新 还会停留在所处页 直观效果更好
                    currentEle.parent().parent().remove()
                }
            })
        }
    })
</script>

sweetalert前端插件

https://github.com/lipis/bootstrap-sweetalert

django自带的序列化组件(serializers模块)

  以后我们用的序列化组件是DRF 这里学自带的是提前瞜一眼效果

from app01 import models
from django.http import JsonResponse

def d_data(request):
    # 前后端分离之后 django orm产生的queryset无法直接被前端识别 还是需要json格式数据(硬通货)
    data_list = []  # [{}, {}, {}] 需求是想要 列表套字典形式 字典里面是一个个键值对数据
    user_queryset = models.User.objects.all()  #这句是惰性查询 如果后续代码不用数据的话 就不会进行查询 为了减轻数据库的压力
    for user_obj in user_queryset:  # 返回给前端一个列表套字典的数据
        # 将字典内的数据封装成json格式
        data_list.append({
            'pk':user_obj.pk,
            'name':user_obj.name,
            'age':user_obj.age,
            'gender':user_obj.gender,
            'gender_real':user_obj.get_gender_display(),  
            'addr':user_obj.addr
        })
    return JsonResponse(data_list,safe=False)  # 返回一个的json格式化数据(列表套字典的形式)一般我们后端只负责返回这种json格式化数据
   '''
   如果我们需要处理大量的数据 用以上方法写起来效率较低 
   那么django为我们提供了一个模块:
   from django.core import serializers
   那么就以上方法代码都不要写 只需要写个查询数据的操作 使用模块即可 推荐使用以下方法
   
   '''
    # 操作代码如下:
    from app01 import models
    from django.http import JsonResponse
    from django.core import serializers  # 导入序列化模块
    
def d_data(request):
    user_queryset = models.User.objects.all()  # 查询数据
    ret = serializers.serialize('json', user_queryset)  # 括号内写上json就已经序列化了 然后把数据放进去
    return HttpResponse(ret)  # 返回HttpResponse即可

BEJSON网站

bejson.com

用来做格式化校验 将返回的数据做一个解析 前端就能拿到标准的json格式化数据

 

批量数据操作(bulk_create方法)

需求:循环插入10万条数据 我们使用bulk_create方法批量插入提高效率
def many_data(request):
    book_list = []
    for i in range(100000):
        # 先用类产生一个对象
        source_book_obj = models.Books(title=f'第{i}本书')
        # 将对象追加到列表中
        book_list.append(source_book_obj)
    models.Books.objects.bulk_create(book_list)  # 批量插入

 

分页器推导流程

  对上一小节批量插入的数据,我们在前端展示的时候发现一个很严重的问题,一页展示了所有的数据,数据量太大,查看不方便

针对数据量大但又需要全部展示给用户观看的情况下,我们统一做法都是做分页处理

  首先我们需要明确的时候,get请求也是可以携带参数的,所以我们在朝后端发送查看数据的同时可以携带一个参数告诉后端我们想看第几页的数据

其次我们还需要知道一个点,queryset对象是支持索引取值和切片操作的,但是不支持负数索引情况

接下来我们就可以推导我们的自定义分页器步骤了

推导思路:

current_page = request.GET.get("page",1)  # 获取用户想访问的页码  如果没有 默认展示第一页
try:  # 由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
  current_page = int(current_page)
except Exception as e:
  current_page = 1
# 还需要定义页面到底展示几条数据
per_page_num = 10  # 一页展示10条数据

# 需要对总数据进行切片操作 需要确定切片起始位置和终止位置
start_page = ? 
end_page = ?
"""
下面需要研究current_page、per_page_num、start_page、end_page四个参数之间的数据关系
per_page_num = 10
current_page                start_page                  end_page
    1                           0                           10
    2                           10                          20
    3                           20                          30  
    4                           30                          40

per_page_num = 5
current_page                start_page                  end_page
    1                           0                           5
    2                           5                           10
    3                           10                          15  
    4                           15                          20
可以很明显的看出规律
start_page = (current_page - 1) * per_page_num
end_page =  current_page* per_page_num
"""

内置方法之divmod 
>>> divmod(100,10)
(10, 0)  # 10页
>>> divmod(101,10)
(10, 1)  # 11页
>>> divmod(99,10)
(9, 9)  # 10页
# 余数只要不是0就需要在第一个数字上加一

# 通过内置方法之divmod 利用总数据量与每页展示数据量得出需要的分页数
book_queryset = models.Book.objects.all()
all_count = book_queryset.count()  # 数据总条数
all_pager, more = divmod(all_count, per_page_num)
if more:  # 有余数则总页数加一
  all_pager += 1

# 最后我们只需要利用start_page和end_page对总数据进行切片取值再传入前端页面就能够实现分页展示
book_list = models.Book.objects.all()[start_page:end_page]
return render(request,'booklist.html',locals())

# 接下来就是前端页面的代码编写了
{% for book in book_list %}
	<p>{{ book.title }}</p>
{% endfor %}
'''
现在我们实现了最简单的分页,但是前端没有按钮去让用户点击需要看第几页,所以我们需要渲染分页器相关代码,
这里我们不做要求直接去bootstrap框架拷贝代码即可
'''


django本身也自带了一个分页器 只是不好用 所以我们自己写!!!

网站不可能将所有的数据全部展示到一页,应该考虑使用分页 每页展示一些
  1.all()结果集支持正数的索引切片
  2.分页相关参数数学关系
  3.后端渲染前端分页代码
  4.后端限制分页展示数量
  5.当页面小于6或者大于N都需要额外限制
  ...
以后可能很多地方都需要使用分页 不可能重复编写 所以封装成了模块

 

 

自定义分页器封装代码

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, 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)

自定义分页器使用

 后端:

def get_book(request):
   book_list = models.Book.objects.all()
   current_page = request.GET.get("page",1)
   all_count = book_list.count()
   page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10)
   page_queryset = book_list[page_obj.start:page_obj.end]
   return render(request,'booklist.html',locals())

前端:

<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            {% for book in page_queryset %}
            <p>{{ book.title }}</p>
            {% endfor %}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

 

Forms组件

# 前戏

  编写一个校验用户名和密码是否合法的功能

需求:
    前端需要自己编写获取用户数据的各种标签
    前端需要自己想方设法的展示错误的提示信息
    后端需要自己想方设法的编写校验代码(很多if判断)
  

上面的三件事有一个人可以一次性帮你搞定>>>:form组件

from主要的三个功能:

          1.数据校验
          2.标签渲染
          3.展示信息

使用方法:

# 基本使用(先建造一个模型类)
# 编写校验性组件forms类
from django import form  # 导入from模块


class MyForm(forms.Form):	# 需要先写一个类
    # 用户名至少三个字符最多八个字符
    username = form.CharField(min_length=3,max_length=8)  # 这么一写就产生校验效果了
    # 年龄最小不能小于0 最大不能超过150
    age = form.IntegerField(min_value=0,max_value=150)
    # 邮箱必须符合邮箱格式(必须要有@关键符号)
    email = form.EmailField()

校验数据

  我们测试上面单个文件可以使用pycharm自带的测试环境 Python Console

from app01 import views  # 先在测试环境中导入模块

# 1.将数据传入实例化对象
form_obj =MyForm({'username':'jason','age':18,'email':'123qq'})  # 产生了需校验的对象

# 2.查看数据是否合法(全部合法结果才是True)
form_obj.is_valid()  # 返回的是布尔值

# 3.查看不符合条件的数据及原因
form_obj.errors  # 返回:{'email': ['Enter a valid email address.']} 
	
# 4.查看符合条件的数据
form_obj.cleaned_data  # 返回符合条件的数据:{'username': 'jason', 'age': 18}

注意事项:

  1.forms类中所有的字段数据默认都是必填的 不能少 

如果想忽略某些字段 可以添加    required=False  表示该字段可以不传值

如下:

email = form.EmailField(required=False)

 

    2.forms类中额外传入的字段数据不会做任何的校验 直接忽略 

 

渲染标签

 views:

def ab_form(request):
    # 1.先产生一个空对象
    form_obj = MyForm()  # 将这个form_obj直接放到页面上使用
    
    return render(request,'ab_form.html',locals())

html:

渲染方式1:

封装程度高 扩展性较差(无法自定义)

主要用于快速生成页面测试功能

<form action="" method="post">  # 自己写form表单
    {{ form_obj.as_p }}  # 空对象添加as_p直接使用
    <input type="submit">  # 自己写提交按钮
</form>
'''
forms组件只负责渲染获取用户数据的标签
	form表单标签和提交按钮需要自己写
'''

除了以上渲染方式 还可以横向展示和列表展示 方法如下:

 {{ form_obj.as_table }}  # 横向展示用户数据
  {{ form_obj.as_ul }}  # 列表形式展示用户数据

展示效果:

一般不指定默认字段的首字母大写 也可以修改字段名称

如何修改字段名称? 通过添加laber

from django import forms
class MyForm(forms.Form):
    # 用户名至少三个字符最多八个字符
    username = forms.CharField(min_length=3,max_length=8,label='用户名',)
    # 年龄最小不能小于0 最大不能超过150
    age = forms.IntegerField(min_value=0,max_value=150,label='年龄')
    # 邮箱必须符合邮箱格式(@关键符号)
    email = forms.EmailField(label='邮箱')  # required=False 该字段可以不传值

展示效果:

 

渲染方式2:封装程度低 扩展性较好 但是字段比较多的情况下不方便 不亚于自己写标签

<form action="" method="post">
    <p>
        {{ form_obj.username.label }}  # 文本提示 比如:用户名 我们就可以自定义文本内容了
        {{ form_obj.username }}  # 获取用户数据的标签
    </p>
    <p>
        {{ form_obj.age.label }}
        {{ form_obj.age }}
    </p>
    <p>
        {{ form_obj.email.label }}
        {{ form_obj.email }}
    </p>
    
    <input type="submit">
</form>

 渲染方式3: 这种封装程度刚刚好 使用for循环无论后端有多少个字段 前端代码都不会变 可以对lable和input框做一些额外的扩展  推荐使用!!!

<form action="" method="post" novalidate>
   {% for form in form_obj %}
      <p>
        {{ form.label }}
        {{ form }}
      </p>
  {% endfor %}
    
    <input type="submit">
</form>

 

展示信息

  forms类中填写的校验性参数前端浏览器会识别并添加校验操作 

但是前端的校验可以在页面检查中任意修改数据  不能指望它!!!

所以后端必须做校验

如何让form表单可以取消浏览器自动添加校验功能的操作:
    <form action="" method="post" novalidate></form>   # 通过添加novalidate参数  前端校验功能就全部失效了

def ab_form(request):
     # 1.先产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        # 2.获取用户数据(request.POST可以看成是个大字典 直接传入即可 因为根据校验数据的特性 只拿自己需要的 多传的直接忽略)
        form_obj = MyForm(request.POST)
        # 3.校验用户数据
        if form_obj.is_valid():
            """
            以下两行代码带你见识froms组件 如何让你的代码更精简
            通过cleaned_data 直接在后端拿到所有符合条件的字典数据 操作如下
            print(form_obj.cleaned_data) 
            假设这里有一个库 我们可以通过(**form_obj.cleaned_data)直接往里面写数据 操作如下
            models.User.objects.create(**form_obj.cleaned_data)  
            """
            return HttpResponse('数据没问题')  # 数据没问题 正常返回
        # 数据有问题(输入不符合要求时)一旦出问题 数据就不更新了 会打印提示信息 我们需要将错误信息展示到前端去 操作如下:
    return render(request,'ab_form.html',locals())

 

数据有问题(输入不符合要求时)一旦出问题 数据就不更新了 会打印提示信息 我们需要将错误信息展示到前端去 操作如下:

   </p>
    {% for form in form_obj %}
        <p>
        {{ form.label }}
        {{ form }}
        """
        在这里添加一个span标签 写入{{ form.errors.0 }}
        输入符合条件的 不提示
        输入不符合条件的 提示相应提示信息
        输入完信息后 原始数据还在
        """
        <span style="color: red">{{ form.errors.0 }}</span>  # 固定写法 照抄即可
        </p>

以上功能是如何实现的呢?

  

操作如下:

label参数后面添加一个error_messages字典形式 添加  页面就会显示你提前写好的提示信息

from django import forms
class MyForm(forms.Form):
    # 用户名至少三个字符最多八个字符
    username = forms.CharField(min_length=3,max_length=8,label='用户名',
                               error_messages={
                                   'min_length':'用户名最短3位',
                                   'max_length':'用户名最长8位',
                                   'required':'用户名必填'
                               }

                               )
    # 年龄最小不能小于0 最大不能超过150
    age = forms.IntegerField(min_value=0,max_value=150,label='年龄')
    # 邮箱必须符合邮箱格式(@关键符号)
    email = forms.EmailField(label='邮箱')  # required=False 该字段可以不传值

 

 

posted @ 2022-05-26 22:04  ji哩咕噜  阅读(66)  评论(0)    收藏  举报