一.Form简介
1.form组件的主要功能
1.生成页面可用的HTML标签 2.对用户提交的数据进行校验 3.保留上次输入内容
2.页面显示的步骤
1.views.py中导入forms模块:from django import froms 2.定义继承自forms.Form的类,类中定义字段(和models中的写法相同) 3.视图函数中创建该类的对象 4.如果是get请求,向页面中渲染form对象。
3.简单使用form组件实现的例子

class Regform(forms.Form): # 查看所有字段(forms->fields->查看__all__)
"""
CharField对应input标签,users、pwd是input标签中的name的值,label对应label标签,widget的值对应input标签的类型
ChoiceField对应select标签,choices中的选项是option的值
"""
users = forms.CharField(label="用户名", widget=forms.TextInput)
pwd = forms.CharField(label="密码", widget=forms.PasswordInput)
choice = forms.ChoiceField(label="性别", choices=((1, "alex"), (2, "egon")))
def form_test(req):
form_obj = Regform() # 实例化Regform对象
if req.method == "POST":
form_obj = Regform(data=req.POST)
if form_obj.is_valid(): # is_valid()是遍历所有提交过来的字段的值是否合法
if req.POST.get("password") != req.POST.get("re_password"):
return render(req, "form_test.html", {"form_obj": form_obj}) # 此处保留在前端界面上的数据
else:
return redirect("/index/")
return render(req, "form_test.html", {"form_obj": form_obj}) # 将form_obj渲染给前端界面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/form_test/" method="post">
{% csrf_token %}
{{ form_obj.as_p }} #生成所有标签,将生成的标签用p标签包起来
#{{ form_obj.as_table }} #用table标签包裹
#{{ form_obj.as_ul }} #用ul表标签包裹
<p>
<label for="{{ form_obj.users.id_for_label }}">{{ form_obj.users.label }}</label>:
{{ form_obj.users }}{{ form_obj.users.errors }} #errors是这个字段的所有出现的错误,errors.0是出现的错误的第一条
</p>
<p>
<label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
{{ form_obj.pwd }}{{ form_obj.pwd.errors }}
</p>
<p>
<label for="{{ form_obj.choice.id_for_label }}">{{ form_obj.choice.label }}</label>
{{ form_obj.choice }}{{ form_obj.choice }}
</p>
<input type="submit">
</form>
</body>
</html>

# print(status)
# print(obj.errors.as_json) #拿到错误信息
#平常使用
obj=FormData(req.POST)
if obj.is_valid(): #is_valid() for循环遍历FormData中所有字段,如果所有字段都合法,返回true,如果有一个不合法,返回false
print(obj.clean()) #输出拿到的正确信息
else:
print(obj.errors.as_json)#如果验证错误输出错误信息
u=obj.errors['username'][0] #拿到错误值(含标签)
#u=obj.errors['username'] #拿到错误的文本值
e=obj.errors["email"][0]
二.Form常用字段和插件
initial
初始值,input框的初始值
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
)
error_messages
重写错误信息
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required":"不能为空", #验证前端中username字段是否为空,为空再提示
"invalid":"格式错误", #格式错误,正则校验错误
"min_length":"用户名最短8位"
}
)
password
class LoginForm(forms.Form):
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.PasswordInput(attrs={'class': 'c1'}, render_value=True) #这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据
)
radioSelect
class LoginForm(forms.Form):
gender = forms.ChoiceField(
choices=((1,"男"),(2,"女")),
label="性别",
initial=2,
widget=forms.RadioSelect()
)
单选Select
class LoginForm(forms.Form):
hobby = forms.ChoiceField(
choices=((1,"篮球"),(2,"足球"),(3,"双色球")),
label="爱好",
initial=3,
widget=forms.Select()
)
多选Select
class LoginForm(forms.Form):
hobby2 = forms.MultipleChoiceField(
choices=((1,"篮球"),(2,"足球"),(3,"双色球")),
label="爱好2",
initial=[1,3],
)
单选checkbox
class LoginForm(forms.Form):
keep = forms.ChoiceField(
choices=(("True",1),("False",0)),
label="是否7天内自动登录",
initial="1", #????
widget=forms.CheckboxInput,
)
多选checkbox
class LoginForm(forms.Form):
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
date类型
class LoginForm(forms.Form):
date = forms.DateField(
widget=forms.TextInput(attrs={"type":"date"}) #必须指定type
)
emailField
email = forms.EmailField(label="邮箱", #EmailField
widget=forms.EmailInput(attrs={"name":"email","placeholder":"邮箱"}), #EmailInput
error_messages={
"required":"不能为空",
"invalid": "格式错误", #验证格式
})
choice字段注意事项
通过重构方法对choice进行实时更新
class LoginForm(forms.Form):
city = forms.ChoiceField(
initial=2,
widget=forms.Select
)
def __init__(self,*args,**kwargs):
super(LoginForm,self).__init__(*args,**kwargs) #参数必须写
self.fields['city'].choices=models.City.objects.all().values_list("id","name") #values_list返回[(),()]类型
通过init给字段设置属性值
#通过init方法给所有字段设置属性
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
print(self.fields)
for filed in self.fields.values():
#设置单个属性值
self.fields["password"].widget.attrs={"class":"form-control"}
#设置多个属性值
filed.widget.attrs.update({"class":"form-control"})
# filed.widget.attrs={"class":"form-control"}
三.Form中内置字段

Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
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.RegexValidator验证器
from django import forms
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class Regexsform(forms.Form):
user = forms.IntegerField(
validators=[RegexValidator(r'^[0-9]+$'),"请输入数字",
RegexValidator(r"^159[0-9]+$"),"数字必须以159开头"]
)
2.自定义验证函数
def mobile_validate(value): #phone字段用此验证,就将phone字段得到的值传参给value
if "alex" in value:
raise ValidationError("错误") #必须自己指定错误
class Regexsform(forms.Form):
phone = forms.CharField(validators=[mobile_validate,],
label="ppp",
error_messages={
'required':"手机号不能为空"
},
widget=forms.TextInput(attrs={
"class":"form-control",
"placeholder":u"手机号码"
})
)
五.Hook钩子方法
在Form中定义钩子方法,也是实现自定义的验证功能
执行顺序:自己先前定义的的校验-->局部钩子-->全局钩子
1.局部钩子
Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
},
widget=forms.widgets.TextInput(attrs={"class": "form-control"})
)
...
# 定义局部钩子,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子
def clean_username(self):
value = self.cleaned_data.get("username") #cleaned_data是上面所有通过验证的字段
if "666" in value:
raise ValidationError("光喊666是不行的")
else:
return value
2.全局钩子
我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验.
class LoginForm(forms.Form):
...
password = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
re_password = forms.CharField(
min_length=6,
label="确认密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
)
...
# 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据
def clean(self):
password_value = self.cleaned_data.get('password')
re_password_value = self.cleaned_data.get('re_password')
if password_value == re_password_value:
return self.cleaned_data #全局钩子要返回所有的数据
else:
self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它
raise ValidationError('两次密码不一致')

六.ModelForm
1.ModelForm认识
这个组件的功能就是把model和form组合起来,假如我们有个model模型Student类,想通过前端处理一些字段的验证,有3种方式:
1.自己手写前端界面中的标签,然后自己写一些验证
2.通过Form组件(需要将要验证的字段重写)
3.以上两种方式都跟model模型没有耦合性,都需要自己一个个配对,现在通过ModelForm可以实现耦合,取出model模型中的任意字段进行验证,这样不用再像form组件那样再重写字段
①首先在视图函数中导入ModelForm
from django.forms import ModelForm
②定义一个类,就叫StudentList,继承自ModelForm
class StudentList(ModelForm):
class Meta: # 想要验证的字段都写在Meta类中
model = models.Student # 对应model中的Student类
fields = "__all__" # 字段,如果是all,就列出所有字段
exclude = None # 排除的字段 exclude = ["name","age"]
help_texts = None # 帮助信息 help_texts = ["name","age"]
from django.forms import widgets as wig # 因为重名,起个别名
widgets = { # 设置字段的类型
"name": wig.Textarea(attrs={"class": "c1"})
}
error_messages = {
"name": {"required": "用户名不能为空"},
"age": {"required": "年龄不能为空"},
}
labels = {"name": "用户名", "age": "年龄"} # 自定义在前端显示的名字
③在url对应的视图函数中实例化此类,并将对象传给前端
def modelfrom(req):
if req.method == "GET":
student_list = StudentList()
return render(req,"studentlist.html",{"student_list":student_list})
else:
student_list = StudentList(req.POST)
if student_list.is_valid():
student_list.save() #提交post请求时,只需要.save()就能保存所有提交的数据
return render(req,"studentlist.html",{"student_list":student_list})
④前端只需要{{student_list.as_p}},所有的字段就能显示,或者for循环遍历student_list,写法和form表单中略有不同,详见下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/modelform/" method="post" novalidate>
{% csrf_token %}
{# {{ student_list.as_p }}#}
{% for i in student_list %}
<label for="{{ i.id_for_label }}">{{ i.label }}</label>
{{ i }}
{% endfor %}
<input type="submit">
</form>
</body>
</html>
2.编辑(添加)数据
只需要加一个instance=obj(obj是model模型中的类实例化的一条对象),前端界面和上④中相同即可
obj = models.Student.objects.filter(pk=1)[0]
if req.method == "GET":
student_list = StudentList(instance=obj) #此处obj为要显示在前端的数据
return render(req,"student_edit.html",{"student_list":student_list})
else:
student_list = StudentList(req.POST,instance=obj)
if student_list.is_valid():
student_list.save() #不加instance为添加记录,如果加上为更新obj的内容
return redirect("/modelform_edit/")
3.其他
外键相关(在model模型中有外键Foreignkey,modelform会生成其对应的所有字段,想解决此种方式,就要修改queryset)
model模型def __init__(self,req,*args,**kwargs):
super().__init__(*args,**kwargs)
for ids,filed in self.fields.items():
if ids == "consultant":
filed.queryset = models.UserInfo.objects.filter(username=req.username) #设置字段名为consultant的外键关联数据
filed.widget.attrs.update({"class": "form-control"})
MultiSelectField
from multiselectfield import MultiSelectField #要使用需安装django-multiselectfield模块
class Customer(models.Model):
"""客户表"""
course = MultiSelectField("咨询课程",choices=course_choices) #多选,modelform生成多选框,如下图

此处如果给控件添加bootstrap样式,生成如下混乱状态:

解决方式:
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for filed in self.fields.values():
print(type(filed))
if not isinstance(filed,MultiSelectFormField):
filed.widget.attrs.update({"class": "form-control"})
七.modelformset_factory
工厂模式用来生成如下效果(主要用来批量编辑使用):

views视图函数
from app01 import models
class StudyForms(ModelForm): class Meta: model = models.StudyRecord fields = "__all__"
from django.forms.models import modelformset_factory
class StudyRecordView(views.View):
def get(self,req): #get请求,用来将后台数据显示到前端界面,如上图
pk = req.GET.get("pk")
formset_obj = modelformset_factory(models.StudyRecord,myforms.StudyForms,extra=0) #models模型中的StudyRecord类,自定义的modelform对象
#formset_obj = formset_obj(queryset=models.StudyRecord.objects.filter(course_record__id=pk)) #设置前端显示的数据,不写的话显示所有
return render(req,"course_record/studyrecord_list.html",{"formset_obj":formset_obj})
def post(self,req): #post请求,用来接受前端发来的数据
print(req.get_full_path())
formset_obj = modelformset_factory(models.StudyRecord,myforms.StudyForms,extra=0)
formset_obj = formset_obj(req.POST)
if formset_obj.is_valid():
formset_obj.save() #更新数据库
return redirect(req.get_full_path())
else:
return render(req,"course_record/studyrecord_list.html",{"formset_obj":formset_obj})
html界面
<tbody>
{% for f_obj in formset_obj %}
<tr role="row" class="odd">
{{ f_obj.id }}
<td>
<input type="checkbox" name="cids" value={{ f_obj.pk }}>
</td>
<td>{{ forloop.counter }}</td>
<td>{{ f_obj.attendance }}</td>
<td>{{ f_obj.score }}</td>
<td>{{ f_obj.homework_note }}</td>
<td class="hidden">{{ f_obj.course_record }}</td> #post请求时,会将所有f_obj的数据发送到后端,除了f_obj.instance..xx,此处用来在前端界面显示
<td>{{ f_obj.instance.course_record }}</td>
<td class="hidden">{{ f_obj.student.name }}</td>
<td>{{ f_obj.instance.student }}</td>
</tr>
{% endfor %}
</tbody>
