• Django-Forms组件


    一、forms组件介绍

    在我们只做项目的过程中,比如注册功能,登录功能等肯定是需要校验的。校验通常在前端和后端都会进行,前端校验可以做一些简单的逻辑判断,减少服务器压力,且对于一些非法数据直接过滤。后端的校验可以说是安全的保障,因为对于专业人士来说,完全可以自己模拟http请求,绕过前端的校验。比如直接用postman发送数据,我们的前端就没办法校验其数据。

    所以,不论前端有没有校验,后端的数据校验是必须的有的。如果是传统的校验,那么会在后端写很多的if判断语句,django帮我们封装了一个forms组件,可以快速字段校验。

    # 借助于forms组件,可以快速实现字段的校验
    	from django.forms import Form
    

    二、forms校验字段

    1.常见的检验属性

    字段名字 表达含义
    max_length 最大长度
    min_length 最小长度
    required 是否必传(默认为True,若设为False可传可不传,不传不会报错,若传了也需要经过检验)
    label 前端自动渲染标签的标签名字
    widget 定制class样式属性,需要从django.forms导入
    error_message 错误信息自定义,将一个字典赋值给他

    2.forms组件的常用属性和方法

    .is_valid()  # 返回布尔值,检验是否通过
    .cleaned_data  # 检验通过的字段,如果所有字段都通过,那么和原数据相同,这个属性不论校验是否通过,里面都会存放着校验通过的字段
    .errors  # 校验失败的信息,通过源码可知通过重写了__str__转成了ul标签的格式,可读性差
    .errors.as_data()  # 校验失败的信息
    .errors.as_data()  # 校验失败的信息,json格式
    

    3.示例

    使用步骤

    1.新建一个py文件,自定义一个类继承django.forms下的Form类

    2.在类中书写要校验的字段

    3.在视图函数中导入类,实例化得到对象使用

    4.若要自动渲染模板,在前端拿到视图函数传的对象后,套下面的渲染方案(在自定义类中最好写上标签的样式不然很丑)

    # 写一个类,类里写要校验的字段
    from django import forms
    class MyForm(forms.Form):
        name = forms.CharField(min_length=3, max_length=16, label='用户名', 
                               error_messages={"min_length": "用户名过短", "min_length": '用户名超出限制'}, 
                               widget=widgets.TextInput(attrs={'class': 'form-control'}))
    
        passwd = forms.CharField(min_length=3, max_length=16, label='密码', 
                                 error_messages={"min_length": "密码太短,请至少三位", "min_length": '密码超出限制'}, 
                                 widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    
        re_passwd = forms.CharField(min_length=3, max_length=16, label='确认密码', 
                                    error_messages={"min_length": "密码太短,请至少三位", "min_length": '密码超出限制'}, 
                                    widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    
        profile = forms.CharField(max_length=200, label='个人简介', 
                                  error_messages={"max_length": '个人简介超出限制'}, 
                                  widget=widgets.Textarea(attrs={'class': 'form-control'}))
        email = forms.EmailField(label='邮箱', error_messages={'required': '必填属性'},
                                 widget=widgets.TextInput(attrs={'class': 'form-control'}))
    
    # 视图函数中
    def register(request):
        # 获取前端数据
        data=request.POST
        # 校验数据是否合法,实例化得到form对象,把要校验的数据传入
        obj=myforms.MyForm(data)
        
        # 校验数据:obj.is_valid() 返回布尔类型
        if obj.is_valid():
            print('校验通过')
            # 校验通过的数据
            print(obj.cleaned_data)  # 不一定是上面传入的数据
        else:
            print(obj.cleaned_data)
            print('校验失败')
            # 哪个字段失败了?失败的原因是什么
            print(obj.errors)  # 打印出来一个标签的格式,通过下面两句去查看了源码,发现重写了__str__
            print(type(obj.errors))
            from django.forms.utils import ErrorDict
    
            print(obj.errors.as_json())
            print(obj.errors.as_data())
    
            # obj.errors.as_ul()
    
        return HttpResponse('ok')
    

    三、forms渲染模板

    1.自动渲染模板

    传统的模板中,需要我们自己写form表单然后写样式,forms组件可以帮助我们直接生成标签。

    使用步骤:

    1.视图函数不变

    2.在模板文件中按照指定语法使用form对象

    生成的标签:<input type="text" name="name" maxlength="32" minlength="3" id="id_name" />
    自动生成的id:是id_字段名
    自定生成是没有样式的,我们可以在类的widget属性中自定义
    

    在前端利用form组件自动生成的标签使用如下

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <hr>
    <h1>手动创建模板</h1>
    <form action="" method="post">
        <p>用户名:<input type="text" name="name"></p>
        <p>邮箱:<input type="text" name="email"></p>
        <p>年龄:<input type="text" name="age"></p>
        <p><input type="submit" value="提交"></p>
    </form>
    
    <hr>
    <h1>半自动渲染模板1</h1>
    <form action="" method="post">
        <p>用户名:{{ form.name }}</p>
        <p>邮箱:{{ form.email }}</p>
        <p>年龄:{{ form.age }}</p>
        <p><input type="submit" value="提交"></p>
    </form>
    
    <h1>半自动渲染模板2(用的最多)</h1>
    <form action="" method="post">
        <p>{{ form.name.label }}:{{ form.name }}</p>
        <p>{{ form.email.label }}:{{ form.email }}</p>
        <p>{{ form.age.label }}:{{ form.age }}</p>
        <p><input type="submit" value="提交"></p>
    </form>
    
    <h1>半自动渲染模板3(用的最多)</h1>
    <form action="" method="post">
        {% for foo in form %}
           <p>{{ foo.label }} :{{ foo }}</p>
        {% endfor %}
    
        <p><input type="submit" value="提交"></p>
    </form>
    
    <h1>全自动(了解)</h1>
    <form action="" method="post">
        {{ form.as_ul }}
        {{ form.as_p }}
       <table>
           {{ form.as_table }}
       </table>
        <p><input type="submit" value="提交"></p>
    </form>
    </body>
    </html>
    

    2.渲染错误信息

    错误信息来自两个位置,一般情况下我们都是使用对象自己的errors,因为forms对象的错误信息的所有的,且会一起打印出来,一般情况下我们都是希望各自打印出错误信息,然后不同的错误信息显示在不同的位置。

    1 form对象.errors # 是一个字典
    2 name对象.errors
    
    # 视图函数
    # 若检验成功,重定向到百度,失败则返回原页面,此时form对象中已经包含了错误信息
    def register(request):
        if request.method=='GET':
            form=myforms.MyForm()
            return render(request, 'register.html',{'form':form})
        else:
            form=myforms.MyForm(request.POST)
            if form.is_valid():
                return redirect('http://www.baidu.com')
            else:
                return render(request, 'register.html',{'form':form})
    
    # 模板
    <form action="" method="post" novalidate>
    	{% for foo in form %}
    	<div class="form-group">
            <label for="">{{ foo.label }}</label>
                {{ foo }}
                # 打印错误信息,因为我们接受的是form对象,当还没校验的时候没有错误信息,则不会打印错误信息
                <span class="text-danger pull-right">{{ foo.errors }}</span>
            </div>
    	{% endfor %}
    	<div class="text-center">
    		<input type="submit" value="提交" class="btn btn-danger">
    	</div>
    </form>
    

    四、局部钩子和全局钩子

    局部钩子和全局钩子都是为了进行更复杂的检验,比如名字不能以sb开头,在注册时,两个输入的密码必须一致。

    局部钩子和全局钩子的使用地方都是在自己写的那个form类中。

    1.局部钩子

    # 局部钩子的触发时间在字段检验后面,全局钩子之前
    # 局部钩子的使用
    1 在自定义的Form类中写一个方法,名字必须是clean_字段名
    2 取出字段的真正值,name=self.cleaned_data.get('name')  # 因为触发时间在检验之后,所以cleaned_data中肯定有值
    3 判断自己的规则,如果判断失败,抛出ValidationError
    4 如果通过,return name
    
    # 局部钩子
        def clean_name(self):
            # name对应的值,如何取到?
            name = self.cleaned_data.get('name')
            if name.startswith('sb'):
                # 不让校验通过
                raise ValidationError('不能以sb开头')
            else:
                # 校验通过,返回name
                return name
    

    2.全局钩子

    # 全局钩子的触发时间在字段检验和局部钩子之后
    # 全局钩子的使用
    1 在自定义的Form类中写一个方法,名字必须是clean
    2 从cleaned中取出字段的真正值
    3 判断自己的规则,如果判断失败,抛出ValidationError
    4 如果通过,self.cleaned_data,在这必须返回self.cleaned_data,因为这是检验的最后一步,返回什么那么我们对象拿到的数据就是什么。
    且此处如果要自己定制,那么就是return自己的字典,如果写return None,那么默认也会返回self.cleaned_data
    
    # 全局钩子
        def clean(self):
            # name=self.cleaned_data.get('name')
            # print(name)
            password = self.cleaned_data.get('password')
            re_password = self.cleaned_data.get('re_password')
            if password == re_password:
                return self.cleaned_data
                # return {'yang':"nb"}
            else:
                raise ValidationError('两次密码不一致')
    

    五、Form组件源码分析

    1.Form组件执行流程

    form组件的执行顺序是,当执行了is_valid(),先检验字段1,再检验字段1的局部钩子,再字段2,然后字段2的局部钩子,最后再全局钩子。

    所以我们在使用局部钩子的时候,在后面的字段可以在cleaned_data里拿到前面的字段值,而在前面的字段无法拿到后面的值

    passwd = forms.CharField(min_length=3, max_length=16, label='密码', 
                                 error_messages={"min_length": "密码太短,请至少三位", "min_length": '密码超出限制'}, 
                                 widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    
        re_passwd = forms.CharField(min_length=3, max_length=16, label='确认密码', 
                                    error_messages={"min_length": "密码太短,请至少三位", "min_length": '密码超出限制'}, 
                                    widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    
    
    def clean_name(self):
        name = self.cleaned_data.get('name')
        if name.startswith == 'sb':
            raise ValidationError('用户名不能以sb开头')
        else:
            return name
    
    def clean_passwd(self):
        passwd = self.cleaned_data.get('passwd')
        re_passwd = self.cleaned_data.get('re_passwd')
        print(passwd)
        print(re_passwd)
        return passwd
    ---------------------
    123
    None
    123
    456
    

    2.源码分析

    我们调用form组件的入口是is_valid,
    1 form.is_valid()->
    2 self.errors(BaseForm类)->
    3 self.full_clean()(BaseForm类)->
    4 self._clean_fields(局部数据校验),self._clean_form(全局数据校验)
    

    image-20201022150706632

    image-20201022150731456

    image-20201022150808888

    image-20201022151007520

    image-20201022151223601

    3.局部钩子和全局钩子部分源码

    self._clean_fields(BaseForm类)
        for name, field in self.fields.items():
            try:
                # 字段自己的校验(最大值,最小值,是不是邮箱格式)
                value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):  # 反射判断有没有clean_字段名
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)
    
    self._clean_form(BaseForm类)  # 全局钩子
        try:
            cleaned_data = self.clean()  # self.clean执行的是自己类的clean方法
        except ValidationError as e:
            self.add_error(None, e) 
    

    4.源码总结

    form组件的源码给了我们一种思路。自己写一个类,如果别人要用,他继承了类之后如果要写了一个方法,我们就执行,不写就不执行,如form类中,我们如果写了clean_xxx或者clean,他就会执行,不写就不执行,也不会报错,我们可以利用反射判断其方法是否存在来实现。这种编程思想叫做面向切面编程AOP,面向对象编程叫OOP

  • 相关阅读:
    hdu6761 Mininum Index // lyndon分解 + duval贪心 + 秦九韶算法
    hdu6762 Mow // 半平面交 模拟 双端队列
    数据库增删改查操作
    移动端自动化概念
    范围查询和模糊查询
    软件测试技能要求总结
    继承
    luogu_P2024 [NOI2001]食物链
    luogu_P4092 [HEOI2016/TJOI2016]树
    luogu_P2887 [USACO07NOV]防晒霜Sunscreen
  • 原文地址:https://www.cnblogs.com/chiyun/p/14066632.html
Copyright © 2020-2023  润新知