表单 API

关于此文档

本文档介绍了 Django 的表单 API 的具体细节。你应该先阅读 使用表单的介绍

绑定和非绑定表单

一个 Form 实例要么是 绑定 到一组数据,要么是 未绑定

  • 如果是 绑定 了一组数据,它就能够验证这些数据,并将表单渲染成 HTML,并在 HTML 中显示数据。
  • 如果是 未绑定,它就不能进行验证(因为没有数据可验证!),但它仍然可以将空白表单渲染为 HTML。
class Form

要创建一个未绑定的 Form 实例,实例化类:

>>> f = ContactForm()

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

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

在这个字典中,键是字段名,对应于 Form 类中的属性。值是你要验证的数据。这些数据通常是字符串,但不要求它们是字符串;你传递的数据类型取决于 Field,我们稍后会看到。

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 实例绑定到一些数据,请创建另一个 Form 实例。没有办法改变一个 Form 实例中的数据。一旦创建了一个 Form 实例,你应该认为它的数据是不可改变的,不管它是否有数据。

使用表单来验证数据

Form.clean()

当你必须为相互依赖的字段添加自定义验证时,在你的 Form 上实现一个 clean() 方法。参见 清理和验证相互依赖的字段 的用法示例。

Form.is_valid()

Form 对象的主要任务是验证数据。有了一个绑定的 Form 实例,调用 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.']}

在这个字典中,键是字段名,值是代表错误信息的字符串列表。错误信息存储在列表中,因为一个字段可以有多个错误信息。

你可以访问 errors,而不必先调用 is_valid()。无论是调用 is_valid() 还是访问 errors,表单的数据都会首先被验证。

验证例程只会被调用一次,无论你访问 errors 或调用 is_valid() 多少次。这意味着,如果验证有副作用,这些副作用将只被触发一次。

Form.errors.as_data()

返回一个 dict,将字段映射到它们的原始 ValidationError 实例。

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

当你需要通过错误 code 来识别错误时,请使用此方法。这样就可以在给定的错误出现时,重写错误信息或在视图中编写自定义逻辑。它还可以用来以自定义格式(如 XML)将错误序列化;例如, as_json() 依赖于 as_data()

需要使用 as_data() 方法是由于向后兼容性。以前,ValidationError 实例一旦被添加到 Form.errors 字典中,其 渲染的 错误信息就会丢失。理想情况下,Form.errors 会存储 ValidationError 实例,并且带有 as_ 前缀的方法可以渲染它们,但为了不破坏那些期望在 Form.errors 中渲染错误信息的代码,必须反过来做。

Form.errors.as_json(escape_html=False)

返回以 JSON 格式序列化的错误。

>>> f.errors.as_json()
{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}],
"subject": [{"message": "This field is required.", "code": "required"}]}

默认情况下,as_json() 不会转义其输出。如果你使用它来处理类似 AJAX 请求的表单视图,客户端解释响应并将错误插入到页面中,你会希望确保在客户端转义结果,以避免跨站点脚本攻击的可能性。你可以在 JavaScript 中使用 element.textContent = errorText 或者使用 jQuery 的 $(el).text(errorText) (而不是它的 .html() 函数)来实现。

如果出于某些原因你不想使用客户端转义,你也可以设置 escape_html=True,错误信息将被转义,这样你就可以直接在 HTML 中使用它们。

Form.errors.get_json_data(escape_html=False)

Form. errors.as_json() 将返回序列化的 JSON,而这个则是返回序列化之前的错误数据。

escape_html 参数的行为如 Form.errors.as_json() 中所述。

Form.add_error(field, error)

该方法允许在 Form.clean() 方法中添加错误到特定字段,或者从表单外部添加错误,例如从视图中添加。

field 参数是应该添加错误的字段名。如果它的值是 None,错误将被视为非字段错误,就像 Form.non_field_errors() 返回的那样。

error 参数可以是一个字符串,或者最好是 ValidationError 的实例。关于定义表单错误的最佳做法,请参见 引发 ValidationError

注意,Form.add_error() 会自动从 cleaned_data 中删除相关字段。

Form.has_error(field, code=None)

本方法返回一个布尔值,表示一个字段是否有特定错误 code 的错误。如果 codeNone,如果字段包含任何错误,它将返回 True

要检查非字段错误,使用 NON_FIELD_ERRORS 作为 field 参数。

Form.non_field_errors()

这个方法从 Form.errors 中返回没有与特定字段关联的错误列表。这包括在 Form.clean() 中引发的 ValidationError 和使用 Form.add_error(None, "...") 添加的错误。

非绑定表单的行为

在没有数据的情况下验证表单是没有意义的,但是,记录一下,以下是未绑定表单的情况:

>>> f = ContactForm()
>>> f.is_valid()
False
>>> f.errors
{}

初始表单值

Form.initial

使用 initial 在运行时声明表单字段的初始值。例如,你可能想用当前会话的用户名来填写 username 字段。

要实现这一目标,请使用 initial 参数到 Form。如果给定这个参数,它应该是一个将字段名映射到初始值的字典。只包含你要指定初始值的字段,没有必要包含表单中的每个字段。例如:

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

这些值只对未绑定的表单显示,如果没有提供特定的值,它们不会被用作后备值。

如果一个 Field 定义了 initial 并且 你在实例化 Form 时包含了 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.get_initial_for_field(field, field_name)

返回一个表单字段的初始数据。如果存在的话,它从 Form.initial 检索数据,否则尝试 Field.initial。可调用的值将被执行。

我们推荐使用 BoundField.initial 而不是 get_initial_for_field() 因为 BoundField.initial 有一个更简单的接口。另外,不像 get_initial_for_field()BoundField.initial 会缓存它的值。这在处理返回值是可能改变的可调用对象时尤其有用(例如,datetime.nowuuid.uuid4):

>>> import uuid
>>> class UUIDCommentForm(CommentForm):
...     identifier = forms.UUIDField(initial=uuid.uuid4)
>>> f = UUIDCommentForm()
>>> f.get_initial_for_field(f.fields['identifier'], 'identifier')
UUID('972ca9e4-7bfe-4f5b-af7d-07b3aa306334')
>>> f.get_initial_for_field(f.fields['identifier'], 'identifier')
UUID('1b411fab-844e-4dec-bd4f-e9b0495f04d0')
>>> # Using BoundField.initial, for comparison
>>> f['identifier'].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')
>>> f['identifier'].initial
UUID('28a09c59-5f00-4ed9-9179-a3b074fa9c30')

检查哪些表格数据已经改变

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 的数据与 initial 中提供的数据不同,那么 has_changed() 将为 True,否则为 False。结果是通过调用 Field.has_changed() 对表单中的每个字段进行计算。

Form.changed_data

changed_data 属性返回表单绑定数据(通常是 request.POST)中与 initial 中提供的数据不同的字段名列表。如果没有不同的数据,则返回一个空列表。

>>> f = ContactForm(request.POST, initial=data)
>>> if f.has_changed():
...     print("The following fields changed: %s" % ", ".join(f.changed_data))
>>> f.changed_data
['subject', 'message']

访问表单中的字段

Form.fields

你可以从 Form 实例的 fields 属性中访问其字段:

>>> for row in f.fields.values(): print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields['name']
<django.forms.fields.CharField object at 0x7ffaac6324d0>

You can alter the field and BoundField of Form instance to change the way it is presented in the form:

>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'
>>> f["subject"].label = "Topic"
>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Topic:</label><input type="text" name="subject" maxlength="100" required id="id_subject">'

请注意不要修改 base_fields 属性,因为这个修改会影响同一 Python 进程中所有后续的 ContactForm 实例:

>>> f.base_fields["subject"].label_suffix = "?"
>>> another_f = CommentForm(auto_id=False)
>>> f.as_div().split("</div>")[0]
'<div><label for="id_subject">Subject?</label><input type="text" name="subject" maxlength="100" required id="id_subject">'

访问“干净的”数据

Form.cleaned_data

Form 类中的每个字段不仅负责验证数据,还负责“清理”数据——将其规范为一致的格式。这是一个很好的功能,因为它允许以各种方式输入特定字段的数据,并总是产生一致的输出。

例如, DateField 将输入规范化为 Python 的 datetime.date 对象。无论你传递给它的是格式为 '1994-07-15' 的字符串,还是 datetime.date 对象,或者其他一些格式,只要它是有效的,DateField 都会将它规范化为 datetime.date 对象。

当你用一组数据创建了一个 Form 实例并对其进行验证后,你就可以通过它的 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'}

请注意,任何基于文本的字段——如 CharFieldEmailField——总是将输入清理成一个字符串。我们将在本文档后面介绍编码的含义。

如果你的数据 没有 验证,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 总是只包含 Form 中定义的字段的键,即使你在定义 Form 时传递了额外的数据。在这个例子中,我们传递了一堆额外的字段给 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'}

Form 有效时,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'}

在上面这个例子中,nick_namecleaned_data 值被设置为一个空字符串,因为 nick_nameCharField,而 CharField 将空值视为空字符串。每个字段类型都知道它的“空”值是什么——例如,对于 DateField,它是 None 而不是空字符串。关于每个字段在这种情况下的行为的全部细节,请参阅下面“内置 Field 类”一节中每个字段的“空值”说明。

你可以编写代码来对特定的表单字段(基于其名称)或整个表单(考虑各种字段的组合)进行验证。更多关于这方面的信息请参见 表单和字段验证

将表单输出为 HTML

Form 对象的第二个任务是将其本身渲染为 HTML。为此,print 对象:

>>> f = ContactForm()
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>

如果表单与数据绑定,HTML 输出将适当地包含该数据。例如,如果一个字段用 <input type="text"> 表示,数据将在 value 属性中。如果一个字段用 <input type="checkbox"> 表示,那么 HTML 将适当地包括 checked

>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> f = ContactForm(data)
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" value="hello" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" value="Hi there" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" value="foo@example.com" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" checked></td></tr>

这个默认输出是一个两列的 HTML 表单,每个字段都有一个 <tr>。请注意以下几点:

  • 为灵活起见,输出 包括 <table></table> 标签,也不包括 <form></form> 标签或 <input type="submit"> 标签。这是你的工作。
  • 每个字段类型都有一个默认的 HTML 表示。CharField<input type="text"> 表示,EmailField<input type="email"> 表示。BooleanField(null=False) 由一个 <input type="checkbox">。请注意,这些只是合理的默认值;你可以通过使用部件来指定一个给定字段使用的 HTML,我们将在后面解释。
  • 每个标签的 HTML name 直接从 ContactForm 类中的属性名中提取。
  • 每个字段的文本标签——例如 'Subject:''Message:''Cc myself:',是根据字段名将所有下划线转换为空格并将第一个字母大写而产生的。同样,请注意这些只是合理的默认值;你也可以手动指定标签。
  • 每个文本标签都被一个 HTML <label> 标签包围,该标签通过其 id 指向相应的表格字段。而它的 id 则是通过在字段名前加上 'id_' 生成的。id 属性和 <label> 标签默认包含在输出中,以遵循最佳实践,但你可以改变这种行为。
  • 输出使用 HTML5 语法,目标是 <!DOCTYPE html>。例如,它使用布尔属性,如 checked 而不是 XHTML 风格的 checked='checked'

虽然当你 print 表单时,<table> 输出是默认的输出样式,但还有其他的输出样式。每种样式都可以作为表单对象的一个方法,每个渲染方法都返回一个字符串。

Default rendering

The default rendering when you print a form uses the following methods and attributes.

template_name

New in Django 4.0.
Form.template_name

The name of the template rendered if the form is cast into a string, e.g. via print(form) or in a template via {{ form }}.

By default, a property returning the value of the renderer's form_template_name. You may set it as a string template name in order to override that for a particular form class.

Changed in Django 4.1:

In older versions template_name defaulted to the string value 'django/forms/default.html'.

render()

New in Django 4.0.
Form.render(template_name=None, context=None, renderer=None)

渲染方法被 __str__ 以及 Form.as_table()Form.as_p()Form.as_ul() 方法调用。所有的参数都是可选的,默认为:

By passing template_name you can customize the template used for just a single call.

get_context()

New in Django 4.0.
Form.get_context()

Return the template context for rendering the form.

可用的上下文:

  • form: 绑定表单
  • fields: 所有绑定字段,除了隐藏字段。
  • ·hidden_fields`: 所有隐藏的绑定字段。
  • errors: 所有与字段无关的或与隐藏字段有关的表单错误。

template_name_label

New in Django 4.0.
Form.template_name_label

The template used to render a field's <label>, used when calling BoundField.label_tag()/legend_tag(). Can be changed per form by overriding this attribute or more generally by overriding the default template, see also 覆盖内置表单模板.

Output styles

As well as rendering the form directly, such as in a template with {{ form }}, the following helper functions serve as a proxy to Form.render() passing a particular template_name value.

These helpers are most useful in a template, where you need to override the form renderer or form provided value but cannot pass the additional parameter to render(). For example, you can render a form as an unordered list using {{ form.as_ul }}.

Each helper pairs a form method with an attribute giving the appropriate template name.

as_div()

Form.template_name_div
New in Django 4.1.

The template used by as_div(). Default: 'django/forms/div.html'.

Form.as_div()
New in Django 4.1.

as_div() renders the form as a series of <div> elements, with each <div> containing one field, such as:

>>> f = ContactForm()
>>> f.as_div()

… gives HTML like:

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

备注

Of the framework provided templates and output styles, as_div() is recommended over the as_p(), as_table(), and as_ul() versions as the template implements <fieldset> and <legend> to group related inputs and is easier for screen reader users to navigate.

as_p()

Form.template_name_p

The template used by as_p(). Default: 'django/forms/p.html'.

Form.as_p()

as_p() renders the form as a series of <p> tags, with each <p> containing one field:

>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>'
>>> print(f.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> <input type="text" name="message" id="id_message" required></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>

as_ul()

Form.template_name_ul

The template used by as_ul(). Default: 'django/forms/ul.html'.

Form.as_ul()

as_ul() renders the form as a series of <li> tags, with each <li> containing one field. It does not include the <ul> or </ul>, so that you can specify any HTML attributes on the <ul> for flexibility:

>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>

as_table()

Form.template_name_table

The template used by as_table(). Default: 'django/forms/table.html'.

Form.as_table()

as_table() renders the form as an HTML <table>:

>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>'
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>

样式化必填或错误的表单行

Form.error_css_class
Form.required_css_class

对必填或有错误的表单行和字段进行样式设计是很常见的。例如,你可能想用粗体显示必填的表格行,用红色突出显示错误。

Form 类有几个钩子,你可以用来给必填行或有错误的行添加 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" 类。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'].legend_tag()
<legend class="required" for="id_subject">Subject:</legend>
>>> f['subject'].label_tag(attrs={'class': 'foo'})
<label for="id_subject" class="foo required">Subject:</label>
>>> f['subject'].legend_tag(attrs={'class': 'foo'})
<legend for="id_subject" class="foo required">Subject:</legend>

设置表单元素的 HTML id 属性和 <label> 标签。

Form.auto_id

默认情况下,表单渲染方法包括:

  • 表单元素的 HTML id 属性。
  • 标签周围对应的 <label> 标签。HTML <label> 标签指定了哪个标签文本与哪个表单元素相关联。这个小小的改进使表单更加可用,也更容易被辅助设备访问。使用 <label> 标签总是一个好主意。

id 属性值是通过将 id_ 预置到表单字段名后生成的。 如果你想改变 id 惯例或完全删除 HTML id 属性和 <label> 标签,这种行为是可以设置的。

使用 Form 构造函数的 auto_id 参数来控制 id 和标签行为。这个参数必须是 TrueFalse 或一个字符串。

如果 auto_idFalse,那么表单输出将不包含 <label> 标签或 id 属性:

>>> f = ContactForm(auto_id=False)
>>> print(f.as_div())
<div>Subject:<input type="text" name="subject" maxlength="100" required></div>
<div>Message:<textarea name="message" cols="40" rows="10" required></textarea></div>
<div>Sender:<input type="email" name="sender" required></div>
<div>Cc myself:<input type="checkbox" name="cc_myself"></div>

如果 auto_id 设置为 True,那么表单输出 包括 <label> 标签,并将使用字段名作为每个表单字段的 id

>>> f = ContactForm(auto_id=True)
>>> print(f.as_div())
<div><label for="subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="subject"></div>
<div><label for="message">Message:</label><textarea name="message" cols="40" rows="10" required id="message"></textarea></div>
<div><label for="sender">Sender:</label><input type="email" name="sender" required id="sender"></div>
<div><label for="cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="cc_myself"></div>

如果 auto_id 被设置为包含格式字符 '%s' 的字符串,那么表单输出将包含 <label> 标签,并将根据格式字符串生成 id 属性。例如,对于格式字符串 'field_%s',名为 subject 的字段将得到 id'field_subject'。继续我们的例子:

>>> f = ContactForm(auto_id='id_for_%s')
>>> print(f.as_div())
<div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message:</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender:</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself:</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>

如果 auto_id 被设置为任何其他的真值——比如一个不包含 %s 的字符串——那么该库就会像 auto_idTrue 一样。

默认情况下,auto_id 被设置为字符串 'id_%s'

Form.label_suffix

一个可翻译的字符串(英文默认为冒号(:)),将在渲染表格时附加在任何标签名称之后。

可以使用 label_suffix 参数自定义该字符,或者完全省略:

>>> f = ContactForm(auto_id='id_for_%s', label_suffix='')
>>> print(f.as_div())
<div><label for="id_for_subject">Subject</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>
>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->')
>>> print(f.as_div())
<div><label for="id_for_subject">Subject:</label><input type="text" name="subject" maxlength="100" required id="id_for_subject"></div>
<div><label for="id_for_message">Message -&gt;</label><textarea name="message" cols="40" rows="10" required id="id_for_message"></textarea></div>
<div><label for="id_for_sender">Sender -&gt;</label><input type="email" name="sender" required id="id_for_sender"></div>
<div><label for="id_for_cc_myself">Cc myself -&gt;</label><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></div>

请注意,只有当标签的最后一个字符不是标点符号时,才会加上标签后缀(在英文中,这些字符是 .!?:)。

Fields can also define their own label_suffix. This will take precedence over Form.label_suffix. The suffix can also be overridden at runtime using the label_suffix parameter to label_tag()/ legend_tag().

Form.use_required_attribute

当设置为 True (默认)时,必填表单字段将有 required HTML 属性。

表单集 实例化表单时使用 use_required_attribute=False 以避免从表单集中添加和删除表单时浏览器验证错误。

设置表单组件的渲染方式

Form.default_renderer

指定 渲染器 用于表单的渲染。默认值为 None,表示使用 FORM_RENDERER 设置中指定的默认渲染器。

你可以在声明你的表单时将其设置为一个类属性,或者使用 Form.__init__()renderer 参数。例如:

from django import forms

class MyForm(forms.Form):
    default_renderer = MyRenderer()

或者:

form = MyForm(renderer=MyRenderer())

字段顺序的注意事项

as_p()as_ul()as_table() 快捷方式中,字段是按照你在表单类中定义的顺序显示的。例如,在 ContactForm 的例子中,字段是按照 subjectmessagesendercc_myself 的顺序定义的。要调整 HTML 输出的顺序,改变这些字段在类中的排列顺序。

还有其他几种方式可以自定义顺序:

Form.field_order

默认情况下 Form.field_order=None,它保留了你在表单类中定义字段的顺序。如果 field_order 是一个字段名的列表,则字段按列表指定的顺序排列,其余字段按默认顺序追加。列表中未知的字段名将被忽略。这使得在子类中可以通过将字段设置为 None 来禁用字段,而不必重新定义排序。

你也可以使用 FormForm.field_order 参数来覆盖字段顺序。如果一个 Form 定义了 field_order并且 你在实例化 Form 时包含了 field_order,那么后者的 field_order 将具有优先权。

Form.order_fields(field_order)

你可以在任何时候使用 order_fields() 对字段进行重新排列,字段名列表如 field_order

如何显示错误

如果你渲染一个绑定的 Form 对象,渲染行为将自动运行表单的验证,如果它还没有发生,HTML 输出将包括验证错误,作为字段附近的 <ul class="errorlist">。错误信息的具体位置取决于你使用的输出方法:

>>> data = {'subject': '',
...         'message': 'Hi there',
...         'sender': 'invalid email address',
...         'cc_myself': True}
>>> f = ContactForm(data, auto_id=False)
>>> print(f.as_div())
<div>Subject:<ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></div>
<div>Message:<textarea name="message" cols="40" rows="10" required>Hi there</textarea></div>
<div>Sender:<ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" required></div>
<div>Cc myself:<input type="checkbox" name="cc_myself" checked></div>
>>> print(f.as_table())
<tr><th>Subject:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="subject" maxlength="100" required></td></tr>
<tr><th>Message:</th><td><textarea name="message" cols="40" rows="10" required></textarea></td></tr>
<tr><th>Sender:</th><td><ul class="errorlist"><li>Enter a valid email address.</li></ul><input type="email" name="sender" value="invalid email address" required></td></tr>
<tr><th>Cc myself:</th><td><input checked type="checkbox" name="cc_myself"></td></tr>
>>> print(f.as_ul())
<li><ul class="errorlist"><li>This field is required.</li></ul>Subject: <input type="text" name="subject" maxlength="100" required></li>
<li>Message: <textarea name="message" cols="40" rows="10" required></textarea></li>
<li><ul class="errorlist"><li>Enter a valid email address.</li></ul>Sender: <input type="email" name="sender" value="invalid email address" required></li>
<li>Cc myself: <input checked type="checkbox" name="cc_myself"></li>
>>> print(f.as_p())
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
<p>Subject: <input type="text" name="subject" maxlength="100" required></p>
<p>Message: <textarea name="message" cols="40" rows="10" required></textarea></p>
<p><ul class="errorlist"><li>Enter a valid email address.</li></ul></p>
<p>Sender: <input type="email" name="sender" value="invalid email address" required></p>
<p>Cc myself: <input checked type="checkbox" name="cc_myself"></p>

自定义错误列表格式

class ErrorList(initlist=None, error_class=None, renderer=None)

默认情况下,表单使用 django.forms.utils.ErrorList 来格式化验证错误。ErrorList 是一个类似列表的对象,其中 initlist 是错误列表。此外,这个类有以下属性和方法。

error_class

渲染错误列表时要使用的 CSS 类。任何提供的类将被添加到默认的 errorlist 类中。

renderer
New in Django 4.0.

指定 渲染器 用于 ErrorList。默认为 None,即使用由 FORM_RENDER 配置指定的默认渲染器。

template_name
New in Django 4.0.

调用 __str__render() 时使用的模板名称。默认情况下,这是 'django/forms/errors/list/default.html',它是 'ul.html' 模板的代理。

template_name_text
New in Django 4.0.

调用 as_text() 时使用的模板名称。默认是 'django/forms/errors/list/text.html'。该模板将错误显示为一个列表,其中包含了一些要点。

template_name_ul
New in Django 4.0.

调用 as_ul() 时使用的模板名称。默认情况下,这是 'django/forms/errors/list/ul.html'。这个模板在 <li> 标签中渲染错误,并使用 error_class 所定义的 CSS 类来包装 <ul>

get_context()
New in Django 4.0.

返回模板中渲染错误的上下文。

可用的上下文:

  • errors: 一个错误列表。
  • error_class:一个 CSS 类的字符串。
render(template_name=None, context=None, renderer=None)
New in Django 4.0.

渲染方法被 __str__ 以及 as_ul() 方法所调用。

所有参数都是可选的,将默认为:

as_text()

使用由 template_name_text 定义的模板渲染错误列表。

as_ul()

使用由 template_name_ul 定义的模板渲染错误列表。

如果你想自定义错误的渲染,这可以通过覆盖 template_name 属性来实现,或者更普遍地通过覆盖默认模板来实现,也可以参见 覆盖内置表单模板

Changed in Django 4.0:

ErrorList 的渲染被移至模板引擎。

4.0 版后已移除: 当调用 __str__ 方法时,返回 str 的能力已被弃用。使用模板引擎代替,它返回一个 SafeString

更精细的输出

as_p()as_ul()as_table() 方法都是快捷方式——它们并不是显示表单对象的唯一方式。

class BoundField

用于显示 Form 实例的单个字段的 HTML 或访问属性。

该对象的 __str__() 方法显示该字段的 HTML。

要检索一个单一的 BoundField,在你的表单中使用字典查询语法,使用字段名作为键:

>>> form = ContactForm()
>>> print(form['subject'])
<input id="id_subject" type="text" name="subject" maxlength="100" required>

要检索所有的 BoundField 对象,请迭代表单:

>>> form = ContactForm()
>>> for boundfield in form: print(boundfield)
<input id="id_subject" type="text" name="subject" maxlength="100" required>
<input type="text" name="message" id="id_message" required>
<input type="email" name="sender" id="id_sender" required>
<input type="checkbox" name="cc_myself" id="id_cc_myself">

特定字段的输出尊重表单对象的 auto_id 设置:

>>> f = ContactForm(auto_id=False)
>>> print(f['message'])
<input type="text" name="message" required>
>>> f = ContactForm(auto_id='id_%s')
>>> print(f['message'])
<input type="text" name="message" id="id_message" required>

BoundField 的属性

BoundField.auto_id

BoundField 的 HTML ID 属性。如果 Form.auto_idFalse,则返回一个空字符串。

BoundField.data

此属性返回由部件的 value_from_datadict() 方法提取的此 BoundField 的数据,如果没有给定则返回 None

>>> unbound_form = ContactForm()
>>> print(unbound_form['subject'].data)
None
>>> bound_form = ContactForm(data={'subject': 'My Subject'})
>>> print(bound_form['subject'].data)
My Subject
BoundField.errors

一个 类似列表的对象,打印时显示为 HTML <ul class="errorlist">

>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''}
>>> f = ContactForm(data, auto_id=False)
>>> print(f['message'])
<input type="text" name="message" required>
>>> f['message'].errors
['This field is required.']
>>> print(f['message'].errors)
<ul class="errorlist"><li>This field is required.</li></ul>
>>> f['subject'].errors
[]
>>> print(f['subject'].errors)

>>> str(f['subject'].errors)
''
BoundField.field

这个 Field 封装的表单类中的表单 BoundField 实例。

BoundField.form

这个 Form 实例与这个 BoundField 绑定。

BoundField.help_text

字段的 help_text

BoundField.html_name

部件的 HTML name 属性中使用的名称。它考虑到了 prefix 的形式。

BoundField.id_for_label

Use this property to render the ID of this field. For example, if you are manually constructing a <label> in your template (despite the fact that label_tag()/legend_tag() will do this for you):

<label for="{{ form.my_field.id_for_label }}">...</label>{{ my_field }}

默认情况下,这将是字段的名称,前缀为 id_ (上面的例子为 "id_my_field")。你可以通过设置 attrs 对字段的部件进行修改。例如,声明一个字段是这样的:

my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'}))

并使用上面的模板,会呈现出这样的效果:

<label for="myFIELD">...</label><input id="myFIELD" type="text" name="my_field" required>
BoundField.initial

使用 BoundField.initial 来检索一个表单字段的初始数据。如果存在,它从 Form.initial 检索数据,否则尝试 Field.initial。值为可调用对象将被执行。参见 初始表单值 获取更多的例子。

BoundField.initial 缓存了它的返回值,这在处理返回值可能改变的可调用对象时特别有用(例如 datetime.nowuuid.uuid4):

>>> from datetime import datetime
>>> class DatedCommentForm(CommentForm):
...     created = forms.DateTimeField(initial=datetime.now)
>>> f = DatedCommentForm()
>>> f['created'].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)
>>> f['created'].initial
datetime.datetime(2021, 7, 27, 9, 5, 54)

建议使用 BoundField.initial 而不是 get_initial_for_field()

BoundField.is_hidden

如果这个 BoundField 的部件被隐藏,返回 True

BoundField.label

The label of the field. This is used in label_tag()/legend_tag().

BoundField.name

表单中该字段的名称:

>>> f = ContactForm()
>>> print(f['subject'].name)
subject
>>> print(f['message'].name)
message
BoundField.use_fieldset
New in Django 4.1.

Returns the value of this BoundField widget's use_fieldset attribute.

BoundField.widget_type

返回包装字段的部件的小写类名,去掉后面的 inputwidget。当构建表单时,布局取决于组件的类型时,可以使用这个方法。例如:

{% for field in form %}
    {% if field.widget_type == 'checkbox' %}
        # render one way
    {% else %}
        # render another way
    {% endif %}
{% endfor %}

BoundField 方法

BoundField.as_hidden(attrs=None, **kwargs)

返回将其表示为 <input type="hidden"> 的 HTML 字符串。

**kwargs 传递给 as_widget()

这个方法主要在内部使用。你应该使用部件来代替。

BoundField.as_widget(widget=None, attrs=None, only_initial=False)

通过渲染通过的部件来渲染该字段,并添加作为 attrs 传递的任何 HTML 属性。 如果没有指定部件,那么将使用该字段的默认部件。

only_initial 是 Django 内部使用的,不应该明确设置。

BoundField.css_classes(extra_classes=None)

当你使用 Django 的渲染快捷方式时,CSS 类被用来指示必填的表单字段或包含错误的字段。如果你是手动渲染表单,你可以使用 css_classes 方法访问这些 CSS 类:

>>> f = ContactForm(data={'message': ''})
>>> f['message'].css_classes()
'required'

如果你想在可能需要的错误和必要的类之外提供一些额外的类,你可以提供这些类作为参数:

>>> f = ContactForm(data={'message': ''})
>>> f['message'].css_classes('foo bar')
'foo bar required'
BoundField.label_tag(contents=None, attrs=None, label_suffix=None, tag=None)

使用 Form.template_name_label 指定的模板,为表单字段渲染一个标签。

可用的上下文:

  • field:这个 BoundField 的实例。
  • contents:默认是 BoundField.labelForm.label_suffix (或者 Field.label_suffix, 如果设置的话)的连接字符串。这可以被 contentslabel_suffix 参数所覆盖。
  • attrs:一个包含 forForm.required_css_classiddictid 是由字段的部件 attrsBoundField.auto_id 产生的。其他的属性可以由 attrs 参数提供。
  • use_tag: A boolean which is True if the label has an id. If False the default template omits the tag.
  • tag: An optional string to customize the tag, defaults to label.

小技巧

在你的模板中 fieldBoundField 的实例。因此 field.field 访问 BoundField.field 是你声明的字段,例如 forms.CharField

要单独呈现一个表单字段的标签,可以调用它的 label_tag() 方法:

>>> f = ContactForm(data={'message': ''})
>>> print(f['message'].label_tag())
<label for="id_message">Message:</label>

如果你想自定义渲染,这可以通过覆盖 Form.template_name_label 属性来实现,或者更普遍地通过覆盖默认模板来实现,也可以参见 覆盖内置表单模板

Changed in Django 4.0:

现在,标签已经使用模板引擎渲染了。

Changed in Django 4.1:

The tag argument was added.

BoundField.legend_tag(contents=None, attrs=None, label_suffix=None)
New in Django 4.1.

Calls label_tag() with tag='legend' to render the label with <legend> tags. This is useful when rendering radio and multiple checkbox widgets where <legend> may be more appropriate than a <label>.

BoundField.value()

使用此方法渲染此字段的原始值,如同 Widget

>>> initial = {'subject': 'welcome'}
>>> unbound_form = ContactForm(initial=initial)
>>> bound_form = ContactForm(data={'subject': 'hi'}, initial=initial)
>>> print(unbound_form['subject'].value())
welcome
>>> print(bound_form['subject'].value())
hi

自定义 BoundField

如果你需要访问模板中表单字段的一些附加信息,而使用 Field 的子类还不够,也可以考虑自定义 BoundField

自定义表单字段可以覆盖 get_bound_field()

Field.get_bound_field(form, field_name)

取一个 Form 的实例和字段名。当在模板中访问该字段时,将使用返回值。它很可能是 BoundField 的一个子类的实例。

例如,如果你有一个 GPSCoordinatesField,并希望能够在模板中访问关于坐标的附加信息,可以按以下方式实现:

class GPSCoordinatesBoundField(BoundField):
    @property
    def country(self):
        """
        Return the country the coordinates lie in or None if it can't be
        determined.
        """
        value = self.value()
        if value:
            return get_country_from_coordinates(value)
        else:
            return None

class GPSCoordinatesField(Field):
    def get_bound_field(self, form, field_name):
        return GPSCoordinatesBoundField(form, self, field_name)

现在你可以在模板中使用 {{form.coordinates.country }} 访问国家。

将上传的文件绑定到表单中

处理有 FileFieldImageField 字段的表单比普通表单要复杂一些。

首先,为了上传文件,你需要确保你的 <form> 元素正确地将 enctype 定义为 "multipart/form-data"

<form enctype="multipart/form-data" method="post" action="/foo/">

其次,当你使用表单时,你需要绑定文件数据。文件数据与普通的表单数据是分开处理的,所以当你的表单包含一个 FileFieldImageField 时,你需要在绑定表单时指定第二个参数。因此,如果我们扩展我们的 ContactForm,包含一个名为 mugshotImageField,我们需要绑定包含 mugshot 图片的文件数据:

# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
...         'message': 'Hi there',
...         'sender': 'foo@example.com',
...         'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
>>> f = ContactFormWithMugshot(data, file_data)

在实践中,你通常会指定 request.FILES 作为文件数据的来源(就像你使用 request.POST 作为表单数据的来源一样):

# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)

构造非绑定表单的方法和以往一样——省略表单数据 文件数据:

# Unbound form with an image field
>>> f = ContactFormWithMugshot()

多部分表格的测试

Form.is_multipart()

如果你正在编写可重用的视图或模板,你可能无法提前知道你的表单是否是一个多部分表单。is_multipart() 方法可以告诉你表单是否需要多部分编码来提交:

>>> f = ContactFormWithMugshot()
>>> f.is_multipart()
True

下面是一个如何在模板中使用的例子:

{% if form.is_multipart %}
    <form enctype="multipart/form-data" method="post" action="/foo/">
{% else %}
    <form method="post" action="/foo/">
{% endif %}
{{ form }}
</form>

子类化表单

如果你有多个共享字段的 Form 类,你可以使用子类来消除冗余。

当你将一个自定义的 Form 类子类化时,生成的子类将包括父类的所有字段,然后是你在子类中定义的字段。

在这个例子中,ContactFormWithPriority 包含了 ContactForm 的所有字段,加上一个额外的字段 priorityContactForm 字段是按顺序排列的:

>>> class ContactFormWithPriority(ContactForm):
...     priority = forms.CharField()
>>> f = ContactFormWithPriority(auto_id=False)
>>> print(f.as_div())
<div>Subject:<input type="text" name="subject" maxlength="100" required></div>
<div>Message:<textarea name="message" cols="40" rows="10" required></textarea></div>
<div>Sender:<input type="email" name="sender" required></div>
<div>Cc myself:<input type="checkbox" name="cc_myself"></div>
<div>Priority:<input type="text" name="priority" required></div>

可以对多个表单进行子类化,将表单视为混搭。在这个例子中,BeatleForm 同时对 PersonFormInstrumentForm 进行子类化(按顺序),它的字段列表包括父类的字段:

>>> from django import forms
>>> class PersonForm(forms.Form):
...     first_name = forms.CharField()
...     last_name = forms.CharField()
>>> class InstrumentForm(forms.Form):
...     instrument = forms.CharField()
>>> class BeatleForm(InstrumentForm, PersonForm):
...     haircut_type = forms.CharField()
>>> b = BeatleForm(auto_id=False)
>>> print(b.as_div())
<div>First name:<input type="text" name="first_name" required></div>
<div>Last name:<input type="text" name="last_name" required></div>
<div>Instrument:<input type="text" name="instrument" required></div>
<div>Haircut type:<input type="text" name="haircut_type" required></div>

可以通过在子类上将字段名称设置为 "None" 来声明性地删除从父类继承的 Field。例如:

>>> from django import forms

>>> class ParentForm(forms.Form):
...     name = forms.CharField()
...     age = forms.IntegerField()

>>> class ChildForm(ParentForm):
...     name = None

>>> list(ChildForm().fields)
['age']

表单前缀

Form.prefix

你可以在一个 <form> 标签中放入多个 Django 表单。

>>> mother = PersonForm(prefix="mother")
>>> father = PersonForm(prefix="father")
>>> print(mother.as_div())
<div><label for="id_mother-first_name">First name:</label><input type="text" name="mother-first_name" required id="id_mother-first_name"></div>
<div><label for="id_mother-last_name">Last name:</label><input type="text" name="mother-last_name" required id="id_mother-last_name"></div>
>>> print(father.as_div())
<div><label for="id_father-first_name">First name:</label><input type="text" name="father-first_name" required id="id_father-first_name"></div>
<div><label for="id_father-last_name">Last name:</label><input type="text" name="father-last_name" required id="id_father-last_name"></div>

也可以在表单类上指定前缀:

>>> class PersonForm(forms.Form):
...     ...
...     prefix = 'person'
Back to Top