Python / 26 Django 表单使用-Field 使用

Django 表单使用-Field 使用

上一节我们主要介绍了 Django 中 Form 类的相关属性和方法,本小节中会继续介绍 Field 类的相关属性与方法,最后还有如何实现自定义的 Field。

1. Field 相关基础

1.1 Field 的 clean() 方法

通过上面两个例子演示,我们对 Django 中的表单应该有了初步的了解。对于 Form 类,最重要的就是定义它的字段(Field),且每个字段都有自定义验证逻辑以及其他一些钩子(hooks)。现在介绍以下 Field 类的一个重要方法: clean()。这个方法传递一个参数,然后要么抛出异常,要么直接返回对应的值。我们现在 Django 的 shell 模式下来试一下这个方法:

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django import forms
>>> f = forms.EmailField()
>>> f.clean('foo@example.com')
'foo@example.com'
>>> f.clean('invalid email address')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/fields.py", line 150, in clean
    self.run_validators(value)
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/fields.py", line 141, in run_validators
    raise ValidationError(errors)
django.core.exceptions.ValidationError: ['Enter a valid email address.']

可以看到,当我们输入了异常的邮件值的时候,调用 clean() 方法会抛出异常。我们可以看看 Django 的代码这个 clean()方法做了哪些工作:

# 源码位置:django/forms/fields.py
# ...

class Field:
    # ...
    
    def to_python(self, value):
        return value

    def validate(self, value):
        if value in self.empty_values and self.required:
            raise ValidationError(self.error_messages['required'], code='required')

    def run_validators(self, value):
        if value in self.empty_values:
            return
        errors = []
        for v in self.validators:
            try:
                v(value)
            except ValidationError as e:
                if hasattr(e, 'code') and e.code in self.error_messages:
                    e.message = self.error_messages[e.code]
                errors.extend(e.error_list)
        if errors:
            raise ValidationError(errors)

    def clean(self, value):
        """
        Validate the given value and return its "cleaned" value as an
        appropriate Python object. Raise ValidationError for any errors.
        """
        value = self.to_python(value)
        self.validate(value)
        self.run_validators(value)
        return value
    
    # ...

源码的逻辑很清晰,clean() 方法就是对输入的数据进行校验,当输入不符合该 Field 的要求时抛出异常,否则返回 value 值。接下来,继续介绍 Field 的一些核心参数。

1.2 Field 核心属性

前面的实验中我们用到的 django 的中的 CharField,并在初始化该 Field 示例时传递了一些参数,如 label、min_length 等。接下来,我们首先看看 Field 对象的一些核心属性:

Field.required:默认情况下,每个 Field 类会假定该 Field 的值时必须提供的,如果我们传递的时空值,无论是 None 还是空字符串(""),在调用 Field 的 clean() 方法时就会抛出异常ValidationError

>>> from django import forms
>>> f = forms.CharField()
>>> f.clean('foo')
'foo'
>>> f.clean('')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/fields.py", line 149, in clean
    self.validate(value)
  File "/root/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/forms/fields.py", line 127, in validate
    raise ValidationError(self.error_messages['required'], code='required')
django.core.exceptions.ValidationError: ['This field is required.']
>>> f = forms.CharField(required=False)
>>> f.clean('')
''

Field.label:是给这个 field 一个标签名;

>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(label='名称')
...     url = forms.URLField(label='网站地址', required=False)
...     comment = forms.CharField()
... 
>>> f = CommentForm()
>>> print(f)
<tr><th><label for="id_name">名称:</label></th><td><input type="text" name="name" required id="id_name"></td></tr>
<tr><th><label for="id_url">网站地址:</label></th><td><input type="url" name="url" id="id_url"></td></tr>
<tr><th><label for="id_comment">Comment:</label></th><td><input type="text" name="comment" required id="id_comment"></td></tr>

可以看到,这个 label 参数最后在会变成 HTML 中的 <label> 元素。

Field.label_suffix:这个属性值是在 label 属性值后面统一加一个后缀。

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(label='Your name')
...     url = forms.URLField(label='网站地址', label_suffix='?', required=False)
...     comment = forms.CharField()
... 
>>> c
>>> print(f.as_p())
<p><label for="id_name">Your name#</label> <input type="text" name="name" required id="id_name"></p>
<p><label for="id_url">网站地址?</label> <input type="url" name="url" id="id_url"></p>
<p><label for="id_comment">Comment#</label> <input type="text" name="comment" required id="id_comment"></p>
>>>

注意:Form 也有 label_suffix 属性,会让所有字段都加上这个属性值。但是如果字段自身定义了这个属性值,则会覆盖全局的 label_suffix,正如上述测试的结果。

Field.initial:指定字段的初始值;

Field.widget:这个就是指定该 Field 转成 HTML 的标签,我们

class LoginForm(forms.Form):
    name = forms.CharField(
        label="账号",
        min_length=4,
        required=True,
        error_messages={'required': '账号不能为空', "min_length": "账号名最短4位"},
        widget=forms.TextInput(attrs={'class': "input-text",
                                      'placeholder': '请输入登录账号'})
    )

    # ...

Field.help_text:给 Field 添加一个描述;

Field.error_messages:该 error_messages 参数可以覆盖由 Form 中对应字段引发错误的默认提示;

Field.validators:可以通过该参数自定义字段数据校验;下面看我们上一讲的实验2中自定义了一个简单的密码校验,如下:

def password_validate(value):
    """
    密码校验器
    """
    pattern = re.compile(r'^(?=.*[0-9].*)(?=.*[A-Z].*)(?=.*[a-z].*).{6,20}$')
    if not pattern.match(value):
        raise ValidationError('密码需要包含大写、小写和数字')
        
class LoginForm(forms.Form):
    # ...
    password = forms.CharField(
        label="密码",
        validators=[password_validate, ],
        min_length=6,
        max_length=20,
        required=True,
        error_messages={'required': '密码不能为空', "invalid": "密码需要包含大写、小写和数字", "min_length": "密码最短8位", "max_length": "密码最长20位"},
        widget=forms.TextInput(attrs={'class': "input-text",'placeholder': '请输入密码', 'type': 'password'}),
        help_text='密码必须包含大写、小写以及数字',
    )
    # ...

Field.disabled:如果为 True,那么该字段将禁止输入,会在对应生成的 input 标签中加上 disabled 属性

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django import forms
>>> class CommentForm(forms.Form):
...     name = forms.CharField(label='Your name', disabled=True)
... 
>>> f = CommentForm()
>>> print(f)
<tr><th><label for="id_name">Your name:</label></th><td><input type="text" name="name" required disabled id="id_name"></td></tr>

Field.widget:这个 widget 的中文翻译是 “小器物,小装置”,每种 Field 都有一个默认的 widget 属性值,Django 会根据它来将 Field 渲染成对应的 HTML 代码。

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.forms.fields import BooleanField,CharField,ChoiceField
>>> BooleanField.widget
<class 'django.forms.widgets.CheckboxInput'>
>>> CharField.widget
<class 'django.forms.widgets.TextInput'>
>>> ChoiceField.widget
<class 'django.forms.widgets.Select'>

2. Django 中的内置 Field

BooleanField:之前演示过,它会被渲染成前端的 checkbox 组件。从源码上看它似乎没有额外特殊的属性。主要就是继承了 Field 类,然后重写了 to_python() 等方法

class BooleanField(Field):
    widget = CheckboxInput

    def to_python(self, value):
        """Return a Python boolean object."""
        # Explicitly check for the string 'False', which is what a hidden field
        # will submit for False. Also check for '0', since this is what
        # RadioSelect will provide. Because bool("True") == bool('1') == True,
        # we don't need to handle that explicitly.
        if isinstance(value, str) and value.lower() in ('false', '0'):
            value = False
        else:
            value = bool(value)
        return super().to_python(value)

    def validate(self, value):
        if not value and self.required:
            raise ValidationError(self.error_messages['required'], code='required')

    def has_changed(self, initial, data):
        if self.disabled:
            return False
        # Sometimes data or initial may be a string equivalent of a boolean
        # so we should run it through to_python first to get a boolean value
        return self.to_python(initial) != self.to_python(data)

CharField:用的最多的,会被渲染成输入框,我们可以通过 widget 属性值控制输入框样式等。这在前面的登录表单例子中也是演示过的。

 name = forms.CharField(
        label="账号",
        min_length=4,
        required=True,
        error_messages={'required': '账号不能为空', "min_length": "账号名最短4位"},
        widget=forms.TextInput(attrs={'class': "input-text",
                                      'placeholder': '请输入登录账号'})
    )
class CharField(Field):
    def __init__(self, *, max_length=None, min_length=None, strip=True, empty_value='', **kwargs):
        self.max_length = max_length
        self.min_length = min_length
        self.strip = strip
        self.empty_value = empty_value
        super().__init__(**kwargs)
        if min_length is not None:
            self.validators.append(validators.MinLengthValidator(int(min_length)))
        if max_length is not None:
            self.validators.append(validators.MaxLengthValidator(int(max_length)))
        self.validators.append(validators.ProhibitNullCharactersValidator())

    def to_python(self, value):
        """Return a string."""
        if value not in self.empty_values:
            value = str(value)
            # 是否去掉首尾空格
            if self.strip:
                value = value.strip()
        if value in self.empty_values:
            return self.empty_value
        return value

    def widget_attrs(self, widget):
        attrs = super().widget_attrs(widget)
        if self.max_length is not None and not widget.is_hidden:
            # The HTML attribute is maxlength, not max_length.
            attrs['maxlength'] = str(self.max_length)
        if self.min_length is not None and not widget.is_hidden:
            # The HTML attribute is minlength, not min_length.
            attrs['minlength'] = str(self.min_length)
        return attrs

除了 Field 属性外,CharField 还有 max_lengthmin_length 等属性。这些也都会反映在渲染的 input 元素上,同时校验器也会添加该属性的校验:

if min_length is not None:
    self.validators.append(validators.MinLengthValidator(int(min_length)))
if max_length is not None:
    self.validators.append(validators.MaxLengthValidator(int(max_length)))

CharField 还是很多 Field 类的父类,比如 RegexFieldEmailField 等。

ChoiceField:前面我们在 widget 属性的小实验中看到了 ChoiceField 对应的 widget 属性值是 Select 类,也即对应 select 元素。我们继续使用前面的登录表单来演示下这个 ChoiceField 类。我们除了添加 Field 之外,也需要添加下前端代码,如下所示。

{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" />
{% if not success %}
<form action="/hello/test_form_view2/" method="POST">
{% csrf_token %}
<div><span>{{ form.name.label }}:</span>{{ form.name }}
<div><span>{{ form.password.label }}:</span>{{ form.password }}
<!------- 新增login_type字段的HTML ------------->
<div><span>{{ form.login_type.label }}:</span>{{ form.login_type }}
<!-------------------------------------------->
<div>
{{ form.save_login }}{{ form.save_login.label }}
</div>
<div><input class="input-text input-red" type="submit" value="登录" style="width: 214px"/></div>
{% if err_msg %}
<div><label class="color-red">{{ err_msg }}</label</div>
{% endif %}
</form>
{% else %}
<p>登录成功</p>
{% endif %}
login_type = forms.ChoiceField(
        label="账号类型",
        required=True,
        initial=1,
        choices=((0, '普通用户'), (1, '管理员'), (2, '其他')),
        error_messages={'required': '必选类型' },
        widget=forms.Select(attrs={'class': "input-text"}),
    )

效果图如下所示。可以看到,这里 initial 属性值表示最开始选中那个选项,而 choices 属性值是一个元组,表示多选框的显示 name 值和实际 value 值。

图片描述

DateField:默认的小部件是 DateInput。它有一个比较重要的属性:input_formats,用于将字符串转换为有效datetime.date对象的格式列表。如果没有提供 input_formats 参数,则默认的格式为:

['%Y-%m-%d',      # '2006-10-25'
 '%m/%d/%Y',      # '10/25/2006'
 '%m/%d/%y']      # '10/25/06'
class DateField(BaseTemporalField):
    widget = DateInput
    input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS')
    default_error_messages = {
        'invalid': _('Enter a valid date.'),
    }

    def to_python(self, value):
        """
        Validate that the input can be converted to a date. Return a Python
        datetime.date object.
        """
        if value in self.empty_values:
            return None
        if isinstance(value, datetime.datetime):
            return value.date()
        if isinstance(value, datetime.date):
            return value
        return super().to_python(value)

    def strptime(self, value, format):
        return datetime.datetime.strptime(value, format).date()

DateTimeField:默认的小部件是 DateTimeInput,这里会校验对应的值是否是datetime.datetimedatetime.date类型,或者按照 input_formats 参数格式化的字符串。同样,如果没有提供 input_formats 参数,则默认的格式为:

['%Y-%m-%d %H:%M:%S',    # '2006-10-25 14:30:59'
 '%Y-%m-%d %H:%M',       # '2006-10-25 14:30'
 '%Y-%m-%d',             # '2006-10-25'
 '%m/%d/%Y %H:%M:%S',    # '10/25/2006 14:30:59'
 '%m/%d/%Y %H:%M',       # '10/25/2006 14:30'
 '%m/%d/%Y',             # '10/25/2006'
 '%m/%d/%y %H:%M:%S',    # '10/25/06 14:30:59'
 '%m/%d/%y %H:%M',       # '10/25/06 14:30'
 '%m/%d/%y']             # '10/25/06'
class DateTimeField(BaseTemporalField):
    widget = DateTimeInput
    input_formats = formats.get_format_lazy('DATETIME_INPUT_FORMATS')
    default_error_messages = {
        'invalid': _('Enter a valid date/time.'),
    }

    def prepare_value(self, value):
        if isinstance(value, datetime.datetime):
            value = to_current_timezone(value)
        return value

    def to_python(self, value):
        """
        Validate that the input can be converted to a datetime. Return a
        Python datetime.datetime object.
        """
        if value in self.empty_values:
            return None
        if isinstance(value, datetime.datetime):
            return from_current_timezone(value)
        if isinstance(value, datetime.date):
            result = datetime.datetime(value.year, value.month, value.day)
            return from_current_timezone(result)
        result = super().to_python(value)
        return from_current_timezone(result)

    def strptime(self, value, format):
        return datetime.datetime.strptime(value, format)

这些类的定义都是比较简单的,都是基于 Field 类,有的基于 CharField 类等。后面我们会重点分析 Field 类。

EmailFieldEmailField 直接继承 CharField,它和 CharField 的一个主要区别就是多加了一个默认的校验器,主要校验输入的值是否是邮箱格式。其实现的代码如下:

class EmailField(CharField):
    widget = EmailInput
    default_validators = [validators.validate_email]

    def __init__(self, **kwargs):
        super().__init__(strip=True, **kwargs)

IntegerField:对应的小部件是 NumberInput,输入整数字符串。它可以输入 min_Valuemax_value 等参数用于控制输入值的范围。其源码如下,和 CharFiled 类的代码比较类似。

class IntegerField(Field):
    widget = NumberInput
    default_error_messages = {
        'invalid': _('Enter a whole number.'),
    }
    re_decimal = re.compile(r'\.0*\s*$')

    def __init__(self, *, max_value=None, min_value=None, **kwargs):
        self.max_value, self.min_value = max_value, min_value
        if kwargs.get('localize') and self.widget == NumberInput:
            # Localized number input is not well supported on most browsers
            kwargs.setdefault('widget', super().widget)
        super().__init__(**kwargs)

        if max_value is not None:
            self.validators.append(validators.MaxValueValidator(max_value))
        if min_value is not None:
            self.validators.append(validators.MinValueValidator(min_value))

    def to_python(self, value):
        """
        Validate that int() can be called on the input. Return the result
        of int() or None for empty values.
        """
        value = super().to_python(value)
        if value in self.empty_values:
            return None
        if self.localize:
            value = formats.sanitize_separators(value)
        # Strip trailing decimal and zeros.
        try:
            value = int(self.re_decimal.sub('', str(value)))
        except (ValueError, TypeError):
            raise ValidationError(self.error_messages['invalid'], code='invalid')
        return value

    def widget_attrs(self, widget):
        attrs = super().widget_attrs(widget)
        if isinstance(widget, NumberInput):
            if self.min_value is not None:
                attrs['min'] = self.min_value
            if self.max_value is not None:
                attrs['max'] = self.max_value
        return attrs

对于 IntegerField 字段输入的值,看 to_python() 方法,首先对于 20.0 这样的形式会先去掉后面的 .0,然后用 int() 方法强转,如果发生异常,那就表明该字段对应的值不是整数,然后可以抛出异常。

DecimalField:它继承自 IntegerField,用于输入浮点数。它有如下几个重要参数:

  • max_value: 最大值
  • min_value: 最小值
  • max_digits: 总长度
  • decimal_places: 小数位数

来看看它的定义的代码,如下:

class DecimalField(IntegerField):
    default_error_messages = {
        'invalid': _('Enter a number.'),
    }

    def __init__(self, *, max_value=None, min_value=None, max_digits=None, decimal_places=None, **kwargs):
        self.max_digits, self.decimal_places = max_digits, decimal_places
        super().__init__(max_value=max_value, min_value=min_value, **kwargs)
        self.validators.append(validators.DecimalValidator(max_digits, decimal_places))

    def to_python(self, value):
        """
        Validate that the input is a decimal number. Return a Decimal
        instance or None for empty values. Ensure that there are no more
        than max_digits in the number and no more than decimal_places digits
        after the decimal point.
        """
        if value in self.empty_values:
            return None
        if self.localize:
            value = formats.sanitize_separators(value)
        value = str(value).strip()
        try:
            # 使用Decimal()方法转换类型
            value = Decimal(value)
        except DecimalException:
            raise ValidationError(self.error_messages['invalid'], code='invalid')
        return value

    def validate(self, value):
        super().validate(value)
        if value in self.empty_values:
            return
        if not value.is_finite():
            raise ValidationError(self.error_messages['invalid'], code='invalid')

    def widget_attrs(self, widget):
        attrs = super().widget_attrs(widget)
        if isinstance(widget, NumberInput) and 'step' not in widget.attrs:
            if self.decimal_places is not None:
                # Use exponential notation for small values since they might
                # be parsed as 0 otherwise. ref #20765
                step = str(Decimal(1).scaleb(-self.decimal_places)).lower()
            else:
                step = 'any'
            attrs.setdefault('step', step)
        return attrs

可以看到在 to_python() 方法中,最后对该字段输入的值使用 Decimal() 方法进行类型转换 。

FloatField:用于渲染成一个只允许输入浮点数的输入框。它同样继承自 IntegerField,因此它对应的小部件也是 NumberInput

class FloatField(IntegerField):
    default_error_messages = {
        'invalid': _('Enter a number.'),
    }

    def to_python(self, value):
        """
        Validate that float() can be called on the input. Return the result
        of float() or None for empty values.
        """
        value = super(IntegerField, self).to_python(value)
        if value in self.empty_values:
            return None
        if self.localize:
            value = formats.sanitize_separators(value)
        try:
            value = float(value)
        except (ValueError, TypeError):
            raise ValidationError(self.error_messages['invalid'], code='invalid')
        return value

    def validate(self, value):
        super().validate(value)
        if value in self.empty_values:
            return
        if not math.isfinite(value):
            raise ValidationError(self.error_messages['invalid'], code='invalid')

    def widget_attrs(self, widget):
        attrs = super().widget_attrs(widget)
        if isinstance(widget, NumberInput) and 'step' not in widget.attrs:
            attrs.setdefault('step', 'any')
        return attrs

其余的 Field 类如 ImageFieldRegexField 就不一一描述了,具体可以参考官方文档以及相应的源码进行学习。

3. 上一节的思考题解答

记得上一节留的那个思考题吗?我们来认真解答下这个代码。其实那个翻译 Field 为 HTML 的核心代码就只有一句:bf = self[name]。我们来详细分析这一行代码的背后,做了哪些事情。

def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
    # 遍历form中的所有field,生成对应的html文本
    for name, field in self.fields.items():
        # ...
        
        # 最核心的一句
        bf = self[name]
        
        if bf.is_hidden:
            # ...
            hidden_fields.append(str(bf))
        else:
            output.append(normal_row % {
                'errors': bf_errors,
                'label': label,
                'field': bf,
                'help_text': help_text,
                'html_class_attr': html_class_attr,
                'css_classes': css_classes,
                'field_name': bf.html_name,
            })
        
    # ...
    
    return mark_safe('\n'.join(output))          

看到 bf = self[name] 这一句,我们第一反应应该时找 Form 类中定义的 __getitem__() 这个魔法函数,可以看到它的源码如下:

# 源码位置:django/forms/forms.py
# ...  

@html_safe
class BaseForm:
    
    # ...
    
    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(f for f in 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]

从这里我们可以知道,bf = self[name] 的执行结果是由下面两条语句得到:

# 得到对应field
field = self.fields[name]
# 返回的结果
field.get_bound_field(self, name)

有了这两个语句,我们可以在 Django 的 shell 进行相关的测试了,具体操作如下:

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django import forms
>>> from hello_app.views import LoginForm
>>> login = LoginForm({'name': 'test1234', 'password': 'SPYinx1234', 'save_login': False})
>>> bf = login['name']
>>> bf
<django.forms.boundfield.BoundField object at 0x7fd7ad9232e0>
>>> str(bf)
'<input type="text" name="name" value="test1234" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name">'
>>> bf = login['password']
>>> str(bf)
'<input type="password" name="password" value="SPYinx1234" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password">'

# 测试后面两条语句
>>> field = login.fields['name']
>>> bf = field.get_bound_field(login, 'name')
>>> print(bf)
<input type="text" name="name" value="test1234" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name">

最后想再继续追下去,弄清楚到底如何翻译成 HTML 代码的,就要继续学习 django/forms/boundfield.py 中的 BoundField 类了。这个就做为课后练习了。

4. 小结

本小节我们介绍了 Django 中 Field 类的相关参数及其含义。接下来我们详细介绍了 Django 为我们准备的内置 Field 并对部分 Field 演示了其前端效果。最后我们还对上一节留下的一个思考题进行了解答。Django Form 表单更多的学习需要多多去官方上参考相关的文档。