Django(form组件)
一.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] views.py简单使用
二.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)
class ConsultRecord(models.Model): """ 跟进记录表 """ customer = models.ForeignKey('Customer', verbose_name="所咨询客户") note = models.TextField(verbose_name="跟进内容...") status = models.CharField("跟进状态", max_length=8, choices=seek_status_choices, help_text="选择客户此时的状态") consultant = models.ForeignKey("UserInfo", verbose_name="跟进人", related_name='records') #此处要用modelform,会将关联的UserInfo表中所有对应数据取出来,要想取出一部分,需要设置此字段的queryset date = models.DateTimeField("跟进日期",) delete_status = models.BooleanField(verbose_name='删除状态', default=False)
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>