从Google的简朴的单个搜索框,到常见的Blog评论提交表单,再到复杂的自定义数据输入接 口,HTML表单一直是交互性网站的支柱。 本章介绍如何用Django对用户经过表单提交的数据进行访问、有效性检查以及其它处理。 与此同时,对象和Form对象。咱们将介绍HttpRequestcss
咱们在第三章讲述View的函数时已经介绍过HttpRequest对象了,但当时并无讲太多。 让咱们回忆下:每一个view函数的第一个参数是一个HttpRequest对象,就像下面这个hello()函数:html
from django.http import HttpResponse def hello(request): return HttpResponse("Hello world")
HttpRequest对象,好比上面代码里的request变量,会有一些有趣的、你必须让本身熟悉的 属性和方法,以便知道能拿它们来作些什么。 在view函数的执行过程当中,你能够用这些属性来获取当前request的一些信息(好比,你正在加载这个页面的用户是谁,或者用的是什么浏览器)。前端
HttpRequest对象包含当前请求URL的一些信息:web
属性/方法 | 说明 | 举例 |
---|---|---|
request.path |
除域名之外的请求路径,以正斜杠开头 | "/hello/" |
request.get_host() |
主机名(好比,一般所说的域名) | "127.0.0.1:8000" or "www.example.com" |
request.get_full_path() |
请求路径,可能包含查询字符串 | "/hello/?print=true" |
request.is_secure() |
若是经过HTTPS访问,则此方法返回True, 不然返回False | True 或者 False |
在view函数里,要始终用这个属性或方法来获得URL,而不要手动输入。 这会使得代码更加灵活,以便在其它地方重用。 下面是一个简单的例子:算法
# BAD! def current_url_view_bad(request): return HttpResponse("Welcome to the page at /current/") # GOOD def current_url_view_good(request): return HttpResponse("Welcome to the page at %s" % request.path)
request.META 是一个Python字典,包含了全部本次HTTP请求的Header信息,好比用户IP地址和用户Agent(一般是浏览器的名称和版本号)。 注意,Header信息的完整列表取决于用户所发送的Header信息和服务器端设置的Header信息。 这个字典中几个常见的键值有:数据库
HTTP_REFERER
,进站前连接网页,若是有的话。 (请注意,它是REFERRER
的笔误。)django
HTTP_USER_AGENT
,用户浏览器的user-agent字符串,若是有的话。 例如: "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17"
.浏览器
REMOTE_ADDR
客户端IP,如:"12.345.67.89"
。(若是申请是通过代理服务器的话,那么它多是以逗号分割的多个IP地址,如:"12.345.67.89,23.456.78.90"
。)服务器
注意,由于 request.META 是一个普通的Python字典,所以当你试图访问一个不存在的键时,会触发一个KeyError异常。 (HTTP header信息是由用户的浏览器所提交的、不该该给予信任的“额外”数据,所以你老是应该好好设计你的应用以便当一个特定的Header数据不存在时, 给出一个优雅的回应。)你应该用 try/except 语句,或者用Python字典的 get() 方法来处理这些“可能不存在的键”:app
# BAD! def ua_display_bad(request): ua = request.META['HTTP_USER_AGENT'] # Might raise KeyError! return HttpResponse("Your browser is %s" % ua) # GOOD (VERSION 1) def ua_display_good1(request): try: ua = request.META['HTTP_USER_AGENT'] except KeyError: ua = 'unknown' return HttpResponse("Your browser is %s" % ua) # GOOD (VERSION 2) def ua_display_good2(request): ua = request.META.get('HTTP_USER_AGENT', 'unknown') return HttpResponse("Your browser is %s" % ua)
咱们鼓励你动手写一个简单的view函数来显示 request.META 的全部数据,这样你就知道里面有什么了。 这个view函数多是这样的:
def display_meta(request): values = request.META.items() values.sort() html = [] for k, v in values: html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v)) return HttpResponse('<table>%s</table>' % '\n'.join(html))
作为一个练习,看你本身能不能把上面这个view函数改用Django模板系统来实现,而不是上面这样来手动输入HTML代码。 也能够试着把前面提到的 request.path 方法或 HttpRequest 对象的其它方法加进去。
除了基本的元数据,HttpRequest对象还有两个属性包含了用户所提交的信息: request.GET 和 request.POST。两者都是类字典对象,你能够经过它们来访问GET和POST数据。
类字典对象
咱们说“request.GET和request.POST是类字典对象”,意思是他们的行为像 Python里标准的字典对象,但在技术底层上他们不是标准字典对象。 好比说,request.GET和request.POST都有get()、keys()和values()方法,你能够用用 for key in request.GET 获取全部的键。
那到底有什么区别呢? 由于request.GET和request.POST拥有一些普通的字典对象所没有的方法。 咱们会稍后讲到。
你可能之前遇到过类似的名字:类文件对象,这些Python对象有一些基本的方法,如read(),用来作真正的Python文件对象的代用品。
POST数据是来自HTML中的〈form〉标签提交的,而GET数据可能来自〈form〉提交也多是URL中的查询字符串(the query string)。
继续本书一直进行的关于书籍、做者、出版社的例子,咱们如今来建立一个简单的view函数以便让用户能够经过书名从数据库中查找书籍。
一般,表单开发分为两个部分: 前端HTML页面用户接口和后台view函数对所提交数据的处理过程。 第一部分很简单;如今咱们来创建个view来显示一个搜索表单:
from django.shortcuts import render_to_response def search_form(request): return render_to_response('search_form.html')
在第三章已经学过,这个view函数能够放到Python的搜索路径的任何位置。 为了便于讨论,我们将它放在 books/views.py 里。
这个 search_form.html 模板,可能看起来是这样的:
<html> <head> <title>Search</title> </head> <body> <form action="/search/" method="get"> <input type="text" name="q"> <input type="submit" value="Search"> </form> </body> </html>
而 urls.py 中的 URLpattern 多是这样的:
from mysite.books import views urlpatterns = patterns('', # ... (r'^search-form/$', views.search_form), # ... )
(注意,咱们直接将views模块import进来了,而不是用相似 from mysite.views import search_form 这样的语句,由于前者看起来更简洁。 咱们将在第8章讲述更多的关于import的用法。)
如今,若是你运行 runserver
命令,而后访问http://127.0.0.1:8000/search-form/
,你会看到搜索界面。 很是简单。
不过,当你经过这个form提交数据时,你会获得一个Django 404错误。 这个Form指向的URL /search/
尚未被实现。 让咱们添加第二个视图函数并设置URL:
# urls.py urlpatterns = patterns('', # ... (r'^search-form/$', views.search_form), (r'^search/$', views.search), # ... ) # views.py def search(request): if 'q' in request.GET: message = 'You searched for: %r' % request.GET['q'] else: message = 'You submitted an empty form.' return HttpResponse(message)
暂时先只显示用户搜索的字词,以肯定搜索数据被正确地提交给了Django,这样你就会知道搜索数据是如何在这个系统中传递的。 简而言之:
在HTML里咱们定义了一个变量q。当提交表单时,变量q的值经过GET(method=”get”)附加在URL /search/上。
处理/search/(search())的视图经过request.GET来获取q的值。
须要注意的是在这里明确地判断q是否包含在request.GET中。就像上面request.META小节里面提到,对于用户提交过来的数据,甚至是正确的数据,都须要进行过滤。 在这里若没有进行检测,那么用户提交一个空的表单将引起KeyError异常:
# BAD! def bad_search(request): # The following line will raise KeyError if 'q' hasn't # been submitted! message = 'You searched for: %r' % request.GET['q'] return HttpResponse(message)
查询字符串参数
由于使用GET方法的数据是经过查询字符串的方式传递的(例如/search/?q=django), 因此咱们可使用requet.GET来获取这些数据。 第三章介绍Django的URLconf系统时咱们比较了Django的简洁的URL与PHP/Java传统的URL,咱们提到将在第七章讲述如何使用传 统的URL。经过刚才的介绍,咱们知道在视图里可使用request.GET来获取传统URL里的查询字符串(例如hours=3)。
获取使用POST方法的数据与GET的类似,只是使用request.POST代替了 request.GET。那么,POST与GET之间有什么不一样?当咱们提交表单仅仅须要获取数据时就能够用GET; 而当咱们提交表单时须要更改服务器数据的状态,或者说发送e-mail,或者其余不只仅是获取并显示数据的时候就使用POST。 在这个搜索书籍的例子里,咱们使用GET,由于这个查询不会更改服务器数据的状态。 (若是你有兴趣了解更多关于GET
和POST
的知识,能够参见http://www.w3.org/2001/tag/doc/whenToUseGet.html。)
既然已经确认用户所提交的数据是有效的,那么接下来就能够从数据库中查询这个有效的数据(一样,在views.py里操做):
from django.http import HttpResponse from django.shortcuts import render_to_response from mysite.books.models import Book def search(request): if 'q' in request.GET and request.GET['q']: q = request.GET['q'] books = Book.objects.filter(title__icontains=q) return render_to_response('search_results.html', {'books': books, 'query': q}) else: return HttpResponse('Please submit a search term.')
让咱们来分析一下上面的代码:
除了检查q是否存在于request.GET以外,咱们还检查来reuqest.GET[‘q’]的值是否为空。
咱们使用Book.objects.filter(title__icontains=q)获取数据库中标题包含q的书籍。 icontains是一个查询关键字(参看第五章和附录B)。这个语句能够理解为获取标题里包含q的书籍,不区分大小写。
这是实现书籍查询的一个很简单的方法。 咱们不推荐在一个包含大量产品的数据库中使用icontains查询,由于那会很慢。 (在真实的案例中,咱们可使用以某种分类的自定义查询系统。 在网上搜索“开源 全文搜索”看看是否有好的方法)
最后,咱们给模板传递来books,一个包含Book对象的列表。 查询结果的显示模板search_results.html以下所示:
<p>You searched for: <strong>{{ query }}</strong></p> {% if books %} <p>Found {{ books|length }} book{{ books|pluralize }}.</p> <ul> {% for book in books %} <li>{{ book.title }}</li> {% endfor %} </ul> {% else %} <p>No books matched your search criteria.</p> {% endif %}
注意这里pluralize的使用,这个过滤器在适当的时候会输出s(例如找到多本书籍)。
同上一章同样,咱们先从最为简单、有效的例子开始。 如今咱们再来找出这个简单的例子中的不足,而后改进他们。
首先,search()视图对于空字符串的处理至关薄弱——仅显示一条”Please submit a search term.”的提示信息。 若用户要从新填写表单必须自行点击“后退”按钮, 这种作法既糟糕又不专业。若是在现实的案例中,咱们这样子编写,那么Django的优点将荡然无存。
在检测到空字符串时更好的解决方法是从新显示表单,并在表单上面给出错误提示以便用户马上从新填写。 最简单的实现方法既是添加else分句从新显示表单,代码以下:
from django.http import HttpResponse from django.shortcuts import render_to_response from mysite.books.models import Book def search_form(request): return render_to_response('search_form.html') def search(request): if 'q' in request.GET and request.GET['q']: q = request.GET['q'] books = Book.objects.filter(title__icontains=q) return render_to_response('search_results.html', {'books': books, 'query': q}) else: **return render_to_response('search_form.html', {'error': True})**
(注意,将search_form()视图也包含进来以便查看)
这段代码里,咱们改进来search()视图:在字符串为空时从新显示search_form.html。 而且给这个模板传递了一个变量error,记录着错误提示信息。 如今咱们编辑一下search_form.html,检测变量error:
<html> <head> <title>Search</title> </head> <body> **{% if error %}** **<p style="color: red;">Please submit a search term.</p>** **{% endif %}** <form action="/search/" method="get"> <input type="text" name="q"> <input type="submit" value="Search"> </form> </body> </html>
咱们修改了search_form()视图所使用的模板,由于search_form()视图没有传递error变量,因此在条用search_form视图时不会显示错误信息。
经过上面的一些修改,如今程序变的好多了,可是如今出现一个问题: 是否有必要专门编写search_form()来显示表单? 按实际状况来讲,当一个请求发送至/search/(未包含GET的数据)后将会显示一个空的表单(带有错误信息)。 因此,只要咱们改变search()视图:当用户访问/search/并未提交任何数据时就隐藏错误信息,这样就移去search_form()视图以及 对应的URLpattern。
def search(request): error = False if 'q' in request.GET: q = request.GET['q'] if not q: error = True else: books = Book.objects.filter(title__icontains=q) return render_to_response('search_results.html', {'books': books, 'query': q}) return render_to_response('search_form.html', {'error': error})
在改进后的视图中,若用户访问/search/而且没有带有GET数据,那么他将看到一个没有错误信息的表单; 若是用户提交了一个空表单,那么它将看到错误提示信息,还有表单; 最后,若用户提交了一个非空的值,那么他将看到搜索结果。
最后,咱们再稍微改进一下这个表单,去掉冗余的部分。 既然已经将两个视图与URLs合并起来,/search/视图管理着表单的显示以及结果的显示,那么在search_form.html里表单的action值就没有必要硬编码的指定URL。 原先的代码是这样:
<form action="/search/" method="get">
如今改为这样:
<form action="" method="get">
action=”“意味着表单将提交给与当前页面相同的URL。 这样修改以后,若是search()
视图不指向其它页面的话,你将没必要再修改action
。
咱们的搜索示例仍然至关地简单,特别从数据验证方面来说;咱们仅仅只验证搜索关键值是否为空。 而后许多HTML表单包含着比检测值是否为空更为复杂的验证。 咱们都有在网站上见过相似如下的错误提示信息:
请输入一个有效的email地址, foo’ 并非一个有效的e-mail地址。
请输入5位数的U.S 邮政编码, 123并不是是一个有效的邮政编码。
请输入YYYY-MM-DD格式的日期。
请输入8位数以上并至少包含一个数字的密码。
关于JavaScript验证
可使用Javascript在客户端浏览器里对数据进行验证,这些知识已超出本书范围。 要注意: 即便在客户端已经作了验证,可是服务器端仍必须再验证一次。 由于有些用户会将JavaScript关闭掉,而且还有一些怀有恶意的用户会尝试提交非法的数据来探测是否有能够攻击的机会。
除了在服务器端对用户提交的数据进行验证(例如在视图里验证),咱们没有其余办法。 JavaScript验证能够看做是额外的功能,但不能做为惟一的验证功能。
咱们来调整一下search()视图,让她可以验证搜索关键词是否小于或等于20个字符。 (为来让例子更为显著,咱们假设若是关键词超过20个字符将致使查询十分缓慢)。那么该如何实现呢? 最简单的方式就是将逻辑处理直接嵌入到视图里,就像这样:
def search(request): error = False if 'q' in request.GET: q = request.GET['q'] if not q: error = True **elif len(q) > 20:** **error = True** else: books = Book.objects.filter(title__icontains=q) return render_to_response('search_results.html', {'books': books, 'query': q}) return render_to_response('search_form.html', {'error': error})
如今,若是尝试着提交一个超过20个字符的搜索关键词,系统不会执行搜索操做,而是显示一条错误提示 信息。 可是,search_form.html里的这条提示信息是:”Please submit a search term.”,这显然是错误的, 因此咱们须要更精确的提示信息:
<html> <head> <title>Search</title> </head> <body> {% if error %} <p style="color: red;">Please submit a search term 20 characters or shorter.</p> {% endif %} <form action="/search/" method="get"> <input type="text" name="q"> <input type="submit" value="Search"> </form> </body> </html>
但像这样修改以后仍有一些问题。 咱们包含万象的提示信息很容易令人产生困惑: 提交一个空表单怎么会出现一个关于20个字符限制的提示? 因此,提示信息必须是详细的,明确的,不会产生疑议。
问题的实质在于咱们只使用来一个布尔类型的变量来检测是否出错,而不是使用一个列表来记录相应的错误信息。 咱们须要作以下的调整:
def search(request): **errors = []** if 'q' in request.GET: q = request.GET['q'] if not q: **errors.append('Enter a search term.')** elif len(q) > 20: **errors.append('Please enter at most 20 characters.')** else: books = Book.objects.filter(title__icontains=q) return render_to_response('search_results.html', {'books': books, 'query': q}) return render_to_response('search_form.html', {**'errors': errors** })
接着,咱们要修改一下search_form.html模板,如今须要显示一个errors列表而不是一个布尔判断。
<html> <head> <title>Search</title> </head> <body> **{% if errors %}** **<ul>** **{% for error in errors %}** **<li>{{ error }}</li>** **{% endfor %}** **</ul>** **{% endif %}** <form action="/search/" method="get"> <input type="text" name="q"> <input type="submit" value="Search"> </form> </body> </html>
虽然咱们一直使用书籍搜索的示例表单,并将起改进的很完美,可是这仍是至关的简陋: 只包含一个字段,q。这简单的例子,咱们不须要使用Django表单库来处理。 可是复杂一点的表单就须要多方面的处理,咱们如今来一下一个较为复杂的例子: 站点联系表单。
这个表单包括用户提交的反馈信息,一个可选的e-mail回信地址。 当这个表单提交而且数据经过验证后,系统将自动发送一封包含题用户提交的信息的e-mail给站点工做人员。
咱们从contact_form.html模板入手:
<html> <head> <title>Contact us</title> </head> <body> <h1>Contact us</h1> {% if errors %} <ul> {% for error in errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} <form action="/contact/" method="post"> <p>Subject: <input type="text" name="subject"></p> <p>Your e-mail (optional): <input type="text" name="email"></p> <p>Message: <textarea name="message" rows="10" cols="50"></textarea></p> <input type="submit" value="Submit"> </form> </body> </html>
咱们定义了三个字段: 主题,e-mail和反馈信息。 除了e-mail字段为可选,其余两个字段都是必填项。 注意,这里咱们使用method=”post”而非method=”get”,由于这个表单会有一个服务器端的操做:发送一封e-mail。 而且,咱们复制了前一个模板search_form.html中错误信息显示的代码。
若是咱们顺着上一节编写search()视图的思路,那么一个contact()视图代码应该像这样:
from django.core.mail import send_mail from django.http import HttpResponseRedirect from django.shortcuts import render_to_response def contact(request): errors = [] if request.method == 'POST': if not request.POST.get('subject', ''): errors.append('Enter a subject.') if not request.POST.get('message', ''): errors.append('Enter a message.') if request.POST.get('email') and '@' not in request.POST['email']: errors.append('Enter a valid e-mail address.') if not errors: send_mail( request.POST['subject'], request.POST['message'], request.POST.get('email', 'noreply@example.com'), ['siteowner@example.com'], ) return HttpResponseRedirect('/contact/thanks/') return render_to_response('contact_form.html', {'errors': errors})
(若是按照书中的示例作下来,这这里可能乎产生一个疑问:contact()视图是否要放在 books/views.py这个文件里。 可是contact()视图与books应用没有任何关联,那么这个视图应该能够放在别的地方? 这毫无紧要,只要在URLconf里正确设置URL与视图之间的映射,Django会正确处理的。 笔者我的喜欢建立一个contact的文件夹,与books文件夹同级。这个文件夹中包括空的__init__.py和views.py两个文件。
如今来分析一下以上的代码:
确认request.method的值是’POST’。用户浏览表单时这个值并不存在,当且仅当表单 被提交时这个值才出现。 (在后面的例子中,request.method将会设置为’GET’,由于在普通的网页浏览中,浏览器都使用GET,而非POST)。判断 request.method的值很好地帮助咱们将表单显示与表单处理隔离开来。
咱们使用request.POST代替request.GET来获取提交过来的数据。 这是必须的,由于contact_form.html里表单使用的是method=”post”。若是在视图里经过POST获取数据,那么request.GET将为空。
这里,有两个必填项,subject 和 message,因此须要对这两个进行验证。 注意,咱们使用request.POST.get()方法,并提供一个空的字符串做为默认值;这个方法很好的解决了键丢失与空数据问题。
虽然email非必填项,但若是有提交她的值则咱们也需进行验证。 咱们的验证算法至关的薄弱,仅验证值是否包含@字符。 在实际应用中,须要更为健壮的验证机制(Django提供这些验证机制,稍候咱们就会看到)。
咱们使用了django.core.mail.send_mail函数来发送e-mail。 这个函数有四个必选参数: 主题,正文,寄信人和收件人列表。 send_mail是Django的EmailMessage类的一个方便的包装,EmailMessage类提供了更高级的方法,好比附件,多部分邮 件,以及对于邮件头部的完整控制。
注意,若要使用send_mail()函数来发送邮件,那么服务器须要配置成可以对外发送邮件,而且在Django中设置出站服务器地址。 参见规范:http://docs.djangoproject.com/en/dev/topics/email/
当邮件发送成功以后,咱们使用HttpResponseRedirect对象将网页重定向至一个包含 成功信息的页面。 包含成功信息的页面这里留给读者去编写(很简单 一个视图/URL映射/一份模板便可),可是咱们要解释一下为什么重定向至新的页面,而不是在模板中直接调用render_to_response()来输 出。
缘由就是: 若用户刷新一个包含POST表单的页面,那么请求将会从新发送形成重复。 这一般会形成非指望的结果,好比说重复的数据库记录;在咱们的例子中,将致使发送两封一样的邮件。 若是用户在POST表单以后被重定向至另外的页面,就不会形成重复的请求了。
咱们应每次都给成功的POST请求作重定向。 这就是web开发的最佳实践。
contact()视图能够正常工做,可是她的验证功能有些复杂。 想象一下假如一个表单包含一打字段,咱们真的将必须去编写每一个域对应的if判断语句?
另一个问题是表单的从新显示。若数据验证失败后,返回客户端的表单中各字段最好是填有原来提交的数据,以便用户查看哪里出现错误(用户也不需再次填写正确的字段值)。 咱们能够手动地将原来的提交数据返回给模板,而且必须编辑HTML里的各字段来填充原来的值。
# views.py def contact(request): errors = [] if request.method == 'POST': if not request.POST.get('subject', ''): errors.append('Enter a subject.') if not request.POST.get('message', ''): errors.append('Enter a message.') if request.POST.get('email') and '@' not in request.POST['email']: errors.append('Enter a valid e-mail address.') if not errors: send_mail( request.POST['subject'], request.POST['message'], request.POST.get('email', `'noreply@example.com`_'), [`'siteowner@example.com`_'], ) return HttpResponseRedirect('/contact/thanks/') return render_to_response('contact_form.html', { 'errors': errors, **'subject': request.POST.get('subject', ''),** **'message': request.POST.get('message', ''),** **'email': request.POST.get('email', ''),** }) # contact_form.html <html> <head> <title>Contact us</title> </head> <body> <h1>Contact us</h1> {% if errors %} <ul> {% for error in errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} <form action="/contact/" method="post"> <p>Subject: <input type="text" name="subject" **value="{{ subject }}"** ></p> <p>Your e-mail (optional): <input type="text" name="email" **value="{{ email }}"** ></p> <p>Message: <textarea name="message" rows="10" cols="50">**{{ message }}**</textarea></p> <input type="submit" value="Submit"> </form> </body> </html>
这看起来杂乱,且写的时候容易出错。 但愿你开始明白使用高级库的用意——负责处理表单及相关校验任务。
Django带有一个form库,称为django.forms,这个库能够处理咱们本章所提到的包括HTML表单显示以及验证。 接下来咱们来深刻了解一下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
。
表单框架最主要的用法是,为每个将要处理的HTML的`` <Form>`` 定义一个Form
类。 在这个例子中,咱们只有一个`` <Form>`` ,所以咱们只需定义一个Form
类。 这个类能够存在于任何地方,甚至直接写在`` views.py`` 文件里也行,可是社区的惯例是把Form
类都放到一个文件中:forms.py
。在存放`` views.py`` 的目录中,建立这个文件,而后输入:
from django import forms class ContactForm(forms.Form): subject = forms.CharField() email = forms.EmailField(required=False) message = forms.CharField()
这看上去简单易懂,而且很像在模块中使用的语法。 表单中的每个字段(域)做为Form
类的属性,被展示成Field
类。这里只用到CharField
和EmailField
类型。 每个字段都默认是必填。要使email
成为可选项,咱们须要指定required=False
。
让咱们钻研到Python解释器里面看看这个类作了些什么。 它作的第一件事是将本身显示成HTML:
>>> from contact.forms import ContactForm >>> f = ContactForm() >>> print f <tr><th><label for="id_subject">Subject:</label></th><td><input type="text" name="subject" id="id_subject" /></td></tr> <tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr> <tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
为了便于访问,Django用`` <label>`` 标志,为每个字段添加了标签。 这个作法使默认行为尽量合适。
默认输出按照HTML的<`` table`` >格式,另外有一些其它格式的输出:
>>> print f.as_ul() <li><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></li> <li><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></li> <li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li> >>> print f.as_p() <p><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></p> <p><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></p> <p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></p>
请注意,标签<table>、<ul>、<form>的开闭合标记没有包含于输出当中,这样你就能够添加额外的行或者自定义格式。
这些类方法只是通常状况下用于快捷显示完整表单的方法。 你一样能够用HTML显示个别字段:
>>> print f['subject'] <input type="text" name="subject" id="id_subject" /> >>> print f['message'] <input type="text" name="message" id="id_message" />
Form
对象作的第二件事是校验数据。 为了校验数据,咱们建立一个新的对Form
象,而且传入一个与定义匹配的字典类型数据:
>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})
一旦你对一个Form
实体赋值,你就获得了一个绑定form:
>>> f.is_bound True
调用任何绑定form的is_valid()
方法,就能够知道它的数据是否合法。 咱们已经为每一个字段传入了值,所以整个Form
是合法的:
>>> f.is_valid() True
若是咱们不传入email
值,它依然是合法的。由于咱们指定这个字段的属性required=False
:
>>> f = ContactForm({'subject': 'Hello', 'message': 'Nice site!'}) >>> f.is_valid() True
可是,若是留空subject
或message
,整个Form
就再也不合法了:
>>> f = ContactForm({'subject': 'Hello'}) >>> f.is_valid() False >>> f = ContactForm({'subject': 'Hello', 'message': ''}) >>> f.is_valid() False
你能够逐一查看每一个字段的出错消息:
>>> f = ContactForm({'subject': 'Hello', 'message': ''}) >>> f['message'].errors [u'This field is required.'] >>> f['subject'].errors [] >>> f['email'].errors []
每个邦定Form
实体都有一个errors
属性,它为你提供了一个字段与错误消息相映射的字典表。
>>> f = ContactForm({'subject': 'Hello', 'message': ''}) >>> f.errors {'message': [u'This field is required.']}
最终,若是一个Form
实体的数据是合法的,它就会有一个可用的cleaned_data
属性。 这是一个包含干净的提交数据的字典。 Django的form框架不但校验数据,它还会把它们转换成相应的Python类型数据,这叫作清理数据。
>>> f = ContactForm({subject': Hello, email: adrian@example.com, message: Nice site!}) >>> f.is_valid() True >>> f.cleaned_data {message': uNice site!, email: uadrian@example.com, subject: uHello}
咱们的contact form只涉及字符串类型,它们会被清理成Unicode对象。若是咱们使用整数型或日期型,form框架会确保方法使用合适的Python整数型或datetime.date
型对象。
在学习了关于Form
类的基本知识后,你会看到咱们如何把它用到视图中,取代contact()
代码中不整齐的部分。 一下示例说明了咱们如何用forms框架重写contact()
:
# views.py from django.shortcuts import render_to_response from mysite.contact.forms import ContactForm def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): cd = form.cleaned_data send_mail( cd['subject'], cd['message'], cd.get('email', 'noreply@example.com'), ['siteowner@example.com'], ) return HttpResponseRedirect('/contact/thanks/') else: form = ContactForm() return render_to_response('contact_form.html', {'form': form}) # contact_form.html <html> <head> <title>Contact us</title> </head> <body> <h1>Contact us</h1> {% if form.errors %} <p style="color: red;"> Please correct the error{{ form.errors|pluralize }} below. </p> {% endif %} <form action="" method="post"> <table> {{ form.as_table }} </table> <input type="submit" value="Submit"> </form> </body> </html>
看看,咱们能移除这么多不整齐的代码! Django的forms框架处理HTML显示、数据校验、数据清理和表单错误重现。
尝试在本地运行。 装载表单,先留空全部字段提交空表单;继而填写一个错误的邮箱地址再尝试提交表单;最后再用正确数据提交表单。 (根据服务器的设置,当send_mail()
被调用时,你将获得一个错误提示。而这是另外一个问题。)
你可能首先注意到:当你在本地显示这个表单的时,message
字段被显示成`` input type=”text”`` ,而它应该被显示成<`` textarea`` >。咱们能够经过设置* widget* 来修改它:
from django import forms class ContactForm(forms.Form): subject = forms.CharField() email = forms.EmailField(required=False) message = forms.CharField(**widget=forms.Textarea** )
forms框架把每个字段的显示逻辑分离到一组部件(widget)中。 每个字段类型都拥有一个默认的部件,咱们也能够容易地替换掉默认的部件,或者提供一个自定义的部件。
考虑一下Field
类表现* 校验逻辑* ,而部件表现* 显示逻辑* 。
一个最常用的校验要求是检查字段长度。 另外,咱们应该改进ContactForm
,使subject
限制在100个字符之内。 为此,仅需为CharField
提供max_length
参数,像这样:
from django import forms class ContactForm(forms.Form): subject = forms.CharField(**max_length=100** ) email = forms.EmailField(required=False) message = forms.CharField(widget=forms.Textarea)
选项min_length
参数一样可用。
让咱们再改进一下这个表单:为字subject
段添加* 初始值* : "I love your site!"
(一点建议,但没坏处。)为此,咱们能够在建立Form
实体时,使用initial
参数:
def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): cd = form.cleaned_data send_mail( cd['subject'], cd['message'], cd.get('email', `'noreply@example.com`_'), [`'siteowner@example.com`_'], ) return HttpResponseRedirect('/contact/thanks/') else: form = ContactForm( **initial={'subject': 'I love your site!'}** ) return render_to_response('contact_form.html', {'form': form})
如今,subject
字段将被那个句子填充。
请注意,传入* 初始值* 数据和传入数据以* 绑定* 表单是有区别的。 最大的区别是,若是仅传入* 初始值* 数据,表单是unbound的,那意味着它没有错误消息。
假设咱们已经发布了反馈页面了,email已经开始源源不断地涌入了。 这里有一个问题: 一些提交的消息只有一两个字,咱们没法得知详细的信息。 因此咱们决定增长一条新的校验: 来点专业精神,最起码写四个字,拜托。
咱们有不少的方法把咱们的自定义校验挂在Django的form上。 若是咱们的规则会被一次又一次的使用,咱们能够建立一个自定义的字段类型。 大多数的自定义校验都是一次性的,能够直接绑定到form类.
咱们但愿`` message`` 字段有一个额外的校验,咱们增长一个`` clean_message()`` 方法到`` Form`` 类:
from django import forms class ContactForm(forms.Form): subject = forms.CharField(max_length=100) email = forms.EmailField(required=False) message = forms.CharField(widget=forms.Textarea) def clean_message(self): message = self.cleaned_data['message'] num_words = len(message.split()) if num_words < 4: raise forms.ValidationError("Not enough words!") return message
Django的form系统自动寻找匹配的函数方法,该方法名称以clean_
开头,并以字段名称结束。 若是有这样的方法,它将在校验时被调用。
特别地,clean_message()
方法将在指定字段的默认校验逻辑执行* 以后* 被调用。(本例中,在必填CharField
这个校验逻辑以后。)由于字段数据已经被部分处理,因此它被从self.cleaned_data
中提取出来了。一样,咱们没必要担忧数据是否为空,由于它已经被校验过了。
咱们简单地使用了len()和split()的组合来计算单词的数量。 若是用户输入字数不足,咱们抛出一个forms.ValidationError
型异常。这个异常的描述会被做为错误列表中的一项显示给用户。
在函数的末尾显式地返回字段的值很是重要。 咱们能够在咱们自定义的校验方法中修改它的值(或者把它转换成另外一种Python类型)。 若是咱们忘记了这一步,None值就会返回,原始的数据就丢失掉了。
HTML表单中自动生成的标签默认是按照规则生成的:用空格代替下划线,首字母大写。如email
的标签是"Email"
。(好像在哪听到过? 是的,一样的逻辑被用于模块(model)中字段的verbose_name
值。 咱们在第五章谈到过。)
像在模块中作过的那样,咱们一样能够自定义字段的标签。 仅需使用label
,像这样:
class ContactForm(forms.Form): subject = forms.CharField(max_length=100) email = forms.EmailField(required=False, **label='Your e-mail address'** ) message = forms.CharField(widget=forms.Textarea)
在上面的`` contact_form.html`` 模板中咱们使用`` {{form.as_table}}`` 显示表单,不过咱们可使用其余更精确控制表单显示的方法。
修改form的显示的最快捷的方式是使用CSS。 尤为是错误列表,能够加强视觉效果。自动生成的错误列表精确的使用`` <ul class=”errorlist”>``,这样,咱们就能够针对它们使用CSS。 下面的CSS让错误更加醒目了:
<style type="text/css"> ul.errorlist { margin: 0; padding: 0; } .errorlist li { background-color: red; color: white; display: block; font-size: 10px; margin: 0 0 3px; padding: 4px 5px; } </style>
虽然,自动生成HTML是很方便的,可是在某些时候,你会想覆盖默认的显示。 {{form.as_table}}和其它的方法在开发的时候是一个快捷的方式,form的显示方式也能够在form中被方便地重写。
每个字段部件(<input type=”text”>, <select>, <textarea>, 或者相似)均可以经过访问{{form.字段名}}进行单独的渲染。
<html> <head> <title>Contact us</title> </head> <body> <h1>Contact us</h1> {% if form.errors %} <p style="color: red;"> Please correct the error{{ form.errors|pluralize }} below. </p> {% endif %} <form action="" method="post"> <div class="field"> {{ form.subject.errors }} <label for="id_subject">Subject:</label> {{ form.subject }} </div> <div class="field"> {{ form.email.errors }} <label for="id_email">Your e-mail address:</label> {{ form.email }} </div> <div class="field"> {{ form.message.errors }} <label for="id_message">Message:</label> {{ form.message }} </div> <input type="submit" value="Submit"> </form> </body> </html>
{{ form.message.errors }}
会在 <ul class="errorlist">
里面显示,若是字段是合法的,或者form没有被绑定,就显示一个空字符串。 咱们还能够把 form.message.errors
看成一个布尔值或者当它是list在上面作迭代, 例如:
<div class="field{% if form.message.errors %} errors{% endif %}"> {% if form.message.errors %} <ul> {% for error in form.message.errors %} <li><strong>{{ error }}</strong></li> {% endfor %} </ul> {% endif %} <label for="id_message">Message:</label> {{ form.message }} </div>
在校验失败的状况下, 这段代码会在包含错误字段的div的class属性中增长一个”errors”,在一个有序列表中显示错误信息。