一、Form基础

(一)Form的作用

Django的Form主要有以下几大作用:

  • 生成HTML标签
  • 验证用户数据(显示错误信息)
  • Form提交保留上次提交数据
  • 初始化页面显示数据

(二)实例

一般网站都有登录页面,如果使用Form应该怎么做呢?

1、创建LoginForm类

from django import forms

class LoginForm(forms.Form):
username = forms.CharField(max_length=32)
password = forms.CharField(max_length=32)

2、views中进行实例化

def login(request):
if request.method == 'GET':
#实例化LoginForm
form = LoginForm()
#发送的是POST请求
form = LoginForm(data=request.POST)
#对数据进行验证
if form.is_valid():
print(form.cleaned_data)
return redirect('/index/')
return render(request,'login.html',{'form':form})

3、login.html中使用

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/login/">
{% csrf_token %}
{{ form }} <!--自动生成html标签-->
<input type="submit"/>
</form>

这样很容易就完成了登录功能,并且Form的作用也都体现出来了。

(三)forms字段和插件

在上述实例中,使用Form需要创建一个Form相关的类,并且在类中写入相关的forms字段,这里涉及到Field类,是所有forms字段最根本的基类,它位于django.forms.Field下:

class Field:
widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_validators = [] # Default set of validators
# Add an 'invalid' entry to default_error_message if you want a specific
# field error message not raised by the field validators.
default_error_messages = {
'required': _('This field is required.'),
}
empty_values = list(validators.EMPTY_VALUES) def __init__(self, *, required=True, widget=None, label=None, initial=None,
help_text='', error_messages=None, show_hidden_initial=False,
validators=(), localize=False, disabled=False, label_suffix=None):
# required -- Boolean that specifies whether the field is required.
# True by default.
# widget -- A Widget class, or instance of a Widget class, that should
# be used for this Field when displaying it. Each Field has a
# default Widget that it'll use if you don't specify this. In
# most cases, the default widget is TextInput.
# label -- A verbose name for this field, for use in displaying this
# field in a form. By default, Django will use a "pretty"
# version of the form field name, if the Field is part of a
# Form.
# initial -- A value to use in this Field's initial display. This value
# is *not* used as a fallback if data isn't given.
# help_text -- An optional string to use as "help text" for this Field.
# error_messages -- An optional dictionary to override the default
# messages that the field will raise.
# show_hidden_initial -- Boolean that specifies if it is needed to render a
# hidden widget with initial value after widget.
# validators -- List of additional validators to use
# localize -- Boolean that specifies if the field should be localized.
# disabled -- Boolean that specifies whether the field is disabled, that
# is its widget is shown in the form but not editable.
# label_suffix -- Suffix to be added to the label. Overrides
# form's label_suffix.
self.required, self.label, self.initial = required, label, initial
self.show_hidden_initial = show_hidden_initial
self.help_text = help_text
self.disabled = disabled
self.label_suffix = label_suffix
widget = widget or self.widget
if isinstance(widget, type):
widget = widget()
else:
widget = copy.deepcopy(widget) # Trigger the localization machinery if needed.
self.localize = localize
if self.localize:
widget.is_localized = True # Let the widget know whether it should display as required.
widget.is_required = self.required # Hook into self.widget_attrs() for any Field-specific HTML attributes.
extra_attrs = self.widget_attrs(widget)
if extra_attrs:
widget.attrs.update(extra_attrs) self.widget = widget messages = {}
for c in reversed(self.__class__.__mro__):
messages.update(getattr(c, 'default_error_messages', {}))
messages.update(error_messages or {})
self.error_messages = messages self.validators = list(itertools.chain(self.default_validators, validators)) super().__init__() def prepare_value(self, value):
return value 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 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 def widget_attrs(self, widget):
"""
Given a Widget instance (*not* a Widget class), return a dictionary of
any HTML attributes that should be added to the Widget, based on this
Field.
"""
return {} 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 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) def __deepcopy__(self, memo):
result = copy.copy(self)
memo[id(self)] = result
result.widget = copy.deepcopy(self.widget, memo)
result.validators = self.validators[:]
return result

Field

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内容后缀

2、字段的基本方法

在这个类中还有一些方法,也就是字段对象的方法:

  • 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

clean

每个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('62514@qq.com')
Out[3]: '62514@qq.com'
...
In [4]: f.clean('')
ValidationError: ['Enter a valid email address.']
  • bound_data
    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

bound_data

它有两个参数,一个是request.POST提交的data,另一个是initial参数初始化的数据,这个方法返回的就是request.POST提交的那个字段值。

  • has_changed
    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

has_changed

它有两个参数,一个是request.POST提交的data,另一个是initial参数初始化的数据,这个方法返回的是布尔值,如果提交的data与初始化数据不一样的话就返回True。

  • get_bound_field
    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)

get_bound_field

它有两个参数,一个是form实例,另一个是创建的Form类字段的字段名称field_name,而它的返回值是BoundField对象。

3、常用字段

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) #抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合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类型

常用字段

一个字段可包含以下参数:

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': '组别名称不能为空',
})

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

在CharField中,它继承了Field,所以它的默认Widget是Field中TextInput,它在HTML 中生成一个<input type="text">。Django的内置插件有:

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

内置插件

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

注意:

  city = fields.TypedChoiceField(
  coerce=lambda x: int(x),
  choices=[(1,'上海',),(2,'北京'),(3,'沙河'),],
  initial=2
  )
  • 数据的实时更新

使用选择插件可以从数据库中获取数据,但是由于字段是静态的,在修改数据后不会进行立即更新,所以需要进行一些处理:

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)

ProcessHours

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、正则验证

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]+$', '请输入数字')],
)

2、对每个字段定义钩子函数

from django.core.exceptions import ValidationError

...
... #方法二、对每个字段定义钩子函数,num是字段名 def clean_v1(self):
value = self.cleaned_data('v1')
if classesAll.objects.all().count():
raise ValidationError('班级已经存在')
return value

3、联合验证,这是在以上验证结束后在进行此验证

   def clean(self):
v1 = self.cleaned_data['v1']
v2 = self.cleaned_data['v2']
if v1 == 'aa' and v2 =='bb':
raise ValidationError('信息错误')
return self.cleaned_data

二、Form源码一览

(一)Form实操

from django.shortcuts import render
from django.shortcuts import redirect
from app01 import models
from app01.forms import UserForm #增加数据
def add_user(request):
user_list = models.UserInfo.objects.all() #用于初始化页面,相当于获取所有用户列表
  if request.method == 'GET':
    obj = UserForm(user_list )
  return render(request,'add_user.html',{'obj':obj})
  else:
    obj = UserForm(request.POST)
    if obj.is_valid():
      models.UserInfo.objects.create(**obj.cleaned_data)
      return redirect('/users/')
    else:
      return render(request,'add_user.html',{'obj':obj}) #修改数据
def edit_user(request,nid):
  if request.method == "GET":
    data = models.UserInfo.objects.filter(id=nid).first()
    obj = UserForm({'username':data.username,'email':data.email})
    return render(request,'edit_user.html',{'obj':obj,'nid':nid})
  else:
    obj = UserForm(request.POST)
    if obj.is_valid():
      models.UserInfo.objects.filter(id=nid).update(**obj.cleaned_data)
      return redirect('/users/')
    else:
      return render(request,'edit_user.html',{'obj':obj,'nid':nid})

(二)源码一览

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

BaseForm中的__init__方法

注意到在__init__方法中:

 self.fields = copy.deepcopy(self.base_fields)

获取字段:

self.fields={
"username":"字段规则对象",
"password":"字段规则对象", }

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

而self.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._error

errors

    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

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)

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

5、属性、方法

从源码中就可以看出来,验证完毕后cleaned_data就有数据了,此时获取验证完毕的数据可以使用:

#获取所有验证成功的数据
form.cleaned_data
form.cleaned_data["username"]

错误信息使用:

form.errors

#每个字段错误信息
form.errors["username"]

6、页面渲染

#渲染username字段错误
{{ form.Username.errors }} #渲染username字段标签
{{ form.Username }}

三、扩展

(一)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="" required />
<class 'django.forms.boundfield.BoundField'> <input type="text" name="password" value="" id="id_password" maxlength="" 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方法,这也就解释了为什么字段可以直接调用该方法。

(二)自定义BaseForm

有些时候Form类中需要request或者其它参数,此时需要自定义BaseForm:

1、BaseRequestForm

class BaseRequestForm(object):
def __init__(self, request, *args, **kwargs):
self.request = request
super(BaseRequestForm, self).__init__(*args, **kwargs)

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'

3、使用

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://docs.djangoproject.com/en/2.2/ref/forms/fields/

      https://www.cnblogs.com/wupeiqi/articles/6144178.html

Django之Form操作的更多相关文章

  1. Django(八)下:Model操作和Form操作、序列化操作

    二.Form操作 一般会创建forms.py文件,单独存放form模块. Form 专门做数据验证,而且非常强大.有以下两个插件: fields :验证(肯定会用的) widgets:生成HTML(有 ...

  2. Django(八)上:Model操作和Form操作

    ↑↑↑点上面的”+”号展开目录 Model和Form以及ModelForm简介 Model操作: 创建数据库表结构 操作数据库表 做一部分的验证 Form操作: 数据验证(强大) ModelForm ...

  3. Python之路-(Django(csrf,中间件,缓存,信号,Model操作,Form操作))

    csrf 中间件 缓存 信号 Model操作 Form操作 csrf: 用 django 有多久,我跟 csrf 这个概念打交道就有久了. 每次初始化一个项目时都能看到 django.middlewa ...

  4. Django之Model操作

    Django之Model操作 本节内容 字段 字段参数 元信息 多表关系及参数 ORM操作 1. 字段 字段列表 AutoField(Field) - int自增列,必须填入参数 primary_ke ...

  5. Python之路【第二十二篇】:Django之Model操作

    Django之Model操作   一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...

  6. Django的Form(二)

    上一篇已经简单介绍了Django的Form,现在开始进阶操作了 ..... 创建Form类的时候,主要涉及到Form字段和Form的插件,字段用于做form验证,插件用来生成HTML DjiangoF ...

  7. Django的form表单

    html的form表单 django中,前端如果要提交一些数据到views里面去,需要用到 html里面的form表单. 例如: # form2/urls.py from django.contrib ...

  8. Django中Form验证

    Django的Form主要具有一下几大功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 一,Form验证 第一种操作:主要是这三个函数 ...

  9. python框架之Django(10)-Form组件

    介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来.与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入 ...

随机推荐

  1. Redis的备份与恢复

    备份 dump.rdb:RDB方式的备份文件 appendonly.aof:AOF方式的备份文件 rdb 备份处理 # 编辑redis.conf文件,找到如下参数,默认开启. save 900 1 s ...

  2. 转 关于HTML5中meta name="viewport" 的用法 不同分辨率手机比例缩放

    移动端的布局不同于pc端,首先我们要知道在移动端中,css中的1px并不等于物理上的1px,因为手机屏幕的分辨率已经越来越高,高像素但是屏幕尺寸却没有发生太大变化,那就意味着一个物理像素点实际上塞入了 ...

  3. C++的命名空间

    作用:防止类,函数,变量等之间重名,比如在代码合并的时候 假如两个头文件中均定义了类Cal,而调用程序同时包含了两个头文件,当在定义Cal c时,程序会报类型重定义的错误.这种问题可以通过命名空间来解 ...

  4. List Comprehension ()(二)

    在list comprehension中加入if条件判断: >>> lines = [line.rstrip() for line in open('script2.py') if ...

  5. IDEA使用Maven搭建JavaWeb项目

    1. 新建项目 2. 填写项目坐标信息 3. 手动生效pom.xml 4. 添加maven常用配置 1) 设置项目统一编码 <!-- 设置编码 --> <properties> ...

  6. 学习java设计模式有用吗?懂这六个原则,编程更轻松

    学习java设计模式有用吗?懂这六个原则,编程更轻松 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实 ...

  7. nohup及crontab定时任务

    一.关于nohup: 1.假设有一 tash.sh 启动是:nohup /home/spex/bin_prodDemo/task.sh &                         一定 ...

  8. Vue学习笔记【27】——Vue路由(设置路由)

    设置路由高亮 css:     .router-link-active, /* vue-router*/    .myactive {      color: red;      font-weigh ...

  9. delphi 读写记录类型文件Record

    type personInfo=Record //定义Record Name:String[10]; Age:integer; end; //写记录包括新键与添加记录 procedure WriteR ...

  10. K-th Closest Distance

    题目链接 题意:n个数,q次查询,查询[l , r] 内, | a[i] - p | 第k大的数 思路:主席树维护下权值大小,二分答案,查询区间[p - mid, p + mid] 的个数 #incl ...