声明:以下的Form、表单等术语都指的的广义的Django表单。
Form要么是绑定了数据的,要么是未绑定数据的。
如果是绑定的,那么它能够验证数据,并渲染表单及其数据,然后生成HTML表单。如果未绑定,则无法进行验证(因为没有数据可以验证!),但它仍然可以以HTML形式呈现空白表单。
若要创建一个未绑定的Form实例,只需简单地实例化该类:
f = ContactForm()
若要绑定数据到表单,可以将数据以字典的形式传递给Form类的构造函数:
>>> post_data = {'subject': 'hello', ... 'message': 'Hi there', ... 'sender': 'foo@example.com', ... 'cc_myself': True} >>> f = ContactForm(post_data)
在这个字典中,键为字段的名称,它们对应于Form类中的字段。 值为需要验证的数据。
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实例一旦创建,它的数据将不可变。
如果你要自定义验证功能,那么你需要重新实现这个clean方法。
还记得我们在前面的自定义验证器章节的内容吗?
这里的clean()方法其实是Django提供给我们的一个钩子,用于添加表单级别的验证功能。
其源代码很简单,就是走个过程:
def clean(self): return self.cleaned_data
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
表单的errors属性保存了错误信息字典:
>>> f.errors {'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
在这个字典中,键为字段的名称,值为错误信息的Unicode字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。
可以直接调用这个方法获得错误信息,不需要先调用is_valid方法,因为在后台,is_valid方法会自动调用errors。
它的源码很简单:
@property def errors(self): """Return an ErrorDict for the data provided for the form.""" if self._errors is None: self.full_clean() return self._errors
self._errors
是一个私有属性,初始值为None,如果它不等于None,说明已经验证出问题了,self.errors
就会直接返回它,否则就调用full_clean()
走一遍验证过程。
返回一个字典,它将字段映射到原始的ValidationError
实例。
>>> f.errors.as_data() {'sender': [ValidationError(['Enter a valid email address.'])], 'subject': [ValidationError(['This field is required.'])]}
返回JSON序列化后的错误信息字典。
>>> f.errors.as_json() {"sender": [{"message": "Enter a valid email address.", "code": "invalid"}], "subject": [{"message": "This field is required.", "code": "required"}]}
向表单特定字段添加错误信息。
field参数为字段的名称,如果参数值为None,该error将作为Form.non_field_errors()
的一个非字段错误。
error参数是具体的错误信息字符串,或者ValidationError
实例。
判断某个字段是否具有指定code的错误。当code为None时,如果字段有任何错误它都将返回True。
返回Form.errors
中不是与特定字段相关联的错误。
验证没有绑定数据的表单是没有意义的,下面的例子展示了这种情况:
>>> f = ContactForm() >>> f.is_valid() False >>> f.errors {}
这里所谓的初始表单值,指的是给前端页面返回空表单的时候,你或许想给表单一些初始默认值。比如,你有可能想用当前会话的用户名来填写 username
表单字段,省去用户重复输入的过程。
只需要在实例化表单的时候使用initial参数,并提供一个字典参数值。例如:
>>> f = ContactForm(initial={"subject": "Hi there!"})
要注意上面的f是个未绑定的表单实例,调用
is_valid()
会返回Fasle。只有像这样传参f = ContactForm(data),明确提供了data参数值,才是绑定表单。
而f = ContactForm(initial={"subject": "Hi there!"}),本质上等同于f = ContactForm()
当在表单类中定义字段时提供了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) <div>Name:<input type="text" name="name" value="instance" required></div> <div>Url:<input type="url" name="url" required></div> <div>Comment:<input type="text" name="comment" required></div>
下面贴出所有Form类的基础父类的源代码(5.0版本):
class BaseForm(RenderableFormMixin): default_renderer = None field_order = None prefix = None use_required_attribute = True template_name_div = "django/forms/div.html" template_name_p = "django/forms/p.html" template_name_table = "django/forms/table.html" template_name_ul = "django/forms/ul.html" template_name_label = "django/forms/label.html" def __init__( self, data=None, files=None, auto_id="id_%s", prefix=None, initial=None, error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None, ): self.is_bound = data is not None or files is not None self.data = MultiValueDict() if data is None else data self.files = MultiValueDict() if files is None else files self.auto_id = auto_id if prefix is not None: self.prefix = prefix self.initial = initial or {} self.error_class = error_class # Translators: This is the default suffix added to form field labels self.label_suffix = label_suffix if label_suffix is not None else _(":") self.empty_permitted = empty_permitted self._errors = None # Stores the errors after clean() has been called. self.fields = copy.deepcopy(self.base_fields) self._bound_fields_cache = {} self.order_fields(self.field_order if field_order is None else field_order) if use_required_attribute is not None: self.use_required_attribute = use_required_attribute if self.empty_permitted and self.use_required_attribute: raise ValueError( "The empty_permitted and use_required_attribute arguments may " "not both be True." ) # Initialize form renderer. Use a global default if not specified # either as an argument or as self.default_renderer. if renderer is None: if self.default_renderer is None: renderer = get_default_renderer() else: renderer = self.default_renderer if isinstance(self.default_renderer, type): renderer = renderer() self.renderer = renderer def order_fields(self, field_order): if field_order is None: return fields = {} for key in field_order: try: fields[key] = self.fields.pop(key) except KeyError: # ignore unknown fields pass fields.update(self.fields) # add remaining fields in original order self.fields = fields def __repr__(self): if self._errors is None: is_valid = "Unknown" else: is_valid = self.is_bound and not self._errors return "<%(cls)s bound=%(bound)s, valid=%(valid)s, fields=(%(fields)s)>" % { "cls": self.__class__.__name__, "bound": self.is_bound, "valid": is_valid, "fields": ";".join(self.fields), } def _bound_items(self): """Yield (name, bf) pairs, where bf is a BoundField object.""" for name in self.fields: yield name, self[name] def __iter__(self): """Yield the form's fields as BoundField objects.""" for name in self.fields: yield self[name] def __getitem__(self, name): """Return a BoundField with the given name.""" try: field = self.fields[name] except KeyError: raise KeyError( "Key '%s' not found in '%s'. Choices are: %s." % ( name, self.__class__.__name__, ", ".join(sorted(self.fields)), ) ) if name not in self._bound_fields_cache: self._bound_fields_cache[name] = field.get_bound_field(self, name) return self._bound_fields_cache[name] @property def errors(self): """Return an ErrorDict for the data provided for the form.""" if self._errors is None: self.full_clean() return self._errors def is_valid(self): """Return True if the form has no errors, or False otherwise.""" return self.is_bound and not self.errors def add_prefix(self, field_name): return "%s-%s" % (self.prefix, field_name) if self.prefix else field_name def add_initial_prefix(self, field_name): """Add an 'initial' prefix for checking dynamic initial values.""" return "initial-%s" % self.add_prefix(field_name) def _widget_data_value(self, widget, html_name): return widget.value_from_datadict(self.data, self.files, html_name) @property def template_name(self): return self.renderer.form_template_name def get_context(self): fields = [] hidden_fields = [] top_errors = self.non_field_errors().copy() for name, bf in self._bound_items(): bf_errors = self.error_class(bf.errors, renderer=self.renderer) if bf.is_hidden: if bf_errors: top_errors += [ _("(Hidden field %(name)s) %(error)s") % {"name": name, "error": str(e)} for e in bf_errors ] hidden_fields.append(bf) else: errors_str = str(bf_errors) fields.append((bf, errors_str)) return { "form": self, "fields": fields, "hidden_fields": hidden_fields, "errors": top_errors, } def non_field_errors(self): return self.errors.get( NON_FIELD_ERRORS, self.error_class(error_class="nonfield", renderer=self.renderer), ) def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, "error_dict"): if field is not None: raise TypeError( "The argument `field` must be `None` when the `error` " "argument contains errors for multiple fields." ) else: error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: if field != NON_FIELD_ERRORS and field not in self.fields: raise ValueError( "'%s' has no field named '%s'." % (self.__class__.__name__, field) ) if field == NON_FIELD_ERRORS: self._errors[field] = self.error_class( error_class="nonfield", renderer=self.renderer ) else: self._errors[field] = self.error_class(renderer=self.renderer) self._errors[field].extend(error_list) if field in self.cleaned_data: del self.cleaned_data[field] def has_error(self, field, code=None): return field in self.errors and ( code is None or any(error.code == code for error in self.errors.as_data()[field]) ) def full_clean(self): """ Clean all of self.data and populate self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return self._clean_fields() self._clean_form() self._post_clean() def _clean_fields(self): for name, bf in self._bound_items(): field = bf.field value = bf.initial if field.disabled else bf.data try: if isinstance(field, FileField): value = field.clean(value, bf.initial) else: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, "clean_%s" % name): value = getattr(self, "clean_%s" % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e) def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data def _post_clean(self): pass def clean(self): return self.cleaned_data def has_changed(self): """Return True if data differs from initial.""" return bool(self.changed_data) @cached_property def changed_data(self): return [name for name, bf in self._bound_items() if bf._has_changed()] @property def media(self): """Return all media required to render the widgets on this form.""" media = Media() for field in self.fields.values(): media += field.widget.media return media def is_multipart(self): return any(field.widget.needs_multipart_form for field in self.fields.values()) def hidden_fields(self): return [field for field in self if field.is_hidden] def visible_fields(self): return [field for field in self if not field.is_hidden] def get_initial_for_field(self, field, field_name): value = self.initial.get(field_name, field.initial) if callable(value): value = value() if ( isinstance(value, (datetime.datetime, datetime.time)) and not field.widget.supports_microseconds ): value = value.replace(microsecond=0) return value
当你需要检查表单的数据是否从初始数据发生改变时,可以使用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
中的数据有区别,则返回True,否则返回False。
返回有变化的字段的列表。
>>> 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']
通过fileds属性访问表单的字段:
>>> 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>
可以修改Form实例的字段来改变字段在表单中的表示:
>>> f.as_table().split('\n')[0] '<tr><th>Name:</th><td><input name="name" type="text" value="instance" required /></td></tr>' >>> f.fields['name'].label = "Username" >>> f.as_table().split('\n')[0] '<tr><th>Username:</th><td><input name="name" type="text" value="instance" required /></td></tr>'
注意不要改变base_fields
属性,因为一旦修改将影响同一个Python进程中接下来所有的ContactForm实例:
>>> f.base_fields['name'].label = "Username" >>> another_f = CommentForm(auto_id=False) >>> another_f.as_table().split('\n')[0] '<tr><th>Username:</th><td><input name="name" type="text" value="class" required /></td></tr>'
Form类中的每个字段不仅负责验证数据,还负责将它们转换为正确的格式。例如,DateField将输入转换为Python的datetime.date对象。无论你传递的是普通字符串'1994-07-15'、DateField格式的字符串、datetime.date对象、还是其它格式的数字,Django将始终把它们转换成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'}
如果你的数据没有通过验证,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'}
在下面的例子中,由于nick_name
的参数required为Flase,所以虽然提供的实际数据中不包含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'}
Form的第二个任务是将它渲染成HTML代码,默认情况下,根据form类中字段的编写顺序,在HTML中以同样的顺序罗列。 我们可以通过print
方法将HTML代码打印出来:
>>> 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将包含数据。
>>> 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>
注意事项:
<table>
和</table>
、<form>
和</form>
以及<input type="submit">
标签。 需要我们程序员手动添加它们。id_字段名
的形式赋予名称。<!DOCTYPE html>
说明。该方法将form渲染成一系列<p>
标签,每个<p>
标签包含一个字段;
>>> 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>
该方法将form渲染成一系列<li>
标签,每个<li>
标签包含一个字段。但不会自动生成</ul>
和<ul>
,所以你可以自己指定<ul>
的任何HTML属性:
>>> 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>
渲染成HTML表格。它与print完全相同,事实上,当你print一个表单对象时,在后台调用的就是as_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>
as_div()
将表单呈现为一系列 <div>
元素,每个 <div>
包含一个字段,例如:
>>> f = ContactForm() >>> f.as_div()
HTML 代码:
<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>
为一些特别强调的或者需要额外显示的内容设置醒目的CSS样式是一种常用做法,也是非常有必要的。比如给必填字段加粗显示,设置错误文字为红色等等。
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
通过赋值不同的字符串,表示给这两类信息添加不同的CSS样式class属性。
上面的例子,其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>
处理带有FileField和ImageField字段的表单比普通的表单要稍微复杂一点。
首先,为了上传文件,你需要确保你的<form>
元素定义enctype
为multipart/form-data
:
<form enctype="multipart/form-data" method="post" action="/foo/">
其次,当你使用表单时,你需要绑定文件数据。文件数据的处理与普通的表单数据是分开的,所以如果表单包含FileField和ImageField,绑定表单时你需要指定第二个参数,参考下面的例子。
# 为表单绑定image字段 >>> 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
作为文件数据的源:
# Bound form with an image field, data from the request >>> f = ContactFormWithMugshot(request.POST, request.FILES)
构造一个未绑定的表单和往常一样,将表单数据和文件数据同时省略:
# Unbound form with an image field >>> f = ContactFormWithMugshot()
id
属性和<label>
标签默认情况下,表单在渲染的时候会自动提供一个<label>
标签和id
属性。id
属性值是id_
加表单字段的名字。
我们可以在Form的构造器中设置参数auto_id=False
来关闭此行为。
>>> f = ContactForm(auto_id=False) >>> print(f.as_table()) <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required></td></tr> <tr><th>Message:</th><td><input type="text" name="message" required></td></tr> <tr><th>Sender:</th><td><input type="email" name="sender" required></td></tr> <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself"></td></tr> >>> print(f.as_ul()) <li>Subject: <input type="text" name="subject" maxlength="100" required></li> <li>Message: <input type="text" name="message" required></li> <li>Sender: <input type="email" name="sender" required></li> <li>Cc myself: <input type="checkbox" name="cc_myself"></li> >>> print(f.as_p()) <p>Subject: <input type="text" name="subject" maxlength="100" required></p> <p>Message: <input type="text" name="message" required></p> <p>Sender: <input type="email" name="sender" required></p> <p>Cc myself: <input type="checkbox" name="cc_myself"></p>
如果auto_id
设置为 True
,则表现形式如下:
>>> f = ContactForm(auto_id=True) >>> print(f.as_table()) <tr><th><label for="subject">Subject:</label></th><td><input id="subject" type="text" name="subject" maxlength="100" required></td></tr> <tr><th><label for="message">Message:</label></th><td><input type="text" name="message" id="message" required></td></tr> <tr><th><label for="sender">Sender:</label></th><td><input type="email" name="sender" id="sender" required></td></tr> <tr><th><label for="cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="cc_myself"></td></tr> >>> print(f.as_ul()) <li><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" required></li> <li><label for="message">Message:</label> <input type="text" name="message" id="message" required></li> <li><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" required></li> <li><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself"></li> >>> print(f.as_p()) <p><label for="subject">Subject:</label> <input id="subject" type="text" name="subject" maxlength="100" required></p> <p><label for="message">Message:</label> <input type="text" name="message" id="message" required></p> <p><label for="sender">Sender:</label> <input type="email" name="sender" id="sender" required></p> <p><label for="cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="cc_myself"></p>
如果auto_id
被设置为包含'%s'
部分的字符串格式,那么就是指定id的值,'%s'
部分会用表单字段的名字填充 。比如auto_id='id_for_%s'
:
>>> f = ContactForm(auto_id='id_for_%s') >>> print(f.as_table()) <tr><th><label for="id_for_subject">Subject:</label></th><td><input id="id_for_subject" type="text" name="subject" maxlength="100" required></td></tr> <tr><th><label for="id_for_message">Message:</label></th><td><input type="text" name="message" id="id_for_message" required></td></tr> <tr><th><label for="id_for_sender">Sender:</label></th><td><input type="email" name="sender" id="id_for_sender" required></td></tr> <tr><th><label for="id_for_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_for_cc_myself"></td></tr> >>> print(f.as_ul()) <li><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></li> <li><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" required></li> <li><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" required></li> <li><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></li> >>> print(f.as_p()) <p><label for="id_for_subject">Subject:</label> <input id="id_for_subject" type="text" name="subject" maxlength="100" required></p> <p><label for="id_for_message">Message:</label> <input type="text" name="message" id="id_for_message" required></p> <p><label for="id_for_sender">Sender:</label> <input type="email" name="sender" id="id_for_sender" required></p> <p><label for="id_for_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_for_cc_myself"></p>
如果auto_id
被设置为任何其它的真值,比如不包含'%s'
的字符串,相当于设置为True。
默认情况下,auto_id
被设置为 'id_%s'
。
实际上,我们在渲染表单的时候都会用到一个渲染器。
而用哪个渲染器是可配置的。
这个配置项叫做default_renderer
,它的默认值为None,表示使用settings中FORM_RENDERER
指定的渲染器。
我们可以在表单类中手动指定default_renderer
,选择我们需要的渲染器:
from django import forms class MyForm(forms.Form): default_renderer = MyRenderer()
或者:
form = MyForm(renderer=MyRenderer())
在我们使用 as_p()
, as_ul()
和as_table()
快捷方式生成HTML页面的input框时,这些input元素的先后顺序和表单类中字段定义的先后顺序是一致的。
如果想要调整顺序,可以使用Form类的field_order
属性。
默认情况下,Form.field_order=None
,表示与字段采用同样顺序。
如果给field_order
提供一个列表值,那么首先按列表中列出的项排出,剩下的继续按原顺序排出。如果列表中有无效的字段名,将被忽略。
from django import forms class MyForm(forms.Form): field_order = ['message', 'subject'] ...
如果你的表单类中含有图片或者文件类型的字段,在表头的处理上会略有不同。
Form类有一个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类添加prefix属性或参数,来给每个字段名添加一个前缀,比如:
>>> mother = PersonForm(prefix="mother") >>> father = PersonForm(prefix="father") >>> print(mother.as_ul()) <li><label for="id_mother-first_name">First name:</label> <input type="text" name="mother-first_name" id="id_mother-first_name" required></li> <li><label for="id_mother-last_name">Last name:</label> <input type="text" name="mother-last_name" id="id_mother-last_name" required></li> >>> print(father.as_ul()) <li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" required></li> <li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" required></li>
或者
>>> class PersonForm(forms.Form): ... ... ... prefix = 'person'
如果你创建一个Form类的子类,那么子类将自动拥有父类所有的字段。如下所示:
>>> class ContactFormWithPriority(ContactForm): ... priority = forms.CharField() >>> f = ContactFormWithPriority(auto_id=False) >>> print(f.as_ul()) <li>Subject: <input type="text" name="subject" maxlength="100" required></li> <li>Message: <input type="text" name="message" required></li> <li>Sender: <input type="email" name="sender" required></li> <li>Cc myself: <input type="checkbox" name="cc_myself"></li> <li>Priority: <input type="text" name="priority" required></li>
可以同时继承多个父类:
>>> 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_ul()) <li>First name: <input type="text" name="first_name" required></li> <li>Last name: <input type="text" name="last_name" required></li> <li>Instrument: <input type="text" name="instrument" required></li> <li>Haircut type: <input type="text" name="haircut_type" required></li>
在子类中,将某个父类中的字段设置为None,可以声明性地删除这个字段,也就是说子类不要父类的这个字段:
>>> from django import forms >>> class ParentForm(forms.Form): ... name = forms.CharField() ... age = forms.IntegerField() >>> class ChildForm(ParentForm): ... name = None >>> list(ChildForm().fields) ['age']
打卡
三、检查表单数据是否被修改,f.has_changed()一条中,应该是:如果request.POST与initial中的数据有区别,则返回True,否则返回False,文中写反了。
已经修改
正在学习中!受益匪浅!