Django表单处理

我们会从手工打造一个简单的搜索页面开始,看看怎样处理浏览器提交而来的数据。然后我们开始使用Django的forms框架。

搜索

web应用上,有两个关于搜索获得巨大成功的故事:Google和Yahoo,通过搜索,他们建立了几十亿美元的业务。几乎每个网站都有很大的比例访问量来自这两个搜索引擎。甚至,一个网站是否成功取决于其站内搜索的质量。因此,在我们这个网站添加搜索功能看起来好一些。

开始,在URLconf (mysite.urls)添加搜索视图。添加类似(r‘^search/$‘,‘mysite.books.views.search‘)设置URL模式。

下一步,在视图模块(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

})

这里有一些需要注意的,首先request.GET,这从Django中怎样访问GET数据;POST数据通过类似的request.POST对象访问。这些对象行为与标准Python字典很像,在附录H中列出来其另外的特性。

什么是 GET and POST数据?

GET 和POST是浏览器使用的两个方法,用于发送数据到服务器端。一般来说,会在html表单里面看到:

<form action="/books/search/" method="get">

它指示浏览器向/books/search/以GET的方法提交数据

关于GET和POST这两个方法之间有很大的不同,不过我们暂时不深入它,如果你想了解更多,可以访问:`http://www.w3.org/2001/tag/doc/whenToUseGet.html`

所以下面这行:

query = request.GET.get(‘q‘, ‘‘)

寻找名为q的GET参数,而且如果参数没有提交,返回一个空的字符串。

注意在request.GET中使用了get()方法,这可能让大家不好理解。这里的get()是每个python的的字典数据类型都有的方法。使用的时候要小心:假设request.GET包含一个‘q‘的key是不安全的,所以我们使用get(‘q‘,
‘‘)提供一个缺省的返回值‘‘(一个空字符串)。如果只是使用request.GET[‘q‘]访问变量,在Get数据时q不可得,可能引发KeyError.

其次,关于Q,Q对象在这个例子里用于建立复杂的查询,搜索匹配查询的任何书籍.技术上Q对象包含QuerySet,可以在附录C中进一步阅读.

在这个查询中,icontains使用SQL的LIKE操作符,是大小写不敏感的。

既然搜索依靠多对多域来实现,就有可能对同一本书返回多次查询结果(例如:一本书有两个作者都符合查询条件)。因此添加.distinct()过滤查询结果,消除重复部分。

现在仍然没有这个搜索视图的模板,可以如下实现:

<!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>

希望你已经很清楚地明白这个实现。不过,有几个细节需要指出:

表单的action是.,表示当前的URL。这是一个标准的最佳惯常处理方式:不使用独立的视图分别来显示表单页面和结果页面;而是使用单个视图页面来处理表单并显示搜索结果。

我们把返回的查询值重新插入到<input>中去,以便于读者可以完善他们的搜索内容,而不必重新输入搜索内容。

在所有使用query和book的地方,我们通过escape过滤器来确保任何可能的恶意的搜索文字被过滤出去,以保证不被插入到页面里。

这对处理任何用户提交数据来说是必须的!否则的话你就开放你的网站允许跨站点脚本(XSS)攻击。在第十九章中将详细讨论了XSS和安全。

不过,我们不必担心数据库对可能有危害内容的查询的处理。 Django的数据库层在这方面已经做过安全处理。【译注:数据库层对查询数据自动Escape,所以不用担心】

现在我们已经作了搜索。进一步要把搜索表单加到所有的页面(例如,在base模板);这个可以由你自己完成。

下面,我们看一下更复杂的例子。事先我们讨论一个抽象的话题:完美表单。

完美表单

表单经常引起站点用户的反感。我们考虑一下一个假设的完美的表单的行为:

§ 它应该问用户一些信息,显然,由于可用性的问题,使用HTML<label>元素和有用的上下文帮助是很重要的。

§ 所提交的数据应该多方面的验证。Web应用安全的金科玉律是从不要相信进来的数据,所以验证是必需的。

§ 如果用户有一些错误,表单应该重新显示详情,错误信息。原来的数据应该已经填好,避免用户重新录入,

§ 表单应该在所有域验证正确前一直重新显示。

建立这样的表单好像需要做很多工作!幸好,Django的表单框架已经设计的可以为你做绝大部分的工作。你只需要提供表单域的描述,验证规则和简单的模板即可。这样就只需要一点的工作就可以做成一个完美的表单。

创建一个回馈表单

做好一个网站需要注意用户的反馈,很多站点好像忘记这个。他们把联系信息放在FAQ后面,而且好像很难联系到实际的人。

一个百万用户级的网站,可能有些合理的策略。如果建立一个面向用户的站点,需要鼓励回馈。我们建立一个简单的回馈表单,用来展示Django的表单框架。

开始,在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是什么?

当Django最初推出的时候,有一个复杂而难用的form系统。用它来构建表单简直就是噩梦,所以它在新版本里面被一个叫做newforms的系统取代了。但是鉴于还有很多代码依赖于老的那个form系统,暂时Django还是同时保有两个forms包。

在本书写作期间,Django的老form系统还是在django.forms中,新的form系统位于django.newforms中。这种状况迟早会改变,django.forms会指向新的form包。但是为了让本书中的例子尽可能广泛地工作,所有的代码中仍然会使用django.newforms。

一个Django表单是django.newforms.Form的子类,就像Django模型是django.db.models.Model的子类一样。在django.newforms模块中还包含很多Field类;Django的文档(http://www.djangoproject.com/documentation/0.96/newforms/)中包含了一个可用的Field列表。

我们的ContackForm包含三个字段:一个topic,它是一个三选一的选择框;一个message,它是一个文本域;还有一个sender,它是一个可选的email域(因为即使是匿名反馈也是有用的)。还有很多字段类型可供选择,如果它们都不满足要求,你可以考虑自己写一个。

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})**

添加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>

最有意思的一行是 {{ 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>

请注意:<table>和<form>标签并没有包含在内;我们需要在模板里定义它们,这给予我们更大的控制权去决定form提交时的行为。Label元素是包含在内的,令访问性更佳(因为label的值会显示在页面上)。

我们的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)

forms框架把每一个字段的显示逻辑分离到一组部件(widget)中。每一个字段类型都拥有一个默认的部件,我们也可以容易地替换掉默认的部件,或者提供一个自定义的部件。

现在,提交这个form没有在后台做任何事情。让我们把我们的校验规则加进去:

def contact(request):

if request.method == ‘POST‘:

form = ContactForm(request.POST)

else:

form = ContactForm()

return render_to_response(‘contact.html‘, {‘form‘: form})

一个form实例可能处在两种状态:绑定或者未绑定。一个绑定的实例是由字典(或者类似于字典的对象)构造而来的,它同样也知道如何验证和重新显示它的数据。一个未绑定的form是没有与之联系的数据,仅仅知道如何显示其自身。

现在可以试着提交一下这个空白的form了。页面将会被重新显示出来,显示一个验证错误,提示我们message字段是必须的。

现在输入一个不合法的email地址,EmailField知道如何验证email地址,大多数情况下这种验证是合理的。

设置初始数据

向form的构造器函数直接传递数据会把这些数据绑定到form,指示form进行验证。我们有时也需要在初始化的时候预先填充一些字段——比方说一个编辑form。我们可以传入一些初始的关键字参数:

form = CommentForm(initial={‘sender‘: ‘[email protected]‘})

如果我们的form总是会使用相同的默认值,我们可以在form自身的定义中设置它们

message = forms.CharField(widget=forms.Textarea(),

**initial="Replace with your feedback"** )

处理提交

当用户填完form,完成了校验,我们需要做一些有用的事情了。在本例中,我们需要构造并发送一个包含了用户反馈的email,我们将会使用Django的email包来完成

首先,我们需要知道用户数据是不是真的合法,如果是这样,我们就要访问已经验证过的数据。forms框架甚至做的更多,它会把它们转换成对应的Python类型。我们的联系方式form仅仅处理字符串,但是如果我们使用IntegerField或者DataTimeField,forms框架会保证我们从中取得类型正确的值。

测试一个form是否已经绑定到合法的数据,使用is_valid()方法:

form = ContactForm(request.POST)

if form.is_valid():

# Process form 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‘, ‘[email protected]‘)

# ...

请注意因为sender不是必需的,我们为它提供了一个默认值。终于,我们要记录下用户的反馈了,最简单的方法就是把它发送给站点管理员,我们可以使用send_mail方法:

from django.core.mail import send_mail

# ...

send_mail(

‘Feedback from your site, topic: %s‘ % topic,

message, sender,

[‘[email protected]‘]

)

send_mail方法有四个必须的参数:主题,邮件正文,from和一个接受者列表。send_mail是Django的EmailMessage类的一个方便的包装,EmailMessage类提供了更高级的方法,比如附件,多部分邮件,以及对于邮件头部的完整控制。发送完邮件之后,我们会把用户重定向到确认的页面。完成之后的视图方法如下:

发送完邮件之后,我们会把用户重定向到确认的页面。完成之后的视图方法如下:

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‘, ‘[email protected]‘)

send_mail(

‘Feedback from your site, topic: %s‘ % topic,

message, sender,

[‘[email protected]‘]

)

return HttpResponseRedirect(‘/contact/thanks/‘)

else:

form = ContactForm()

return render_to_response(‘contact.html‘, {‘form‘: form})

在POST之后立即重定向

在一个POST请求过后,如果用户选择刷新页面,这个请求就重复提交了。这常常会导致我们不希望的行为,比如重复的数据库记录。在POST之后重定向页面是一个有用的模式,可以避免这样的情况出现:在一个POST请求成功的处理之后,把用户导引到另外一个页面上去,而不是直接返回HTML页面。

自定义校验规则

假设我们已经发布了反馈页面了,email已经开始源源不断地涌入了。只有一个问题:一些email只有寥寥数语,很难从中得到什么详细有用的信息。所以我们决定增加一条新的校验:来点专业精神,最起码写四个字,拜托。

我们有很多的方法把我们的自定义校验挂在Django的form上。如果我们的规则会被一次又一次的使用,我们可以创建一个自定义的字段类型。大多数的自定义校验都是一次性的,可以直接绑定到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

这个新的方法将在默认的字段校验器之后被调用(在本例中,就是CharField的校验器)。因为字段数据已经被部分地处理掉了,我们需要从form的clean_data字典中把它弄出来。

我们简单地使用了len()和split()的组合来计算单词的数量。如果用户输入了过少的词,我们扔出一个ValidationError。这个exception的错误信息会被显示在错误列表里。

在函数的末尾显式地返回字段的值非常重要。我们可以在我们自定义的校验方法中修改它的值(或者把它转换成另一种Python类型)。如果我们忘记了这一步,None值就会返回,原始的数据就丢失掉了。

自定义视感

修改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>

虽然我们可以方便地使用form来生成HTML,可是默认的渲染在多数情况下满足不了我们的应用。{{form.as_table}}和其它的方法在开发的时候是一个快捷的方式,form的显示方式也可以在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 }}会在<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>

在校验失败的情况下,这段代码会在包含错误字段的div的class属性中增加一个”errors”,在一个有序列表中显示错误信息。

从模型创建表单

我们弄个有趣的东西吧:一个新的form,提交一个新出版商的信息到我们第五章的book应用。

一个非常重要的Django的开发理念就是不要重复你自己(DRY)。Any Hunt和Dave
Thomas在《实用主义程序员》里定义了这个原则:

在系统内部,每一条(领域相关的)知识的片断都必须有一个单独的,无歧义的,正式的表述。

我们的出版商模型拥有一个名字,地址,城市,州(省),国家和网站。在form中重复这个信息无疑违反了DRY原则。我们可以使用一个捷径:form_for_model():

from models import Publisher

from django.newforms import form_for_model

PublisherForm = form_for_model(Publisher)

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})

add_publisher.html文件几乎跟我们的contact.html模板一样,所以不赘述了。记得在URLConf里面加上:(r‘^add_publisher/$‘,
‘mysite.books.views.add_publisher‘).

还有一个快捷的方法。因为从模型而来的表单经常被用来把新的模型的实例保存到数据库,从form_for_model而来的表单对象包含一个save()方法。一般情况下够用了;你想对提交的数据作进一步的处理的话,无视它就好了。

form_for_instance()是另外一个方法,用于从一个模型对象中产生一个初始化过的表单对象,这个当然给“编辑”表单提供了方便。

转载请注明本文出处:http://blog.csdn.net/wolaiye320/article/details/51880715

时间: 2024-10-10 17:30:03

Django表单处理的相关文章

7Python全栈之路系列之Django表单

Python全栈之路系列之Django表单 从Request对象中获取数据 HttpRequest对象包含当前请求URL的一些信息: 熟悉/方法 描述 例如 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() 是

django表单使用forms.ModelForm

win7 python2.7 django 1.6.5 因为使用数据库sqlite,项目中有models.py,所以使用方便的forms.ModelForm处理表单. 上传图像文件使用默认的defaultstorage. 1.settings.py里增加两行media的定义: #Media MEDIA_ROOT = 'e:/Depot/media' MEDIA_URL = '/media/' 2.models.py #coding: utf8 from django.db import mode

Django表单——概述

参考文献: https://docs.djangoproject.com/zh-hans/2.0/topics/forms/ HTML 表单 一个表单必须明确两个要素: where: 应返回与用户输入相对应的数据的URL how:应该返回数据的HTTP方法 GET和POST GET和POST是处理表单时唯一使用的HTTP方法. 使用POST方法返回Django的登录表单,其中浏览器捆绑表单数据,对其进行编码以进行传输,将其发送到服务器,然后接收其响应. 相反,GET将提交的数据捆绑成一个字符串,

关于创建Django表单Forms继承BaseForm的问题

在创建Django表单时,因为需要验证用户输入的验证码是否正确,因此需要在session里提取当前验证码的值和POST提交过来的值进行比对,如图: 1 form.py 2 3 from django import forms 4 5 class LoginForms(forms.Form): 6 check_code = forms.CharField() 7 def clean_check_code(self): 8 if self.request.session.get('CheckCode

Django表单API详解

声明:以下的Form.表单等术语都指的的广义的Django表单. Form要么是绑定了数据的,要么是未绑定数据的. 如果是绑定的,那么它能够验证数据,并渲染表单及其数据,然后生成HTML表单.如果未绑定,则无法进行验证(因为没有数据可以验证!),但它仍然可以以HTML形式呈现空白表单. 表单类原型:class Form[source] 若要创建一个未绑定的Form实例,只需简单地实例化该类: f = ContactForm() 若要绑定数据到表单,可以将数据以字典的形式传递给Form类的构造函数

Django表单上传

任务描述:实现表单提交(上传文件) 1.项目目录: 2.源代码: regist.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>regist</title> </head> <body> <h3>regist</h3> <hr> <

Django 表单

学习参考:http://www.runoob.com/django/django-form.html HTTP 请求 HTTP协议以"请求-回复"的方式工作.客户发送请求时,可以在请求中附加数据.服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务. GET 方法 我们在之前项目的C:\Users\XCC\HelloWorld\HelloWorld路径下创建一个 search.py 文件,用于接收用户的请求: # -*- coding: utf-8 -*- fro

Django表单form ajax应用(上)

一.项目说明 学习django版本1.8.2,把之前零散学习的知识整合下,主要涉及到: 项目开始,ajax数据调用,注册,数据录入,数据修改,数据删除,数据建模等完成一个完整的前后台功能简单的web.数据库默认用sqlite 1.创建djano项目:     $cd  /data1/DjangoProject/     $django-admin startproject School     $cd School     $python manage.py startapp student  

17.Django表单验证

Django提供了3中方式来验证表单 官网文档:https://docs.djangoproject.com/en/1.9/ref/validators 1.表单字段验证器 a.引入:from django.core.exceptions import ValidationError b.定义验证方法,验证不通过抛一个ValidationError异常 def validate_name(value): 验证不通过 raise ValidationError("%s的信息已经存在"%v