表单验证
本片开始一下表单的验证内容。
比如我们要控制输入,不能空,不能过长,邮箱正确,密码强度等等。
普遍的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类。这里只用到CharField和EmailField类型。 每一个字段都默认是必填。要使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,但是,如果留空subject或message,整个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>
小结
这篇终于算是写完了,由于工作忙,一直没得空写完。东西比较多,还得慢慢消化才行。
到这里,基础的初级知识基本就结束了。后边进入所谓‘高级阶段’,希望自己能继续坚持学习完并记录在这里。