表单

从 Request 中获取数据

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() 如果通过HTTPS访问,则此方法返回True, 否则返回False True 或者 False

request.META

request.META 是一个Python字典,包含了所有本次HTTP请求的Header信息,比如用户IP地址和用户Agent(通常是浏览器的名称和版本号)。

因为 request.META 是一个普通的Python字典,因此当你试图访问一个不存在的键时,会触发一个KeyError异常。 (HTTP header信息是由用户的浏览器所提交的、不应该给予信任的"额外"数据,因此你总是应该好好设计你的应用以便当一个特定的Header数据不存在时,给出一个优雅的回应。)你应该用 try/except 语句,或者用Python字典的 get() 方法来处理这些"可能不存在的键":

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


提交的数据信息

除了基本的元数据,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拥有一些普通的字典对象所没有的方法。

获取参数

需要注意的是获取参数值之前需要先判断一下是否存在值。这里做了两个判断,一个是判断是否存在参数'q',另外一个是判断参数'q'是否为空。

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']
        return render_to_response('search_results.html', {'query': q})
    else:
        return HttpResponse('Please submit a search term.')


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可以提供多种形式:

>>> from contact.forms import ContactForm
>>> f = ContactForm()
>>> print f
>>> print f.as_ul()
>>> print f.as_p()


在视图、模板中使用 form

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


改变表单字段显示

你可能首先注意到:当你在本地显示这个表单的时,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类表现校验逻辑 ,而部件表现显示逻辑

设置初始值

为字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', '[email protected]_'),
                ['[email protected]_'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm(
            initial={'subject': 'I love your site!'}
        )
    return render_to_response('contact_form.html', {'form': form})


自定义校验规则

有很多的方法把自定义校验挂在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)