注册页面图解:
前端为了用户体验会做一些校验,不满足校验要求会报错
服务端也会对数据进行一些校验,不满足校验要求会报错
数据库也会对数据进行一些校验,不满足校验要求会报错
form组件和modleform组件就是让我们的数据校验过程更加简单一些,功能非常强大
1.能够帮我们生成HTML标签
2.标签中保留之前用户输入的数据
3.数据校验
- 1.在views.py文件中创建一个自定义的form类
from django import forms #需要先导入这个forms
class RegisterForm(forms.Form):
phone = forms.CharField()
username = forms.CharField()
password = forms.CharFied()
#用label参数可以将HTML中的label标签中内容显示为中文
phone = forms.CharField(label='手机号')
username = forms.CharField(label='用户名')
password = forms.CharField(label='密码')
def register(request):
if request.method == 'GET':
form = RegisterForm() #将上面的form类进行实例化
return render(request,'register.html',{'form':form})#将实例化后的对象返回到html中生成对应的属性的标签
- 3.创建一个html文件,比如叫作register.html,内容如下
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>注册页面</h1>
<form action='/register/' method='post' novalidate> #novalidate取消浏览器自带的错误提示
{% csrf_token %}
<div>
<!--{{ form.phone.id_for_label }}相当于绑定下面生成的input标签中的id属性 -->
<!-- {{ form.phone.label }}相当于把后台自定义的中文渲染到标签中 -->
<label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
<!-- {{form.phone}}相当于生成这个input标签<input type="text" name="phone" required="" id="id_phone"> -->
{{ form.phone }}
</div>
<div>
<label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
{{ form.username }}
</div>
<div>
<label for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
{{ form.password }}
</div>
<input type='submit'>
</form>
</body>
</html>
保留原数据和校验功能
class RegisterForm(forms.Form):
#每个字段,默认都有一个required=True的参数,表示该字段数据不能为空
#phone = form.CharField(label='手机号',required=True)
phone = forms.CharField(
label='手机号',
required=True,
#错误提示信息的自定制
error_messages={
'required':'小敏敏提示您,不能为空!',
}
)
username = forms.CharField(label='用户名')
password = forms.CharField(label='密码')
def register(request):
if request.method == 'GET':
form = RegisterForm()
return render(request,'register.html',{'form':form})
else:
print(request.POST)
form = RegisterForm(data=request.POST)
#把数据交给了RegisterForm,那么在通过form来渲染标签时,就将原来的的数据以默认值的形式(value='值')生成在了对应标签上
if form.is_valid(): #执行校验,如果所有数据都校验通过了,返回True,但凡有一个数据没有通过校验,返回False
print('通过校验的数据',form.cleaned_data)
return HttpResponse('ok')
print('错误信息>>>',form.errors)
return render(request,'register.html',{'form':form})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
<!-- 给错误信息定制一些css样式 -->
.error_msg{
font-size:12px;
color:red;
}
</style>
</head>
<body>
<h1>注册页面</h1>
<!-- form标签的这个novalidate属性,就是把浏览器自带的必填提示信息去掉 -->
<form action='/register/' method='post' novalidate>
{% csrf_token %}
<!-- {{ form.errors }} 表示存放着所有字段的错误信息 -->
<div>
<label for='{{ form.phone.id_for_label }}'>{{ form.phone.label }}</label>
{{ form.phone }}
<!-- form.phone.errors.0 表示展示一个错误信息 -->
<span class='error_msg'>{{ form.phone.errors.0 }}</span>
</div>
<div>
<label for='{{ form.username.id_for_label }}'>{{ form.username.label }}</label>
{{ form.username }}
<span class="error_msg">{{ form.username.errors.0 }}</span>
</div>
<div>
<label for='{{ form.password.id_for_label }}'>{{ form.password.label }}</label>
{{ form.password }}
<span class='error_msg'>{{ form.password.errors.0 }}</span>
</div>
<input type='submit'>
</body>
</html>
#生成input标签有个默认值
username = fomrs.CharField(label='用户名',initial='小红')
- widget 插件的意思,能够定制我们的标签显示效果
示例1 密文输入框
password = forms.CharField(
label='密码',
#CharField默认插件是TextInput相当于type='text',下面相当于修改type='password',完整写法forms.widgets.PasswordInput,下面是简写
widget = forms.PasswordInput,
)
示例2 给标签加上一些其他属性
password = forms.CharField(
label='密码',
#attrs={'标签属性':'属性值'}
widget=forms.PasswordInput(attrs={'class':'c1 c2','placeholder':'请输入密码'}),
)
- 生成单选下拉框 ChoiceField默认插件是widget=forms.Select
#sex = forms.fields.ChoiceField() fields可以不用写
sex = forms.ChoiceField(
choices =((1,'女'),(2,'男')),
#(1,'女')生成一个option标签,value值为1,文本内容为女
label ='性别',
# initial =2 #初始值
)
- 单选radio框 widget=forms.RadioSelect
sex = forms.ChoiceField(
choices=((1,'女'),(2,'男')),
label='性别',
#initial =2,
#widget=forms.Select, #ChoiceField的默认插件
#input,type='radio'的单选框
#widget=forms.RadioSelect,
#修改插件,并设置属性
widget=forms.RadioSelect(attrs={'class':'c1'})
)
- 多选下拉框 MultipleChoiceField
hobby = forms.MultipleChoiceField(
choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
label='爱好'
)
- 多选checkbox框 widget=forms.CheckboxSelectMultiple
hobby = forms.MultipleChoiceField(
choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
label = '爱好',
#widget=forms.CheckboxInput, #做单选功能的复(多)选框形式,必须勾选协议的那个选框
widget=forms.CheckboxSelectMultiple,
)
- 单选功能的复选框形式,像那种勾选同意协议的那个选框 widget=forms.CheckboxInput
hobby = forms.MultipleChoiceField(
label='同意勾选协议',
widget=forms.CheckboxInput,
)
bday = forms.CharField(
label='生日',
widget=forms.TextInput(attrs={'type':'date'}), 只要设置一个date属性就可以了
)
单选或者多选框使用数据库中的数据
- 方式1 forms.ModelChoiceField
models中的模型类
class Publish(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
form类中的字段写法
#生成选择框,并使用数据库中的数据 必须要用这个ModelChoiceField
publish = forms.ModelChoiceField(
queryset=models.Publish.objects.all(), #必须是queryset
)
会自动生成单选下拉框,并且option标签value属性对应的值,是queryset参数对应的queryset集合里面的models模型类对象的id值,option标签的文本内容是每个models模型类对象。
#生成标签的时候,页面显示第一层会有个自带的'--------',第二个往后才是数据
publish = forms.ChoiceField(
choices=models.Publish.objects.all().values_list('id','name')
#quertset[(1,'xx出版社'),]
)
#生成标签的时候,没有上面那个自带的'--------',直接就显示的是数据
required=True, 是否不为空
widget=None, HTML插件,设置插件相当于可以更改标签并设置属性
label=None, 用于生成label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息{'required':'不能为空',}
validators=[], 自定义验证规则
disabled=False, 是否可以编辑,默认False,可编辑
max_length=None, 最大长度
min_length=None, 最小长度
strip=True, 是否移除用户输入空白
max_value=None, 最大值
min_value=None, 最小值
...
- DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
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
...
regex, 自定义正则表达式
max_length=None 最大长度
min_length=None 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid':'...'}
error_messages={'invalid':'邮箱格式不正确'}
allow_empty_file=False 是否允许空文件
注:需要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='' 空值的默认值
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
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=''
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类型 复制代码
数据校验功能
格式不对的错误信息定制
'initial':'邮箱格式不对',
简单校验示例
form类
class DataForm(forms.Form):
#不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
label='用户名',
max_length=10,
min_length=4,
error_messages={
'required':'不能为空',
'max_length':'太长啦,受不了',
'min_length':'太短啦,不舒服',
}
)
#可以为空
phone = forms.CharField(
label='手机号',
required=False,
)
#要满足邮箱格式,不能为空
email = forms.EmailField(
label='邮箱',
error_messages={
'invalid':'邮箱格式不对', #invalid验证邮箱格式的
}
)
#提交的数据不能超出选项
sex = forms.ChoiceField(
label='性别',
choices=((1,'女'),(2,'男')),
widget=forms.RadioSelect,
)
#form只校验类中写的属性对应的那些数据
#向csrf_token这些不会校验
def data(request):
if request.method == 'GET':
form = DataForm()
return render(request,'data.html',{'form':form})
else:
form = DataForm(request.POST)
if form.is_valid(): #做校验,都通过返回True
#校验成功之后的数据form.cleaded_data里面不包含没有校验的数据,也就是不会校验form类中没有指定的属性对应的数据
print(form.cleaded_data)# {'username': 'chao', 'phone': '', 'email': 'asdf@xx.com', 'sex': '1'}
return HttpResponse('ok')
else:
print(form.errors)
return render(request,'data.html',{'form':form})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/data/" method="post" novalidate>
{% csrf_token %}
<div>
<label for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
{{ form.username }}
{{ form.username.errors.0 }}
</div>
<div>
<label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
{{ form.phone }}
{{ form.phone.errors.0 }}
</div>
<div>
<label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
{{ form.email }}
{{ form.email.errors.0 }}
</div>
<div>
<label for="{{ form.sex.id_for_label }}">{{ form.sex.label }}</label>
{{ form.sex }}
{{ form.sex.errors.0 }}
</div>
<input type="submit">
</form>
</body>
</html>
RegexValidator验证器
from django.core.validators import RegexValidator
class DataForm(forms.Form):
#不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
...
#RegexValidator('正则','不符合正则时的错误信息')
validators=[RegexValidator(r'^a','用户名必须以a开头'),RegexValidator(r'b$','用户名必须以b结尾')]
)
校验函数
import re
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
#ValidationError是django提供的一个数据校验失败的一个错误类型
#先定义一个函数,函数中我们可以做数据的校验,如果数据校验失败,我们可以raise ValidationError('错误信息')
def mobile_match(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('手机号格式有误')
使用
phone =forms.CharField(
label='手机号',
validators=[mobile_match,], #列表里面写我们自己定义的函数名
)
局部钩子
class DataForm(forms.Form):
#不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
label='用户名',
max_length=10,
min_length=4,
)
#可以为空
phone = forms.CharField(
required=False,
validators=[mobile_match,],
)
。。。
#局部钩子,能够对username这个字段的数据进行一个更高级的校验
#语法 clean_字段名称
def clean_username(self):
uname = self.cleaned_data.get('username') #clean_data.get 获取数据
if '666' in uname:
raise ValidationErroe('不能光喊6,没有用')
#如果校验通过,需要return这个值
return uname
def clean_phone(self):
pass
全局钩子
class DataForm(forms.Form):
#密码
password = forms.CharField()
#再次确认密码
confirm_password=forms.CharField()
#多个字段进行比较时,一般都是用全局钩子固定函数名clean
def clean(self):
p1 = self.clean_data.get('password')
p2 = self.clead_data.get('confirm_password')
if p1 != p2:
#直接raise错误,这个错误信息保存到了全局错误信息里面
#也就是self.errors里面
#所以要用add_error('属性名','错误提示信息')
self.add_error('confirm_password','抱歉,两次密码不一致')
self.add_error('password','抱歉,两次密码不一致')
#如果校验通过,必须return self.cleaned_data
return self.cleaned_data
源码部分,主要看clean_data,为什么会在没有走视图函数的时候,就能获取数据
def _clean_fields(self):
for name, field in self.fields.items():
# print(name, field)
# name属性名称,field是CharField()类的实例化对象
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
# 将CharField中的max_length,required..
value = field.clean(value)
# 将属性对应的CharField里面的简单校验规则进行了校验
self.cleaned_data[name] = value
#self.cleaned_data['username'] = 'abc'
if hasattr(self, 'clean_%s' % name): #clean_username
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
简单总结
首先对所有的我们自己定义的类中的属性进行循环,self.fields能够获取到所有的属性,循环过程中先对属性,比如username=form.CharField(max_length=12),里面的参数校验规则进行了校验,这个校验完成之后,给self.clean_data这个字典进行了赋值 self.clead_data['username'] ='sad'
然后对该字段的局部钩子进行了校验,在局部钩子中我们可以拿到self.clean_data['username']这个数据进行处理,别忘了局部钩子校验成功之后,要return这个字段的值,然后才进入下一次循环,处理下一个属性
当上面的所有循环执行完之后,才执行我们的全局钩子,全局钩子中校验成功之后别忘了return self.clean_data
静态文件配置和templates模板配置
可以在app01/app02应用文件夹下分别创建一个static名称的文件夹,存放自己应用的静态文件
可以在app01/app02应用文件夹下分别创建一个templates名称的文件夹,存放自己应用的html文件
django寻找html文件静态文件的查找顺序是先找总配置中的templates或者
statics文件夹,如果找不到对应文件,就去每个应用下的static或者templates去找对
应文件,顺序是按照settings.py配置文件中的INDSTALL_APP中注册app的顺序进行查
找,找到就返回,不在继续往下找了,所以要注意,最好在static或者templates下面创
建一个以应用名为名称的文件夹,将文件放到这里面,以防文件名冲突导致的问题。