Python入门自学进阶-Web框架——14、Django的Form验证

Django的Form验证

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="logintest.html" method="post">
        <p>用户名称:<input type="text" name="username" placeholder="请输入用户名"></p>
        <p>用户邮箱:<input type="text" name="email" placeholder="请输入用户邮箱"></p>
        <p>用户邮箱:<input type="password" name="pwd" placeholder="请输入用户密码"></p>
        {% csrf_token %}
        <input type="submit" value="Form提交">
        <input id="ajax_submit" type="button" value="Ajax提交">
    </form>
    <script src="/static/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#ajax_submit").click(function () {
                $.ajax({
                    url: "logintest.html",
                    data: {username:'root',email:'ww123@qq.com',pwd:'123123',csrfmiddlewaretoken: '{{ csrf_token }}'},
                    type:'POST',
                    success:function (arg) {
                        console.log(arg);
                    }
                })
            })
        })
    </script>
</body>
</html>
def logintest(req):
    if req.method == "GET":
        return render(req,'logintest.html')
    elif req.method == "POST":
        u = req.POST.get("username")
        e = req.POST.get("email")
        p = req.POST.get("pwd")
        print("对获取的数据进行处理:1,校验;2,数据库操作",u,e,p)

        return render(req,'logintest.html',{})

存在的几个问题:
1、对用户提交的数据进行验证,要求提示信息准确,即哪个字段的输入不符合要求,在哪个字段进行提示错误信息;
2、如果提交的数据项比较多,后台req.POST.get()会大量出现;
3、如果要进行数据库操作,如使用filter()或create(),参数中要写大量如username=,email=,pwd=。。。等长的参数;
4、对于前端,如果数据项验证失败,即通过form提交了表单,此时前端的所有数据都清空了,而我们期望正确的输入框数据还在(当然,ajax提交不涉及这个问题,ajax提交后数据依然存在);

用户提交数据的验证

长度、类型、格式验证,重用性要高。

验证分为前端后后端

后端,使用一个模板:
- 邮箱格式
- 用户,名称长度>4
- 密码,长度>7

Django提供了Form验证类模板:

# 定义模板
from django import forms
class LoginForm(forms.Form):
    # 模板中的元素
    username = forms.CharField(min_length=4)
    email = forms.EmailField()

使用验证类进行验证:

def logintest(req):
    if req.method == "GET":
        return render(req,'logintest.html')
    elif req.method == "POST":
        obj = LoginForm(req.POST)
        # 验证
        status = obj.is_valid()
        value_right = obj.clean()
        print('value_right:',value_right)
        value_error = obj.errors
        print('value_error:',value_error)
        print('status:',status)
        if obj.is_valid():
            value_right = obj.clean()
            # create(**value_right)   数据库增加记录,参数直接使用**value_right
        else:
            value_error = obj.errors.as_json()
            print('value_error_asjson:',value_error)
        return render(req,'logintest.html',{})

先定义一个模板,这个模板要继承自django的forms中的Form类,然后定义元素,注意,这里的变量名,如username、email不是随意取的,必须与前台form表单中各提交数据标签的name值一致,然后就是具有特定验证功能的CharField()、EmailField()等。在视图函数中使用这个模板
进行验证,先实例化一个模板对象,将req.POST作为参数,这样就会自动获取POST中的对应值进行验证。生成对象并不能验证,还需要调用is_valid()方法,这时才会进行验证,结果为True或False,验证无措,才会返回True,只要有一个字段验证错误,就是False。clean()方法是获取验证正确的字段的一个字典,errors则是错误字段及错误信息的一个无序列表字符串,使用as_json()转换为json字符串。

 因为email字段输入的不符合emailed格式,验证错误,value_error,即errors显示email的错误列表信息,转换为json字符串显示格式,is_valid()结果为false。clean()只是正确字段的字典。

这里有一个问题,就是如果模板中没有定义的字段,在clean()中不能获取,如这里的pwd字段,还需要使用POST.get()获取。

通过错误信息的json格式,可以看到错误的种类,即code的值,这里有invalid——格式不符合,min_length——最小长度不够,required——字段需要值,即字段为空了等。相应的message就是对应错误代码的说明信息,可以是汉字说明。 

# 定义模板
from django import forms
class LoginForm(forms.Form):
    # 模板中的元素
    username = forms.CharField(min_length=4,error_messages={"min_length":"用户名长度不能小于4","required":"用户名不能为空"})
    email = forms.EmailField(error_messages={"invalid":"邮箱名格式不符合要求","required":"邮箱不能为空"})

运行打印结果:

value_right: {}
value_error: <ul class="errorlist"><li>username<ul class="errorlist"><li>用户名不能为空</li></ul></li><li>email<ul class="errorlist"><li>邮箱不能为空</li></ul></li></ul>
status: False
value_error_asjson: {"username": [{"message": "\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a", "code": "required"}], "email": [{"message": "\u90ae\u7bb1\u4e0d\u80fd\u4e3a\u7a7a", "code": "required"}]}

转换为json时,汉字转换为Unicode编码了。

 对于errors属性,通过打印结果看,好像是一个字符串,实际是什么呢?通过type(obj.errors),打印出类型:<class 'django.forms.utils.ErrorDict'>,是一个错误字典,看一下这个类的源代码:

@html_safe
class ErrorDict(dict):
    """
    A collection of errors that knows how to display itself in various formats.

    The dictionary keys are the field names, and the values are the errors.
    """
    def as_data(self):
        return {f: e.as_data() for f, e in self.items()}

    def get_json_data(self, escape_html=False):
        return {f: e.get_json_data(escape_html) for f, e in self.items()}

    def as_json(self, escape_html=False):
        return json.dumps(self.get_json_data(escape_html))

    def as_ul(self):
        if not self:
            return ''
        return format_html(
            '<ul class="errorlist">{}</ul>',
            format_html_join('', '<li>{}{}</li>', self.items())
        )

    def as_text(self):
        output = []
        for field, errors in self.items():
            output.append('* %s' % field)
            output.append('\n'.join('  * %s' % e for e in errors))
        return '\n'.join(output)

    def __str__(self):
        return self.as_ul()

继承自字典dict,我们看到了as_json(),返回的是json.dumps(),即转换为json格式字符串。而其__str__()是返回as_ul(),看as_ul(),其格式就是我们看到的打印的结果。

因为errors是一个<class 'django.forms.utils.ErrorDict'>,使用obj.errors['email']访问一下,即
print(obj.errors['email'])结果为:<ul class="errorlist"><li>邮箱不能为空</li></ul>,这是不是一个字符串呢?打印类型print(type(obj.errors['email'])),结果为:<class 'django.forms.utils.ErrorList'>,是一个列表,可以通过下标获取:print('value_error:',value_error['email'][0]),结果:value_error: 邮箱不能为空。

也就是说,在生成obj对象时,相关的错误信息就存在对象中了,可以将此对象传递给前端:

def logintest(req):
    if req.method == "GET":
        return render(req,'logintest.html')
    elif req.method == "POST":
        obj = LoginForm(req.POST)
        # 验证
        status = obj.is_valid()
        value_right = obj.clean()
        print('value_right:',value_right)
        value_error = obj.errors
        print('value_error:',value_error['email'][0])

        print('status:',status)
        if obj.is_valid():
            value_right = obj.clean()
            # create(**value_right)   数据库增加记录,参数直接使用**value_right
        else:
            value_error = obj.errors.as_json()
            print('value_error_asjson:',value_error)
        return render(req,'logintest.html',{'oo':obj})
<form action="logintest.html" method="post">
        <p>用户名称:<input type="text" name="username" placeholder="请输入用户名"><span>{{ oo.errors.username.0 }}</span></p>
        <p>用户邮箱:<input type="text" name="email" placeholder="请输入用户邮箱"><span>{{ oo.errors.email.0 }}</span></p>
        <p>用户邮箱:<input type="password" name="pwd" placeholder="请输入用户密码"></p>

前端只需显示一个错误信息,所以只取索引0的值。对于第一个get请求,没有传递oo对象,对于django来说,没有的对象,返回的就是null,但对于其他语言,有可能出错。

现在的还有个问题是,form提交后,如果有字段出错,希望字段还保留输入的信息,要实现这个功能,就不能我们自己写input标签,需要Form来实现。

obj = LoginForm()
print(obj['username'])
print(obj['email'])

结果为:
<input type="text" name="username" minlength="4" required id="id_username">
<input type="email" name="email" required id="id_email">

对于不传参数的对象,obj['username']是生成一个input标签。

传递一个参数:obj=LoginForm({'username':'qwert','email':'qw@123}),则结果为

<input type="text" name="username" value="qwert" minlength="4" required id="id_username">
<input type="email" name="email" value="dd@123" required id="id_email">

在前端,可以使用这种方式自动生成input标签:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="logintest.html" method="post">
        <p>用户名称:{{ oo.username }}<span>{{ oo.errors.username.0 }}</span></p>
        <p>用户邮箱:{{ oo.email }}<span>{{ oo.errors.email.0 }}</span></p>
        <p>用户邮箱:<input type="password" name="pwd" placeholder="请输入用户密码"></p>
        {% csrf_token %}
        <input type="submit" value="Form提交">
        <input id="ajax_submit" type="button" value="Ajax提交">
    </form>
    <script src="/static/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#ajax_submit").click(function () {
                $.ajax({
                    url: "logintest.html",
                    data: {username:'root',email:'ww123@qq.com',pwd:'123123',csrfmiddlewaretoken: '{{ csrf_token }}'},
                    type:'POST',
                    success:function (arg) {
                        console.log(arg);
                    }
                })
            })
        })
    </script>
</body>
</html>
def logintest(req):
    if req.method == "GET":
        obj = LoginForm()
        return render(req,'logintest.html',{'oo':obj})
    elif req.method == "POST":
        obj = LoginForm(req.POST)
        if obj.is_valid():
            value_right = obj.clean()
            # create(**value_right)   数据库增加记录,参数直接使用**value_right
        else:
            return render(req,'logintest.html',{'oo':obj})

这样就能在输入错误后保留原数据。

 需要先运行is_valid()然后才能clean()

ajax实现提交验证,保留原输入值无需实现,ajax本身不刷新页面,输入值一直保持

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .error-msg{
            color: red;
            font-size: 16px;
        }
    </style>
</head>
<body>
    <form action="loginajax.html" method="post">
        <p>用户名称:<input type="text" name="username" placeholder="请输入用户名"><span></span></p>
        <p>用户邮箱:<input type="text" name="email" placeholder="请输入用户邮箱"></p><span></span></p>
        <p>用户密码:<input type="password" name="pwd" placeholder="请输入用户密码"></p>
        {% csrf_token %}
        <input type="submit" value="Form提交">
        <input id="ajax_submit" type="button" value="Ajax提交">
    </form>
    <script src="/static/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#ajax_submit").click(function () {
                $.ajax({
                    url: "loginajax.html",
                    data: $('form').serialize(),
                    type:'POST',
                    success:function (arg) {
                        $('.error-msg').remove(); // 将具有class=error-msg的所有标签删除
                        var v1 = JSON.parse(arg);
                        //v1是一个json对象,内容:{status: false, error: '{"email": [{"message": "\\u90ae\\u7bb1\\u540d\\u683c\\u…0d\\u7b26\\u5408\\u8981\\u6c42", "code": "invalid"}]}', data: null}
                        //注意:其error的值为字符串
                        if(!v1.status){
                            var error_obj = JSON.parse(v1.error);  //这里要对v1.error再次进行解析,因为其是字符串,否则下面的each出错
                            $.each(error_obj,function (k,v) {
                                var tag = document.createElement('span');
                                tag.className = 'error-msg';
                                tag.innerHTML = v[0].message;
                                $("input[name='" + k +"']").after(tag);
                            })
                        }else {
                            location.href = 'loginajax.html';
                        }

                    }
                })
            })
        })
    </script>
</body>
</html>

前端:获取表单的数据,可以使用serialize()函数,这个获取到的数据可以直接作为ajax的data值使用。

def loginajax(req):
    if req.method == "GET":
        return render(req,'loginajax.html')
    elif req.method == "POST":
        ret = {'status':True,'error':None,'data':None}
        obj = LoginForm(req.POST)
        if obj.is_valid():
            print(obj.clean())
            print(req.POST.get('pwd'))
        else:
            res_str = obj.errors.as_json()
            ret['status'] = False
            ret['error'] = res_str
        return HttpResponse(json.dumps(ret))

后端ajax提交需要直接HttpResponse返回一个json格式的字符串,使用了dumps()方法。

上面的实现方式,对于前端,在错误信息处理上,进行了两次JSON.parse(),有些繁琐。

对后端返回的错误信息进行处理:

def loginajax(req):
    if req.method == "GET":
        return render(req,'loginajax.html')
    elif req.method == "POST":
        ret = {'status':True,'error':None,'data':None}
        obj = LoginForm(req.POST)
        if obj.is_valid():
            print(obj.clean())
            print(req.POST.get('pwd'))
        else:
            # res_str = obj.errors.as_json()
            # ret['status'] = False
            # ret['error'] = res_str
            ret['status'] = False
            ret['error'] = obj.errors.as_data()
            # obj.errors.as_data()的值为:{'username': [ValidationError(['用户名不能为空'])], 'email': [ValidationError(['邮箱名格式不符合要求'])]}
            # 其value中是如下类的实例:django.core.exceptions.ValidationError
            # 将其进行一次反序列化,即将ValidationError(['用户名不能为空'])进行一次反序列化
            # 最后将ret反序列化,这样一共进行了两次反序列化
            # ValidationError(['用户名不能为空'])进行一次反序列化,因为其在ret中,所以可以在反序列化ret时,使用cls=参数,指定一个进行反序列化的类
        return HttpResponse(json.dumps(ret,cls=JsonCustomEncode))

from django.core.validators import ValidationError
class JsonCustomEncode(json.JSONEncoder):
    def default(self, field):
        if isinstance(field,ValidationError):
            return {'code':field.code,'message':field.message}
        else:
            return json.JSONEncoder.default(self,field)

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .error-msg{
            color: red;
            font-size: 16px;
        }
    </style>
</head>
<body>
    <form action="loginajax.html" method="post">
        <p>用户名称:<input type="text" name="username" placeholder="请输入用户名"><span></span></p>
        <p>用户邮箱:<input type="text" name="email" placeholder="请输入用户邮箱"></p><span></span></p>
        <p>用户密码:<input type="password" name="pwd" placeholder="请输入用户密码"></p>
        {% csrf_token %}
        <input type="submit" value="Form提交">
        <input id="ajax_submit" type="button" value="Ajax提交">
    </form>
    <script src="/static/jquery-3.6.0.js"></script>
    <script>
        $(function () {
            $("#ajax_submit").click(function () {
                $.ajax({
                    url: "loginajax.html",
                    data: $('form').serialize(),
                    type:'POST',
                    success:function (arg) {
                        $('.error-msg').remove(); // 将具有class=error-msg的所有标签删除
                        var v1 = JSON.parse(arg);
                        //v1是一个json对象,内容:{status: false, error: '{"email": [{"message": "\\u90ae\\u7bb1\\u540d\\u683c\\u…0d\\u7b26\\u5408\\u8981\\u6c42", "code": "invalid"}]}', data: null}
                        //注意:其error的值为字符串
                        if(!v1.status){
                            // var error_obj = JSON.parse(v1.error);  //这里要对v1.error再次进行解析,因为其是字符串,否则下面的each出错
                            var error_obj = v1.error;  //对于后端两次反序列化的返回结果,v1.error是一个json对象了,不需要再次进行JSON.parse解析了,重点
                            alert(typeof error_obj);
                            $.each(error_obj,function (k,v) {
                                // k:username或email
                                // v:[{},{},{},]
                                var tag = document.createElement('span');
                                tag.className = 'error-msg';
                                tag.innerHTML = v[0].message;
                                $("input[name='" + k +"']").after(tag);
                            })
                        }else {
                            location.href = 'loginajax.html';
                        }

                    }
                })
            })
        })
    </script>
</body>
</html>

以上是Form验证的基本用法,日常开发中需要的其他一些问题:

- 除了字符验证和邮件验证,还有哪些验证,可不可以自己定制规则
- 除了生成input标签,是否可以生成其他标签
- 显示默认值


版权声明:本文为kaoa000原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。