前戏
我们之前是通过html的form表单来提交数据,提交到服务器之后,我们需要对某些字段做判断,比如用户名密码的长度,格式正确不正确。如果用户输入的内容不正确就要在页面上显示对应的错误信息。当然我们可以通过if..elif来进行判断,但是这样写的话,代码很冗余。而Django的form组件就提供了我们这些校验的功能。
普通验证
先来看看不使用form来判断用户名不能小于6位长度是怎么做的
视图函数
def register(request):
error_msg = ''
if request.method=="POST":
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if len(user)<6:
error_msg='用户名长度不符合'
else:
error_msg = '注册成功'
return render(request, 'register.html',{'error_msg': error_msg})
html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>
用户名:<input type="text" name="user">
</p>
<p>
密码:<input type="text" name="pwd">
</p>
<p>
<input type="submit" value="提交">
<p style="color: red">{{ error_msg }}</p>
</p>
</form>
</body>
</html>
使用form组件进行验证
使用form验证,需要定义一个类,我们在views.py定义一个RegisterForm类
from django import forms
class RegisterForm(forms.Form): # 继承Form类
user = forms.CharField(label='用户名')
pwd = forms.CharField(label='密码')
在修改视图函数
def register(request):
form_obj = RegisterForm() # 实例化类
if request.method == "POST":
# 实例化form对象的时候,把post提交过来的数据直接传进去
form_obj = RegisterForm(request.POST) # form_obj就是提交的数据
# 调用form_obj校验数据的方法
if form_obj.is_valid():
return HttpResponse("注册成功")
return render(request, 'register.html',{'form_obj': form_obj})
修改html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
{{ form_obj.as_p }}
<button>注册</button>
</form>
</body>
</html>
这样,页面就生成了两个input框
我们可以通过 form_obj.cleaned_data 来获取页面输入的数据,这个必须要放在 form_obj.is_valid() 下面,要不然会报错。
上面的html文件还可以这样写
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>//只获取label值
{{ form_obj.user }} //一个input框
</p>
<p>
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
</p>
<button>注册</button>
</form>
</body>
</html>
接下来我们给字段加上长度校验,更改RegisterForm
class RegisterForm(forms.Form): # 继承Form类
user = forms.CharField(label='用户名', min_length=6)
pwd = forms.CharField(label='密码', min_length=6)
修改html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate> novalidate不校验
{% csrf_token %}
<p>
{{ form_obj.user.label }}
{{ form_obj.user }}
</p>
<p>
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
</p>
<button>注册</button>全局的错误提示
</form>
</body>
</html>
上面的form_obj.errors是全局的校验,用户名和密码都会校验,如果只想校验某个字段,只需要按照下面的方式写
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
<p>只对user输入框做校验
{{ form_obj.user.errors.0 }} 取错误的第一个
</p>
<p>
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
</p>
<button>注册</button>
</form>
</body>
</html>
常用字段和插件
插件是用于生成HTML的,例如上面生成的input框默认类型是“text”,我们可以使用插件让生成的input框是“password”
initial
input框里的默认值
from django import forms
class RegisterForm(forms.Form): # 继承Form类
user = forms.CharField(label='用户名',
min_length=6,
initial='zouzou'
)
error_message
自定义错误信息
from django import forms
class RegisterForm(forms.Form): # 继承Form类
user = forms.CharField(label='用户名',
min_length=6,
initial='zouzou',
error_messages={
"min_length":"长度不符合要求",
"required":"不能为空",
"invalid":"格式错误"
}
)
password
上面生成的input类型是type,我们使用插件来让生成的input标签的类型为pasword
首先需要导入
from django.forms import
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form): # 继承Form类
pwd = forms.CharField(label='密码',
min_length=6,
widget=widgets.PasswordInput()
)
当然,也可以设置属性
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'})
ChoiceField
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form): # 继承Form类
gender = forms.ChoiceField(
choices=(("1","男"),("2","女"))
)
默认是个下拉框,可以添加RadioSelect让它成为单选框
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form): # 继承Form类
gender = forms.ChoiceField(
choices=(("1","男"),("2","女")),
widget=widgets.RadioSelect
)
也可以让它成为一个多选
单选checkbox
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form): # 继承Form类
gender = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=widgets.CheckboxInput()
)
多选checkbox
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form): # 继承Form类
gender = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3], # 默认选择1和3
widget=forms.widgets.CheckboxSelectMultiple()
)
上面的是写死的,我们可以从数据库中获取数据
from django import forms
from django.forms import widgets
from appTest01 import models
class RegisterForm(forms.Form): # 继承Form类
gender = forms.ChoiceField(
choices=models.Press.objects.all().values_list('id','name'), # 从数据库中获取
label="性别",
widget=forms.widgets.CheckboxSelectMultiple()
)
需要说明的是,这样获取到的数据是项目重启之后再数据库里存在的值,如果项目启动之后你在数据库添加了新的数据,它不会显示在页面上的,如果想显示,我们就要重写构造方法
from django import forms
from django.forms import widgets
from appTest01 import models
class RegisterForm(forms.Form): # 继承Form类
def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['gender'].choices=models.Press.objects.all().values_list('id','name')
gender = forms.ChoiceField(
# choices=models.Press.objects.all().values_list('id','name'), # 从数据库中获取
label="爱好",
widget=forms.widgets.CheckboxSelectMultiple()
)
我们来看下self.fields是什么
print(self.fields)
OrderedDict([('user', <django.forms.fields.CharField object at 0x048D6B10>), ('pwd', <django.forms.fields.CharField object at 0x048D6B50>), ('gender', <django.forms.fields.ChoiceField object at 0x048D6B90>)])
1 Field
2 required=True, 是否允许为空
3 widget=None, HTML插件
4 label=None, 用于生成Label标签或显示内容
5 initial=None, 初始值
6 help_text='', 帮助信息(在标签旁边显示)
7 error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
8 validators=[], 自定义验证规则
9 localize=False, 是否支持本地化
10 disabled=False, 是否可以编辑
11 label_suffix=None Label内容后缀
12
13
14 CharField(Field)
15 max_length=None, 最大长度
16 min_length=None, 最小长度
17 strip=True 是否移除用户输入空白
18
19 IntegerField(Field)
20 max_value=None, 最大值
21 min_value=None, 最小值
22
23 FloatField(IntegerField)
24 ...
25
26 DecimalField(IntegerField)
27 max_value=None, 最大值
28 min_value=None, 最小值
29 max_digits=None, 总长度
30 decimal_places=None, 小数位长度
31
32 BaseTemporalField(Field)
33 input_formats=None 时间格式化
34
35 DateField(BaseTemporalField) 格式:2015-09-01
36 TimeField(BaseTemporalField) 格式:11:12
37 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
38
39 DurationField(Field) 时间间隔:%d %H:%M:%S.%f
40 ...
41
42 RegexField(CharField)
43 regex, 自定制正则表达式
44 max_length=None, 最大长度
45 min_length=None, 最小长度
46 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
47
48 EmailField(CharField)
49 ...
50
51 FileField(Field)
52 allow_empty_file=False 是否允许空文件
53
54 ImageField(FileField)
55 ...
56 注:需要PIL模块,pip3 install Pillow
57 以上两个字典使用时,需要注意两点:
58 - form表单中 enctype="multipart/form-data"
59 - view函数中 obj = MyForm(request.POST, request.FILES)
60
61 URLField(Field)
62 ...
63
64
65 BooleanField(Field)
66 ...
67
68 NullBooleanField(BooleanField)
69 ...
70
71 ChoiceField(Field)
72 ...
73 choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
74 required=True, 是否必填
75 widget=None, 插件,默认select插件
76 label=None, Label内容
77 initial=None, 初始值
78 help_text='', 帮助提示
79
80
81 ModelChoiceField(ChoiceField)
82 ... django.forms.models.ModelChoiceField
83 queryset, # 查询数据库中的数据
84 empty_label="---------", # 默认空显示内容
85 to_field_name=None, # HTML中value的值对应的字段
86 limit_choices_to=None # ModelForm中对queryset二次筛选
87
88 ModelMultipleChoiceField(ModelChoiceField)
89 ... django.forms.models.ModelMultipleChoiceField
90
91
92
93 TypedChoiceField(ChoiceField)
94 coerce = lambda val: val 对选中的值进行一次转换
95 empty_value= '' 空值的默认值
96
97 MultipleChoiceField(ChoiceField)
98 ...
99
100 TypedMultipleChoiceField(MultipleChoiceField)
101 coerce = lambda val: val 对选中的每一个值进行一次转换
102 empty_value= '' 空值的默认值
103
104 ComboField(Field)
105 fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
106 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
107
108 MultiValueField(Field)
109 PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
110
111 SplitDateTimeField(MultiValueField)
112 input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
113 input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
114
115 FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
116 path, 文件夹路径
117 match=None, 正则匹配
118 recursive=False, 递归下面的文件夹
119 allow_files=True, 允许文件
120 allow_folders=False, 允许文件夹
121 required=True,
122 widget=None,
123 label=None,
124 initial=None,
125 help_text=''
126
127 GenericIPAddressField
128 protocol='both', both,ipv4,ipv6支持的IP格式
129 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
130
131 SlugField(CharField) 数字,字母,下划线,减号(连字符)
132 ...
133
134
其余的内置字段
ModelForm
在上面我们使用form来验证了字段,假如我们注册的时候,数据表里的每个字段都要显示在页面上,难道我们需要一个一个写吗?答案肯定不是的,上面我们的类继承了Form,现在我们继承ModelForm就能达成我们的需求。
from django import forms
from crm import models
from django.core.exceptions import ValidationError
# 注册form
class RegForm(forms.ModelForm): # 继承ModelForm,比Form功能强大
'''
这里重写之后会把Meta里的覆盖,这里如果没写,Meta里写了,Meta里的也不会生效
'''
password = forms.CharField(
label='密码',
min_length=6,
widget=forms.widgets.PasswordInput,
error_messages={
'min_length': '最小长度为6位',
'required': '密码不能为空',
}
)
re_password = forms.CharField(
label='确认密码',
widget=forms.widgets.PasswordInput,
)
class Meta: # 展示内容的配置
model = models.UserProfile # 用户表
# fields = '__all__' # 页面上显示数据表里的所有字段
# exclude = ['',''] # 页面上不显示某些字段
fields = ['username', 'password', 're_password', 'name', 'department'] # 页面上指定显示某些字段
widgets = { # 使用插件改写密码输入框的type类型
'username': forms.widgets.EmailInput(attrs={'class':'form-control'}), # 把username输入框的type改为email,添加一个class属性form-control
'password': forms.widgets.PasswordInput, # 改写密码输入框的type类型为password
}
'''
定义页面显示的内容
'''
labels = {
'username':'用户名',
'password':"密码",
're_password': '确认密码',
'name':'姓名',
'department':'部门'
}
def __init__(self, *args, **kwargs): # 获取每一个字段,往里面加class属性
super().__init__(*args, **kwargs)
for filed in self.fields.values():
filed.widget.attrs.update({'class':'form-control'})
'''
这里判断密码和确认密码是不是相同,如果不同,给出错误信息
'''
def clean(self):
pwd = self.cleaned_data.get('password')
re_pwd = self.cleaned_data.get('re_password')
if pwd == re_pwd:
return self.cleaned_data
self.add_error('re_password','两次密码不一致') # 给re_password添加一个错误信息
raise ValidationError('两次密码不一致') # 抛出错误信息
ModelForm的强大之处不止于此,比如你要编辑一条数据,你是不是需要把这条数据查询出来,在输入框里显示,然后在进行编辑,之前我们查询出来之后,然后一个一个的循环显示在输入框里。ModelForm提供了简单的方法。
# 编辑客户
def edit_customer(request, edit_id):
# 根据id查询出要编辑的客户对象
obj = models.Customer.objects.filter(id=edit_id).first()
# 将查询到的对象和对应的html渲染
form_obj = CustomerForm(instance=obj) 重要
if request.method == 'POST':
# 将提交的数据和要修改的实例交给form对象
form_obj = CustomerForm(request.POST, instance=obj) 重要
if form_obj.is_valid():
# 修改后保存
form_obj.save()
return redirect(reverse('customer'))
return render(request,'crm/edit_customer.html',{'form_obj': form_obj})
说明: form_obj = CustomerForm(request.POST, instance=obj) 里如果没有instance=obj,则是新增
自定义校验规则
虽然Django里的Form给我们提供了一些常用的规则,但往往满足不了产品经理的sb需求,这时候就要我们自己定义校验规则了,Django给我们提供了两种校验规则,一种是可以通过正则的方式,另一种是自定义函数。
通过正则的方式
先来写个form验证的
from django import forms
from django.core.validators import RegexValidator
class Phone(forms.Form):
phone = forms.CharField(
label='手机号',
validators=[
RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')
]
)
这里要导入RegexValidator,然后在里面写正则,第一个参数是正则表达式,第二个是错误信息
在来写对应的视图函数
from appTest01 import forms
def register2(request):
form_obj = forms.Phone()
if request.method == 'POST':
form_obj = forms.Phone(request.POST)
if form_obj.is_valid():
return HttpResponse('注册成功')
return render(request, 'register1.html', {"form_obj": form_obj})
最后来写html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
{{ form_obj.phone.label }}
{{ form_obj.phone }}
<button>提交</button>
{{ form_obj.errors.phone.0 }}
</form>
</body>
</html>
这样我们就通过了正则来验证了我们输入的手机号是不是符合格式
通过自定义函数来验证
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
def check(value):
if 'ma' in value:
raise ValidationError('输入的有非法字符')
class Phone(forms.Form):
phone = forms.CharField(
label='手机号',
validators=[
check
]
)
其他的地方都不需要改,如果输入框里包含“ma”则认为非法