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

posted @ 2020-12-01 10:26  王寄鱼  阅读(134)  评论(0编辑  收藏  举报