django -- form
前戏
我们之前是通过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> {{ form_obj.user.label }} //只获取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_obj.errors }} 全局的错误提示 </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> {{ form_obj.user.label }} {{ form_obj.user }} {{ form_obj.user.errors }} 只对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 widgets
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 UUIDField(CharField) uuid类型
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”则认为非法