深入理解Django的Form表單

2022-01-02 SRE運維充電站
一、深入理解Django的Form表單1.1、話不多說,先看示例1.1.1、創建Form類
from django.forms import Form
from django.forms import widgets
from django.forms import fields
 
class MyForm(Form):
    user = fields.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'})
    )
 
    gender = fields.ChoiceField(
        choices=((1, '男'), (2, '女'),),
        initial=2,
        widget=widgets.RadioSelect
    )
 
    city = fields.CharField(
        initial=2,
        widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    )
 
    pwd = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

1.1.2、創建View視圖函數
from django.shortcuts import render, redirect
from .forms import MyForm
 
 
def index(request):
    if request.method == "GET":
        obj = MyForm()
        return render(request, 'index.html', {'form': obj})
    elif request.method == "POST":
        obj = MyForm(request.POST, request.FILES)
        if obj.is_valid():
            values = obj.clean()
            print(values)
        else:
            errors = obj.errors
            print(errors)
        return render(request, 'index.html', {'form': obj})
    else:
        return redirect('http://top.toptops.top')

1.1.3、模板生成HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form action="/" method="POST" enctype="multipart/form-data">
      <p>{{ form.user }} {{ form.user.errors }}</p>
      <p>{{ form.gender }} {{ form.gender.errors }}</p>
      <p>{{ form.city }} {{ form.city.errors }}</p>
      <p>{{ form.pwd }} {{ form.pwd.errors }}</p>
      <input type="submit"/>
  </form>
</body>

至此很容易就完成了登錄功能,並且也使用到了Form表單的作用;1.2、forms欄位和插件

在上述實例中,使用Form需要創建一個Form相關的類,並且在類中寫入相關的forms欄位,這裡涉及到Field類,是所有forms欄位最根本的基類,它位於django.forms.Field下;

創建Form類時,主要涉及到 【欄位】 和 【插件】,欄位用於對用戶請求數據的驗證,插件用於自動生成HTML;

1.2.1、欄位的基本參數在這個類中的__init__方法中有很多參數,這也就是欄位參數;
  required=True,               是否允許為空
  widget=None,                 HTML插件
  label=None,                  用於生成Label標籤或顯示內容
  initial=None,                初始值
  help_text='',                幫助信息(在標籤旁邊顯示)
  error_messages=None,         錯誤信息 {'required': '不能為空', 'invalid': '格式錯誤'}
  show_hidden_initial=False,   是否在當前插件後面再加一個隱藏的且具有默認值的插件(可用於檢驗兩次輸入是否一直)
  validators=[],               自定義驗證規則
  localize=False,              是否支持本地化
  disabled=False,              是否可以編輯
  label_suffix=None            Label內容後綴

1.2.2、欄位的基本方法1.2.2.1、clean
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

每個Field實例都有一個clean()方法,它接受一個參數並引發django.forms.ValidationError 異常或返回clean值;
$ python manage.py shell
In [1]: from django import forms
In [2]: f = forms.EmailField()
In [3]: f.clean('1284808408@.com')
Out[3]: '1284808408@qq.com'
...
In [4]: f.clean('1284808408')
ValidationError: ['Enter a valid email address.']

1.2.2.2、bound_data它有兩個參數,一個是request.POST提交的data,另一個是initial參數初始化的數據,這個方法返回的就是request.POST提交的那個欄位值;
def bound_data(self, data, initial):
    """
    Return the value that should be shown for this field on render of a
    bound form, given the submitted POST data for the field and the initial
    data, if any.

    For most fields, this will simply be data; FileFields need to handle it
    a bit differently.
    """
    if self.disabled:
        return initial
    return data

1.2.2.3、has_changed它有兩個參數,一個是request.POST提交的data,另一個是initial參數初始化的數據,這個方法返回的是布爾值,如果提交的data與初始化數據不一樣的話就返回True;
def has_changed(self, initial, data):
    """Return True if data differs from initial."""
    # Always return False if the field is disabled since self.bound_data
    # always uses the initial value in this case.
    if self.disabled:
        return False
    try:
        data = self.to_python(data)
        if hasattr(self, '_coerce'):
            return self._coerce(data) != self._coerce(initial)
    except ValidationError:
        return True
    # For purposes of seeing whether something has changed, None is
    # the same as an empty string, if the data or initial value we get
    # is None, replace it with ''.
    initial_value = initial if initial is not None else ''
    data_value = data if data is not None else ''
    return initial_value != data_value

1.2.2.4、get_bound_field它有兩個參數,一個是form實例,另一個是創建的Form類欄位的欄位名稱field_name,而它的返回值是BoundField對象;
def get_bound_field(self, form, field_name):
    """
    Return a BoundField instance that will be used when accessing the form
    field in a template.
    """
    return BoundField(form, self, field_name)

1.2.3、欄位的基本方法
Field
    required=True,               是否允許為空
    widget=None,                 HTML插件
    label=None,                  用於生成Label標籤或顯示內容
    initial=None,                初始值
    help_text='',                幫助信息(在標籤旁邊顯示)
    error_messages=None,         錯誤信息 {'required': '不能為空', 'invalid': '格式錯誤'}
    show_hidden_initial=False,   是否在當前插件後面再加一個隱藏的且具有默認值的插件(可用於檢驗兩次輸入是否一直)
    validators=[],               自定義驗證規則
    localize=False,              是否支持本地化
    disabled=False,              是否可以編輯
    label_suffix=None            Label內容後綴
 
 
CharField(Field)
    max_length=None,             最大長度
    min_length=None,             最小長度
    strip=True                   是否移除用戶輸入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             總長度
    decimal_places=None,         小數位長度
 
BaseTemporalField(Field)
    input_formats=None          時間格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            時間間隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定製正則表達式
    max_length=None,            最大長度
    min_length=None,            最小長度
    error_message=None,         忽略,錯誤信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允許空文件
 
ImageField(FileField)      
    ...
    註:需要PIL模塊,pip3 install Pillow
    以上兩個字典使用時,需要注意兩點:
        - form表單中 enctype="multipart/form-data"
        - view函數中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                選項,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默認select插件
    label=None,                Label內容
    initial=None,              初始值
    help_text='',              幫助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查詢資料庫中的數據
    empty_label="----",   # 默認空顯示內容
    to_field_name=None,        # HTML中value的值對應的欄位
    limit_choices_to=None      # ModelForm中對queryset二次篩選
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   對選中的值進行一次轉換
    empty_value= ''            空值的默認值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   對選中的每一個值進行一次轉換
    empty_value= ''            空值的默認值
 
ComboField(Field)
    fields=()                  使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象類,子類中可以實現聚合多個字典去匹配一個值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件選項,目錄下文件顯示在頁面中
    path,                      文件夾路徑
    match=None,                正則匹配
    recursive=False,           遞歸下面的文件夾
    allow_files=True,          允許文件
    allow_folders=False,       允許文件夾
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1時候,可解析為192.0.2.1, PS:protocol必須為both才能啟用
 
SlugField(CharField)           數字,字母,下劃線,減號(連字符)
    ...
 
UUIDField(CharField)           uuid類型
    ...

註:UUID是根據MAC以及當前時間等創建的不重複的隨機字符串
>>> import uuid
    # make a UUID based on the host ID and current time
    >>> uuid.uuid1()    # doctest: +SKIP
    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

    # make a UUID using an MD5 hash of a namespace UUID and a name
    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')

    # make a random UUID
    >>> uuid.uuid4()    # doctest: +SKIP
    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')

    # make a UUID using a SHA-1 hash of a namespace UUID and a name
    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')

    # make a UUID from a string of hex digits (braces and hyphens ignored)
    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')

    # convert a UUID to a string of hex digits in standard form
    >>> str(x)
    '00010203-0405-0607-0809-0a0b0c0d0e0f'

    # get the raw 16 bytes of the UUID
    >>> x.bytes
    b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'

    # make a UUID from a 16-byte string
    >>> uuid.UUID(bytes=x.bytes)
    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')

from django.forms import fields as ffields
from django.forms import widgets as fwidgets

group_name = ffields.CharField(
max_length=32,
required=True,
widget=fwidgets.TextInput(attrs={'class': 'form-control'}),
error_messages={
'required': '名稱不能為空',
})

1.2.4、常用插件每一個欄位都有一個默認的插件,這時為了在頁面生成html標籤時,生成該欄位對應的html標籤;
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

在CharField中,它繼承了Field,所以它的默認Widget是Field中TextInput,它在HTML 中生成一個<input type="text">
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

1.2.5、選擇插件(常用選擇插件)
# 單radio,值為字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
 
# 單radio,值為字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )
 
# 單select,值為字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
 
# 單select,值為字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.Select
# )
 
# 多選select,值為列表
# user = fields.MultipleChoiceField(
#     choices=((1,'上海'),(2,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )
 
 
# 單checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )
 
 
# 多選checkbox,值為列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )

在使用選擇標籤時,需要注意choices的選項可以從資料庫中獲取,但是由於是靜態欄位 獲取的值無法實時更新,那麼需要自定義構造方法從而達到此目的。
  city = fields.TypedChoiceField(
  coerce=lambda x: int(x),
  choices=[(1,'上海',),(2,'北京'),(3,'望京'),],
  initial=2
  )

# 使用django提供的ModelChoiceField和ModelMultipleChoiceField欄位來實現

from app01.models import *
from django.forms.models import ModelChoiceField  #方法二實時更新
class classesForm(forms.Form):
    #方法二,切記model類中需要有__str()__方法
    class_select_model =ModelChoiceField(
        queryset=classesAll.objects.all(),
        to_field_name = 'id',#指的是value值
    )
  #方法一
    class_select = fields.ChoiceField(
        # choices=classesAll.objects.values_list('id','name'),可以省略
        widget=widgets.Select()
    )
    def __init__(self,*args,**kwargs):
        super(classesForm,self).__init__(*args,**kwargs)
        self.fields['class_select'].widget.choices=classesAll.objects.values_list('id','name')

class ProcessHours(models.Model):
    """
    工時庫
    """
    create_user = models.ForeignKey(UserInfo, verbose_name='生產管理者', null=True, blank=True)
    process_hours_group = models.ForeignKey(ProcessGroup, verbose_name='組別名稱',null=True,blank=True)
    process_hours_type = models.ForeignKey(ProcessPrice,verbose_name='工序類別',null=True,blank=True)
    process_hours_name = models.CharField(verbose_name='工序名稱',null=True, max_length=128,blank=True)
    process_hours_all = models.DecimalField(verbose_name='工時',null=True, blank=True,decimal_places=2,max_digits=10)

    def __str__(self):
        return '%s-%s' % (self.process_hours_group, self.process_hours_name)

from django.forms import widgets as fwidgets
from django.forms import Form
from django.forms import fields as ffields

class ProcessHoursForm(Form):
    create_user_id = ffields.ChoiceField(
        widget=fwidgets.Select(attrs={'class': 'form-control'}),
        choices=[],

    )
    process_hours_group_id = ffields.ChoiceField(
        widget=fwidgets.Select(attrs={'class': 'form-control'}),
        choices=[],
    )
    process_hours_type_id = ffields.ChoiceField(
        widget = fwidgets.Select(attrs={'class': 'form-control'}),
        choices=[],
    )
    def __init__(self, *args, **kwargs):
        super(ProcessHoursForm, self).__init__(*args, **kwargs)
        self.fields['create_user_id'].choices = models.UserInfo.objects.values_list('id','nickname')
        self.fields['process_hours_group_id'].choices = models.ProcessGroup.objects.values_list('id','group_name')
        self.fields['process_hours_type_id'].choices = models.ProcessPrice.objects.values_list('id','process_type')

1.3、表單和欄位驗證1.3.1、正則驗證
from django.forms import Form
from django.forms import fields
from django.core.validators import RegexValidator
 
class TestForm(Form):

   v1= fields.CharField(
        disabled=True
    )
   v2= fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字')],
    )

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
    )

import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
 
# 自定義驗證規則
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手機號碼格式錯誤')
 
 
class PublishForm(Form):
 
 
    title = fields.CharField(max_length=20,
                            min_length=5,
                            error_messages={'required': '標題不能為空',
                                            'min_length': '標題最少為5個字符',
                                            'max_length': '標題最多為20個字符'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': '標題5-20個字符'}))
 
 
    # 使用自定義驗證規則
    phone = fields.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手機不能為空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                          'placeholder': u'手機號碼'}))
 
    email = fields.EmailField(required=False,
                            error_messages={'required': u'郵箱不能為空','invalid': u'郵箱格式錯誤'},
                            widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))

1.3.2、對每個欄位定義鉤子函數
from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
 
class FInfo(forms.Form):
    username = fields.CharField(max_length=5,
                                validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
    email = fields.EmailField()

    def clean_username(self):
        """
        Form中欄位中定義的格式匹配完之後,執行此方法進行驗證
        :return:
        """
        value = self.cleaned_data['username']
        if "666" in value:
            raise ValidationError('666已經被玩爛了...', 'invalid')
        return value

1.4、深入Form源碼,探查高級方法1.4.1、Form實例化可以根據以下源碼分析無論是GET還是POST請求都需要進行Form類的實例化時;
class BaseForm:
    """
    The main implementation of all the Form logic. Note that this class is
    different than Form. See the comments by the Form class for more info. Any
    improvements to the form API should be made to this class, not to the Form
    class.
    """
    default_renderer = None
    field_order = None
    prefix = None
    use_required_attribute = True

    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 = {} if data is None else data
        self.files = {} 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.

        # The base_fields class attribute is the *class-wide* definition of
        # fields. Because a particular *instance* of the class might want to
        # alter self.fields, we create self.fields here by copying base_fields.
        # Instances should always modify self.fields; they should not modify
        # self.base_fields.
        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

        # 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

1.4.2、is_valid
  def is_valid(self):
        """Return True if the form has no errors, or False otherwise."""
        return self.is_bound and not self.errors

源碼說如果這個form沒有錯誤就返回True,否則就是False;
self.is_bound = data #data是傳入的request.POST

@property
    def errors(self):
        """Return an ErrorDict for the data provided for the form."""
        if self._errors is None:
            self.full_clean()
        return self._error

---
---

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()

初始化 self.cleaned_data = {}執行每一個欄位的自定義驗證鉤子函數  self._clean_fields()執行全局驗證的鉤子函數  self._clean_form()
def _post_clean(self):
    """
    An internal hook for performing additional cleaning after form cleaning
    is complete. Used for model validation in model forms.
    """
    pass

1.4.3、self._clean_fields()
def _clean_fields(self):
    for name, field in self.fields.items():
        # value_from_datadict() gets the data from the data dictionaries.
        # Each widget type knows how to retrieve its own data, because some
        # widgets split data over several HTML fields.
        if field.disabled:
            value = self.get_initial_for_field(field, name)
        else:
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
        try:
            if isinstance(field, FileField):
                initial = self.get_initial_for_field(field, name)
                value = field.clean(value, 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
                self.cleaned_data[name] = value
        except ValidationError as e:
            self.add_error(name, e)

1.4.4、self._clean_form()
def _clean_form(self):
    try:
        #返回cleaned_data
        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

1.4.5、屬性、方法從源碼中就可以看出,驗證完畢後cleaned_data就有數據了,此時獲取驗證完畢的數據可以使用;
#獲取所有驗證成功的數據
form.cleaned_data
form.cleaned_data["username"]

form.errors
#每個欄位錯誤信息
form.errors["username"]

1.4.6、頁面渲染
#渲染username欄位錯誤
{{ form.Username.errors }}

#渲染username欄位標籤
 {{ form.Username }}

二、深入擴展Form表單的BoundField類2.1、BoundField類BoundField類是什麼呢?看個例子就知道了。
...

    form = LoginForm(data=request.POST)

    for field in form:
        print(type(field),field)

...

<class 'django.forms.boundfield.BoundField'> <input type="text" name="username" value="admin" id="id_username" maxlength="32" required />
<class 'django.forms.boundfield.BoundField'> <input type="text" name="password" value="123" id="id_password" maxlength="32" required />

這個就是form對象中的欄位將自定義Form類中的欄位與form實例進行綁定聯繫,具體方法可以參考下面源碼。
@html_safe
class BoundField:
    "A Field plus data"
    def __init__(self, form, field, name):
        self.form = form
        self.field = field
        self.name = name
        self.html_name = form.add_prefix(name)
        self.html_initial_name = form.add_initial_prefix(name)
        self.html_initial_id = form.add_initial_prefix(self.auto_id)
        if self.field.label is None:
            self.label = pretty_name(name)
        else:
            self.label = self.field.label
        self.help_text = field.help_text or ''

    def __str__(self):
        """Render this field as an HTML widget."""
        if self.field.show_hidden_initial:
            return self.as_widget() + self.as_hidden(only_initial=True)
        return self.as_widget()

    @cached_property
    def subwidgets(self):
        """
        Most widgets yield a single subwidget, but others like RadioSelect and
        CheckboxSelectMultiple produce one subwidget for each choice.

        This property is cached so that only one database query occurs when
        rendering ModelChoiceFields.
        """
        id_ = self.field.widget.attrs.get('id') or self.auto_id
        attrs = {'id': id_} if id_ else {}
        attrs = self.build_widget_attrs(attrs)
        return [
            BoundWidget(self.field.widget, widget, self.form.renderer)
            for widget in self.field.widget.subwidgets(self.html_name, self.value(), attrs=attrs)
        ]

    def __bool__(self):
        # BoundField evaluates to True even if it doesn't have subwidgets.
        return True

    def __iter__(self):
        return iter(self.subwidgets)

    def __len__(self):
        return len(self.subwidgets)

    def __getitem__(self, idx):
        # Prevent unnecessary reevaluation when accessing BoundField's attrs
        # from templates.
        if not isinstance(idx, (int, slice)):
            raise TypeError
        return self.subwidgets[idx]

    @property
    def errors(self):
        """
        Return an ErrorList (empty if there are no errors) for this field.
        """
        return self.form.errors.get(self.name, self.form.error_class())

    def as_widget(self, widget=None, attrs=None, only_initial=False):
        """
        Render the field by rendering the passed widget, adding any HTML
        attributes passed as attrs. If a widget isn't specified, use the
        field's default widget.
        """
        if not widget:
            widget = self.field.widget

        if self.field.localize:
            widget.is_localized = True

        attrs = attrs or {}
        attrs = self.build_widget_attrs(attrs, widget)
        auto_id = self.auto_id
        if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
            if not only_initial:
                attrs['id'] = auto_id
            else:
                attrs['id'] = self.html_initial_id

        if not only_initial:
            name = self.html_name
        else:
            name = self.html_initial_name

        kwargs = {}
        if func_supports_parameter(widget.render, 'renderer') or func_accepts_kwargs(widget.render):
            kwargs['renderer'] = self.form.renderer
        else:
            warnings.warn(
                'Add the `renderer` argument to the render() method of %s. '
                'It will be mandatory in Django 2.1.' % widget.__class__,
                RemovedInDjango21Warning, stacklevel=2,
            )
        return widget.render(
            name=name,
            value=self.value(),
            attrs=attrs,
            **kwargs
        )

    def as_text(self, attrs=None, **kwargs):
        """
        Return a string of HTML for representing this as an <input type="text">.
        """
        return self.as_widget(TextInput(), attrs, **kwargs)

    def as_textarea(self, attrs=None, **kwargs):
        """Return a string of HTML for representing this as a <textarea>."""
        return self.as_widget(Textarea(), attrs, **kwargs)

    def as_hidden(self, attrs=None, **kwargs):
        """
        Return a string of HTML for representing this as an <input type="hidden">.
        """
        return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)

    @property
    def data(self):
        """
        Return the data for this BoundField, or None if it wasn't given.
        """
        return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)

    def value(self):
        """
        Return the value for this BoundField, using the initial value if
        the form is not bound or the data otherwise.
        """
        data = self.initial
        if self.form.is_bound:
            data = self.field.bound_data(self.data, data)
        return self.field.prepare_value(data)

    def label_tag(self, contents=None, attrs=None, label_suffix=None):
        """
        Wrap the given contents in a <label>, if the field has an ID attribute.
        contents should be mark_safe'd to avoid HTML escaping. If contents
        aren't given, use the field's HTML-escaped label.

        If attrs are given, use them as HTML attributes on the <label> tag.

        label_suffix overrides the form's label_suffix.
        """
        contents = contents or self.label
        if label_suffix is None:
            label_suffix = (self.field.label_suffix if self.field.label_suffix is not None
                            else self.form.label_suffix)
        # Only add the suffix if the label does not end in punctuation.
        # Translators: If found as last label character, these punctuation
        # characters will prevent the default label_suffix to be appended to the label
        if label_suffix and contents and contents[-1] not in _(':?.!'):
            contents = format_html('{}{}', contents, label_suffix)
        widget = self.field.widget
        id_ = widget.attrs.get('id') or self.auto_id
        if id_:
            id_for_label = widget.id_for_label(id_)
            if id_for_label:
                attrs = dict(attrs or {}, **{'for': id_for_label})
            if self.field.required and hasattr(self.form, 'required_css_class'):
                attrs = attrs or {}
                if 'class' in attrs:
                    attrs['class'] += ' ' + self.form.required_css_class
                else:
                    attrs['class'] = self.form.required_css_class
            attrs = flatatt(attrs) if attrs else ''
            contents = format_html('<label{}>{}</label>', attrs, contents)
        else:
            contents = conditional_escape(contents)
        return mark_safe(contents)

    def css_classes(self, extra_classes=None):
        """
        Return a string of space-separated CSS classes for this field.
        """
        if hasattr(extra_classes, 'split'):
            extra_classes = extra_classes.split()
        extra_classes = set(extra_classes or [])
        if self.errors and hasattr(self.form, 'error_css_class'):
            extra_classes.add(self.form.error_css_class)
        if self.field.required and hasattr(self.form, 'required_css_class'):
            extra_classes.add(self.form.required_css_class)
        return ' '.join(extra_classes)

    @property
    def is_hidden(self):
        """Return True if this BoundField's widget is hidden."""
        return self.field.widget.is_hidden

    @property
    def auto_id(self):
        """
        Calculate and return the ID attribute for this BoundField, if the
        associated Form has specified auto_id. Return an empty string otherwise.
        """
        auto_id = self.form.auto_id  # Boolean or string
        if auto_id and '%s' in str(auto_id):
            return auto_id % self.html_name
        elif auto_id:
            return self.html_name
        return ''

    @property
    def id_for_label(self):
        """
        Wrapper around the field widget's `id_for_label` method.
        Useful, for example, for focusing on this field regardless of whether
        it has a single widget or a MultiWidget.
        """
        widget = self.field.widget
        id_ = widget.attrs.get('id') or self.auto_id
        return widget.id_for_label(id_)

    @cached_property
    def initial(self):
        data = self.form.get_initial_for_field(self.field, self.name)
        # If this is an auto-generated default date, nix the microseconds for
        # standardized handling. See #22502.
        if (isinstance(data, (datetime.datetime, datetime.time)) and
                not self.field.widget.supports_microseconds):
            data = data.replace(microsecond=0)
        return data

    def build_widget_attrs(self, attrs, widget=None):
        if not widget:
            widget = self.field.widget
        attrs = dict(attrs)  # Copy attrs to avoid modifying the argument.
        if widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute:
            attrs['required'] = True
        if self.field.disabled:
            attrs['disabled'] = True
        return attrs

BoundField

其中有一些方法可以參考,像errors方法,這也就解釋了為什麼欄位可以直接調用該方法;2.2、自定義BaseForm有些時候Form類中需要request或者其它參數,此時需要自定義BaseForm:2.2.1、BaseRequestForm
class BaseRequestForm(object):
    def __init__(self, request, *args, **kwargs):
        self.request = request
        super(BaseRequestForm, self).__init__(*args, **kwargs)

2.2.2、BaseForm
class BaseForm(BaseRequestForm,forms.Form):

    def __init__(self, request,*args, **kwargs):
        super(BaseForm, self).__init__(request,*args, **kwargs)
        # 統一給Form生成欄位添加樣式
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'

2.2.3、Form使用
from django.core.exceptions import ValidationError
from django import forms 
from django.forms import fields 

class LoginForm(BaseForm):

    username = fields.CharField()
    password = fields.CharField()
    code = django_fields.CharField(
        error_messages={'required': '驗證碼不能為空.'}
    )

 #從request的session中取出驗證碼進行驗證    
    def clean_code(self):
        if self.request.session.get('CheckCode').upper() != self.request.POST.get('code').upper():
            raise ValidationError(message='驗證碼錯誤', code='invalid')

在視圖中進行LoginForm實例化時傳入request參數即可:
form = LoginForm(request=request, data=request.POST)

參考博文 :https://www.cnblogs.com/wupeiqi/articles/6144178.htmlhttps://www.cnblogs.com/shenjianping/p/11548311.htmlhttps://www.cnblogs.com/dai-zhe/p/14965836.html

相關焦點

  • Django Form表單API詳解
    對上述功能進行詳細的講解,通過本節知識的學習,你會對 Django 表單系統有更加深入的認識,在本節中我們會穿插介紹一些小的應用實例以便於讀者更好的理解這些 API 。Form表單輸出為HTML1) print()方法輸出表單將 Form 渲染成 HTML 代碼方法非的簡單只需要 print() 即可,默認情況下,根據 form 類中欄位的編寫順序,在 HTML 中以同樣的順序羅列。
  • Django Form表單完整使用流程
    編寫如下代碼定義 Form 表單欄位:from django import formsclass TitleSearch(forms.Form): title=forms.CharField(label='書名',label_suffix='',error_messages={'required':'請輸入正確的title'})
  • python測試開發django -142.Bootstrap 表單(form)
    收錄於話題 #django</p> </div> <div> <label><input type="checkbox"> Check box 複選框</label> </div> <button type
  • 一篇文章帶你了解Django Form組件(入門篇)
    Form組件主要用於驗證表單數據。為什麼需要Form組件注:Form組件,只適用於,前後端未分離的項目中,主要用於驗證表單數據,所以,關鍵字是表單!!!比如像嗶哩嗶哩的註冊界面。,因為html form表單提交是刷新頁面提交的!霧草,沒了,這是少的,如果有十幾個???,那不就氣死了好像我記得我上學時,好多網站都是這。。。好像我也罵了很久,直到前後端分離時,才好一點!
  • Django基礎(9): 表單Forms的高級使用技巧
    ')        widgets = {            'name': Textarea(attrs={'cols': 80, 'rows': 20}),  # 關鍵是這一行        }        labels = {            'name': _('Author'),        }        help_texts
  • Django Form基於Model定義表單
    初識表單系統ModelFormDjango 的表單系統充分考慮到這個問題,並給開發者提供了 ModelForm,使用它就可以實現基於 Model 的定義自動生成表單,這就大大簡化了根據 Model 生成表單的過程,很好解決了我們遇到的問題,下面就讓我們一起來看一下,它是如何實現這個過程的吧。
  • Django限制表單中ForeignKey對應下拉菜單選項數量的兩種經典方法
    我們希望某個用戶在使用表單創建或編輯某篇新文章時,表單上類別對應的下拉菜單選項不顯示所有類別,而只顯示用戶自己創建的類別,我們該如何實現?小編我今天就提供兩種經典方法供參考。如果不出意外,Django模型models.py應該是如下所示:from django.db import modelsfrom django.contrib.auth.models import Userclass Article(models.Model): """文章模型""" title = models.CharField
  • 一篇文章淺析Django Form組件相關知識
    Form組件的理解沒有使用Form組件時在一般情況下,我們如果編寫輸入框時,在Html中,一般都是這樣寫的。這個Form,裡面的欄位,就可以理解為input標籤,只不過是在後端寫的。/form>...
  • Django 2.0 項目實戰: 擴展Django自帶User模型,實現用戶註冊與登錄
    INSTALLED_APPS = [    'reg.apps.RegConfig',    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages
  • Django HTML表單實現用戶登錄退出(含源碼)
    HTML表單實現用戶的登錄通過前一節的學習,通過 HTML 表單並不難實現用戶的登錄功能,那麼大家先思考一下,用戶登錄的邏輯打開是怎麼樣的呢?分析這個邏輯,大家也可以去體驗一下其他網站的登錄功能,從用戶的註冊到登錄最後用戶退出,這整個的流程都需要大家細細的品味,並發現其中的規律,並且學以致用。當自己不熟練的時候,學會去借鑑其他人的經驗,往往是一個不錯的選擇。
  • Django ModelForm用法詳解
    常用的Meta選項在上節一中,我們 class Meta 中使用了一些元數據項,比如說 exclude、labels 以及 fields,當然還有些其他的選項,在 Django 官方網站 ModelForm 的定義如下所示modelform_factory(model,form = ModelForm,fields = None,exclude
  • 8個能提高Django開發效率的Python包
    describe_form將顯示模型的表單定義,然後您可以將其複製/粘貼到forms.py中。(注意,這將生成一個普通的Django表單,而不是一個模型表單。) notes命令可以在整個項目中顯示所有帶有TODO、FIXME等內容的注釋。Django-extensions中還包含一些有用的抽象基類,可用於您自己的模型中。
  • Django表單系統工作原理詳述
    通過繼承 Form 對象,定義所需要的表單欄位,基本上完成了表單的定義。它可以自動生成 HTML,完成欄位值的校驗,並給出相應錯誤的提示信息。
  • HTML中表單form的相關知識
    在Javascript 中,頁面上的每一對<form> 標記都解析為一個對象,即form 對象。可以通過document.forms 獲取以源順序排列的文檔中所有form 對象的集合。
  • python測試開發django-114.ModelForm中局部鉤子(clean_)和全局鉤子校驗
    收錄於話題 #django 前言在實際開發中,不僅僅是對輸入框字符的格式校驗,比如註冊功能,註冊帳號還得校驗資料庫是否已經有帳號被註冊過了。
  • html中form表單標籤的詳細介紹
    本篇將介紹的是html中form表單標籤的用法,有興趣的朋友可以學習一下!在html中,表單是經常用到的,用來與用戶交互並提交數據。今天要介紹的就是表單標籤form標籤,接下來我們就一起來看看它的用法吧!「form」作為英文單詞有「表格」的意思,那它在html中作為標籤又充當什麼樣的角色呢!
  • Django+Vue項目學習第一篇:django後臺搭建
    最近在學習Django和Vue,經過一段時間的摸索終於把前後端調通了,初步達到了學習的目的:  使用Vue寫前端頁面;  使用Django處理後臺邏輯,生成數據返給前端;  利用axios發送網絡請求,包含get請求、post請求、攜帶參數的請求; Django如何接收不同類型請求頭對應的請求參數,例如表單數據、json
  • Django實戰教程: 開發企業級應用智能文檔管理系統smartdoc(1)
    我們用戶註冊登錄功能交給了django-allauth, 所以把allauth也進去了。如果你不了解django-allauth,請閱讀django-allauth教程(1): 安裝,用戶註冊,登錄,郵箱驗證和密碼重置(更新)。
  • JavaScript之阻止form表單提交三種方式
    今天和大家分享一下,關於如何阻止form表單的提交。一、為什麼要阻止form表單的提交呢?以及使用場景我們在工作中,比如一個登錄頁面,當填完用戶登錄信息後,需要對用戶信息進行校驗,如果有問題,那麼就不應該將數據提交到後臺,而是在前臺阻止,接下來我們看一下,如何阻止form表單的幾種方式1、表單第一種阻止方式:form結合onclick事件來阻止form表單的提交<head><meta charset="UTF-8"
  • HTML+CSS:使用form表單控制項,與用戶交互
    今天這篇文章我們主要來看一下表單的控制項都有哪些,如何使用表單標籤,與用戶交互。(1)網站怎樣與用戶進行交互?答案是使用HTML表單(form)。表單是可以把瀏覽者輸入的數據傳送到伺服器端,這樣伺服器端程序就可以處理表單傳過來的數據。