1. 程式人生 > Django入門教學 >26 Django 表單使用-Field 使用

26 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('[email protected]') '[email protected]' >>> 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 表單更多的學習需要多多去官方上參考相關的文件。