Guest author: Simon Willison
本章作者是Simon Willison
After following along with the last chapter, you should now have a fully functioning if somewhat simple site. In this chapter, well deal with the next piece of the puzzle: building views that take input from readers.
经过上一章,你应该对简单网站有个全面的认识。这一章,来处理web开发的下一个难题:建立用户输入的视图。
Well start by making a simple search form by hand and looking at how to handle data submitted from the browser. From there, well move on to using Djangos forms framework.
我们会从手工打造一个简单的搜索页面开始,看看怎样处理浏览器提交而来的数据。然后我们开始使用Django的forms框架。
The Web is all about search. Two of the Nets biggest success stories, Google and Yahoo, built their multi-billion-dollar businesses around search. Nearly every site sees a large percentage of traffic coming to and from its search pages. Often the difference between the success or failure of a site is the quality of its search. So it looks like wed better add some searching to our fledgling books site, no?
在web应用上,有两个关于搜索获得巨大成功的故事:Google和Yahoo,通过搜索,他们建立了几十亿美元的业务。几乎每个网站都有很大的比例访问量来自这两个搜索引擎。甚至,一个网站是否成功取决于其站内搜索的质量。因此,在我们这个网站添加搜索功能看起来好一些。
Well start by adding the search view to our URLconf (mysite.urls ). Recall that this means adding something like (r'^search/$', 'mysite.books.views.search') to the set of URL patterns.
开始,在URLconf (mysite.urls )添加搜索视图。添加类似 (r'^search/$','mysite.books.views.search') 设置URL模式。
Next, well write this search view into our view module (mysite.books.views ):
下一步,在视图模块(mysite.books.views )中写这个 search 视图:
from django.db.models import Q from django.shortcuts import render_to_response from models import Book def search(request): query = request.GET.get('q', '') if query: qset = ( Q(title__icontains=query) | Q(authors__first_name__icontains=query) | Q(authors__last_name__icontains=query) ) results = Book.objects.filter(qset).distinct() else: results = [] return render_to_response("books/search.html", { "results": results, "query": query })
There are a couple of things going on here that you havent yet seen. First, theres request.GET . This is how you access GET data from Django; POST data is accessed through a similar request.POST object. These objects behave exactly like standard Python dictionaries with some extra features covered in Appendix H.
这里有一些需要注意的,首先 request.GET ,这是如何从Django中访问GET数据;POST数据通过类似的 request.POST 对象访问。这些对象行为与标准Python字典很像,在附录H中列出来其另外的特性。
Whats GET and POST Data?
什么是 GET and POST 数据?
GET and POST are the two methods that browsers use to send data to a server. Most of the time, youll see them in HTML form tags:
GET 和POST 是浏览器使用的两个方法,用于发送数据到服务器端。 一般来说,会在html表单里面看到:
<form action="/books/search/" method="get">
This instructs the browser to submit the form data to the URL /books/search/ using the GET method.
它指示浏览器向/books/search/以GET的方法提交数据
There are important differences between the semantics of GET and POST that we wont get into right now, but see http://www.w3.org/2001/tag/doc/whenToUseGet.html if you want to learn more.
关于GET和POST这两个方法之间有很大的不同,不过我们暂时不深入它,如果你想了解更多,可以访问: http://www.w3.org/2001/tag/doc/whenToUseGet.html 。
So the line:
所以下面这行:
query = request.GET.get('q', '')
looks for a GET parameter named q and returns an empty string if that parameter wasnt submitted.
寻找名为 q 的GET参数,而且如果参数没有提交,返回一个空的字符串。
Note that were using the get() method on request.GET , which is potentially confusing. The get() method here is the one that every Python dictionary has. Were using it here to be careful: it is not safe to assume that request.GET contains a 'q' key, so we use get('q', '') to provide a default fallback value of '' (the empty string). If we merely accessed the variable using request.GET['q'] , that code would raise a KeyError if q wasnt available in the GET data.
注意在 request.GET 中使用了 get() 方法,这可能让大家不好理解。这里的 get() 是每个python的的字典数据类型都有的方法。使用的时候要小心:假设 request.GET 包含一个 'q' 的key是不安全的,所以我们使用 get('q', '') 提供一个缺省的返回值 '' (一个空字符串)。如果只是使用 request.GET['q'] 访问变量,在Get数据时 q 不可得,可能引发 KeyError .
Second, what about this Q business? Q objects are used to build up complex queries in this case, were searching for any books where either the title or the name of one of the authors matches the search query. Technically, these Q objects comprise a QuerySet, and you can read more about them in Appendix C.
其次,关于 Q , Q 对象在这个例子里用于建立复杂的查询,搜索匹配查询的任何书籍.技术上 Q 对象包含QuerySet,可以在附录C中进一步阅读.
In these queries, icontains is a case-insensitive search that uses the SQL LIKE operator in the underlying database.
在这个查询中, icontains 使用SQL的 LIKE 操作符,是大小写不敏感的。
Since were searching against a many-to-many field, its possible for the same book to be returned more than once by the query (e.g., a book with two authors who both match the search query). Adding .distinct() to the filter lookup eliminates any duplicate results.
既然搜索依靠多对多域来实现,就有可能对同一本书返回多次查询结果(例如:一本书有两个作者都符合查询条件)。因此添加 .distinct() 过滤查询结果,消除重复部分。
Theres still no template for this search view, however. This should do the trick:
现在仍然没有这个搜索视图的模板,可以如下实现:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Search{% if query %} Results{% endif %}</title> </head> <body> <h1>Search</h1> <form action="." method="GET"> <label for="q">Search: </label> <input type="text" name="q" value="{{ query|escape }}"> <input type="submit" value="Search"> </form> {% if query %} <h2>Results for "{{ query|escape }}":</h2> {% if results %} <ul> {% for book in results %} <li>{{ book|escape }}</l1> {% endfor %} </ul> {% else %} <p>No books found</p> {% endif %} {% endif %} </body> </html>
Hopefully by now what this does is fairly obvious. However, there are a few subtleties worth pointing out:
希望你已经很清楚地明白这个实现。不过,有几个细节需要指出:
The forms action is . , which means the current URL. This is a standard best practice: dont use separate views for the form page and the results page; use a single one that serves the form and search results.
表单的action是 . , 表示当前的URL。这是一个标准的最佳惯常处理方式:不使用独立 的视图分别来显示表单页面和结果页面;而是使用单个视图页面来处理表单并显示搜索结果。
We reinsert the value of the query back into the <input> . This lets readers easily refine their searches without having to retype what they searched for.
我们把返回的查询值重新插入到 <input> 中去,以便于读者可以完善他们的搜索内容, 而不必重新输入搜索内容。
Everywhere query and book is used, we pass it through the escape filter to make sure that any potentially malicious search text is filtered out before being inserted into the page.
在所有使用 query 和 book 的地方,我们通过 escape 过滤器来确保任何 可能的恶意的搜索文字被过滤出去,以保证不被插入到页面里。
Its vital that you do this with any user-submitted content! Otherwise you open your site up to cross-site scripting (XSS) attacks. Chapter 19 discusses XSS and security in more detail.
这对处理任何用户提交数据来说是 必须 的!否则的话你就开放你的网站允许跨站点脚本 (XSS)攻击。在第十九章中将详细讨论了XSS和安全。
However, we dont need to worry about harmful content in your database lookups we can simply pass the query into the lookup as is. This is because Djangos database layer handles this aspect of security for you.
不过,我们不必担心数据库对可能有危害内容的查询的处理。 Django的数据库层在这方面已经做过安全处理。 【译注:数据库层对查询数据自动Escape,所以不用担心】
Now we have a working search. A further improvement would be putting a search form on every page (i.e., in the base template); well let you handle that one yourself.
现在我们已经作了搜索。进一步要把搜索表单加到所有的页面(例如,在base模板);这个可以由你自己完成。
Next, well look at a more complex example. But before we do, lets discuss a more abstract topic: the perfect form.
下面,我们看一下更复杂的例子。事先我们讨论一个抽象的话题:完美表单。
完美表单
Forms can often be a major cause of frustration for the users of your site. Lets consider the behavior of a hypothetical perfect form:
表单经常引起站点用户的反感。我们考虑一下一个假设的完美的表单的行为:
It should ask the user for some information, obviously. Accessibility and usability matter here, so smart use of the HTML <label> element and useful contextual help are important.
它应该问用户一些信息,显然,由于可用性的问题, 使用HTML <label> 元素和有用的 上下文帮助是很重要的。
The submitted data should be subjected to extensive validation. The golden rule of Web application security is never trust incoming data, so validation is essential.
所提交的数据应该多方面的验证。Web应用安全的金科玉律是从不要相信进来的数据,所以验证是必需的。
If the user has made any mistakes, the form should be redisplayed with detailed, informative error messages. The original data should be prefilled, to save the user from having to reenter everything.
如果用户有一些错误,表单应该重新显示详情,错误信息。原来的数据应该已经填好,避免用户重新录入,
The form should continue to redisplay until all of the fields have been correctly filled.
表单应该在所有域验证正确前一直重新显示。
Constructing the perfect form seems like a lot of work! Thankfully, Djangos forms framework is designed to do most of the work for you. You provide a description of the forms fields, validation rules, and a simple template, and Django does the rest. The result is a perfect form with very little effort.
建立这样的表单好像需要做很多工作!幸好,Django的表单框架已经设计的可以为你做绝大部分的工作。你只需要提供表单域的描述,验证规则和简单的模板即可。这样就只需要一点的工作就可以做成一个完美的表单。
创建一个回馈表单
The best way to build a site that people love is to listen to their feedback. Many sites appear to have forgotten this; they hide their contact details behind layers of FAQs, and they seem to make it as difficult as possible to get in touch with an actual human being.
做好一个网站需要注意用户的反馈,很多站点好像忘记这个。他们把联系信息放在FAQ后面,而且好像很难联系到实际的人。
When your site has millions of users, this may be a reasonable strategy. When youre trying to build up an audience, though, you should actively encourage feedback at every opportunity. Lets build a simple feedback form and use it to illustrate Djangos forms framework in action.
一个百万用户级的网站,可能有些合理的策略。如果建立一个面向用户的站点,需要鼓励回馈。我们建立一个简单的回馈表单,用来展示Django的表单框架。
Well start by adding adding (r'^contact/$', 'mysite.books.views.contact') to the URLconf, then defining our form. Forms in Django are created in a similar way to models: declaratively, using a Python class. Heres the class for our simple form. By convention, well insert it into a new forms.py file within our application directory:
开始,在URLconf里添加 (r'^contact/$', 'mysite.books.views.contact') ,然后定义表单。 在Django中表单的创建类似MODEL:使用Python类来声明。这里是我们简单表单的类。为了方便,把它写到新的 forms.py 文件中,这个文件在app目录下。
from django import newforms as forms TOPIC_CHOICES = ( ('general', 'General enquiry'), ('bug', 'Bug report'), ('suggestion', 'Suggestion'), ) class ContactForm(forms.Form): topic = forms.ChoiceField(choices=TOPIC_CHOICES) message = forms.CharField() sender = forms.EmailField(required=False)
New Forms? What?
New Forms是什么?
When Django was first released to the public, it had a complicated, confusing forms system. It made producing forms far too difficult, so it was completely rewritten and is now called newforms. However, theres still a fair amount of code that depends on the old form system, so for the time being Django ships with two form packages.
当Django最初推出的时候,有一个复杂而难用的form系统。用它来构建表单简直就是噩梦,所以它在新版本里面被一个叫做newforms的系统取代了。但是鉴于还有很多代码依赖于老的那个form系统,暂时Django还是同时保有两个forms包。
As we write this book, Djangos old form system is still available as django.forms and the new form package as django.newforms . At some point that will change and django.forms will point to the new form package. However, to make sure the examples in this book work as widely as possible, all the examples will refer to django.newforms .
在本书写作期间,Django的老form系统还是在 django.forms 中,新的form系统位于 django.newforms 中。这种状况迟早会改变, django.forms 会指向新的form包。 但是为了让本书中的例子尽可能广泛地工作,所有的代码中仍然会使用 django.newforms 。
A Django form is a subclass of django.newforms.Form , just as a Django model is a subclass of django.db.models.Model . The django.newforms module also contains a number of Field classes; a full list is available in Djangos documentation at http://www.djangoproject.com/documentation/0.96/newforms/.
一个Django表单是 django.newforms.Form 的子类,就像Django模型是 django.db.models.Model 的子类一样。在django.newforms模块中还包含很多Field类;Django的文档( http://www.djangoproject.com/documentation/0.96/newforms/ )中包含了一个可用的Field列表。
Our ContactForm consists of three fields: a topic, which is a choice among three options; a message, which is a character field; and a sender, which is an email field and is optional (because even anonymous feedback can be useful). There are a number of other field types available, and you can write your own if they dont cover your needs.
我们的 ContactForm 包含三个字段:一个topic,它是一个三选一的选择框;一个message,它是一个文本域;还有一个sender,它是一个可选的email域(因为即使是匿名反馈也是有用的)。还有很多字段类型可供选择,如果它们都不满足要求,你可以考虑自己写一个。
The form object itself knows how to do a number of useful things. It can validate a collection of data, it can generate its own HTML widgets, it can construct a set of useful error messages and, if were feeling lazy, it can even draw the entire form for us. Lets hook it into a view and see it in action. In views.py :
form对象自己知道如何做一些有用的事情。它能校验数据集合,生成HTML“部件”,生成一集有用的错误信息,当然,如果你确实很懒,它也能绘出整个form。现在让我们把它嵌入一个视图,看看怎么样使用它。在views.py里面:
from django.db.models import Q from django.shortcuts import render_to_response from models import Book **from forms import ContactForm** def search(request): query = request.GET.get('q', '') if query: qset = ( Q(title__icontains=query) | Q(authors__first_name__icontains=query) | Q(authors__last_name__icontains=query) ) results = Book.objects.filter(qset).distinct() else: results = [] return render_to_response("books/search.html", { "results": results, "query": query }) **def contact(request):** **form = ContactForm()** **return render_to_response('contact.html', {'form': form})**
and in contact.html :
添加contact.html文件:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Contact us</title> </head> <body> <h1>Contact us</h1> <form action="." method="POST"> <table> {{ form.as_table }} </table> <p><input type="submit" value="Submit"></p> </form> </body> </html>
The most interesting line here is {{ form.as_table }} . form is our ContactForm instance, as passed to render_to_response . as_table is a method on that object that renders the form as a sequence of table rows (as_ul and as_p can also be used). The generated HTML looks like this:
最有意思的一行是 {{ form.as_table }}。form是ContactForm的一个实例,我们通过render_to_response方法把它传递给模板。as_table是form的一个方法,它把form渲染成一系列的表格行(as_ul和as_p也是起着相似的作用)。生成的HTML像这样:
<tr> <th><label for="id_topic">Topic:</label></th> <td> <select name="topic" id="id_topic"> <option value="general">General enquiry</option> <option value="bug">Bug report</option> <option value="suggestion">Suggestion</option> </select> </td> </tr> <tr> <th><label for="id_message">Message:</label></th> <td><input type="text" name="message" id="id_message" /></td> </tr> <tr> <th><label for="id_sender">Sender:</label></th> <td><input type="text" name="sender" id="id_sender" /></td> </tr>
Note that the <table> and <form> tags are not included; you need to define those yourself in the template, which gives you control over how the form behaves when it is submitted. Label elements are included, making forms accessible out of the box.
请注意:<table>和<form>标签并没有包含在内;我们需要在模板里定义它们,这给予我们更大的控制权去决定form提交时的行为。Label元素是包含在内的,令访问性更佳(因为label的值会显示在页面上)。
Our form is currently using a <input type="text"> widget for the message field. We dont want to restrict our users to a single line of text, so well swap in a <textarea> widget instead:
我们的form现在使用了一个<input type=”text”>部件来显示message字段。但我们不想限制我们的用户只能输入一行文本,所以我们用一个<textarea>部件来替代:
class ContactForm(forms.Form): topic = forms.ChoiceField(choices=TOPIC_CHOICES) message = forms.CharField(**widget=forms.Textarea()** ) sender = forms.EmailField(required=False)
The forms framework separates out the presentation logic for each field into a set of widgets. Each field type has a default widget, but you can easily override the default, or provide a custom widget of your own.
forms框架把每一个字段的显示逻辑分离到一组部件(widget)中。每一个字段类型都拥有一个默认的部件,我们也可以容易地替换掉默认的部件,或者提供一个自定义的部件。
At the moment, submitting the form doesnt actually do anything. Lets hook in our validation rules:
现在,提交这个form没有在后台做任何事情。让我们把我们的校验规则加进去:
def contact(request): if request.method == 'POST': form = ContactForm(request.POST) else: form = ContactForm() return render_to_response('contact.html', {'form': form})
A form instance can be in one of two states: bound or unbound. A bound instance is constructed with a dictionary (or dictionary-like object) and knows how to validate and redisplay the data from it. An unbound form has no data associated with it and simply knows how to display itself.
一个form实例可能处在两种状态:绑定或者未绑定。一个绑定的实例是由字典(或者类似于字典的对象)构造而来的,它同样也知道如何验证和重新显示它的数据。一个未绑定的form是没有与之联系的数据,仅仅知道如何显示其自身。
Try clicking Submit on the blank form. The page should redisplay, showing a validation error that informs us that our message field is required.
现在可以试着提交一下这个空白的form了。页面将会被重新显示出来,显示一个验证错误,提示我们message字段是必须的。
Try entering an invalid email address as well. The EmailField knows how to validate email addresses, at least to a reasonable level of doubt.
现在输入一个不合法的email地址,EmailField知道如何验证email地址,大多数情况下这种验证是合理的。
Setting Initial Data
设置初始数据
Passing data directly to the form constructor binds that data and indicates that validation should be performed. Often, though, we need to display an initial form with some of the fields prefilled for example, an edit form. We can do this with the initial keyword argument:
向form的构造器函数直接传递数据会把这些数据绑定到form,指示form进行验证。我们有时也需要在初始化的时候预先填充一些字段——比方说一个编辑form。我们可以传入一些初始的关键字参数:
form = CommentForm(initial={'sender': 'user@example.com'})
If our form will always use the same default values, we can configure them in the form definition itself:
如果我们的form总是会使用相同的默认值,我们可以在form自身的定义中设置它们
message = forms.CharField(widget=forms.Textarea(), **initial="Replace with your feedback"** )
处理提交
Once the user has filled the form to the point that it passes our validation rules, we need to do something useful with the data. In this case, we want to construct and send an email containing the users feedback. Well use Djangos email package to do this.
当用户填完form,完成了校验,我们需要做一些有用的事情了。在本例中,我们需要构造并发送一个包含了用户反馈的email,我们将会使用Django的email包来完成
First, though, we need to tell if the data is indeed valid, and if it is, we need access to the validated data. The forms framework does more than just validate the data, it also converts it into Python types. Our contact form only deals with strings, but if we were to use an IntegerField or DateTimeField , the forms framework would ensure that we got back a Python integer or datetime object, respectively.
首先,我们需要知道用户数据是不是真的合法,如果是这样,我们就要访问已经验证过的数据。forms框架甚至做的更多,它会把它们转换成对应的Python类型。我们的联系方式form仅仅处理字符串,但是如果我们使用IntegerField或者DataTimeField,forms框架会保证我们从中取得类型正确的值。
To tell whether a form is bound to valid data, call the is_valid() method:
测试一个form是否已经绑定到合法的数据,使用is_valid()方法:
form = ContactForm(request.POST) if form.is_valid(): # Process form data
Now we need access to the data. We could pull it straight out of request.POST , but if we did, wed miss out on the type conversions performed by the forms framework. Instead, we use form.clean_data :
现在我们要访问数据了。我们可以从request.POST里面直接把它们取出来,但是这样做我们就丧失了由framework为我们自动做类型转换的好处了。所以我们要使用form.clean_data:
if form.is_valid(): topic = form.clean_data['topic'] message = form.clean_data['message'] sender = form.clean_data.get('sender', 'noreply@example.com') # ...
Note that since sender is not required, we provide a default when its missing. Finally, we need to record the users feedback. The easiest way to do this is to email it to a site administrator. We can do that using the send_mail function:
请注意因为sender不是必需的,我们为它提供了一个默认值。终于,我们要记录下用户的反馈了,最简单的方法就是把它发送给站点管理员,我们可以使用send_mail方法:
from django.core.mail import send_mail # ... send_mail( 'Feedback from your site, topic: %s' % topic, message, sender, ['administrator@example.com'] )
The send_mail function has four required arguments: the email subject, the email body, the from address, and a list of recipient addresses. send_mail is a convenient wrapper around Djangos EmailMessage class, which provides advanced features such as attachments, multipart emails, and full control over email headers.
send_mail方法有四个必须的参数:主题,邮件正文,from和一个接受者列表。send_mail是Django的EmailMessage类的一个方便的包装,EmailMessage类提供了更高级的方法,比如附件,多部分邮件,以及对于邮件头部的完整控制。 发送完邮件之后,我们会把用户重定向到确认的页面。完成之后的视图方法如下:
Having sent the feedback email, well redirect our user to a static confirmation page. The finished view function looks like this:
发送完邮件之后,我们会把用户重定向到确认的页面。完成之后的视图方法如下:
from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.core.mail import send_mail from forms import ContactForm def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): topic = form.clean_data['topic'] message = form.clean_data['message'] sender = form.clean_data.get('sender', 'noreply@example.com') send_mail( 'Feedback from your site, topic: %s' % topic, message, sender, ['administrator@example.com'] ) return HttpResponseRedirect('/contact/thanks/') else: form = ContactForm() return render_to_response('contact.html', {'form': form})
Redirect After POST
在POST之后立即重定向
If a user selects Refresh on a page that was displayed by a POST request, that request will be repeated. This can often lead to undesired behavior, such as a duplicate record being added to the database. Redirect after POST is a useful pattern that can help avoid this scenario: after a successful POST has been processed, redirect the user to another page rather than returning HTML directly.
在一个POST请求过后,如果用户选择刷新页面,这个请求就重复提交了。这常常会导致我们不希望的行为,比如重复的数据库记录。在POST之后重定向页面是一个有用的模式,可以避免这样的情况出现:在一个POST请求成功的处理之后,把用户导引到另外一个页面上去,而不是直接返回HTML页面。
1d0p2u <a href=”http://azetemwaeinp.com/“>azetemwaeinp</a>, [url=http://lcaytpsbsxkh.com/]lcaytpsbsxkh[/url], [link=http://hgjnhjgnqlch.com/]hgjnhjgnqlch[/link], http://curhltadaokb.com/
Imagine weve launched our feedback form, and the emails have started tumbling in. Theres just one problem: some of the emails are just one or two words, hardly enough for a detailed missive. We decide to adopt a new validation policy: four words or more, please.
假设我们已经发布了反馈页面了,email已经开始源源不断地涌入了。只有一个问题:一些email只有寥寥数语,很难从中得到什么详细有用的信息。所以我们决定增加一条新的校验:来点专业精神,最起码写四个字,拜托。
There are a number of ways to hook custom validation into a Django form. If our rule is something we will reuse again and again, we can create a custom field type. Most custom validations are one-off affairs, though, and can be tied directly to the form class.
我们有很多的方法把我们的自定义校验挂在Django的form上。如果我们的规则会被一次又一次的使用,我们可以创建一个自定义的字段类型。大多数的自定义校验都是一次性的,可以直接绑定到form类.
We want additional validation on the message field, so we need to add a clean_message method to our form:
我们希望message字段有一个额外的校验,我们增加一个clean_message方法:
class ContactForm(forms.Form): topic = forms.ChoiceField(choices=TOPIC_CHOICES) message = forms.CharField(widget=forms.Textarea()) sender = forms.EmailField(required=False) def clean_message(self): message = self.clean_data.get('message', '') num_words = len(message.split()) if num_words < 4: raise forms.ValidationError("Not enough words!") return message
This new method will be called after the default field validator (in this case, the validator for a required CharField ). Because the field data has already been partially processed, we need to pull it out of the forms clean_data dictionary.
这个新的方法将在默认的字段校验器之后被调用(在本例中,就是CharField的校验器)。因为字段数据已经被部分地处理掉了,我们需要从form的clean_data字典中把它弄出来。
We naively use a combination of len() and split() to count the number of words. If the user has entered too few words, we raise a ValidationError . The string attached to this exception will be displayed to the user as an item in the error list.
我们简单地使用了len()和split()的组合来计算单词的数量。如果用户输入了过少的词,我们扔出一个ValidationError。这个exception的错误信息会被显示在错误列表里。
It is important that we explicitly return the value for the field at the end of the method. This allows us to modify the value (or convert it to a different Python type) within our custom validation method. If we forget the return statement, then None will be returned, and the original value will be lost.
在函数的末尾显式地返回字段的值非常重要。我们可以在我们自定义的校验方法中修改它的值(或者把它转换成另一种Python类型)。如果我们忘记了这一步,None值就会返回,原始的数据就丢失掉了。
The quickest way to customize the forms presentation is with CSS. The list of errors in particular could do with some visual enhancement, and the <ul> has a class attribute of errorlist for that exact purpose. The following CSS really makes our errors stand out:
修改form的显示的最快捷的方式是使用CSS。错误的列表可以做一些视觉上的增强,<ul>标签的class属性为了这个目的。下面的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>
While its convenient to have our forms HTML generated for us, in many cases the default rendering wont be right for our application. {{ form.as_table }} and friends are useful shortcuts while we develop our application, but everything about the way a form is displayed can be overridden, mostly within the template itself.
虽然我们可以方便地使用form来生成HTML,可是默认的渲染在多数情况下满足不了我们的应用。{{form.as_table}}和其它的方法在开发的时候是一个快捷的方式,form的显示方式也可以在form中被方便地重写。
Each field widget (<input type="text"> , <select> , <textarea> , or similar) can be rendered individually by accessing {{ form.fieldname }} . Any errors associated with a field are available as {{ form.fieldname.errors }} . We can use these form variables to construct a custom template for our contact form:
每一个字段部件(<input type=”text”>, <select>, <textarea>, 或者类似)都可以通过访问{{form.字段名}}进行单独的渲染。任何跟字段相关的错误都可以通过{{form.fieldname.errors}}访问。我们可以同这些form的变量来为我们的表单构造一个自定义的模板:
<form action="." method="POST"> <div class="fieldWrapper"> {{ form.topic.errors }} <label for="id_topic">Kind of feedback:</label> {{ form.topic }} </div> <div class="fieldWrapper"> {{ form.message.errors }} <label for="id_message">Your message:</label> {{ form.message }} </div> <div class="fieldWrapper"> {{ form.sender.errors }} <label for="id_sender">Your email (optional):</label> {{ form.sender }} </div> <p><input type="submit" value="Submit"></p> </form>
{{ form.message.errors }} will display as a <ul class="errorlist"> if errors are present and a blank string if the field is valid (or the form is unbound). We can also treat form.message.errors as a Boolean or even iterate over it as a list, for example:
{{ form.message.errors }} 会在 <ul class="errorlist"> 里面显示,如果字段是合法的,或者form没有被绑定,就显示一个空字符串。我们还可以把 form.message.errors 当作一个布尔值或者当它是list在上面做迭代:
<div class="fieldWrapper{% if form.message.errors %} errors{% endif %}"> {% if form.message.errors %} <ol> {% for error in form.message.errors %} <li><strong>{{ error|escape }}</strong></li> {% endfor %} </ol> {% endif %} {{ form.message }} </div>
In the case of validation errors, this will add an errors class to the containing <div> and display the list of errors in an ordered list.
在校验失败的情况下, 这段代码会在包含错误字段的div的class属性中增加一个”errors”,在一个有序列表中显示错误信息。
Lets build something a little more interesting: a form that submits a new publisher to our book application from Chapter 5.
我们弄个有趣的东西吧:一个新的form,提交一个新出版商的信息到我们第五章的book应用。
An important rule of thumb in software development that Django tries to adhere to is Dont Repeat Yourself (DRY). Andy Hunt and Dave Thomas in The Pragmatic Programmer define this as follows:
一个非常重要的Django的开发理念就是不要重复你自己(DRY)。Any Hunt和Dave Thomas在《实用主义程序员》里定义了这个原则:
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
在系统内部,每一条(领域相关的)知识的片断都必须有一个单独的,无歧义的,正式的表述。
Our Publisher model class says that a publisher has a name, address, city, state_province, country, and website. Duplicating this information in a form definition would break the DRY rule. Instead, we can use a useful shortcut: form_for_model() :
我们的出版商模型拥有一个名字,地址,城市,州(省),国家和网站。在form中重复这个信息无疑违反了DRY原则。我们可以使用一个捷径:form_for_model():
from models import Publisher from django.newforms import form_for_model PublisherForm = form_for_model(Publisher)
PublisherForm is a Form subclass, just like the ContactForm class we created manually earlier on. We can use it in much the same way:
PublisherForm是一个Form子类,像刚刚手工创建的ContactForm类一样。我们可以像刚才一样使用它:
from forms import PublisherForm def add_publisher(request): if request.method == 'POST': form = PublisherForm(request.POST) if form.is_valid(): form.save() return HttpResponseRedirect('/add_publisher/thanks/') else: form = PublisherForm() return render_to_response('books/add_publisher.html', {'form': form})
The add_publisher.html file is almost identical to our original contact.html template, so it has been omitted. Also remember to add a new pattern to the URLconf: (r'^add_publisher/$', 'mysite.books.views.add_publisher') .
add_publisher.html 文件几乎跟我们的contact.html模板一样,所以不赘述了。记得在URLConf里面加上: (r'^add_publisher/$', 'mysite.books.views.add_publisher') .
Theres one more shortcut being demonstrated here. Since forms derived from models are often used to save new instances of the model to the database, the form class created by form_for_model includes a convenient save() method. This deals with the common case; youre welcome to ignore it if you want to do something a bit more involved with the submitted data.
还有一个快捷的方法。因为从模型而来的表单经常被用来把新的模型的实例保存到数据库,从 form_for_model 而来的表单对象包含一个 save() 方法。一般情况下够用了;你想对提交的数据作进一步的处理的话,无视它就好了。
form_for_instance() is a related method that can create a preinitialized form from an instance of a model class. This is useful for creating edit forms.
form_for_instance() 是另外一个方法,用于从一个模型对象中产生一个初始化过的表单对象,这个当然给“编辑”表单提供了方便。
This chapter concludes the introductory material in this book. The next 13 chapters deal with various advanced topics, including generating content other than HTML (Chapter 11), security (Chapter 19), and deployment (Chapter 20).
这一章已经完成了这本书的介绍性的材料。接下来的十三个章节讨论了一些高级的话题,包括生成非html内容(第11章),安全(第19章)和部署(第20章)。
After these first seven chapters, you should know enough to start writing your own Django projects. The rest of the material in this book will help fill in the missing pieces as you need them.
在本书最初的七章后,我们(终于)对于使用Django构建自己的网站已经知道的够多了,接下来的内容可以在需要的时候阅读。
Well start in Chapter 8 by doubling back and taking a closer look at views and URLconfs (introduced first in Chapter 3).
第八章里我们会更进一步地介绍视图和URLConfs(介绍见第三章)。
关于本评注系统
本站使用上下文关联的评注系统来收集反馈信息。不同于一般对整章做评注的做法, 我们允许你对每一个独立的“文本块”做评注。一个“文本块”看起来是这样的:
一个“文本块”是一个段落,一个列表项,一段代码,或者其他一小段内容。 你选中它会高亮度显示:
要对文本块做评注,你只需要点击它旁边的标识块:
我们会仔细阅读每个评论,如果可能的话我们也会把评注考虑到未来的版本中去:
如果你愿意你的评注被采用,请确保留下你的全名 (注意不是昵称或简称)
Many, many thanks to Jack Slocum; the inspiration and much of the code for the comment system comes from Jack's blog, and this site couldn't have been built without his wonderful
YAHOO.ext
library. Thanks also to Yahoo for YUI itself.