表单,在前端页面中属于最常见的一个东西了。基本上网站信息的提交都用到了表单,所以下面来学习Django中优雅的表单系统:Form
表单的主要作用是在网页上提供一个图形用户页面,用作采集和提供用户输入数据。
表单的基本结构:
get:使用URL传参:http://服务器地址?name1 = value&name2=value2(?表示传递参数,?后面采用name=value的形式传递,多个参数之间,用&链接)URL传参不安全,所有信息可在地址栏看到,并且可以通过地址栏随机传递其他数据。URL传递数据量有限,只能传递少量数据。
post:使用HTTP请求传递数据。URL地址栏不可见,比较安全。且传递数据量没有限制。
表单提交中中get和post方式的区别
input标签是输入框,是表单中最重要的部分。
输入文本框
输入密码框
输入按钮
重置
提交
文件
多选框
代表多选框默认选择项
单选框,注意name需一样
时间
多选框
name:是指名字,因为提交的是键值对,所以必须要指定名字,否则无法提交,即使提交了也没有意义。
value:文本框的内容,一般用在不能输入的类型中,如改变按钮的名字等。
placeholder:占位内容,通常用于显示
readonly:只读模式,设置后无法修改输入框的内容
disabled:禁用状态
size:由于输入框是单行的,所以只能设置宽度
maxlength:限制输入框最大输入的字符个数
开发中表单提交是很常见的,表单的提交方式也有很多种。
1,使用submit按钮提交表单
2,使用button按钮提交表单
3,使用js进行表单提交,将form表单进行标记,将form表单中的某个元素设置成点击事件,点击时候调用js函数,再用JS。
$("#id").submit()
label元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果您在label元素内点击文本,就会触发此控件。也就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
带有两个输入字段和相关标记的简单HTML表单:
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类型
...
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
widget是form表单最重要的参数之一,指定渲染Widget时使用的widget类,举个例子:就是说这个form字段在HTML页面中为文本输入框,密码输入框,单选框,多选框。。。。。
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput()
单radio值为字符串
user_type_choice = (
(0, u'普通用户'),
(2, u'高级用户'),
)
user_type = forms.IntegerField(initial=2,
widget=forms.widgets.RadioSelect(choices=user_type_choice,))
user_type_choice = (
(0, u'普通用户'),
(2, u'高级用户'),
)
user_type = forms.IntegerField(initial=2,
widget=forms.widgets.Select(choices=user_type_choice,))
user_type_choice = (
(0, u'普通用户'),
(2, u'高级用户'),
)
user_type = forms.IntegerField(initial=[1, ],
widget=forms.widgets.SelectMultiple(choices=user_type_choice,))
user_type = forms.CharField(widget=forms.widgets.CheckboxInput())
值为列表
user_type_choice = (
(0, u'普通用户'),
(2, u'高级用户'),
)
user_type = forms.CharField( initial=[2, ],
widget=forms.widgets.CheckboxSelectMultiple(
choices=user_type_choice,
))
在使用选择标签的时候,需要注意choices的选项可以从数据库获取,但是由于是静态子弹,获取的值无法更新,那么需要自定义构造方法从而达到目的。
首先我们看一下下面的案例:
#/usr/bin/env python
#-*- coding:utf-8 -*-
from django.shortcuts import render
# Create your views here.
def user_list(request):
host = request.POST.get('host')
port = request.POST.get('port')
mail = request.POST.get('mail')
mobile = request.POST.get('mobile')
#这里有个问题,如果,这个from表单有20个input,你在这里是不是的取20次?
#验证:
#输入不能为空,并且有的可以为空有的不可以为空
#如果email = 11123123 这样合法吗?
#如果mobile = 11123123 这样合法吗?
#如果ip = 11123123 这样合法吗?
'''
你在这里是不是需要做一大堆的输入验证啊?并且有很多这种页面会存在这种情况,如果每个函数都这样做估计就累死了
'''
return render(request,'user_list.html')
针对于上面的问题,如何解决呢?——那就是Form表单
通常提交表单数据就是由HTML表单向后台传递信息,后台通过request.GET() 或者 request.POST()获取。
建一个Blog项目,并在template下新建两个html页面,一个注册页面命名为register,一个欢迎页面为welcome。
1,创建project
django-admin startproject Blog
2,创建APP
python manage.py startapp user1
3,修改settings配置
在INSTALLED_APPS中添加APP:user1
在TEMPLATES中查看“DIRS”内容,如果有template,请保持原样,如果没有,则添加
'DIRS': [os.path.join(BASE_DIR, 'templates')]
项目目录如下:
给template下两个html页面register和welcome填充内容。
register.html
{% csrf_token %}
用户名:
密码:
welcome.html
welcome to this page
在user1/models.py中创建表结构,代码如下:
from django.db import models
# Create your models here.
class BlogUser(models.Model):
username = models.CharField(max_length=200, unique=True)
password = models.CharField(max_length=200)
ChariField 字符串字段,用于较短的字符串,需要max_length来指定VARCHAR数据库字段的大小。
同时,修改Bolg/settings.py的内容:
找到下面这段代码,并注释掉:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
然后写入下面代码:
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
# 这里可以指定使用的数据库类型,例如mysql
'ENGINE': 'django.db.backends.mysql',
'NAME': 'blog_demo',
'USER':'root',
'PASSWORD':'******',
'HOST':'localhost',
'PORT':'3306',
}
}
如果这里不熟悉请参考:Django连接MySQL数据库
并且,映射数据库,这一步不能忘记:
在终端创建表
1,生成同步数据库的代码:
python manage.py makemigrations
2,同步数据库(也就是对数据库执行真正的迁移动作):
python manage.py migrate
我们重构views.py中的代码
from django.shortcuts import render
# Create your views here.
from user1.models import BlogUser
def register(request):
if request.method =='GET':
return render(request, 'register.html')
elif request.method == 'POST':
bloguser = BlogUser()
bloguser.username = request.POST.get('username')
bloguser.password = request.POST.get('password')
bloguser.save()
return render(request, 'welcome.html')
get() 中的username和password是取的register 里的username的值。以获取(request)注册的信息,保存为save()。
然后运行项目,并注册信息,代码如下:
1, 运行项目
python manage.py runserver
2,在浏览器中输入:
http://127.0.0.1:8000/user/register/
然后我们注册信息,并提交。我们可以看到上面的register.html中用户名是唯一的,所以当我们注册相同的名称时候回报错500,并且传输失败。
下面在数据库看一下我们的注册信息
其中,3,5不显示,因为我输入了相同的用户名。如果输入不重复,则显示下面界面。
在上面的基础上,我们再实现一个表单流程,继续了解form表单的知识。
from django.shortcuts import render, HttpResponse
# Create your views here.
from django import forms
class LoginForm(forms.Form):
account = forms.CharField()
password = forms.CharField()
email = forms.CharField()
def login(request):
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
return HttpResponse("登录成功")
else:
form = LoginForm()
return render(request, 'user6/login.html',{'form':form})
然后,去配置urls一些基本的配置(比如模板,路由等)。
我们直接点开login.html 内容如下:
我们打开Django项目,从127.0.0.1:8000/user6/login/ 进入,如下:
直接访问地址就显示出这样一个简单的界面,由HTML文件可以看到并没有js代码对数据有效性进行验证,我们随机输入账号,密码,邮箱,则提交,显示登陆成功。如下:
从上面的代码我们发现,前端一个 {{ form }} 就能做出一个完整强大的表单。但是我们只能用account password 做名称吗?
不是的,这里我们可以定制其名字,并且可以限制输入位数等等各种操作。
在form里有一个参数:error_messages 在他这里就可以定义报错内容
class UserInfo(forms.Form):
# required是否可以为空,如果为False,则说明可以为空
email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'})
# 如果required 不写默认为Ture
host = forms.CharField(error_messages={'required':u'主机不能为空'})
port = forms.CharField(error_messages={'required':u'端口不能为空'})
mobile = forms.CharField(error_messages={'required':u'手机不能为空'})
class UserInfo(forms.Form):
# required是否可以为空,如果为False,则说明可以为空
email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'})
# 如果required 不写默认为Ture
host = forms.CharField(error_messages={'required':u'主机不能为空'})
port = forms.CharField(error_messages={'required':u'端口不能为空'})
mobile = forms.CharField(error_messages={'required':u'手机不能为空'},
# 这里默认是TextInput 标签
widget = forms.TextInput(attrs={'class':'form-control',
'placeholder':u'手机号码'}))
class UserInfo(forms.Form):
# required是否可以为空,如果为False,则说明可以为空
email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'})
# 如果required 不写默认为Ture
host = forms.CharField(error_messages={'required':u'主机不能为空'})
port = forms.CharField(error_messages={'required':u'端口不能为空'})
mobile = forms.CharField(error_messages={'required':u'手机不能为空'},
# 这里默认是TextInput 标签
widget = forms.TextInput(attrs={'class':'form-control',
'placeholder':u'手机号码'}))
# 我们再这里新增一个备份
memo = forms.CharField(required=False,
widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备份'}))
同样的HTML代码也要新增:
主机 : {{ obj.host }}{{ errors.host }}
端口 : {{ obj.port }}{{ errors.port }}
邮箱 : {{ obj.email }}{{ errors.email }}
手机 : {{ obj.mobile }}{{ errors.mobile }}
备注:{{ obj.memo }}{{ errors.memo }}
import re
from django import forms
from django.core.exceptions import ValidationError
def mobile_validate(value):
# 正则匹配
mobile_re = re.compile(r'^(13[0-9]|15[0123456789]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError("手机号码格式错误")
class UserInfo(forms.Form):
# required是否可以为空,如果为False,则说明可以为空
email = forms.EmailField(required=False, error_messages={'required': u'邮箱不能为空'})
host = forms.CharField(error_messages={'required': u'主机不能为空'})
port = forms.CharField(error_messages={'required': u'端口不能为空'})
mobile = forms.CharField(# 应用我们自己定义的规则
validators=[mobile_validate,],
error_messages={'required': u'手机不能为空'},
# 这里默认使用TextInput 标签
widget=forms.TextInput(attrs={'class': 'form-control',
'placeholder': u'手机号码'}))
# 我们新增一个备份
memo = forms.CharField(required=False,
widget=forms.TextInput(attrs={'class': 'form-control',
'placeholder': u'备份'}))
def user_list(request):
# 创建了这个对象
obj = UserInfo()
if request.method == 'POST':
# 获取用户输入一句话就搞定
user_input_obj = UserInfo(request.POST)
# 判断用户输入是否合法
if user_input_obj.is_valid():
# 获取用户输入
data = user_input_obj.clean()
print(data)
else:
# 如果发生错误,捕获异常
# 这里原来什么都没写,默认是ul的样式,默认是as_ul(),
# 如果我们写成as_data()返回的就是一个原生的字符串
# 还有一个as_json
error_msg = user_input_obj.errors.as_data()
# 然后把错误信息返回
print(error_msg)
# 然后把对象传给html,在把错误信息传递过去
return render(request, 'user6/register.html', {'obj': obj, 'errors': error_msg,})
# 将对象传给html
return render(request, 'user6/register.html', {'obj':obj,})
当我们输入不合法的时候,或者不是email格式的时候,会出现如下错误:
这样后端我们就有一套验证的机制了,就是通过is_valid()来判断用户输入是否合法!如果不合法就把返回的信息发送出去,如果合法获取数据,继续操作即可!
class UserInfo(forms.Form):
user_type_choice = (
(0,u'普通用户'),
(1,u'高级用户'),
)
user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice,
attrs={'class':'form-control'}))
# required是否可以为空,如果为False,则说明可以为空
email = forms.EmailField(required=False,error_messages={'required':u'邮箱不能为空'})
# 如果required 不写默认为Ture
host = forms.CharField(error_messages={'required':u'主机不能为空'})
port = forms.CharField(error_messages={'required':u'端口不能为空'})
mobile = forms.CharField(error_messages={'required':u'手机不能为空'},
# 这里默认是TextInput 标签
widget = forms.TextInput(attrs={'class':'form-control',
'placeholder':u'手机号码'}))
# 我们再这里新增一个备份
memo = forms.CharField(required=False,
widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备份'}))
HTML内更改如下:
用户类型:{{ obj.user_type }}{{ errors.user_type }}
主机 : {{ obj.host }}{{ errors.host }}
端口 : {{ obj.port }}{{ errors.port }}
邮箱 : {{ obj.email }}{{ errors.email }}
手机 : {{ obj.mobile }}{{ errors.mobile }}
备注:{{ obj.memo }}{{ errors.memo }}
这个后端验证是必须要有验证机制的,前端可以不写但是后端必须写,前端的JS是可以被禁用掉的,在实际的生产环境中比如登录和验证的时候,我们一般都使用 JQuery+AJAX 来判断用户的输入是否为空,假如JS被禁用(浏览器端可以设置禁用JS效果)的话,我们这个认证屏障是不是就消失了呢?(虽然一般不会禁用但是还是存在风险),所以我们一般做两种认证,在前端做一遍认证,在后端做一遍认证。
import re
from django import forms
from django.core.exceptions import ValidationError
#自定义方法
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 UserInfo(forms.Form):
user_type_choice = (
(0, u'普通用户'),
(1, u'高级用户'),)
user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice,attrs={'class':'form-control'}))
email = forms.EmailField(required=True,error_messages={'required':u'邮箱不能为空'}) #required是否可以为空,如果为False说明可以为空
host = forms.CharField(error_messages={'required':u'主机不能为空'}) #如果required不写默认为Ture
port = forms.CharField(error_messages={'required':u'端口不能为空'})
#默认mobile里有一个默认为空的机制,我们在原有的参数里增加怎们自定义的方法
mobile = forms.CharField(validators=[mobile_validate,],#应用咱们自己定义的规则
error_messages={'required':u'手机不能为空'},
widget=forms.TextInput(attrs={'class':'form-control','placeholder':u'手机号码'})
#这里默认是TextInput,标签
)
#咱们在新增一个备注
memo = forms.CharField(required=False,
widget=forms.Textarea(attrs={'class':'form-control','placeholder':u'备注'}))
def user_list(request):
obj = UserInfo() #创建了这个对象
if request.method == 'POST':
#获取用户输入一句话就搞定
user_input_obj = UserInfo(request.POST)
if user_input_obj.is_valid(): #判断用户输入是否合法
data = user_input_obj.clean() #获取用户输入
print data
else:
#如果发生错误,捕捉错误
error_msg = user_input_obj.errors.as_data()#这里原来什么都没写,默认是ul的样式,默认是as_ul(),如果我们写成as_data()返回的就是一个原生的字符串
#还有一个as_json
print error_msg #打印一下然后看下他的类型
#然后把错误信息返回
return render(request,'user_list.html',{'obj':obj,'errors':error_msg,})#然后把对象传给html,在把错误信息传递过去
return render(request,'user_list.html',{'obj':obj,})#然后把对象传给html
def clean_username(self): # 函数名必须已clean_字段名的格式
user = self.cleaned_data.get("username")
if not User.objects.filter(username=user):
if not user.isdigit():
if re.findall(r"^[A-Za-z0-9_\-\u4e00-\u9fa5]+$",user):
return user # 通过检测,原数据返回 self.cleaned_data.get("username")
else:
raise ValidationError('用户名存在非法字符') # 没通过检测抛出错误,必须用ValidationError
else:
raise ValidationError("用户名不能为纯数字")
else:
raise ValidationError("该用户名已存在")
def clean(self): # 必须命名为clean
# 判断是否都通过检测,都不为None
if self.cleaned_data.get("password") and self.cleaned_data.get("check_pwd"):
if self.cleaned_data.get("password") == self.cleaned_data.get("check_pwd"):
return self.cleaned_data # 如果两次密码相同,返回干净的字典数据
else:
raise ValidationError("输入密码不一致") # 没通过检测返回异常信息
else:
return self.cleaned_data
参考文献:https://blog.csdn.net/qq_42068900/article/details/80904596