表单验证

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

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

普遍的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="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_mail
2 from django.http import HttpResponseRedirect
3 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="{{subject}}"></p>
19         <p>Your e-mail (optional): <input type="text" name="email" value="{{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 forms
2 
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>

小结

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

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

 

posted @ 2014-07-29 15:09  棉花年度  阅读(237)  评论(0编辑  收藏  举报