Form组件-细节及补充
校验顺序: 1自己内部的校验规则——2局部钩子(按照字段的顺序来)——3全局钩子
视图函数中执行结果的细节
from django.shortcuts import render,HttpResponse ##引入校验的类 from app01.my_forms import UserForm #视图函数 def my_forms(request): if request.method == 'GET': form = UserForm() return render(request, 'index.html',{'form':form}) if request.method == 'POST': # print(request.POST) """ ############### 一、################# # 测验 f = UserForm({'name': 'whww', 'email': '123','xxx':'xxx'}) # print(f.is_valid())#返回布尔值,只有全校验成功才返回true if f.is_valid(): #如果全部交验成功,以字典形式返回键值对 print(f.cleaned_data) #{'name': 'whww', 'email': '123@qq.com'} else: #如果校验不成功,表示有不符合要求的字段 #校验成功的字段 print(f.cleaned_data)#{'name': 'whww'} # print(type(f.cleaned_data)) #<class 'dict'> #校验不成功的字段:格式——{'name':['.........']} print(f.errors)#<ul class="errorlist"><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul> # print(type(f.errors)) #<class 'django.forms.utils.ErrorDict'> print(f.errors.get('email')) #<ul class="errorlist"><li>Enter a valid email address.</li></ul> """ """ ################# 二、############## (1) if 所有字段校验成功,则f.cleaned_data, {'name': 'whww', 'email': '123@qq.com'} (2) 如果前面两个字段校验成功,那么后面的'xxx'就不校验了,也不管他,返回name与email校验的结果; 如果前面校验的字段不够,比如没有email,只有name跟xxx,会返回false (3) if没有校验成功: 正确的放在 f.cleaned_data ——>{'name':'whw',...} ——字典类型 错误的放在 f.errors ——>{'email':[............]} ——自定义的字典类型 jango.forms.utils.ErrorDict """ ##校验用户输入的信息 #form表单的name属性值应当与forms组件的字段名称一致 form = UserForm(request.POST) print(form.is_valid()) if form.is_valid(): print(form.cleaned_data) #只有全部验证成功了才执行 return HttpResponse('OK!') else: print(form.errors) #全局钩子错误 ##获取全局钩子错误 full_errors = form.errors.get('__all__') #带着参数返回当前页面 return render(request,'index.html',locals())
生成Html标签的三种方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Forms组件</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.css"> <link rel="stylesheet" href="/static/whw_css.css"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> {# 方式一 直接手写不用渲染 name属性的值要跟校验的form类的属性值一致#} <form action="" method="post"> {% csrf_token %} <p>用户名: <input type="text" name="name"></p> <p>密码: <input type="text" name="pwd"></p> <p>确认密码: <input type="text" name="r_pwd"></p> <p>邮箱: <input type="text" name="email"></p> <p>手机号: <input type="text" name="tel"></p> <input type="submit" value="提交"> </form> {# 方式二 forms组件的渲染方式一 这种方式相对自由~比如我想给用户名这里单独加一个“这个用户已经注册”的提示 可以用这种方式 #} ###注意这个novalidate 阻止浏览器自带的弹窗校验 <form action="" method="post" novalidate > {% csrf_token %} <p>用户名 {{ form.name }}<span class="pull-right">{{ form.name.errors.0 }}</span></p> <p>密码 {{ form.pwd }}<span class="pull-right">{{ form.pwd.errors.0 }}</span></p> <p>确认密码 {{ form.r_pwd }}<span class="pull-right">{{ form.r_pwd.errors.0 }}</span><span class="pull-right">{{ full_errors.0 }}</span></p> <p>邮箱 {{ form.email }}<span class="pull-right">{{ form.email.errors.0 }}</span></p> <p>手机号 {{ form.tel }}<span class="pull-right">{{ form.tel.errors.0 }}</span></p> <input type="submit" value="确认"> </form> {# 方式三 forms组件的渲染方式二 for循环批量添加~但是这种方式没办法为单独的标签添加额外的校验 #} <form action="" method="post"> {% csrf_token %} {% for f in form %} <p> {# 注意~这里for可以放id~打一个id pycharm会自动提示对应标签的id #} <label for="">{{ f.label }}</label> {{ f }} </p> {% endfor %} <input type="submit" value="确认"> </form> </div> </div> </div> </body> </html>
视图函数中的操作-获取校验数据、批量写入数据库
def index(request): if request.method == 'GET': #Get请求时实例化一个MyForm对象并用它去前端生成对应的标签 form_obj = MyForm() return render(request, 'index.html', {'form_obj': form_obj}) elif request.method == 'POST': post_data= request.POST # print(post_data) # 用POST中的值实例化一个MyForm对象 form_obj = MyForm(post_data) if form_obj.is_valid(): #验证每个字段传过来的数据是不是正确的 #cleaned_data方法 data = form_obj.cleaned_data #cleaned_data获取到的是一个是一个字典 print(data) #先把authors pop出来~因为书跟作者是多对多关系~需要单独处理,而且得到的author_data是一个QuerySet对象,里面装着选中的author对象们 author_data = data.pop('authors') print(author_data) #除了多对多关系 其他字段统一加入数据库中~将字典打散 book_obj = models.Book.objects.create(**data) #多对多关系 单独处理 book_obj.authors.add(*author_data) return HttpResponse('ok') else: # 校验不成功的话~带着错误信息与之前的数据再渲染前端页面~这样用户之前的输入的数据不会丢失而且能显示错误信息 return render(request, 'index.html', {'form_obj': form_obj})
关于正则校验
import re
# 引入正则相关抛出的异常 from django.core.validators import RegexValidator from django.core.exceptions import ValidationError #正则校验1 写一个函数 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') class Myform(forms.Form): title = forms.Charfield( label='书名', max_length=16, min_length=12, initial = 'xx', help_text = 'aaaa', required=False, error_messages={'max_length':'xxxx'.....}, widget = forms.widgets.TextInput(attrs={'type':'date'}), widget = forms.widgets.PasswordInput(attrs={'class':'...'},render_value=True), #正则校验1 在里面调用 validators = [RegexValidator(r'^a','错误信息'),mobile_validate,]
''' #正则校验2 或者可以直接在内部写简单的正则~但是要用RegexValidator方法抛出异常! validators=[RegexValidator(r'^a','必须以a开头'),],
'''
) price=forms.IntergerField...
关于__init__方法
在自定义的MyForm类中重写init方法,主要实现两个功能~批量添加样式常用~关于choices看下面总结的
class MyForm(forms.Form): ... #(1)指定choices值 注意必须有后面两个动态参数 def __init__(self,*args,**kwargs):
# 别忘了执行父类的init方法 super().__init__(*args,**kwargs) # 单选下拉框与单选radio self.fields['city'].choices = models.City.objects.all().values_list('pk','name') #[(1,'包头') #(2)批量添加样式 def __init__(self,*args,**kwargs):
# 别忘了执行一下父类的init方法 super().__init__(*args,**kwargs) for field in self.fields: self.fields[field].widget.attrs.update({ 'class': 'form-control' })
关于选择的下拉框
写死的方式
单选框
sex = forms.ChoiceField( choices=( ('1','男'), ('2','女'), ), # 注意 Select是原生的~加样式要在Select中加 # widget=forms.widgets.RadioSelect() #RadioSelect是拓展的~不可以加样式 widget=forms.widgets.Select(attrs={'class': 'form-control'}) )
复选框
authors = forms.MultipleChoiceField( choices=( ('1', 'whw'), ('2', 'naruto'), ('3','sasuke'), ), widget=forms.widgets.CheckboxSelectMultiple(), )
从数据库中提取数据—常用
单选框
publish = forms.ModelChoiceField( # 必须是queryset这个关键字参数,从数据库中获取到所有的数据 queryset=models.Publish.objects.all(), # widget=forms.widgets.Select(attrs={'class': 'form-control'}) widget=forms.widgets.Select() )
复选框
authors = forms.ModelMultipleChoiceField( queryset=models.Author.objects.all(), # widget = forms.widgets.SelectMultiple(attrs={'class': 'form-control'}) widget = forms.widgets.SelectMultiple() )
关于单选的checkbox——七天内自动登陆
单选的checkbox常用在登陆页面中~提示用户是否7天内自动登陆~
class TestForm2(forms.Form): # keep生成一个复选框标签 keep = forms.ChoiceField( # 注意 这里choices里的第一个值是True与False~后台只识别这两个! choices=( ('True',1), ('False',0), ), label="是否7天内自动登录", # 默认值是1 被选中 initial="1", # 样式 widget=forms.widgets.CheckboxInput(), )
单选的checkbox:
只提供两个值 ~字符串形式的True与False~~choices中元组的第一个值只能是这两个!后面的值可以是任意的
form只是帮我们做校验~校验选择内容的时候~就是看在没在我们的choices里面~里面有这个值表示合法~没有会在前端提示错误!
选中:'True'~form只是帮我们做校验,校验选择内容的时候,就是看在没在我们的choices里面,里面有这个值,表示合法,没有就不合法。其实就是看True与False。
没选中:'False'
---保存到数据库里面 keep:'True'
后台判断:
if keep == 'True': session 设置有效期7天 else: pass
局部钩子与全局钩子
局部钩子
——错误的话抛出异常;正确的话记得return val!
#局部钩子 #用户名是否已经存在/不能为纯数字 def clean_name(self): val = self.cleaned_data.get('name') #cleaned_data是一个有序字典 # 数据库中检测 ret = models.UserInfo.objects.filter(name=val) if ret: raise ValidationError('该用户名已经注册了!') elif val.isdigit(): raise ValidationError('用户名不能全为数字') else: return val #手机号必须为11位 def clean_phone(self): val = self.cleaned_data.get('phone') if len(val) == 11: return val else: raise ValidationError('手机号必须为11位') #邮箱已经注册过了 def clean_email(self): val = self.cleaned_data.get('email') # 数据库中检测 ret = models.UserInfo.objects.filter(email=val) if ret: raise ValidationError('这个邮箱已经注册过了!') else: return val
全局钩子
# 全局钩子 def clean(self): p1 = self.cleaned_data.get('password') p2 = self.cleaned_data.get('r_password') if p1 == p2: # 将cleaned_data全部返回 return self.cleaned_data else:
#将全局钩子的错误显示在某个字段的错误里 self.add_error('r_password','两次输入的密码不一致') # 需要注意全局钩子的错误在form_obj对象中~局部获取不到! # raise ValidationError('两次输入的密码不一致!') 这样写的话局部取不到
局部钩子的校验顺序问题
局部钩子中其实也可以校验其他的字段,但是要注意执行顺序!
像上面这样的定义的顺序~可以在birthday的局部钩子中的cleaned_data拿到password与r_password通过校验的值~在这里可以进行密码与确认密码的校验(当然在r_password的cleaned_data中当然也可以~只是为了说明问题)~~
但是,在name的局部钩子不能拿到password与r_password通过校验的值!因为name在两者之前!校验name的时候还没有他们两个校验后的值!
password字段输入错误后保留输入数据
# 针对password~输错的话会清空输入框~在widget设置render_value=True可以不清空 password = forms.CharField(max_length=20, label='密码', widget=widgets.PasswordInput(render_value=True,attrs={'class': 'form-control', 'placeholder': '密码'}), error_messages={'required': '输入不能为空', }, )
all