文档章节

【Django】表单

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:17
字数 9998
阅读 9
收藏 0

HTML表单

HTML中,表单是<form>...</form>之间元素的集合,它们允许访问者输入文本、选择选项、操作对象等等,然后将信息发送回服务器。
某些表单的元素---文本框和复选框---非常简单而且内建于HTML本身。其它的表单会复杂些:例如弹出一个日期选择对话框的界面、允许你移动滚动条的界面、使用JavaScriptCSS以及HTML表单元素<input>来实现操作控制的界面。
<input>元素一样,一个表单必须指定两样东西:

  • 目的地:响应用户输入数据的URL
  • 方式: 发送数据所使用的HTTP方法;
    例如,Django Admin站点的登录表单包含几个<input>元素:type='text'用于用户名,type='password'用于密码,type='submit'用于Log in按钮。它还包含一些用户看不到的隐藏的文本字段,Django使用它们来决定下一步的行为。
    它还告诉浏览器表单数据应该发往<form>action属性指定的URL---/admin/,而且应该使用method属性指定的HTTP方法---post
    当触发<input type='submit' value='Log in'>元素时,数据将发送给/admin/

GET和POST

处理表单时只会用到GETPOST方法。
Django的登录表单使用POST方法,在这个方法中浏览器组合表单数据,对它们进行编码以用于传输,将它们发送到服务器然后接收它的响应。
相反,GET组合提交的数据为一个字符串,然后使用它来生成一个URL。这个URL将包含数据发送的地址以及数据的键和值。
GETPOST用于不同的目的。
用于改变系统状态的请求---例如,给数据库带来变化的请求---应该使用POSTGET只应该用于不会影响系统状态的请求。

Django在表单中的角色

处理表单是一件很复杂的事情。考虑一下DjangoAdmin站点,不同类型的大量数据项需要在一个表单中准备好、渲染成HTML、使用一个方便的界面编辑、返回给服务器、验证并清除,然后保存或者向后继续处理。
Django的表单功能可以简化并自动化大部分这些工作,并且还可以比大部分程序自己所编写的代码更安全。
Django会处理表单工作中的三个显著不同的部分:

  • 准备数据、重构数据,以便下一步渲染;
  • 为数据创建HTML表单;
  • 接收并处理客户端提交的表单和数据;

可以手工编写代码来实现,但是Django可以帮你完成所有这些工作。

Django中的表单

HTML<form>只是其机制的一部分。
在一个Web应用中,‘表单’可能是指HTML <form>、或者生成它的DjangoForm、或者提交时发送的结构化数据、或者这些部分的总和。

Django的Form类

表单系统的核心部分是DjangoForm类。Django的模型描述一个对象的逻辑结构、行为以及展现给我们的方式,与此类似, Form类描述一个表单并决定它如何工作和展现。
就像模型类的属性映射到数据库的字段一样,表单类的字段会映射到HTML<input>表单的元素(ModelForm通过一个Form映射模型类的字段到HTML表单的<input>元素。DjangoAdmin站点就是基于这个)。
表单的字段本身也是类,它们管理表单的数据并在表单提交时进行验证。DateFieldFileField处理的数据类型差别很大,必须完成不同的事情。
表单字段在浏览器中呈现给用户的是一个HTML'widget'---用户界面的一个片段。每个字段类型都有一个合适的默认Widget类,需要时可以覆盖。

实例化、处理和渲染表单

Django中渲染一个对象时,我们通常:

  1. 在视图中获得它(例如,从数据库中获取);
  2. 将它传递给模板上下文;
  3. 使用模板变量将它扩展为HTML标记语言
    当我们在视图中处理模型实例时,我们一般从数据库中获取它。当我们处理表单时,一般在视图中实例化它。

构建一个表单

在Django中构建一个表单

Form类

我们已经计划好了我们的HTML表单应该呈现的样子。在Django中,我们的起始点是这里:

  forms.py
  from django import forms
  class NameForm(forms.Form):
        your_name = forms.CharField(label='Your name', max_length=100)

它定义一个Form类,只带有一个字段(your_name)。我们已经对这个字段使用一个友好的标签,当渲染时它将出现在<label>中。
Form的实例具有一个is_valid()方法,它为所有的字段运行验证程序。当调用这个方法时,如果所有的字段都包含合法的数据,它将:

  • 返回True
  • 将表单的数据放到cleaned_data属性中;
    完整的表单,第一次渲染时,看上去将像:

    <label for='your_name'>Your name:</label>
    <input id='your_name' type='text' name='your_name' maxlength='100' required/>

    注意它不包含<form>标签和提交按钮,我们必须自己在模板中手动提供它们。

视图

发送给Django网站的表单数据通过一个视图处理,一般和发布这个表单的是同一个视图。这允许我们重用一些相同的逻辑。
我们需要在URL对应的视图中实例化我们将要发布的表单。

  views.py
  from django.shortcuts import render
  from django.http import HttpResponseRedirect
  from .forms import NameForm
  def get_name(request):
      #if this is a POST request we need to process the form data
      if request.method == 'POST':
         #create a form instance and populate it with data from the request:
         form = NameForm(request.POST)
         #check whether it's valid:
        if form.is_valid():
            #process the data in form.cleaned_data as required
            #...
            #redirect to a new URL
            return HttpResponseRedirect('/thanks/')
      #if a GET(or any other method) we will create a blank form
      else:
            form = NameForm()
      return render(request, 'name.html', {'form' : form})

如果访问视图的是一个GET请求,它将创建一个空的表单实例并将它放到要渲染的模板上下文中。只是我们在第一次访问该URL时预期发生的情况。
如果表单的提交使用POST请求,那么视图将再次创建一个表单实例并使用请求中的数据填充它:form = NameForm(request.POST)。这叫做“绑定数据至表单”(它现在是一个绑定的表单)。
我们调用表单的is_valid()方法:如果它不为True,我们将带着这个表单返回到模板。这时表单不再为空(未绑定),所以HTML表单将用之前提交的数据填充,然后可以根据要求编辑并改正它。
如果is_valid()Ture,我们将能够在cleaned_data属性中找到所有合法的表单数据。在发送HTTP重定向给浏览器告诉它下一步的去向之前,我们可以用这个数据来更新数据库或者做其它处理。

模板

我们不需要在name.html模板中做很多工作,最简单的例子是:

  <form action="/your-name/" method="post">
      {%csrf_token%}
      {{form}}
      <input type='submit' value="Submit" />
  </form>

根据{{form}},所有的表单字段和它们的属性将通过Django的模板语言拆分成HTML标记语言。

现在我们有一个可以工作的网页表单,它通过Django Form描述、通过视图处理并渲染成一个HTML <form>

Django Form类详解

所有的表单类都作为django.forms.Form的子类创建,包括你在Django管理站点中遇到的ModelForm
模型和表单
实际上,如果你的表单打算直接用来添加和编辑Django的模型,ModelForm可以节省你的许多时间、精力和代码,因为它将根据Model类构建一个表单以及适当的字段和属性。

绑定的和未绑定的表单实例

绑定的和未绑定的表单之间的区别非常重要:

  • 未绑定的表单没有关联的数据。当渲染给用户时,它将为空或者包含默认的值。
  • 绑定的表单具有提交地数据,因此可以用来检验数据是否合法。如果渲染一个不合法的绑定的表单,它将包含内联的错误信息,告诉用户如何纠正数据。
    表单的is_bound属性将告诉你一个表单是否具有绑定的数据。

字段详解

考虑一个比上面的迷你示例更有用的一个表单,在一个网站上实现“contact me”功能:

  forms.py
  from django import forms
  class ContactForm(forms.Form):
        subject = forms.CharField(max_length=100)
        message = forms.CharField(widget=forms.Textarea)
        sender = forms.EmailField()
        cc_myself = forms.BooleanField(required=False)

我们前面的表单只使用一个字段your_name,它是一个CharField。在这个例子中,我们的表单具有四个字段:subject、message、sender和cc_myself。共用到三种字段类型:CharField、EmailField和BooleanField。

窗口小部件

每个表单字段都有一个对应的Widget类,它对应一个HTML表单Widget,例如<input type='text'>
在大部分情况下,字段都具有一个合理的默认Widget。例如,默认情况下,CharField具有一个TextInput Widget,它在HTML中生成一个<input type='text'>。如果你需要<textarea>,在定义表单字段时你应该指定一个合适的Widget,例如我们定义的message字段。

字段的数据

不管表单提交的是什么数据,一旦通过调用is_valid()成功验证(is_valid()返回True),验证后的表单数据将位于form.cleaned_data字典中。这些数据已经为Python的类型。
注意,此时,依然可以从request.POST中直接访问到未验证的数据,但是访问验证后的数据更好一些。
下面是在视图中如何处理表单数据:

  views.py
  from django.core.mail import send_mail
  if form.is_valid():
     subject = form.cleaned_data['subject']
     message = form.cleaned_data['message']
     sender = form.cleaned_data['sender']
     cc_myself = form.cleaned_data['cc_myself']
     recipients = ['info@example.com']
     if cc_myself:
        recipients.append(sender)
     send_mail(subject, message, sender, recipient)
     return HttpResponseRedirect('/thanks/')

有些字段类型需要一些额外的处理。例如,使用表单上传的文件需要不同地处理(它们可以从request.FILES获取,而不是request.POST)。

使用表单模板

你需要做的就是将表单实例放进模板的上下文。如果你的表单在Context中叫做form,那么{{form}}将正确的渲染它的<label>和<input>元素。

表单渲染的选项

不要忘记,表单的输出不包含<form>标签和表单的submit按钮,必须自己提供它们。
对于<label>/<input>对,还有几个输出选项:

  • {{form.as_table}}以表格的形式将它们渲染在<tr>标签中;
  • {{form.as_p}}将它们渲染在

    标签中;

  • {{form.as_ul}} 将它们渲染在
  • 标签中;

注意,你必须自己提供<table>或<ul>元素。
下面是ContactForm实例的输出{{form.as_p}}

<p><label for="id_subject">Subject:</label>
<input id="id_subject" type="text" name="subject" maxlength="100" required /></p>
<p><label for="id_message">Message:</label>
<textarea name="message" id="id_message" required></textarea></p>
<p><label for="id_sender">Sender:</label>
<input type="email" name="sender" id="id_sender" required /></p>
<p><label for="id_cc_myself">Cc myself:</label>
<input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>

每个表单字段具有一个ID属性并设置为id_<field-name>。

手工渲染字段

我们没有必要非要让Django来分拆表单的字段:如果我们喜欢,我们可以手工来做(这样允许你重新对字段排序)。每个字段都是表单的一个属性,可以使用{{form.name_of_field}}访问,并将在Django模板中正确地渲染。例如:

{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
<div class="fieldWrapper">
{{ form.sender.errors }}
<label for="{{ form.sender.id_for_label }}">Your email address:</label>
{{ form.sender }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.errors }}
<label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
{{ form.cc_myself }}
</div>

完整的<label>元素还可以使用label_tag()生成,例如:

<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>

渲染表单的错误信息

当然,这个便利性的代价是更多的工作。直到现在,我们没有担心如何展示错误信息,因为Django已经帮我们处理好。在下面的例子中,我们将自己处理每个字段的错误和表单整体的各种错误。注意,表单和模板顶部的{{form.non_field_errors}}查找每个字段的错误。
使用{{form.name_of_field.errors}}显示表单错误的一个清单,并渲染成一个ul。看上去可能像:

<ul class="errorlist">
<li>Sender is required.</li>
</ul>

这个ul有一个errorlist的CSS样式表,你可以用它来定义外观。如果你希望进一步自定义错误信息的显示,你可以迭代它们来实现:

{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
    <li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}

迭代表单字段

如果你为你的每个表单字段使用相同的HTML,你可以使用{%for%}循环迭代每个字段来减少重复的代码。

{% for field in form %}
<div class="fieldWrapper">
    {{ field.errors }}
    {{ field.label_tag }} {{ field }}
    {% if field.help_text %}
    <p class="help">{{ field.help_text|safe }}</p>
    {% endif %}
</div>
{% endfor %}

迭代隐藏和可见的字段

如果你正在手工布局模板中的一个表单,而不是依赖Django 默认的表单布局,你可能希望将<input type="hidden"> 字段与非隐藏的字段区别对待。例如,因为隐藏的字段不会显示,在该字段旁边放置错误信息可能让你的用户感到困惑 —— 所以这些字段的错误应该有区别地来处理。

Django 提供两个表单方法,它们允许你独立地在隐藏的和可见的字段上迭代:hidden_fields() 和visible_fields()。下面是使用这两个方法对前面一个例子的修改:

{# Include the hidden fields #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# Include the visible fields #}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
    {{ field.errors }}
    {{ field.label_tag }} {{ field }}
</div>
{% endfor %}

这个示例没有处理隐藏字段中的任何错误信息。通常,隐藏字段中的错误意味着表单被篡改,因为正常的表单填写不会改变它们。然而,你也可以很容易地为这些表单错误插入一些错误信息显示出来。

可重用的表单模板

如果你的网站在多个地方对表单使用相同的渲染逻辑,你可以保存表单的循环到一个单独的模板中来减少重复,然后在其它模板中使用include标签来重用它:

# In your form template:
{% include "form_snippet.html" %}

# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
    {{ field.errors }}
    {{ field.label_tag }} {{ field }}
</div>
{% endfor %}

如果传递到模板上下文中的表单对象具有一个不同的名称,你可以使用include标签的with参数来对它起个别名:

  {% include "form_snippet.html" with form=comment_form %}

绑定的表单和未绑定的表单

表单要么是绑定的,要么是未绑定的。

  • 如果是绑定的,那么能够验证数据,并渲染表单及其数据成HTML。
  • 如果是未绑定的,那么它不能够完成验证(因为没有可验证的数据),但是仍然能渲染成空白的表单成HTML。

class Form

若要创建一个未绑定的表单实例,只需要简单地实例化该类:

>>>f = ContactForm()

若要绑定数据到表单,可以将数据以字典的形式传递给表单类的构造函数的第一个参数:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)

在这个字典中,键为字段的名称,它们对应于表单类中的属性。值为需要验证的数据。它们通常为字符串,但是没有强制要求必须是字符串。传递的数据类型取决于字段。

Form.is_bound

如果在运行时你要区分绑定的表单和未绑定的表单,可以检查下表单is_bound属性的值。

>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True

注意,传递一个空的字典将创建一个带有空数据的绑定的表单:

>>> f = ContactForm({})
>>> f.is_bound
True

如果你有一个绑定的表单实例但是想修改下数据,或者你想绑定一个未绑定的表单到某些数据,你需要创建另外一个表单列表。Form实例的数据没有办法修改。表单实例一旦创建,你应该将它的数据视为不可变的,无论它有没有数据。

使用表单来验证数据

Form.clean()

当你需要为相互依赖的字段添加自定义的验证时,你可以实现表单的clean()方法。

Form.is_valid()

表单对象的首要任务就是验证数据。对于绑定的表单实例,可以调用is_valid()方法来执行验证并返回一个表示数据是否合法的布尔值。

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}    
>>> f = ContactForm(data)
>>> f.is_valid()
True

让我们试下非法的数据。下面的情形中,subject 为空(默认所有字段都是必需的)且sender 是一个不合法的邮件地址:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors

访问errors属性可以获得错误信息的一个字典。

>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is     required.']}

在这个字典中,键为字段的名称,值为表示错误信息的Unicode字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。
你可以访问errors而不必须先调用is_valid()。表单的数据将在第一次调用is_valid()或者访问errors时验证。
验证只会调用一次,无论你访问errors或者调用is_valid()多少次。这意味着,如果验证过程有副作用,这些副作用将只触发一次。

Form.non_field_errors()

这个方法返回Form.errors中不是与特定字段相关联的错误。它包含在Form.clean()中引发的ValidationError和使用Form.add_error(None,'...')添加的错误。

动态的初始值

Form.initial

表单字段的初始值使用initial声明。例如,你可能希望使用当前会话的用户名填充username字段。
使用Form的initial参数可以实现。该参数是字段名到初始值的一个字典。只需要包含你期望给出初始值的字段,不需要包含表单中的所有字段。例如:

>>> f = ContactForm(initial={'subject': 'Hi there!'})

这些值只显示在没有绑定的表单中,即使没有提供特定值它们也不会作为后备的值。
注意,如果字段有定义initial,而实例化表单时也提供initial,那么后面的initial将优先。在下面的例子中,initial在字段和表单实例化中都有定义,此时后者具有优先权。

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(initial='class')
...     url = forms.URLField()
...     comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name"   value="instance" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td>        </tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>

检查表单数据是否改变

Form.has_changed()
当你需要检查表单的数据是否从初始数据发生改变时,可以使用表单的has_changed()方法。

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data, initial=data)
>>> f.has_changed()
False

当提交表单时,我们可以重新构建表单并提供初始值,这样可以实现比较:

>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()

如果request.POST中的数据与inital中的不同,has_changed()将为True,否则为False。计算的结果是通过调用表单每个字段的Field.has_changed()得到的。

访问'clean'的数据

Form.cleaned_data
表单类中的每个字段不仅负责验证数据,还负责‘清洁’它们---将它们转换为正确的格式。这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。
一旦你创建一个表单实例并通过验证后,你就可以通过它的cleaned_data属性访问清洁的数据:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

如果你的数据没有通过验证,cleaned_data字典中只包含合法的字段:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}

cleaned_data 始终只 包含表单中定义的字段,即使你在构建表单 时传递了额外的数据。在下面的例子中,我们传递一组额外的字段给ContactForm 构造函数,但是cleaned_data 将只包含表单的字段:

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True,
...         'extra_field_1': 'foo',
...         'extra_field_2': 'bar',
...         'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}

当表单合法时,cleaned_data 将包含所有字段的键和值,即使传递的数据不包含某些可选字段的值。在下面的例子中,传递的数据字典不包含nick_name 字段的值,但是cleaned_data 任然包含它,只是值为空:

>>> from django import forms
>>> class OptionalPersonForm(forms.Form):
...     first_name = forms.CharField()
...     last_name = forms.CharField()
...     nick_name = forms.CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}

在上面的例子中,cleaned_data 中nick_name 设置为一个空字符串,这是因为nick_name 是CharField而 CharField 将空值作为一个空字符串。每个字段都知道自己的“空”值 —— 例如,DateField 的空值是None 而不是一个空字符串。关于每个字段空值的完整细节,参见“内建的Field 类”一节中每个字段的“空值”提示。

表单必填行和错误行的样式表

Form.error_css_class
Form.required_css_class
将必填的表单行和有错误的表单行定义不同的样式表特别常见。例如,你想将必填的表单行以粗体显示,将错误以红色显示。
表单类具有一对钩子,可以使用它们来添加class属性给必填的行或者有错误的行:只需要简单地设置Form.error_css_class或 Form.required_css_class属性:

from django import forms
class ContactForm(forms.Form):
    error_css_class = 'error'
    required_css_class = 'required'

    # ... and the rest of your fields here

一旦你设置好,将根据需要,设置行的"error" 或"required" CSS类型。 其HTML 看上去将类似:

>>> f = ContactForm(data)
>>> print(f.as_table())
<tr class="required"><th><label class="required"     for="id_subject">Subject:</label>    ...
<tr class="required"><th><label class="required" for="id_message">Message:</label>    ...
<tr class="required error"><th><label class="required" for="id_sender">Sender:</label>      ...
<tr><th><label for="id_cc_myself">Cc myself:<label> ...
>>> f['subject'].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f['subject'].label_tag(attrs={'class': 'foo'})
<label for="id_subject" class="foo required">Subject:</label>

从模型创建表单

ModelForm

如果你正在构建一个数据库驱动的应用,那么你应该会有与Django的模型紧密映射的表单。举个例子,你也许会有个BlogComment模型,并且你还想创建一个表单让大家提交评论到这个模型中。在这种情况下,在表单中定义定义字段是冗余的,因为你已经在模型中定义了字段。
基于这个原因,Django提供一个辅助类来让你从Django的模型创建表单。例如,

>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)

字段类型

生成的表单类中将具有和指定的模型字段对应的表单字段。字段顺序为fields属性中指定的顺序。
每个模型字段有一个对应的默认表单字段。比如,模型中的CharField对应表单中的CharField。模型中的ManyToManyField字段会表现成MultipleChoiceField字段。

模型表单的验证

验证模型表单主要有两步:

  1. 验证表单;
  2. 验证模型实例;

与普通的表单验证类似,模型表单的验证在调用is_valid()或者访问errors属性时隐式调用,或者通过full_clean()显式调用,尽管在实际应用中很少使用后一种方法。
模型的验证Model.full_clean()在表单验证这一步的内部触发,紧跟在表单的clean()方法调用之后。

save()方法

每个模型表单还具有一个save()方法。这个方法根据表单绑定的数据创建并保存数据库对象。模型表单的子类可以用关键字参数instance接收一个已经存在的模型实例。如果提供,save()将更新这个实例。如果没有提供,save()将创建模型的一个新实例。

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

注意,如果表单没有验证,save()调用将通过检查form.errors来进行验证。如果表单中的数据不合法,将引发ValuelError。
save() 接受一个可选的commit 关键字参数,其值为True 或False。如果save() 时commit=False,那么它将返回一个还没有保存到数据库的对象。这种情况下,你需要调用返回的模型实例的save()。这是很有用的,如果你想在保存之前自定义一些处理了,或者你想使用特定的模型保存选项。
使用commit=False 的另外一个副作用是在模型具有多对多关系的时候。如果模型具有多对多关系而且当你保存表单时指定commit=False,Django 不会立即为多对多关系保存表单数据。这是因为只有实例在数据库中存在时才可以保存实例的多对多数据。

为了解决这个问题,每当你使用commit=False 保存表单时,Django 将添加一个save_m2m() 方法到你的模型表单子类。在你手工保存有表单生成的实例之后,你可以调用save_m2m() 来保存多对多的表单数据。例如:

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()

save_m2m() 只在你使用save(commit=False) 时才需要。当你直接使用save(),所有的数据 —— 包括多对多数据 —— 都将保存而不需要任何额外的方法调用。例如:

# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do   anything else.
>>> new_author = f.save()

处理关系的字段Fields which handle relationships

两个字段可用于表示模型之间的关系:ModelChoiceField和ModelMultipleChoiceField。这两个字段都需要单个queryset参数,用于创建字段的选择。在表单验证时,这些字段将把一个模型对象(在ModelChoiceField的情况下)或多个模型对象(在ModelMultipleChoiceField的情况下)放置到表单的cleaned_data字典。
对于更复杂的用法,可以在声明表单字段时指定queryset=None,然后在表单的__init__()方法中填充queryset

  class FooMultipleChoiceForm(forms.Form):
        foo_select = forms.ModelMultipleChoiceField(queryset=None)
        def __init__(self, *args, **kwargs):
              super(FooMultipleChoiceForm, self).__init__(*args, **kwargs)
              self.fields['foo_select'].queryset = ...

ModelChoiceField

class ModelChoiceField(**kwargs)

  • 默认的Widget: Select
  • 空值: None
  • 规范化为: 一个模型实例
  • 验证给定的id是否存在于查询集中
  • 错误信息的键: required, invalid_choice

可以选择一个单独的模型对象,适用于表示一个外键字段。ModelChoiceField默认widget不适用选择数量很大的情况,在大于100项时应该避免使用它。
需要一个单独参数:
queryset: 将导出字段选择的模型对象的QuerySet,将用于验证用户的选择。
ModelChoiceField有两个可选参数:

  • empty_label: 默认情况下,ModelChoiceField使用的<select>小部件将在列表顶部有一个空选项。您可以使用empty_label属性更改此标签的文本(默认为"__"),也可以完全禁用空白标签通过将empty_label设置为None:

    #A custom empty label
    field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)")
    #No empty label
    field2 = forms.ModelChoiceField(queryset=..., empty_label=None)

    请注意如果需要用到ModelChoiceField有一个默认的初始值,则不会创建空选项(不管empty_label的值)。

  • to_field_name: 这个参数用于指定要用作字段小部件选项的值的字段。确保它是模型的唯一字段,否则选定的值可以匹配多个对象。默认情况下,它设置为None,在这种情况下,将使用每个对象的主键。

    #No custom to_field_name
    field1 = forms.ModelChoiceField(queryset=...)

    会渲染成:

    <select id='id_field1' name='field1'>
    <option value='obj1.pk'>Object1</option>
    <option value='obj2.pk'>Object2</option>
    ...
    </select>

    和:

    #to_field_name provided
    field2 = forms.ModelChoiceField(queryset=... to_field_name='name')

    会生成:

    <select id='id_field2' name='field2'>
    <option value='obj1.name'>Object1</option>
    <option value='obj2.name'>Object2</option>
    ...
    </select>

ModelMultipleChoiceField

  • 默认的Widget: SelectMultiple
  • 控制: QuerySet(self.queryset.none())
  • 规范化为: 模型实例的一个QuerySet。
  • 错误信息的键: required, list, invalid_choice, invalid_pk_value

允许选择适合于表示多对多关系的一个或多个模型对象。queryset是必需的参数。
queryset:将导出字段选择的模型对象的QuerySet,将用于验证用户的选择。

表单验证和字段验证

表单验证发生在数据清洗时。如果需要自定义这个过程,有几个不同的地方可以修改,每个地方的目的不一样。表单处理过程中要运行三种类别的验证方法:它们通常在你调用表单的is_valid()方法时执行。还有其它方法可以触发验证过程(访问errors属性或直接调用full_clean()),但是通常情况下不需要。
一般情况下,如果处理的数据有问题,每个验证方法都会引发ValidationError,并将相关信息传递给ValidationError构造器。如果没有引发ValidationError,这些方法应该返回验证后的(规整化的,清洗后的)数据的Python对象。
大部分验证应该可以使用validators完成,它们可以很容易地重用。Validators是简单的函数(或可调用对象),它们接收一个参数并对非法的输入抛出ValidationErrorValidators在字段的to_pythonvalidate方法调用之后运行。
表单的验证分成几个步骤,它们可以定制或覆盖:

  • 字段的to_python()方法是验证的第一步。它将值强制转换为正确的数据类型,如果不能转换则引发ValidationError。这个方法从Widget接收原始的值并返回转换后的值。例如,FloatField将数据转换为Pythonfloat或引发ValidationError
  • 字段的validate()方法处理字段的特别的验证,这种验证不适合validator。它接收一个已经转换成正确数据类型的值,并在发现错误时引发ValidationError。这个方法不返回任何东西且不应该改变任何值。当您遇到不能或不想放在validator中的验证逻辑时,应该覆盖它来处理验证。
  • 字段的run_validators()方法运行字段所有的Validator,并将所有的错误信息聚合成一个单一的ValidationError。你应该不需要覆盖这个方法。
  • Filed子类的clean()方法。它负责以正确的顺序运行to_pythonvalidaterun_validators并传播它们的错误。如果任何时刻、任何方法引发ValidationError,验证将停止并引发这个错误。这个方法返回验证后的数据,这个数据在后面将插入到表单的cleaned_data字典中。
  • 表单子类中的clean_<fieldname>方法---<fieldname>通过表单中的字段名称替换。这个方法完成于特定属性相关的验证,这个验证与字段的类型无关。这个方法没有任何输入的参数。你需要查找self.cleaned_data中该字段的值,记住此时它已经是一个Python对象而不是表单中提交的原始字符串(它位于cleaned_data中是因为字段的clean()方法已经验证过一次数据)。
    例如,如果你想验证名为serialnumberCharField的内容是否唯一,clean_serialnumber()将是实现这个功能的理想之处。你需要的不是一个特别的字段(它只是一个CharField),而是一个特定于表单字段的特定验证,并规整化数据。
    这个方法返回的值将代替cleaned_data中已经存在的值,因此它必须是cleaned_data中字段的值或一个新的清洗后的值。
  • 表单子类的clean()方法。这个方法可以实现需要同时访问表单多个字段的验证。这里你可以验证如果提供字段A,那么字段B必须包含一个合法的邮件地址以及类似的功能。这个方法可以返回一个完全不同的字典,该字典将用作cleaned_data
    因为字段的验证方法在调用clean()时会运行,你还可以访问表单的errors属性,它包含验证每个字段时的所有错误。
    注意,你覆盖的Form.clean()引发的任何错误将不会与任何特定的字段关联。它们位于一个特定的‘字段’(叫做__all__)中,如果需要可以通过non_field_errors()方法访问。如果你想添加一个特定字段的错误到表单中,需要调用add_error()
    还要注意,覆盖ModelForm子类的clean()方法需要特殊的考虑。

这些方法按照以上给出的顺序执行,一次验证一个字段。也就是说,对于表单中的每个字段(按照它们在表单定义中出现的顺序),先运行Field.clean(),然后运行clean_<fieldname>()。每个字段的这两个方法都执行完之后,最后运行Form.clean()方法,无论前面的方法是否抛出过异常。

下面由上面每个方法的示例。
我们已经提到过,所有这些方法都可以抛出ValidationError。对于每个字段,如果Field.clean()方法抛出ValidationError,那么将不会调用该字段对应的clean_<fieldname>()方法。但是,剩余的字段的验证方法仍然会执行。

在实践中应用验证

使用Validator

Django的表单(以及模型)字段支持使用简单的函数和类用于验证,它们叫做Validator。Validator是可调用对象或函数,它接收一个值,如果该值合法则什么也不返回,否则抛出ValidationError。它们可以通过字段的validators参数传递给字段的构造函数,或者定义在Field类的default_validators属性中。
简单的Validator可以用于在字段内部验证值,让我们看下Django的SlugField:

  from django.forms import CharField
  form django.core import validators
  class SlugField(CharField):
        default_validators = [validators.validate_slug]

正如你所看到的,SlugField只是一个带有自定义Validator的CharField,它们验证提交的文本符合某些字符规则。这也可以在字段定义时实现,所以:

  slug = forms.SlugField()

等同于:

  slug = forms.CharField(validators=[validtors.validate_slug])

常见的情形,例如验证邮件地址和正则表达式,可以使用Django中已经存在的Validator类处理。

表单字段的默认验证

让我们首先创建一个自定义的表单字段,它验证其输入是一个由逗号分隔的邮件地址组成的字符串。完整的类像这样:

  from django import forms
  from django.core.validators import validate_mail
  class MultiEmailField(forms.Field):
        def to_python(self, value):
            """Nomalize data to a list of strings."""
            #Return an empty list if no input was given.
            if not value:
                return []
             return value.split(',')
        def validate(self, value):
            """Check  if value consists only of valid emails."""
            #Use the parent's handling of required fields, etc.
            super(MultiEmailField, self).validate(value)
            for email in value:
                validate_email(email)

使用这个字段的每个表单都将在处理该字段数据之前运行这些方法。这个验证特定于该类型的字段,与后面如何使用它无关。
让我们来创建一个简单的ContactForm来向你演示如何使用这个字段:

  class ContactForm(forms.Form):
        subject = forms.CharField(max_lenght=100)
        message = forms.CharField()
        sender = forms.EmailField()
        recipients = MultiEmailField()
        cc_myself = forms.BooleanField(required=False)

只需要简单地使用MultiEmailField,就和其它表达字段一样。当调用表单的is_valid()方法时,MultiEmailField.clean()方法将作为验证过程的一部分运行,它将调用自定义的to_python()和validate()方法。

验证特定字段属性

继续上面的例子,假设在ContactForm中,我们想确保recipients字段始终包含"fred@example.com"。这是特定于我们这个表达的验证,所以我们不打算将它放在通用的MultiEmailField类中,我们将编写一个运行在recipients字段上的验证方法,像这样:

  from django import forms
  class ContactForm(forms.Form):
        #Everything as before.
        ...
        def clean_recipients(self):
              data = self.cleaned_data['recipients']
              if 'fred@example.com' not in data:
                 raise forms.ValidationError("You have forgotten about Fred!")
              #Always return a value to use as the new cleaned data, even if this method didn't change it.
              return data

验证相互依赖的字段

假设我们添加另外一个需求到我们的ContactForm表单中:如果cc_myself字段为True,那么subject必须包含单词"help"。我们的这个验证包含多个字段,所以表单的clean()方法是个不错的地方。注意,我们这里讨论的是表单的clean()方法,之前我们编写过字段的clean()方法。区别字段和表单之间的差别非常重要。字段是单个数据,表单是字段的集合。
在调用表单clean()方法的时候,所有字段的验证方法已经执行完,所以self.cleaned_data填充的是目前为止已经合法的数据。所以你需要记住这个事实,你需要验证的字段可能没有通过初始的字段检查。
在这一步,有两种方法报告错误。最简单的方法是在表单的顶端显示错误。你可以在clean()方法中抛出ValidationError来创建错误。例如,

  from django import forms
  class ContactForm(forms.Form):
        #Everythign as before.
        ...
        def clean(self):
            cleaned_data = super(ContactForm, self).clean()
            cc_myself = cleaned_data.get("cc_myself")
            subject = cleaned_data.get("subject")
            if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                 raise forms.ValidationError(
                "Did not send for 'help' in the subject despite "
                "CC'ing yourself."
            )

在这段代码中,如果抛出验证错误,表单将在表单的顶部显示(通常是)描述该问题的一个错误信息。
注意,示例代码中super(ContactForm, self).clean()的调用是为了维持父类中的验证逻辑。如果你的表单继承自另外一个在clean()方法中没有返回一个cleaned_data字典的表单,那么不要把cleand_data联系到super()的结果而要使用self.cleaned_data。

  def clean(self):
      super(ContactForm, self).clean()
      cc_myself = self.cleaned_data.get('cc_myself')
      ...

第二种方法涉及将错误消息关联到某个字段。在这种情况下,让我们在表单的显示中分别关联一个错误信息到“subject” 和“cc_myself” 行。在实际应用中要小心,因为它可能导致表单的输出变得令人困惑。我们只是向你展示这里可以怎么做,在特定的情况下,需要你和你的设计人员确定什么是好的方法。我们的新代码(代替前面的示例)像这样:

from django import forms
class ContactForm(forms.Form):
# Everything as before.
...

def clean(self):
    cleaned_data = super(ContactForm, self).clean()
    cc_myself = cleaned_data.get("cc_myself")
    subject = cleaned_data.get("subject")

    if cc_myself and subject and "help" not in subject:
        msg = "Must put 'help' in subject when cc'ing yourself."
        self.add_error('cc_myself', msg)
        self.add_error('subject', msg)

add_error() 的第二个参数可以是一个简单的字符串,但更倾向是ValidationError的一个实例

编写Validator

验证器是一个可调用的对象,它接受一个值,并在不符合一些规则时候抛出ValidationError异常。验证器有助于在不同类型的字段之间重复使用验证逻辑。
例如,这个验证器只允许偶数:

  from django.core.exceptions import ValidationError
  from django.utils.translation import ugettext_lazy as _
  def validate_even(value):
      if value % s != 0:
        raise ValidationError(
        _('%(value)s is not an even number'),
        params={'value': value},
      )

你可以通过字段的validators参数将它添加到模型字段中:

   from django.db import models
   class MyModel(models.Model):
         even_field = models.IntegerField(validators=[validate_even])

由于值在验证器运行之前会转化为Python,你可以在表单上使用相同的验证器:

    from django import forms
    class MyForm(forms.Form):
          even_field = forms.IntegerField(validators=[validate_even])

你可以可以使用带有__call__()方法的类,实现更复杂或可配置的验证器。

Validator如何运行

参考:

  1. http://python.usyiyi.cn/translate/django_182/topics/forms/index.html
  2. http://python.usyiyi.cn/translate/django_182/ref/forms/api.html
  3. http://python.usyiyi.cn/translate/django_182/topics/forms/modelforms.html
  4. http://python.usyiyi.cn/translate/django_182/ref/forms/fields.html
  5. http://python.usyiyi.cn/translate/django_182/ref/forms/validation.html

本文转载自:http://www.jianshu.com/p/5903f415891a

AllenOR灵感
粉丝 11
博文 2635
码字总数 83001
作品 0
程序员
私信 提问
【Django】CSRF verification failed. Request aborted.

在使用Django提交Post表单时遇到如下错误: 原因在"帮助"中已经写的很清楚了。 一般而言,这可以发生时,有一个真正的跨站请求伪造,或当Django的CSRF的机制还没有正确使用。 对于POST表单,...

晨曦之光
2012/03/01
627
0
【Django】settings

作者: Django 团队 译者: weizhong2004@gmail.com 翻译开始日期: 2006-04-04 翻译完成日期: 2006-04-04 修订日期: 2006-05-06 原文版本: 2789 Django settings 文件包含你的 Django 安装的所...

晨曦之光
2012/03/01
581
0
【Django】错误集

结尾加"/". APPEND_SLASH 默认值: True 是否给URL添加一个结尾的斜线. 只有安装了 CommonMiddleware 之后,该选项才起作用. (参阅 middleware 文档). 参阅 PREPEND_WWW. 原文链接:http://bl...

晨曦之光
2012/03/01
4K
1
【Django】admin使用

1.在 settings.py文件的INSTALLED_APPS部分中,加入'django.contrib.admin', 2.在urls.py文件中urlpatterns中,加入(r'^admin/', include(admin.site.urls)), 同时从django.contrib引入admin......

晨曦之光
2012/03/01
1K
0
【Django】Django命令(Manager.py)

django-admin.py startproject mysite 该命令在当前目录创建一个 mysite 目录。 django-admin.py这个文件在C:\Python27\Lib\site-packages\django\bin文件夹里,可以把该目录添加到系统Path里...

晨曦之光
2012/03/01
2.8K
0

没有更多内容

加载失败,请刷新页面

加载更多

浅谈prototype原型模式

一、原型模式简介 原型(Prototype)模式是一种对象创建型模式,他采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。 原型模式的特点: 1、由原型对...

青衣霓裳
28分钟前
8
0
shell mysql 备份

#!/bin/bash time2=$(date "+%Y-%m-%d-%H:%M:%S") /usr/local/mysql/bin/mysqldump -uroot -p ad > /usr/local/mysql/backup/"$time2".sql 变量引用原来是这么用的。......

奋斗的小牛
36分钟前
4
0
Jmeter监控Linux服务器操作

系统:Win7 64位 工具:Jmeter 4.0 要准备好的插件:JMeterPlugins-Standard-1.4.0,ServerAgent-2.2.1 解压JMeterPlugins-Standard-1.4.0.zip,将其中\lib\ext\JMeterPlugins-Standard.jar......

魔鬼妹子
36分钟前
5
0
系列文章:云原生Kubernetes日志落地方案

在Logging这块做了几年,最近1年来越来越多的同学来咨询如何为Kubernetes构建一个日志系统或者是来求助在这过程中遇到一系列问题如何解决,授人以鱼不如授人以渔,于是想把我们这些年积累的经...

Mr_zebra
37分钟前
5
0
入门必备!快速学会用Aspose.Words在表格中插入和删除列!

Aspose.Words For .Net(点击下载)是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft W...

mnrssj
42分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部