之前我们已经学习了HTML中的基本标签——form表单,我们经常使用它向后台提交数据,常规的form表单是这样的:
<form action="" method="post"> <p>用户名:<input type="text" name="user"></p> <p>密码:<input type="text" name="pwd"></p> <P><input type="submit"></P> </form>
但是有许多时候我们这样使用非常不方便,为什么这样说了,上面的form表单中只有两个提交项,
但是实际中肯定不止两个,如果使用上面的方式,那么一旦有哪一个提交项不符合规定,整个form表单都得重写,
这是后用户肯定会抱怨,“凭什么错了一个全部得重写”,所以这样不方便,还有一个就是在对输入的东西值进行校验的时候,
每次都得先从前面取值,再在后台校验,烦不烦,能不能在后台生成得时候就设置好了?
这就是jango中form组件可以非常好的解决得问题。下面会一步一步解开解开它得面纱。
一、在Django中构建一个from表单
下面创建一个最简单的form表单:
from django.shortcuts import render #导入forms组件 from django import forms class LoginForm(forms.Form): user = forms.CharField(max_length=10) pwd = forms.CharField() def login(request): #实例化一个对象 form_obj = LoginForm() return render(request,"login.html",{"form_obj":form_obj})
在前端页面中展示form_obj:
{{ form_obj.as_p }}
as_p是一个特殊的属性,常见的有:
{{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中{{ form.as_p }}
将它们渲染在<p>
标签中{{ form.as_ul }}
将它们渲染在<li>
标签中
下面是效果:
可以看一下前端页面:
可以看到它自动给你生成了两个input标签,但是还缺一个提交按钮,form组件能不能做了,答案是不能,还得你自己写。
刚才展示了一种前端加载得方法,还有另外一种比较灵活得方法,不过需要我们添加更多的标签。
<form action="" novalidate method="post"> {% csrf_token %} <div> <label for="">用户名</label> {{ form_obj.user }} </div> <div> <label for="">密码</label> {{ form_obj.pwd }} </div> <input type="submit"> </form>
得到得效果就是:
貌似现在还看不出来form组件得作用,不急我们慢慢来。
注意:
(1)novalidate这个属性是关闭浏览器的自动校验。
(2)Django 原生支持一个简单易用的跨站请求伪造的防护。当提交一个启用CSRF 防护的POST
表单时,你必须使用上面例子中的csrf_token
模板标签
二、form组件的两大功能展示
1.保存之前的输入值
def login(request): #如果进行的是提交操作 if request.method == "POST": #再次实例化一个对象,但是与get请求不同的是,这里form_obj是有值。 form_obj = LoginForm(request.POST) return render(request, "login.html", {"form_obj": form_obj}) #实例化一个对象 form_obj = LoginForm() return render(request,"login.html",{"form_obj":form_obj})
注意:两个form_obj是不一样的,一个是带着值进行渲染的,一个是空值,这也是为什么提交不会清除掉原来的值得原因了。
2.校验
Form的实例具有一个is_valid()方法,它会对输入的字段进行校验,如果所有的字段都合法,那么他就会:
- 返回True
- 将表单的数据放到cleaned_data属性中。
在创建类的时候,我们其实可以给这个属性加上约束,就像这样:
user = forms.CharField(max_length=10,min_length=5) #最大长度为10,最小长度为5,这样就可以校验了
From实例的对象还有一个errors方法,是所有字段所有约束的错误信息,我们可以将其打印出来:
def login(request): #如果进行的是提交操作 if request.method == "POST": #再次实例化一个对象,但是与get请求不同的是,这里form_obj是有值。 form_obj = LoginForm(request.POST) if form_obj.is_valid(): return HttpResponse("登录成功") else: print(form_obj.errors) print(type(form_obj.errors)) print(form_obj.errors["user"]) print(type(form_obj.errors["user"])) return render(request, "login.html", {"form_obj": form_obj}) #实例化一个对象 form_obj = LoginForm() return render(request,"login.html",{"form_obj":form_obj})
下面是打印结果:
print(form_obj.errors) #这是个什么东西我们不知道 <ul class="errorlist"><li>user<ul class="errorlist"><li>Ensure this value has at least 5 characters (it has 3).</li></ul></li></ul> #打印一下类型 print(type(form_obj.errors)) #可以猜测出它是一个错误字典 <class 'django.forms.utils.ErrorDict'> #既然是字典,那么每个属性肯定就是键了,取出一个键 print(form_obj.errors["user"]) #这是什么我们也不知道,但是其中有英文报错 #最少5个,你只有3个 <ul class="errorlist"><li>Ensure this value has at least 5 characters (it has 3).</li></ul> #打印一下类型 print(type(form_obj.errors["user"])) #可以看到是一个列表,列表中的是各种约束的报错信息 <class 'django.forms.utils.ErrorList'> #在这里不管有几个报错信息,我们只取一个就够了,如果还有错误,接着改就是了,这也符合现实情况。 print(form_obj.errors["user"][0]) #成功取出了报错信息 Ensure this value has at least 5 characters (it has 3).
既然这样,在前端就可以显示了:
<div> <label for="">用户名</label> {{ form_obj.user }}<span>{{ form_obj.user.errors.0 }}</span> </div>
显示效果就是:
但是是英文的,可不可以是中文了?当然可以:
在属性的约束中有一个error_messages,可以让我们自行进行约束:
class LoginForm(forms.Form): user = forms.CharField(max_length=10,min_length=5, error_messages={ "min_length":"最小长度为5", "max_length":"最大长度为10", "required":"不能为空", #不能为空 })
结果如下:
能不能跳一跳样式了》这样不好看,这个后面再说,现在还有一个问题,密码问题,
我们发现输入的密码不能隐藏。
之所以一直没有举例,是因为文本输入有Charfield,并没有Passwordfield,在这里需要借助widget这个约束进行密码输入。
先进行导入:
from django.forms import widgets
然后设计类的时候调用:
pwd = forms.CharField( error_messages={ "required":"不能为空", }, widget=widgets.PasswordInput() )
效果如下:
在widgets中有很多限制,不是只有PasswordInput这一个,还有TextInput、CheckboxInput、NumberInput等。
在widgets中还可以进行属性的设置,例如:
pwd = forms.CharField( error_messages={ "required":"不能为空", }, widget=widgets.PasswordInput(attrs={"class":"active","id":"s1"}) )
这样就可以自行设计样式了,在前端查看:
三、高级进阶:钩子的设定
很多时候,我们想要自行设计校验的手段,该如果做了,Django给我们提供了钩子这一手段:
先来看一段源码:
if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value
这段源码是能够设置钩子的来源。
#局部钩子 def clean_user(self): val1 = self.cleaned_data.get("user") #如果这个字符串全部都是由数组组成 if not val1.isdigit(): return val1 else: # 注意这个报错信息已经确定了 raise ValidationError("用户名不能全部是数字组成")
还可以设置全局钩子:
def clean(self): pwd=self.cleaned_data.get("pwd") repeat_pwd=self.cleaned_data.get("repeat_pwd") if pwd==repeat_pwd: print("yes") return self.cleaned_data else: print("no") raise ValidationError("两次密码不一致!")
为什么有全局钩子了,因为每一个钩子都是和某个具体的字段绑定,只能获取自己的字段值,不能获取其他的值,所以需要全局钩子。
from django.shortcuts import render,HttpResponse # Create your views here. from django import forms from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.forms import widgets import re from appo1 import models class LoginForm(forms.Form): user = forms.CharField(min_length=6,max_length=12, error_messages={ "required":"用户名不能为空", "min_length":"最小长度为5", "max_length":"最大长度为12" }, widget=widgets.TextInput(attrs={"id":"s1"}) ) pwd = forms.CharField(min_length=6, error_messages={ "required":"密码不能为空", "min_length":"最小长度为6" } ) repeat_pwd = forms.CharField( error_messages={ "required": "必须输入", } ) email = forms.EmailField( error_messages={ "required": "", "invalid":"格式错误" } ) phone = forms.CharField( error_messages={ "required": "必须输入", } ) #必须以clear起手,否则不能尽心检测 def clean_user(self): import re val1 = self.cleaned_data.get("user") ret = re.findall(r'^_', val1) if not val1.isdigit(): if ret: return val1 else: raise ValidationError("必须以下划线开头") else: raise ValidationError("用户名不能全部为数字组成") def clean_pwd(self): val2=self.cleaned_data.get("pwd") if not val2.isdigit(): return val2 else: raise ValidationError("密码不能全部为数字!") def clean_phone(self): val3 = self.cleaned_data.get("phone") if val3.isdigit(): if len(val3) != 11: raise ValidationError("亲,号码是11位!") else: return val3 else: raise ValidationError("号码必须是数字!") def clean(self): pwd=self.cleaned_data.get("pwd") repeat_pwd=self.cleaned_data.get("repeat_pwd") if pwd==repeat_pwd: print("yes") return self.cleaned_data else: print("no") raise ValidationError("两次密码不一致!") def login(request): if request.method == "POST": form_obj = LoginForm(request.POST) if form_obj.is_valid(): user = request.POST.get("user") pwd = request.POST.get("pwd") email = request.POST.get("email") phone = request.POST.get("phone") ret = models.user_info.objects.create(user=user,pwd=pwd,email=email,phone=phone) return HttpResponse("你好,欢迎回来!") else: ret = form_obj.errors.get("__all__") #{{form_obj.repwd}} < span > {{form_obj.errors.repwd.0}}{{form_obj.non_field_errors.0}} < / span > #还可以在前端直接传 return render(request, "login.html", {"form_obj": form_obj,"ret":ret}) form_obj = LoginForm() return render(request,"login.html",{"form_obj":form_obj}) import json def user_check(request): response = {"is_reg": False} user = request.POST.get("user") ret = models.user_info.objects.filter(user=user) print(ret) if ret: response["is_reg"] = True
四、form组件补充知识
1.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类型 ...
你可以在里面选择属性的类型以及约束。
2.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
在witgits中选择使用
3.常用插件选择
# 单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 # )