Django的Forms组件主要有以下几大功能:
- 页面初始化,生成HTML标签
- 校验用户数据(显示错误信息)
- HTML Form提交保留上次提交数据
一、小试牛刀
1、定义Form类
from django import forms
class EmpForm(forms.Form):
name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
"min_length": "用户名太短。"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")


class Emp(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
salary = models.DecimalField(max_digits=8, decimal_places=2)
2、设计url与视图对应关系


from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
path('add_emp/', views.add_emp),
]
3、视图函数


from django.shortcuts import render,redirect
from app01 import models
# Create your views here.
from django.http.response import HttpResponse
def index(request):
return HttpResponse("okok!")
from app01.MyForms import EmpForm
def add_emp(request):
if request.method == "GET":
form = EmpForm() # 初始化form对象
return render(request, "add_emp.html", {"form":form}) #传送一个实例对象
else:
form = EmpForm(request.POST) # 将数据传给form对象
if form.is_valid(): # 进行校验
data = form.cleaned_data # 校验通过的数据,字典
print(data)#{'name': 'alex01', 'age': 12, 'salary': Decimal('21')}
models.Emp.objects.create(**data)
return redirect("/index/")
else: # 校验失败
print(form.errors) #<ul class="errorlist"><li>name<ul class="errorlist"><li>用户名太短。</li></ul></li><li>age<ul class="errorlist"><li>This field is required.</li></ul></li><li>salary<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
'''<ul class="errorlist">
<li>
name
<ul class="errorlist">
<li>用户名太短。</li> 可能会有多个错误
...
</ul>
</li>
<li>age<ul class="errorlist"><li>This field is required.</li></ul></li>
<li>salary<ul class="errorlist"><li>This field is required.</li></ul></li>
</ul>'''
return render(request, "add_emp.html", {"form": form}) #传送一个带错误信息实例对象
4、模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h3>添加员工</h3>
{#1、自己手动写HTML页面#}
{#<form action="" method="post">#}
{# <p>姓名:<input type="text" name="name"></p>#}
{# <p>年龄:<input type="text" name="age"></p>#}
{# <p>工资:<input type="text" name="salary"></p>#}
{# <input type="submit">#}
{#</form>#}
{#2、通过form对象的as_p方法实现#}
{#<form action="" method="post" novalidate>#}
{# {% csrf_token %}#}
{# {{ form.as_p }}#}
{# <input type="submit">#}
{#</form>#}
</body>
</html>


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate> <!--novalidate novalidate 属性规定当提交表单时不对其进行验证。如果使用该属性,则表单(浏览器)不会验证表单的输入。-->
{% csrf_token %}
<div>
<label for="id_{{ form.name.name }}">姓名</label> <!--form.name.name等价于表单类的字段名 等价于 name age salary ... -->
{{ form.name }} <span>{{ form.name.errors.0 }}<!--form.name.errors.0 表单类可能定义了多个错误信息 先显示第一个 解决完 --></span>
</div>
<div>
<label for="id_{{ form.age.name }}">年龄</label>
{{ form.age }} <span>{{ form.age.errors.0 }}</span>
</div>
<div>
<label for="id_salary">工资</label>
{{ form.salary }} <span>{{ form.salary.errors.0 }}</span>
</div>
<input type="submit">
</form>
<!--以下为浏览器显示的页面-->
<!--<form action="" method="post" novalidate>-->
<!--<input type="hidden" name="csrfmiddlewaretoken" value="oVa3cSt17v5ie9bQBWo3MEBiv32zysmCAvY2QMCGHk3aDyaMFPhnR8ikIniQoA0H">-->
<!--<div>-->
<!--<label for="id_name">姓名</label>-->
<!--<input type="text" name="name" value="alex" minlength="5" required id="id_name"> <span>用户名太短。</span>-->
<!--</div>-->
<!--<div>-->
<!--<label for="id_age">年龄</label>-->
<!--<input type="number" name="age" required id="id_age"> <span>This field is required.</span>-->
<!--</div>-->
<!--<div>-->
<!--<label for="id_salary">工资</label>-->
<!--<input type="number" name="salary" step="0.01" required id="id_salary"> <span>This field is required.</span>-->
<!--</div>-->
<!--<input type="submit">-->
<!--</form>-->
</body>
</html>


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %} <!-- 遍历form表单类所有的字段信息 field 相当于 第三种方式的 form.name/form.age/form.salary-->
<div>
<label for="id_{{ field.name }}">{{ field.label }}</label>
<!--发现:name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!","min_length": "用户名太短。"}) 表单类定义的字段属性 字段都可以在前端调用 field.label -->
{{ field }} <span>{{ field.errors.0 }}</span>
<!-- field.errors.0 跟这个error_messages={"required": "该字段不能为空!","min_length": "用户名太短。"} 有关联-->
</div>
{% endfor %}
<input type="submit">
</form>
</body>
</html>
附:Django内置的字段及属性


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类型
...
二、局部钩子和全局钩子
1、定义Form类


from django import forms
from django.core.exceptions import ValidationError #导包 from django.core.exceptions import ValidationError
from app01 import models
class EmpForm(forms.Form):
name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
"min_length": "用户名太短。"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请确认工资")
# 局部钩子
def clean_name(self): # 名称不能随便起,必须要clean_类属性名称
val = self.cleaned_data.get("name")
if val.isdigit():
raise ValidationError("用户名不能全是数字。")
elif models.Emp.objects.filter(name=val):
raise ValidationError("用户名已存在。")
else:
return val
def clean(self):
salary = self.cleaned_data.get("salary")
r_salary = self.cleaned_data.get("r_salary")
if salary != r_salary:
raise ValidationError("工资输入有误。")
else:
return self.cleaned_data
from django import forms
from django.core.exceptions import ValidationError
from app01 import models
class EmpForm(forms.Form):
name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
"min_length": "用户名太短。"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请再输入工资")
def clean_name(self): # 局部钩子
val = self.cleaned_data.get("name")
if val.isdigit():
raise ValidationError("用户名不能是纯数字")
elif models.Emp.objects.filter(name=val):
raise ValidationError("用户名已存在!")
else:
return val
def clean(self): # 全局钩子 确认两次输入的工资是否一致。
val = self.cleaned_data.get("salary")
r_val = self.cleaned_data.get("r_salary")
if val == r_val:
return self.cleaned_data
else:
raise ValidationError("请确认工资是否一致。")
2、视图函数


from django.shortcuts import render,redirect
from app01 import models
# Create your views here.
from django.http.response import HttpResponse
def index(request):
return HttpResponse("okok!")
from app01.MyForms import EmpForm
def add_emp(request):
if request.method == "POST":
# data = request.POST.get()
form = EmpForm(request.POST)
if form.is_valid():
print(1111,form.cleaned_data)
models.Emp.objects.create(**form.cleaned_data)
else:
print(form.errors)#<ul class="errorlist"><li>name<ul class="errorlist"><li>用户名太短。</li></ul></li><li>__all__<ul class="errorlist nonfield"><li>工资输入有误。</li></ul></li></ul>
clear_errors = form.errors.get("__all__") # 获取全局钩子错误信息
return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})
else:
form = EmpForm()
return render(request, "add_emp.html", {"form": form})
'''源码解析 MyForms.py:from django import forms 《--- views.py:from app01.MyForms import EmpForm
第一步:form = EmpForm() 生成一个EmpForm表单类对象(该类继承了from django import forms里面的forms.Form类)
第二步:POST请求接受客户的请求数据 进行验证 form = EmpForm(request.POST)---》form.is_valid()
1、
def is_valid(self):
"""Return True if the form has no errors, or False otherwise."""
return self.is_bound and not self.errors
2、 @property
def errors(self):
"""Return an ErrorDict for the data provided for the form."""
if self._errors is None:
self.full_clean()
return self._errors
3、
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()
4、MyForms.py有自定义的局部钩子 就会调用 MyForms.py的局部钩子
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[name] = value
except ValidationError as e:
self.add_error(name, e)
5、MyForms.py有自定义的全局钩子 就会调用 MyForms.py的全局钩子
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
'''
def add_emp(request):
if request.method == "GET":
form = EmpForm() # 初始化form对象
return render(request, "add_emp.html", {"form":form})
else:
form = EmpForm(request.POST) # 将数据传给form对象
if form.is_valid(): # 进行校验
data = form.cleaned_data
data.pop("r_salary")
models.Emp.objects.create(**data)
return redirect("/index/")
else: # 校验失败
clear_errors = form.errors.get("__all__") # 获取全局钩子错误信息
return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})
3、模板文件


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
<div>
<label for="id_{{ form.name.name }}">姓名</label>
{{ form.name }} <span>{{ form.name.errors.0 }}</span>
</div>
<div>
<label for="id_{{ form.age.name }}">年龄</label>
{{ form.age }} <span>{{ form.age.errors.0 }}</span>
</div>
<div>
<label for="id_salary">工资</label>
{{ form.salary }} <span>{{ form.salary.errors.0 }}{{ clear_errors.0 }}</span>
</div>
<div>
<label for="id_r_salary">请再输入工资</label>
{{ form.r_salary }} <span>{{ form.r_salary.errors.0 }}{{ clear_errors.0 }}</span>
</div>
<input type="submit">
</form>
</body>
</html>
另一种方法导入app01/models模型类的数据,其它方法与上面一致