首页 > 代码库 > 表单验证

表单验证

本片开始一下表单的验证内容。

比如我们要控制输入,不能空,不能过长,邮箱正确,密码强度等等。

普遍的js前端验证

简单说明,不是学习Django的重点。

我们只需要修改上篇中的search_form.html模板,给submit添加一个客户端事件即可,修改如下

 1 <html> 2 <head> 3     <title>Search</title> 4     <script> 5         function test() { 6             var key = document.getElementById(q); 7             if (key != null && key.value.trim().length > 0) { 8                 alert("search key is ok!"); 9                 return true;10             }11             else {12                 alert("search key is empty!");13                 return false;14             }15         }16     </script>17 </head>18 <body>19     {% if error %}20     <p style="color:red;">Please submit a search term</p>21     {% endif %}22     <!--<form action="/forms/search/" method="get">-->23     <form action="" method="get">24         <input type="text" id="q" name="q">25         <input type="submit" value="Search" onclick="return test();">26     </form>27 </body>28 </html>
<input type="submit" value="http://www.mamicode.com/Search" onclick="return test();">这里添加了客户端的点击事件,并返回校验结果,如果为False将阻止表单提交。

简单的验证

之前已经验证为空,则给出提示,那如果我们要控制检索词长度呢?显然根据error为True或False是不能区分那种错误的。我们修改视图函数如下

 1 def search(request): 2     errors = [] 3     if q in request.GET : 4         q = request.GET[q] 5         if not q: 6             errors.append(Enter a search term.) 7         elif len(q)>20: 8             errors.append(enter most 20 charactors.) 9         else:10             books = Book.objects.filter(title__icontains=q)11             return render_to_response(search_results.html,12             {books: books, query: q})13 14     return render_to_response(search_form.html,{errors:errors})

将错误信息改为列表,为空时填写为空提示,过长时填写过长提示。

因为模板参数变了,修改模板

 1 <body> 2     {% if errors %} 3     <ul> 4         {% for error in errors %} 5         <li><p style="color:red;">{{error}}</p></li> 6         {% endfor %}  7     </ul> 8     {% endif %} 9     10     <form action="" method="get">11         <input type="text" id="q" name="q">12         <input type="submit" value="Search">13     </form>14 </body>15 </html>

再看我们的结果

虽然我们的目的达到了,但是如果有多种验证需求,我们要枚举所有验证,if else出来吗?显然这是不合理的。

编写Contact表单

我们从contact_form.html模板入手:

 1 <html> 2 <head> 3     <title>Contact us</title> 4 </head> 5 <body> 6     <h1>Contact us</h1> 7  8     {% if errors %} 9     <ul>10         {% for error in errors %}11         <li>{{ error }}</li>12         {% endfor %}13     </ul>14     {% endif %}15 16     <form action="/contact/" method="post">17         {% csrf_token %}18         <p>Subject: <input type="text" name="subject"></p>19         <p>Your e-mail (optional): <input type="text" name="email"></p>20         <p>Message: <textarea name="message" rows="10" cols="50"></textarea></p>21         <input type="submit" value="Submit">22     </form>23 </body>24 </html>

我们复制了前一个模板search_form.html中错误信息显示的代码。form的method设置为”post”而非”get”,因为这个表单提交之后会有一个服务器端的操作:发送一封e-mail。

对应的contact视图如下:

 1 def contact(request): 2     errors = [] 3     if request.method == POST: 4         if not request.POST.get(subject, ‘‘): 5             errors.append(Enter a subject.) 6         if not request.POST.get(message, ‘‘): 7             errors.append(Enter a message.) 8         if request.POST.get(email) and @ not in request.POST[email]: 9             errors.append(Enter a valid e-mail address.)10         if not errors:11             try:12                 send_mail(13                     request.POST[subject],14                     request.POST[message],15                     request.POST.get(email, noreply@example.com),16                     [siteowner@example.com],17                     )18             except:19                 return HttpResponse("exception.")20 21             return HttpResponseRedirect(/contact/thanks/)22     return render_to_response(contact_form.html,23         {errors: errors},context_instance=RequestContext(request))

除了必要的模块(比如HttpResponse,render_to_response等以前都提过),这里我们要新引入几个模块,如下

1 from django.core.mail import send_mail2 from django.http import HttpResponseRedirect3 from django.template import Context,RequestContext

这是我们这个contact视图用到的。(在写这些内容的时候,我遇到了些问题记录在contact表单错误解决记录,这里就不赘述了)

由于视图要发送一封邮件,但是我们本地没有做更多邮件配置(参见这里),我们可以Console backend输出邮件信息。

只需要在settings.py中增加如下一句即可:

EMAIL_BACKEND = django.core.mail.backends.console.EmailBackend

邮件发送成功需要转向thanks视图,如下

1 def thanks(request):2     return HttpResponse("thanks for your suggestion!")

我们使用HttpResponseRedirect对象将网页重定向至一个包含成功信息的页面,原因就是: 若用户刷新一个包含POST表单的页面,那么请求将会重新发送造成重复。 这通常会造成非期望的结果,比如说重复的数据库记录;在我们的例子中,将导致发送两封同样的邮件。 如果用户在POST表单之后被重定向至另外的页面,就不会造成重复的请求了。

配置URLconf如下

1 url(r^contact/$,contact),2 url(r^contact/thanks/$,thanks),

我们的视图是可以正常运行的,运行效果就不贴了,contact表单错误解决记录里都有提到。

当我们提交失败(email验证失效)后,数据全部丢失了!这显示是很糟糕的情况。我们修改视图函数,解决这个问题。

 1 def contact(request): 2     errors = [] 3     if request.method == POST: 4         if not request.POST.get(subject, ‘‘): 5             errors.append(Enter a subject.) 6         if not request.POST.get(message, ‘‘): 7             errors.append(Enter a message.) 8         if request.POST.get(email) and @ not in request.POST[email]: 9             errors.append(Enter a valid e-mail address.)10         if not errors:11             try:12                 send_mail(13                     request.POST[subject],14                     request.POST[message],15                     request.POST.get(email, noreply@example.com),16                     [siteowner@example.com],17                     )18             except:19                 return HttpResponse("exception.")20 21             return HttpResponseRedirect(/contact/thanks/)22         23     return render_to_response(contact_form.html,24                               {errors: errors,25                                ‘subject‘:request.POST.get(‘subject‘,‘‘),26                                ‘message‘:request.POST.get(‘message‘,‘‘),27                                ‘email‘:request.POST.get(‘email‘,‘‘),28                                },29                               context_instance=RequestContext(request))

 我们在视图中增加了模型的参数,以便返回contact_form.html模型是能自动填充这些提交失败的数据。修改模型接收这些参数

 1 <html> 2 <head> 3     <title>Contact us</title> 4 </head> 5 <body> 6     <h1>Contact us</h1> 7  8     {% if errors %} 9     <ul>10         {% for error in errors %}11         <li>{{ error }}</li>12         {% endfor %}13     </ul>14     {% endif %}15 16     <form action="/contact/" method="post">17         {% csrf_token %}18         <p>Subject: <input type="text" name="subject" value="http://www.mamicode.com/{{subject}}"></p>19         <p>Your e-mail (optional): <input type="text" name="email" value="http://www.mamicode.com/{{email}}"></p>20         <p>Message: <textarea name="message" rows="10" cols="50">{{message}}</textarea></p>21         <input type="submit" value="Submit">22     </form>23 </body>24 </html>

这时候,当我们提交表单失败时,错误的数据会自动再次被填充到表单中,运行效果如下

Form类

直到这里,我们并没有解决如果有多种验证需求,我们要枚举所有验证,if else出来吗?的问题。下面使用Django自带的form库来解决这个问题,使用她来重写contact表单应用。

小插曲

 Django的newforms库  

在Django社区上会经常看到django.newforms这个词语。当人们讨论django.newforms,其实就是这里将要介绍的django.forms。  

改名其实有历史原因的。 当Django一次向公众发行时,它有一个复杂难懂的表单系统:django.forms。后来它被完全重写了,新的版本改叫作:django.newforms,这样人们还可以通过名称,使用旧版本。 当Django 1.0发布时,旧版本django.forms就不再使用了,而django.newforms也终于可以名正言顺的叫做:django.forms。 

Form类使用

表单框架最主要的用法是,为每一个将要处理的HTML的 <Form> 定义一个Form类。 在这个例子中,我们只有一个 <Form> ,因此我们只需定义一个Form类。 这个类可以存在于任何地方,甚至直接写在 views.py 文件里也行,但是社区的惯例是把Form类都放到一个文件中:forms.py。在存放 views.py  的目录中,创建这个文件,然后输入:

1 from django import forms2 3 class ContactForm(forms.Form):4     subject = forms.CharField()5     email = forms.EmailField(required=False)6     message = forms.CharField()

这看上去简单易懂,很像在Django模型中使用的语法。

表单中的每一个字段(域)作为Form类的属性,被展现成Field类。这里只用到CharFieldEmailField类型。 每一个字段都默认是必填。要使email成为可选项,我们需要指定required=False。

让我们在交互式窗口看看这段代码做了些什么。

记住,这里我们不能简单的使用Python命令进入交互模式,因为我们需要加载Django的运行环境,所以我们应该使用命令 

Python manage.py shell

认输出按照HTML的<table>标签格式,也可以是<ul>格式,如下:

ContactForm类每个属性都默认生成input标签,name是类的属性名,id是‘id_‘+类的属性名。

Form验证

Form对象做的第二件事是校验数据。 为了校验数据,我们创建一个新的对Form象,并且传入一个与定义匹配的字典类型数据:

f = ContactForm({‘subject‘: ‘Hello‘, ‘email‘: ‘adrian@example.com‘, ‘message‘: ‘Nice site!‘})

旦你对一个Form实体赋值,你就得到了一个绑定form,调用任何绑定form的is_valid()方法,就可以知道它的数据是否合法。如果我们不传入email值,它依然是合法的。因为我们指定这个字段的属性required=False,但是,如果留空subjectmessage,整个Form就不再合法了:

你可以逐一查看每个字段及整个Form的出错消息:

最终,如果一个Form实体的数据是合法的,它就会有一个可用的cleaned_data属性。 这是一个包含干净的提交数据的字典。 Django的form框架不但校验数据,它还会把它们转换成相应的Python类型数据,这叫做清理数据。

在视图中使用Form对象

我们修改之前的contact视图,使用Form对象来重写

 1 def contact(request): 2     if request.method == POST: 3         form = ContactForm(request.POST) 4         if form.is_valid(): 5             cd=form.cleaned_data 6             send_mail( 7                 cd[subject], 8                 cd[message], 9                 cd.get(email, noreply@example.com),10                     [siteowner@example.com],11                 )12             return HttpResponseRedirect(/contact/thanks/)13     else:14         form = ContactForm()15     return render_to_response(contact_form.html,{form:form},context_instance=RequestContext(request))

我们将各个字段使用ContactForm给包装起来了,初始化需要的参数刚好是包含在request.POST中。为什么呢?因为如果不是提交操作,我们返回contact_form.html模型,传递的参数是一个空的ContactForm,相当于给用户一个页面,让用户自己去填写初始化参数。

再看我们修改的contact_form.html模型:

 1 <html> 2 <head> 3     <title>Contact us</title> 4 </head> 5 <body> 6     <h1>Contact us</h1> 7  8     {% if form.errors %} 9         <p style="color:red;">please correct the error{{ form.errors|pluralize }} below</p>  10     {% endif %}11 12     <form action="" method="post">13         {% csrf_token %}14         <table>15             {{form.as_table}}16         </table>17         <input type="submit" value="Submit">18     </form>19 </body>20 </html>

我们修改了errors,使用Forms自身的errors进行判定。页面的输入框使用form来自己填充,你可以自己选择是table格式或者ul格式。这里展示的结果就是ContactForm内定义的字段。

我们看下运行效果:

必填项为空的字段进行了提示,邮箱的验证也给出浮框提示,效果比自己写要漂亮很多。

其他Form设置

修改ContactForm如下

1 class ContactForm(forms.Form):2     subject = forms.CharField(max_length=10)3     email = forms.EmailField(required=False, label=‘Your e-mail address‘ )4     message = forms.CharField(widget=forms.Textarea)

我们为subject增加了最大长度校验,为email展示做了label修改,为message指定展示标签,效果如下

设置初始值

 1 def contact(request): 2     if request.method == POST: 3         form = ContactForm(request.POST) 4         if form.is_valid(): 5             cd=form.cleaned_data 6             send_mail( 7                 cd[subject], 8                 cd[message], 9                 cd.get(email, noreply@example.com),10                     [siteowner@example.com],11                 )12             return HttpResponseRedirect(/contact/thanks/)13     else:14         form = ContactForm(initial={subject: I love your site!})15     return render_to_response(contact_form.html,{form:form},context_instance=RequestContext(request))

页面第一展示时,subject字段将被那个句子填充。

传入初始值(initial) 数据和传入数据以绑定表单(new一个ContactForm对象)是有区别的。 最大的区别是,如果仅传入* 初始值* 数据,表单是unbound的,那意味着它没有错误消息。

自定义校验规则

 1 from django import forms 2  3 class ContactForm(forms.Form): 4     subject = forms.CharField(max_length=100) 5     email = forms.EmailField(required=False) 6     message = forms.CharField(widget=forms.Textarea) 7  8     def clean_message(self): 9         message = self.cleaned_data[message]10         num_words = len(message.split())11         if num_words < 4:12             raise forms.ValidationError("Not enough words!")13         return message

Django的form系统自动寻找匹配的函数方法,该方法名称以clean_开头,并以字段名称结束。 如果有这样的方法,它将在校验时被调用。clean_message()方法将在指定字段的默认校验逻辑执行之后被调用,比如默认的非空校验之后。

定制Form设计

修改contact_form.html如下,改动比较大

 1 <html> 2 <head> 3     <title>Contact us</title> 4     <style type="text/css"> 5         ul.errorlist { 6             margin: 0; 7             padding: 0; 8         } 9 10         .errorlist li {11             background-color: red;12             color: white;13             display: block;14             font-size: 10px;15             margin: 0 0 3px;16             padding: 4px 5px;17         }18     </style>19 </head>20 <body>21     <h1>Contact us</h1>22 23     {% if form.errors %}24     <p style="color: red;">25         Please correct the error{{ form.errors|pluralize }} below.26     </p>27     {% endif %}28 29     <form action="" method="post">30         {% csrf_token %}31         <div class="field">32             {{ form.subject.errors }}33             <label for="id_subject">Subject:</label>34             {{ form.subject }}35         </div>36         <div class="field">37             {{ form.email.errors }}38             <label for="id_email">Your e-mail address:</label>39             {{ form.email }}40         </div>41         <div class="field{% if form.message.errors %} errors{% endif %}">42             {% if form.message.errors %}43             <ul>44                 {% for error in form.message.errors %}45                 <li><strong>{{ error }}</strong></li>46                 {% endfor %}47             </ul>48             {% endif %}49             <label for="id_message">Message:</label>50             {{ form.message }}51         </div>52         <input type="submit" value="Submit">53     </form>54 </body>55 </html>

运行效果如下:

我们看一下它生成的html

 1 <form action="" method="post"> 2         <input type="hidden" name="csrfmiddlewaretoken" value="3NfChix4d8Ttgqjc7mNfobwDYbY3HqWw"> 3         <div class="field"> 4             <ul class="errorlist"><li>确保该变量包含不超过 10 字符 (目前字符数 17)。</li></ul> 5             <label for="id_subject">Subject:</label> 6             <input id="id_subject" maxlength="10" name="subject" type="text" value="I love your site!"> 7         </div> 8         <div class="field"> 9             <label for="id_email">Your e-mail address:</label>10             <input id="id_email" name="email" type="email">11         </div>12         <div class="field errors">13             <ul>14                 <li><strong>这个字段是必填项。</strong></li>15             </ul>16             17             <label for="id_message">Message:</label>18             <textarea cols="40" id="id_message" name="message" rows="10"></textarea>19         </div>20         <input type="submit" value="Submit">21     </form>

{{ form.subject.errors }} 会在 <ul class="errorlist"> 里面显示。

如果字段是合法的,或者form没有被绑定,就什么都不显示,如下是初始化时的html

 1 <form action="" method="post"> 2         <input type="hidden" name="csrfmiddlewaretoken" value="3NfChix4d8Ttgqjc7mNfobwDYbY3HqWw"> 3         <div class="field"> 4             <label for="id_subject">Subject:</label> 5             <input id="id_subject" maxlength="10" name="subject" type="text" value="I love your site!"> 6         </div> 7         <div class="field"> 8             <label for="id_email">Your e-mail address:</label> 9             <input id="id_email" name="email" type="email">10         </div>11         <div class="field">12             <label for="id_message">Message:</label>13             <textarea cols="40" id="id_message" name="message" rows="10"></textarea>14         </div>15         <input type="submit" value="Submit">16     </form>

小结

这篇终于算是写完了,由于工作忙,一直没得空写完。东西比较多,还得慢慢消化才行。

到这里,基础的初级知识基本就结束了。后边进入所谓‘高级阶段’,希望自己能继续坚持学习完并记录在这里。