1.生成页面可用的HTML标签 2.对用户提交的数据进行校验 3.保留上次输入内容
1.views.py中导入forms模块:from django import froms 2.定义继承自forms.Form的类,类中定义字段(和models中的写法相同) 3.视图函数中创建该类的对象 4.如果是get请求,向页面中渲染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]
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"}
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类型 复制代码
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开头"]
)
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"手机号码"
})
)
在Form中定义钩子方法,也是实现自定义的验证功能
执行顺序:自己先前定义的的校验-->局部钩子-->全局钩子
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
我们在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(‘两次密码不一致‘)

这个组件的功能就是把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>
只需要加一个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/")
外键相关(在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"})
工厂模式用来生成如下效果(主要用来批量编辑使用):

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>
原文:https://www.cnblogs.com/ly0123/p/11938652.html