一、Form介绍
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在很多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等是否正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息。Django form组件就实现了上面所述的功能。
form组件的主要功能如下:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上次输入内容
1、普通方式手写注册功能
views.py
# 注册 def register(request): error_msg = "" if request.method == "POST": username = request.POST.get("name") pwd = request.POST.get("pwd") # 对注册信息做校验 if len(username) < 6: # 用户长度小于6位 error_msg = "用户名长度不能小于6位" else: # 将用户名和密码存到数据库 return HttpResponse("注册成功") return render(request, "register.html", {"error_msg": error_msg})
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> </head> <body> <form action="/reg/" method="post"> {% csrf_token %} <p> 用户名: <input type="text" name="name"> </p> <p> 密码: <input type="password" name="pwd"> </p> <p> <input type="submit" value="注册"> <p style="color: red">{{ error_msg }}</p> </p> </form> </body> </html>
2、使用form组件实现注册功能
views.py
#导入forms: from django import forms # 按照Django form组件的要求自己写一个RegForm类 class RegForm(forms.Form): name = forms.CharField(label="用户名") pwd = forms.CharField(label="密码") 再写一个视图函数: # 使用form组件实现注册方式 def register(request): form_obj = RegForm() if request.method == "POST": # 实例化form对象的时候,把post提交过来的数据直接传进去 form_obj = RegForm(request.POST) # 调用form_obj校验数据的方法 if form_obj.is_valid(): #print(form_obj.cleaned_data) user = form_obj.cleaned_data['user'] pwd = form_obj.cleaned_data['pwd'] gender = form_obj.cleaned_data['gender'] models.User.objects.create(user=user, pwd=pwd, gender=gender) #form_obj.errors #后台打印未通过校验的日志 return redirect('/index/') return render(request, "register2.html", {"form_obj": form_obj})
login2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册2</title> </head> <body> <form action="/reg2/" method="post" novalidate autocomplete="off"> #novalidate关闭前端校验功能 {% csrf_token %} <div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} </div> <div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div> <div> <input type="submit" class="btn btn-success" value="注册"> </div> </form> <span>{{ form_obj.errors }}</span> 所有字段的错误提示 </body> </html>
看网页效果发现 也验证了form的功能:
- 前端页面是form类的对象生成的 -->生成HTML标签功能
- 当用户名和密码输入为空或输错之后页面都会提示 -->用户提交校验功能
- 当用户输错之后再次输入上次的内容还保留在input框 -->保留上次输入内容
form_obj.name #name字段的input框
form_obj.name.label #name字段的中文显示内容
form_obj.name.id_for_label #name字段的id
form_obj.name.errors #name字段所有未通过校验的错误信息(是一个列表li格式)
form_obj.name.errors.0 #name字段第一个未通过检验的错误信息
form_obj.errors #所有字段的错误信息
当调用form_obj.is_valid()方法后,如果校验成功后,校验成功的值会以字典的形式保存在form_obj.cleaned_data中
可以从字典中获取到相应的key和value再保存到数据库中
if form_obj.is_valid(): print(form_obj.cleaned_data) 打印form_obj.cleaned_data可以看到通过校验后得到的字典 {'user': 'admin123', 'pwd': '11111111', 'gender': '1'} 将字段保存到数据库中 if form_obj.is_valid(): print(form_obj.cleaned_data) #user = form_obj.cleaned_data['user'] #pwd = form_obj.cleaned_data['pwd'] #gender = form_obj.cleaned_data['gender'] #models.User.objects.create(user=user, pwd=pwd, gender=gender) models.User.objects.create(**form_obj.cleaned_data) #可以直接将字典打散后保存到数据库中 return redirect('/index/')
二、常用字段与插件
创建Form类时,主要涉及到【字段】和【插件】,字段用于对用户请求数据的验证,widget插件用于自动生成HTML。
Django Form所有内置字段
Field #基类,所有其他类型的字段都继承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 ... 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) ChoiceField(Field) #单选 ... choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) required=True, 是否必填 widget=None, 插件,默认select插件 label=None, Label内容 initial=None, 初始值 help_text='', 帮助提示 MultipleChoiceField(ChoiceField) #多选 ... GenericIPAddressField protocol='both', both,ipv4,ipv6支持的IP格式 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 RegexField(CharField) regex, 自定制正则表达式 max_length=None, 最大长度 min_length=None, 最小长度 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} URLField(Field) ... BooleanField(Field) ... NullBooleanField(BooleanField) ... 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= '' 空值的默认值 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='' SlugField(CharField) 数字,字母,下划线,减号(连字符) ... UUIDField(CharField) uuid类型
常用字段类型
- CharField 默认生成input框
- ChoiceField 单选,默认生成select框
- MultipleChoiceField 多选,默认生成select框
- DateField 日期,格式:2015-09-01
常用属性:
- initial 初始值
- label 中文提示
- min_length 最小长度
- required 是否必填
- error_messages 自定义错误提示
- widget 插件
- choices 选择
1、CharField常用属性
1.1、initial
初始值,input框里面的初始值。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", #生成label标签并显示内容 initial="张三" # 设置默认值 ) pwd = forms.CharField(min_length=6, label="密码")
1.2、error_messages
重写错误信息。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码")
1.3、widget插件
PasswordInput
input输入框的type类型为password class LoginForm(forms.Form): ... pwd = forms.CharField( min_length=6, label="密码", widget=forms.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
2、ChoiceField常用插件类型, 默认生成select框
2.1、radioSelect
单radio值为字符串,自动生成ul里面的2个li,样式可通过css调整 class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码",widget=forms.widgets.PasswordInput()) gender = forms.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.RadioSelect() )
2.2、单选Select
下拉式单选框 class LoginForm(forms.Form): ... # 定义爱好 hobby = forms.ChoiceField(label='爱好', choices=((1,'抽烟'),(2,'喝酒'),(3,'烫头')), widget=forms.Select(), )
2.3、多选Select
下拉式多选框,按ctrl可多选 class LoginForm(forms.Form): # 定义爱好 hobby = forms.ChoiceField(label='爱好', choices=((1,'抽烟'),(1,'喝酒'),(3,'烫头')), widget=forms.SelectMultiple(), ) 也可以写成下面这种格式,直接使用MultipleChoiceField多选模式。 class LoginForm(forms.Form): hobby = forms.MultipleChoiceField( label='爱好', choices=((1,'抽烟'),(1,'喝酒'),(3,'烫头')), )
2.4、单选checkbox
class LoginForm(forms.Form): ... #是否记住密码 keep = forms.ChoiceField( label='记住密码', widget=forms.CheckboxInput(), )
2.5、多选checkbox
class LoginForm(forms.Form): ... # 定义爱好 hobby = forms.ChoiceField( label='爱好', choices=((1,'抽烟'),(2,'喝酒'),(3,'烫头')), widget=forms.CheckboxSelectMultiple(), ) #也可以直接使用MultipleChoiceField class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( label='爱好', choices=((1,'抽烟'),(2,'喝酒'),(3,'烫头')), initial=[1, 2], )
关于choice的注意事项:
问题1:
hobby字段中choice的内容如果想要从数据库中获取到的话,form中就要能够动态的从models中获取数据
hobby = forms.MultipleChoiceField( label='爱好', choices=models.Hobby.objects.all().vlaues_list('id','name'), #动态从models中的Hobby类中获取id和name这两个字段的内容 )
问题2:
在使用选择标签时,由于是静态字段获取的值无法实时更新,那么需要自定义构造方法从而达到此目的。
方式一:重写__init__方法
from django.shortcuts import render, HttpResponse from django import forms from app01 import models class MyForm(forms.Form): ... # 定义爱好 hobby = forms.ChoiceField( label='爱好', #这里就不需要写choices属性了,在下面的__init__方法中定义 widget=forms.CheckboxSelectMultiple(), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['hobby'].choices = models.Hobby.objects.all().values_list('id', 'name') #self.fields就是包含了所有字段的对象,是一个字典类型,可以通过[]获取hobby这个字段对象,再通过.choice
方式二:使用queryset方法
from django.shortcuts import render, HttpResponse from django import forms from app01 import models class MyForm(forms.Form): ... # 定义爱好,使用ModelMultipleChoiceField方法 hobby = forms.ModelMultipleChoiceField( label='爱好', #使用queryset方法 queryset=models.Hobby.objects.all() )
问题3:
{user.get_gender_display}
前端模板中有choice时,可以直接通过get_name_display方法显示id对应的内容
三、自定义校验器
方式一:
使用提供好的校验器,从django.core.validators源码里面可以看到所有的定义好的校验器
from django.forms import Form from django.core.validators import RegexValidator #RegexValidator导入正则校验器 class MyForm(forms.Form): user = forms.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], )
方式二:
自己写函数,定义校验器
import re from django.forms import Form from django.core.exceptions import ValidationError 例子1: def name_validate(value): #value就是需要校验的值 if 'sb' in vlaue: #校验value中是否存在'sb' raise ValidationErron('用户名格式错误,不符合社会主义核心价值观') #抛出的异常信息 例子2: def mobile_validate(value): #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 PublishForm(forms.Form): title = fields.CharField(max_length=20, min_length=5, error_messages={'required': '标题不能为空', 'min_length': '标题最少为5个字符', 'max_length': '标题最多为20个字符'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '标题5-20个字符'})) # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], 调用自定义校验器 error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = fields.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
校验顺序
orm_obj = RegForm(request.POST)
# 调用form_obj校验数据的方法,将request.POST提交的数据交给is_valid方法进行校验
if form_obj.is_valid():
#is_valid方法获取到数据后,
- 先进行内置校验器的校验,校验通过后
- 再看是否有自定义校验器的方法,校验通过后
- 再看是否有局部钩子自定义的校验器,局部钩子命名必须写成clean_对象名,例如clean_name,clean_pwd,不通过返回ValidationError错误信息,校验通过返回原来的值,
- 再看是否有全局钩子自定义的校验器,全局钩子直接调用clean方法,在里面写校验规则,不通过返回ValidationError错误信息,校验通过后返回cleaned_data字典,
- 全都校验通过后,返回cleaned_data字典
- is_valid里面存放了一个错误信息的字典,和一个正确信息的字典
四、局部钩子和全局钩子
局部钩子命名规则为clean_对象名称,例如上面定义了username、pwd对象,那么可以定义clean_username、clean_pwd的局部钩子进行规则校验
#例子:定义一个手机号校验的局部钩子
def clean_phone(self): value = self.cleaned_data.get('phone') # 没有通过校验规则 抛出ValidationError if not re.match(r'^1[3-9]\d{9}$',value): raise ValidationError('手机号格式不正确') # 通过校验规则 返回当前的值 return value
全局钩子
编写全局钩子预留的clean方法,可以获取到多个字段并进行校验
例子:定义一个校验2次输入密码是否一致的全局钩子
def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') # 通过校验 返回self.cleaned_data if pwd == re_pwd: return self.cleaned_data # 没有通过校验 抛出ValidationError self.add_error('re_pwd','两次密码不一致!!!!!!!') raise ValidationError('两次密码不一致')
前端可以通过fomr_obj.errors.__all__ 获取到内置校验器的全部错误信息
不能通过__all__来调取全局钩子的错误信息,需要通过对象的non_field_errors来获取错误信息列表
{{ form_obj.non_field_errors.0 }}
五、文件上传例子
a、自定义上传
def upload_file(request): if request.method == "POST": obj = request.FILES.get('fafafa') f = open(obj.name, 'wb') for chunk in obj.chunks(): f.write(chunk) f.close() return render(request, 'file.html')
b、Form上传文件实例
models.py from django.db import models class UploadFile(models.Model): userid = models.CharField(max_length = 30) file = models.FileField(upload_to = './%Y%m%d/upload/') date = models.DateTimeField(auto_now_add=True) views.py class FileForm(forms.Form): ExcelFile = forms.FileField() def UploadFile(request): uf = AssetForm.FileForm(request.POST,request.FILES) if uf.is_valid(): upload = models.UploadFile() upload.userid = 1 upload.file = uf.cleaned_data['ExcelFile'] upload.save() #print upload.file
六、form组件实现注册页面校验
urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^reg/$', views.reg), ]
views.py
from django.shortcuts import render, HttpResponse from django import forms # Create your views here. #定义form类 class RegForm(forms.Form): # 定义用户名对象 user = forms.CharField(label='用户名', #定义前端页面中label显示的内容 # widget=forms.widgets.TextInput(attrs=({'class': "form-control", 'placeholder':'Username'})),这两种写法都可以 #定义input的type类型,attrs为input定义class和placeholder等属性 widget=forms.TextInput(attrs=({'class': "form-control", 'placeholder':'Username'})), #定义验证条件,最小长度和最大长度 min_length=6, max_length=32, ) # 定义密码对象 pwd = forms.CharField(label='密码', # widget=forms.widgets.PasswordInput(attrs=({'class': 'form-control', 'placeholder':'Password'}))), widget=forms.PasswordInput(attrs=({'class': 'form-control', 'placeholder':'Password'})), min_length=6, max_length=32, ) def reg(request): #实例化form类 form_obj = RegForm() if request.method == 'POST': #将请求内容传递给实例化对象进行内容检验 form_obj = RegForm(request.POST) #判断检验结果,返回的结果是布尔值,如果验证通过返回True,如果验证未通过返回False if form_obj.is_valid(): user = form_obj.cleaned_data['user'] pwd = form_obj.cleaned_data['pwd'] models.User.objects.create(user=user, pwd=pwd) return redirect('/index/') #返回页面,并将实例化对象返回给页面进行渲染 return render(request, 'reg.html', {'form_obj': form_obj})
reg.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>reg</title> {% load staticfiles %} <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"> </head> <body> <div class="col-lg-4 col-lg-offset-4" style="margin-top: 100px"> <form class="form-horizontal" action="" method="post" style="height: 300px;" novalidate> {% csrf_token %} <div class="form-group"> {#label中通过id_for_label引入for名称,类.对象.label引入label内容描述 #} <label for="{{ form_obj.user.id_for_label }}" class="col-lg-2 control-label">{{ form_obj.user.label }}</label> {#生成input框,input框的class和placeholder等属性需要通过form组件定义后传递#} <div class="col-lg-10">{{ form_obj.user }}</div> {#显示对象报错的提示内容,也可以不通过form组件显示,通过js实现#} <span>{{ form_obj.user.errors }}</span> </div> <div class="form-group"> <label for="{{ form_obj.pwd.id_for_label }}" class="col-lg-2 control-label">{{ form_obj.pwd.label }}</label> <div class="col-lg-10">{{ form_obj.pwd }}</div> <span>{{ form_obj.pwd.errors }}</span> </div> <div class="form-group"> <div class="col-lg-10 col-lg-offset-2"> <button class="btn btn-default">注册</button> </div> </div> </form> </div> </body> </html>