自定义分页器和forms组件

 


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

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

  utils可以在每个应用下创建 具体结合实际情况,我们到了后期封装代码的时候 不再局限于函数还是尽量朝面向对象去封装

  我们自定义的分页器是基于bootstrap样式来的 所以你需要提前导入bootstrap  

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)
utils

  后端代码

 
封装了get方法传参  直接用GET获取的即可

 

 使用方法

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
 
遗忘小知识
querset是列表的样式支持切片索引
举例Queryset对象样子:# <QuerySet [<User: User object>, <User: User object>]>

  前端代码

 
{% for book_obj in page_pagequerset %}
<p>{{ book_obj.pk }}</p>    #取值展示

{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}<!--直接传输html标签 添加筛选器标识安全-->

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

二 Forms组件

  2.1 前戏(第一次就是介绍自定义空字典没有值 可能在再次进入时会有中值产生 所以提前写好行内式span标签样式 等待有值触发(span是行内标签没有值不显示))

  一个注册功能:获取用户名和密码 利用form表单提交数据,在后端判断用户名和密码是否符合一定的条件,用户名中不能含有金瓶mei,密码不能少于三位,如果符合条件需要你将提示信息展示到前端页面

def ab_form(request):
    back_dic = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if '金瓶mei' in username:
            back_dic['username'] = '不符合社会主义核心价值观'
        if len(password) < 3:
            back_dic['password'] = '不能太短 不好!'
    """
    无论是post请求还是get请求
    页面都能够获取到字典 只不过get请求来的时候 字典值都是空的
    而post请求来之后 字典可能有值
    """
    return render(request,'ab_form.html',locals())

<form action="" method="post">
    <p>username:
        <input type="text" name="username">
        <span style="color: red">{{ back_dic.username }}</span>
    </p>
    <p>password:
        <input type="text" name="password">
        <span style="color: red">{{ back_dic.password }}</span>
    </p>
    <input type="submit" class="btn btn-info">
</form>
View Code

  2.2 forms的功能

  1.手动书写前端获取用户数据的html代码        渲染html代码
  2.后端对用户数据进行校验              校验数据
  3.对不符合要求的数据进行前端提示          展示提示信息

  forms组件能够完成的事情
  1.渲染html代码
  2.校验数据
  3.展示提示信息

  为什么数据校验非要去后端 不能在前端利用js直接完成呢?
  数据校验前端可有可无,但是后端必须要有!!!
  因为前端的校验是弱不禁风的 你可以直接修改,或者利用爬虫程序绕过前端页面直接朝后端提交数据

  eg:购物网站 :选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验,实际是获取到用户选择的所有商品的主键值,然后在后端查询出所有商品的价格 再次计算一遍
  如果跟前端一致 那么完成支付如果不一致直接拒绝

  2.3 基本使用(在views模块中书写类似models书写形式)

 
from django import forms
class MyForm(forms.Form):
    # username字符串类型最小3位最大8位
    username = forms.CharField(min_length=3,max_length=8)
    # password字符串类型最小3位最大8位
    password = forms.CharField(min_length=3,max_length=8)
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField()
复制代码

  校验数据

 
"""
1.测试环境的准备 可以自己拷贝代码准备
2.其实在pycharm里面已经帮你准备一个测试环境
    python console 打开之后导入py文件然后写入代码即刻
"""
from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法 注意该方法只有在所有的数据全部合法的情况下才会返回True form_obj.is_valid() False
# 3 查看所有校验通过的数据 form_obj.cleaned_data {'username': 'jason', 'password': '123'}
# 4 查看所有不符合校验规则以及不符合的原因 form_obj.errors { 'email': ['Enter a valid email address.'] }
# 5 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略 form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'}) form_obj.is_valid() True
# 6 校验数据 默认情况下 类里面所有的字段都必须传值 form_obj = views.MyForm({'username':'jason','password':'123'}) form_obj.is_valid() False """ 也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传 """
 

  渲染标签

  forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox),不能帮你渲染提交按钮

  后端

def index(request):
    # 1 先产生一个空对象
    form_obj = MyForm()
    # 2 直接将该空对象传递给html页面
    return render(request,'index.html',locals())

  前端

 
# 前端利用空对象做操作
    <p>第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用</p>
    {{ form_obj.as_p }}
    {{ form_obj.as_ul }}
    {{ form_obj.as_table }}
<p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用</p> <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p> <p>{{ form_obj.password.label }}:{{ form_obj.password }}</p> <p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>

<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p> {% for form in form_obj %} <p>{{ form.label }}:{{ form }}</p> {% endfor %}
 

  label属性默认展示的是类中定义的字段首字母大写的形式,也可以自己修改 直接给字段对象加label属性即可

username = forms.CharField(min_length=3,max_length=8,label='用户名')

  展示提示信息

  浏览器会自动帮你校验数据 但是前端的校验弱不禁风,如何让浏览器不做校验

<form action="" method="post" novalidate>

  后端

 
def index(request):
    # 1 先产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        # 获取用户数据并且校验
        """
        1.数据获取繁琐
        2.校验数据需要构造成字典的格式传入才行
        ps:但是request.POST可以看成就是一个字典
        """
        # 3.校验数据
        form_obj = MyForm(request.POST)               《=======这里验证前端拿到对象就可以拿到错误信息①
        # 4.判断数据是否合法
        if form_obj.is_valid():
            # 5.如果合法 操作数据库存储数据
            return HttpResponse('OK')
        # 6.不合法 有错误 无需在后端写,只要在前端写上form.errors.0就可以了
    # 2 直接将该空对象传递给html页面
    return render(request,'index.html',locals())
 

  前端

 
<form action="" method="post" novalidate>{% for form in form_obj %}    (是包含关系提交数据的方法的)
{% for form in form_obj %}
  <p>
    {{ form.label }}:{{ form }}
    <span style="color: red">{{ form.errors.0 }}</span>    《======拿错误信息②
  </p>
{% endfor %}
</form>
 
 

 

 

  总结:

  1.必备的条件 get请求和post传给html页面对象变量名必须一样

  2.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改更加的人性化

  修改错误的提示信息

 
class MyForm(forms.Form):
    # username字符串类型最小3位最大8位
    username = forms.CharField(min_length=3,max_length=8,label='用户名',
                               error_messages={
                                   'min_length':'用户名最少3位',
                                   'max_length':'用户名最大8位',
                                   'required':"用户名不能为空"
                               }
                               )
    # password字符串类型最小3位最大8位
    password = forms.CharField(min_length=3,max_length=8,label='密码',
                               error_messages={
                                   'min_length': '密码最少3位',
                                   'max_length': '密码最大8位',
                                   'required': "密码不能为空"
                               }
                               )
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'invalid':'邮箱格式不正确',
                                 'required': "邮箱不能为空"
                             }
                             )
 

三 钩子函数(HOOK)

  在特定的节点自动触发完成响应操作

  钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则 钩子函数在类里面书写方法即可

  在forms组件中有两类钩子
  1.局部钩子
  当你需要给单个字段增加校验规则的时候可以使用  1.校验用户名中不能含有666 只是校验username字段 局部钩子

 
 # 局部钩子
    def clean_username(self):      #会自动提示出来命名(clean_字段名)触发条件是本字段成功验证后的第二道关卡触发(然后进行处理再放回)
        # 获取到用户名
        username = self.cleaned_data.get('username')
        if '666' in username:
            # 提示前端展示错误信息
            self.add_error('username','光喊666是不行滴~')
        # 将钩子函数钩去出来数据再放回去
        return username
 

  2.全局钩子
  当你需要给多个字段增加校验规则的时候可以使用 2.校验密码和确认密码是否一致 password confirm两个字段 全局钩子

 
 def clean(self):    #第二道卡上面解释同意(但是是操作两个字段叫全局钩子是需要 操控多个字段 clean(self)固定写法)    在forms 自定义类中 全局和局部函数名是有固定的写法并不是自定义
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not confirm_password == password:
            self.add_error('confirm_password','两次密码不一致')
        # 将钩子函数钩出来数据再放回去
        return self.cleaned_data
 

 

 

 forms组键自我理解(

confirm_password=forms.CharField(min_length=3,max_length=8,widget=forms.PasswordInput) #密文形式输入 

 

四 forms组件其他参数及补充知识点

  label 字段名
  error_messages 自定义报错信息
  initial 默认值
  required 控制字段是否必填

  如何为不同的input添加样式

widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
# 多个属性值的话 直接空格隔开即可

  第一道关卡里面还支持正则校验

validators=[
            RegexValidator(r'^[0-9]+$', '请输入数字'),
            RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
        ]

 


 

 

  其他类型渲染

 
# radio
    gender = forms.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )
    # select
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
    # 多选
    hobby1 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
    # 单选checkbox
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
    # 多选checkbox
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
 

 五 forms组件源码

 
""
切入点:
    form_obj.is_valid()
"""
def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
   return self.is_bound and not self.errors
   # 如果is_valid要返回True的话 那么self.is_bound要为True self.errors要为Flase
  
  
self.is_bound = data is not None or files is not None  # 只要你传值了肯定为True


@property
def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

# forms组件所有的功能基本都出自于该方法
def full_clean(self):
      self._clean_fields()  # 校验字段 + 局部钩子
    self._clean_form()  # 全局钩子
    self._post_clean()  
 

 



 





  

posted @ 2021-05-26 20:31  欧阳锦涛  阅读(63)  评论(0)    收藏  举报
TOP 底部