第八篇:forms组件,cookie和session
forms组件
常常用于注册页面的用户输入的数据进行校验
- 渲染页面
- 校验数据
- 展示信息
在使用forms组件的时候,需要在应用下新建一个py文件,例如:
myforms.py
在文件中写一个类:
from django import forms
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(max_length=8,min_length=3)
password = forms.CharField(max_length=8,min_length=3)
# email字段必须填写符合邮箱格式的数据
email = forms.EmailField()
forms校验数据
数据校验语法:
- .is_valid() 校验是否符合规则
- .cleaned_data 获取通过校验的数据
- .errors 获取校验失败以及校验失败的原因,返回的是一个li套ul的,前端展示会打乱布局
# 1.传入待校验的数据 用自己写的类 传入字典格式的待校验的数据
form_obj = views.MyRegForm({'username':'jason','password':'12','email':'123456'})
# 2.判断数据是否符合校验规则
form_obj.is_valid() # 该方法只有在所有的数据全部符合校验规则才会返回True
False
# 3.如何获取校验之后通过的数据
form_obj.cleaned_data
{'username': 'jason'}
# 4.如何获取校验失败及失败的原因
form_obj.errors
{
'password': ['Ensure this value has at least 3 characters (it has 2).'],
'email': ['Enter a valid email address.']
}
# 5.注意 forms组件默认所有的字段都必须传值 也就意味着传少了是肯定不行的 而传多了则没有任何关系 只校验类里面写的字段 多传的直接忽略了
form_obj = views.MyRegForm({'username':'jason','password':'123456'})
form_obj.is_valid()
Out[12]: False
form_obj.errors
Out[18]: {'email': ['This field is required.']}
# 多传的hobby字段,在MyRegForm类中并没有限制,因此,这里的hobby会被忽略,只校验类中的字段
form_obj = views.MyRegForm({'username':'jason','password':'123456',"email":'123@qq.com',"hobby":'hahahaha'})
form_obj.is_valid()
Out[14]: True
form_obj.cleaned_data
Out[15]: {'username': 'jason', 'password': '123456', 'email': '123@qq.com'}
form_obj.errors
Out[16]: {}
forms渲染标签
forms组件只帮忙渲染获取用户输入、选择、下拉、文件的标签,不渲染按钮和form表单的标签
渲染出来的每一个input提示信息都是类中字段,首字母大写(除非在类中的字段括号内利用label定义提示信息)
1、先写一个类
from django import forms
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(max_length=8,min_length=3)
password = forms.CharField(max_length=8,min_length=3)
# email字段必须填写符合邮箱格式的数据
email = forms.EmailField()
2、urls.py
url(r'^reg/', views.reg)
3、views.py
def teg(request):
# 1、先生成一个空的对象
form_obj = MyRegForm()
# 2、直接将该对象传给前端页面
return render(request, 'reg.html', locals())
4、在reg.html页面中进行测试
第一种渲染方式:多个p标签 本地测试方便 封装程度太高了 不便于扩展
# reg.html
{{ form_obj.as_p }}
# reg.html
{{ form_obj.as_ul }}
# reg.html
{{ form_obj.as_table }}
第二种渲染方式:扩展性较高,书写较为繁琐
{{ form_obj.username.id_for_label }} # 获取字段的id
{{ form_obj.username.label }} # 获取的是字段的提示信息User
{{ form_obj.username }} # 为类中的username产生一个input框
# reg.html
<label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>{{ form_obj.username }}
{{ form_obj.password.label }}{{ form_obj.password }}
{{ form_obj.email.label }}{{ form_obj.email }}
第三种渲染方式,推荐使用:
不管class类中定义多个字段,for循环下,所有的字段都可以渲染出来
# reg.html
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>
{% endfor %}
forms渲染错误信息(提示信息)
1、前端自动校验
前端代码:
# reg.html
<form action="" method="post">
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p>
{% endfor %}
<input type="submit">
</form>
后端代码:
# views.py
def reg(request):
# 1、先生成一个空的对象
form_obj = MyRegForm()
if request.method == 'POST':
# 3、获取用户输入的所有数据并交给forms组件校验
form_obj = MyRegForm(request.POST) # 对上面的空对象重新赋值
# 4、获取校验结果
if form_obj.is_valid():
return HttpResponse('数据没有问题!')
else:
# 5.后端打印失败字段和提示信息
print(form_obj.errors)
# 2、直接将该对象传给前端页面
return render(request, 'reg.html', locals())
数据校验必须前后端都有,但是前端校验弱不禁风,可有可无,而后端的校验则必须要非常全面
因此们可以取消浏览器自动帮我们校验的功能:
# form表单取消前端浏览器自动校验功能
<form action="" method="post" novalidate> # 在form标签内设置novalidate
前端代码:
<form action="" method="post" novalidate>
{% for form in form_obj %}
<p>
{{ form.label }}{{ form }}
<sanp>{{ form.errors.0 }}</sanp>
</p>
{% endfor %}
<input type="submit">
</form>
前端代码form.errors如果不索引0,那么取出的是一个ul套li的形式,会打乱页面布局:
def reg(request):
# 1、先生成一个空的对象
form_obj = MyRegForm() # 第一次get请求传入空字典,只是为了渲染页面
if request.method == 'POST':
# 3、获取用户输入的所有数据并交给forms组件校验
form_obj = MyRegForm(request.POST) # 对上面的空对象重新赋值,此时post请求,是为了校验
# 4、获取校验结果
if form_obj.is_valid():
return HttpResponse('数据没有问题!')
else:
# 5.后端打印失败字段和提示信息
print(form_obj.errors)
# 2、直接将该对象传给前端页面
return render(request, 'reg.html', locals())
效果如下:
但我们发现,上面的效果并不是很好,提示信息为英文,报错信息也没办法自定义,input框中输入密码也是明文,因此我们需要更近一步的完善,为此我们需要forms组件中的常用参数。
forms组件中常用参数
- label input框的提示信息
- error_messages 自定义报错的提示信息
- required 设置字段是否允许为空
- initial 设置默认值
- widget 控制type类型及属性(调整input框的样式)
label设置input框的提示信息:
# 在定义的类中的字段括号内添加参数
username = forms.CharField(max_length=8, min_length=3, label='用户名:')
效果如下:
error_messages自定义报错信息:
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(
max_length=8, min_length=3, label='用户名:',
error_messages={
'max_length': '用户名最长8个字符!',
'min_length': '用户名最短3个字符!',
'required': '用户名不能为空!',
}
)
# 设置邮箱校验
# email字段必须填写符合邮箱格式的数据
email = forms.EmailField(label='邮箱',error_messages={
'required':'邮箱必填',
'invalid':'邮箱格式不正确'
})
字段默认必填,可以通过required设置字段为空:
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(
max_length=8, min_length=3, label='用户名:',
error_messages={
'max_length': '用户名最长8个字符!',
'min_length': '用户名最短3个字符!'
},
required=False
)
initial设置为默认值:
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(
max_length=8, min_length=3, label='用户名:',
error_messages={
'max_length': '用户名最长8个字符!',
'min_length': '用户名最短3个字符!'
},
required=False, initial='我是默认值'
)
widget控制type类型及属性(调整input框的样式)
# 设置widget=forms.widgets.passwordInput(),使密码变为密文显示
# attr设置input框的样式
password = forms.CharField(max_length=8,min_length=3,label='密码',
widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
)
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})
全局或局部钩子
使用全局或者局部钩子,其实相当于第二层的校验,多了一层校验
全局钩子:针对多个字段,一般用于第二次确认密码是否一致
局部钩子:针对单个字段,校验用户名中不能包含666
# 定义类
class MyRegForm(forms.Form):
# 用户名最少3位最多8位
username = forms.CharField(max_length=8, min_length=3, label='用户名:')
password = forms.CharField(max_length=8, min_length=3, label='密码:')
confirm_password = forms.CharField(max_length=8, min_length=3, label='密码:')
# 全局钩子
def clean(self):
# 校验密码和确认密码是否一致
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
# 展示提示信息
self.add_error('confirm_password','两次密码不一致')
return self.cleaned_data
# 局部钩子
def clean_username(self):
username = self.cleaned_data.get('username')
if '666' in username:
self.add_error('username','光喊666是不行的')
return username
# 如果你想同时操作多个字段的数据你就用全局钩子
# 如果你想操作单个字段的数据 你就用局部钩子
forms补充知识点
正则校验
RegexValidator
phone = forms.CharField(
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
]
)
利用forms组件校验注册功能
首先在应用下新建一个myforms.py
文件
# myforms.py
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(
min_length=3, max_length=8, label='用户名:',
error_messages={
'min_length': '用户名最短3个字符!',
'max_length': '用户名最长8个字符!',
'required': '用户名不能为空!'
},
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(
min_length=3, max_length=8, label='密码:',
error_messages={
'min_length': '密码最短3个字符!',
'max_length': '密码最长8个字符!',
'required': '密码不能为空!'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
confirm_password = forms.CharField(
min_length=3, max_length=8, label='确认密码:',
error_messages={
'min_length': '确认密码最短3个字符!',
'max_length': '确认密码最长8个字符!',
'required': '确认密码不能为空!'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
email = forms.EmailField(
label='邮箱',
error_messages={
'required': '邮箱不能为空',
'invalid': '邮箱格式不正确'
},
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
# 钩子函数
# 局部钩子校验用户名是否已存在
def clean_username(self):
username = self.cleaned_data.get('username')
is_alive = models.UserInfo.objects.filter(username=username)
if is_alive:
self.add_error('username', '用户名已存在!')
return username
# 全局钩子校验密码与确认密码是否一致
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if password != confirm_password:
self.add_error('confirm_password', '两次密码不一致!')
return self.cleaned_data
在views.py中书写逻辑代码:
# views.py
from app01.myforms import MyRegForm
def register(request):
# 1、先生成form_obj对象
form_obj = MyRegForm()
# if request.is_ajax(): # 判断当前请求是否是ajax
if request.method == 'POST':
# 定义一个与ajax回调函数交互的字典
back_dic = {'code': 1000, 'msg': ''}
# 校验数据 用户名 密码和确认密码
form_obj = MyRegForm(request.POST)
if form_obj.is_valid():
clean_data = form_obj.cleaned_data # 用变量接收正确的结果,clean_data = {'username', 'password', 'confirm_password', 'email'}
# 将确认密码的键值对删除
clean_data.pop('confirm_password')
# 获取用户头像文件
avatar_obj = request.FILES.get('avatar')
# 判断用户头像是否为空
if avatar_obj:
# 添加到clean_data中
clean_data['avatar'] = avatar_obj # clean_data = {'username', 'password', 'avatar_obj', 'email'}
models.UserInfo.objects.create_user(**clean_data)
back_dic['msg'] = '注册成功!'
back_dic['url'] = '/login/'
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic)
# 2、将form_obj对象返回给html页面
return render(request, 'register.html', locals())
html页面
# register.py
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<div class="row">
<h2 class="text-center">注册页面</h2>
<div class="col-md-8 col-md-offset-2">
<form id="myform">
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.id_for_label }}">{{ form.label }}</label>
{{ form }}
<span style="color: red" class="pull-right"></span>
</div> {% endfor %}
<div class="form-group">
<label for="id_avatar">用户头像:<img src="/static/image/default.jpg" alt="" width="80" style="margin-left: 10px" id="id_img"></label>
<input type="file" name="myfile" id="id_avatar" style="display: none">
</div>
<input type="button" value="取消" class="btn btn-default btn-sm">
<input type="button" value="注册" class="btn btn-primary btn-sm pull-right" id="id_submit">
</form>
</div> </div></div>
<script>
$('#id_avatar').change(function () {
// 1、先获取用户上传的头像文件
var avatarFile = $(this)[0].files[0];
// 2、利用文件阅读器对象
var myFileReader = new FileReader();
// 3、将文件交由阅读器对象读取
myFileReader.readAsDataURL(avatarFile);
// 4、修改img标签的src属性,等待文件阅读器对象读取文件之后再操作img标签
myFileReader.onload = function(){
$('#id_img').attr('src', myFileReader.result)
}
});
// 点击按钮触发ajax提交动作
$('#id_submit').on('click', function () {
// 1、先生成一个内置对象 FormData var myFormData = new FormData();
// 2、添加普通键值对
{#console.log($('#myform').serializeArray())#}
$.each($('#myform').serializeArray(), function (index, obj) {
myFormData.append(obj.name, obj.value)
});
// 3、添加文件数据
myFormData.append('avatar', $('#id_avatar')[0].files[0])
// 4、发送数据
$.ajax({
url: '',
type: 'post',
data: myFormData,
// 两个关键性参数
contentType: false,
processData: false,
success: function (data) {
if (data.code===1000){
// 注册成功之后 应该跳转到后端返回过来的url
location.href = data.url
}else{
$.each(data.msg, function (index, obj) {
// 1、先手动拼接字段名所对应的input框的id值
var targetId = '#id_' + index; // id_username
// 2、利用id选择器查找标签
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
});
$('input').focus(function () {
// 移除span标签内部的文本 还需要移除div标签的class中的has-error属性
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
django的cookie和session
cookie:保存在服务端的键值对
session:保存在服务端上的键值对
cookie与session的作用:
- 解决http协议的无状态特征
- 保存信息
cookie产生
当用户第一次登陆成功之后,服务端会返回一个随机字符串,保存在客户端浏览器上,之后再次超服务端发送请求,只需要携带该随机字符串,服务端就能够识别当前用户身份。
cookie可以设置超时时间,超过一定的时间,cookie就会失效。
cookie虽然是保存在客户端浏览器上的,但是是服务端设置的,浏览器也是可以拒绝服务端的要求,不保存cookies。
操作cookie其实就是在操作HttpResponse(render,redirect,,,一次请求一次响应):
obj = HttpResponse('...')
return obj
obj1 = render(...)
return obj1
obj2 = redirect(...)
"""
通过对象的方式进行键值对的赋值
"""
# 设置cookie
obj.set_cookie()
# 获取cookie
request.COOKIES.get()
# 删除cookie
obj.delete_cookie()
Django中操作Cookie
获取Cookie
request.COOKIES['key']
request.COOKIES.get(key)
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
- default: 默认值
- salt: 加密盐
- max_age: 后台控制过期时间
设置Cookie
rep = HttpResponse(...)
rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)
参数:
- key, 键
- value='', 值
- max_age=None, 超时时间
- expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
- path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
- domain=None, Cookie生效的域名
- secure=False, https传输
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
删除Cookie
def logout(request):
rep = redirect("/login/")
rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值
return rep
利用cookie写登录功能
1、先定义路由和视图的关系
# urls.py
url(r'^login/', views.login)
2、编写登录页面
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<form action="" method="post">
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<input type="submit">
</form>
</body>
</html>
3、编写视图函数
# views.py
# 登录函数
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 保存用户登录状态
obj = redirect('/home')
# 设置cookie
obj.set_cookie('username', 'jason')
return obj
return render(request, 'login.html')
# 功能函数
def home(request):
# 检验浏览器是否有对应的cookie
if request.COOKIES.get('username'):
return HttpResponse('我是home页面,只要登录成功就可以看到我。')
else:
return redirect('/login')
上面简单的写了利用cookie的登录功能,但是如果有很多功能都需要验证是否携带cookie,我们不可能在每个功能中去验证,因此需要我们写一个登录认证装饰器。
登录认证装饰器版
在views.py
中添加登录认证装饰器
# 登录认证装饰器
from functools import wraps
def login_auth(func):
@wraps(func)
def inner(request, *args, **kwargs):
if request.COOKIES.get('username'):
res = func(request, *args, **kwargs)
return res
else:
return redirect('/login')
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 保存用户登录状态
obj = redirect('/home')
# 设置cookie
obj.set_cookie('username', 'jason')
return obj
return render(request, 'login.html')
@login_auth
def home(request):
return HttpResponse('我是home页面,只要登录成功就可以看到我。')
高级版登录功能
需求一:
当用户访问A网站,但是发现没有登录,将用户跳转到登录页面,用户登录成功后,自动跳转到原来想访问的A页面。
需求二:
当用户没有访问其他页面,直接访问登录页面,登录成功后,将用户跳转到home页面。
request的方法:
通过request拿到用户想要访问的路径。
print('request.path_info:',request.path_info) # 只拿路径部分 不拿参数
print('request.get_full_path():',request.get_full_path()) # 路径加参数
# 打印结果
request.path_info: /home/
request.get_full_path(): /home/?username=jason&password=123
views.py
文件改动代码如下:
# 登录认证装饰器
from functools import wraps
def login_auth(func):
@wraps(func)
def inner(request, *args, **kwargs):
target_url = request.path_info # 获取路径
if request.COOKIES.get('username'):
res = func(request, *args, **kwargs)
return res
else:
return redirect(f'/login/?next={target_url}') # 将路径拼接到url后面
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# 第一种方式,若next后面没有路径,直接跳转到默认值
# target_url = request.GET.get('next', '/home/')
# 第二种方式,对next后面有没有路径进行判断
target_url = request.GET.get('next')
if target_url:
# 保存用户登录状态
obj = redirect(target_url)
else:
obj = redirect('/home/')
# 设置cookie
obj.set_cookie('username', 'jason')
return obj
return render(request, 'login.html')
退出登录功能,清除cookie
urls.py
新退出登录的路由与视图的关系
# urls.py
url(r'^logout/', views.logout)
views.py
中新增退出的相关视图函数
# views.py
@login_auth
def logout(request):
obj = HttpResponse('注销成功!')
obj.delete_cookie('username')
return obj
设置cookie的超时时间
obj.set_cookie('username', 'jason', max_age=3)
session语法
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
# 会话session的key
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
这用于确保前面的会话数据不可以再次被用户的浏览器访问
例如,django.contrib.auth.logout() 函数中就会调用它。
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
保存在服务端上的键值对
设置:request.session['key'] = value
- django内部会自动生成一个随机字符串
- 去django_session表中存储数据,键就是随机字符串,值就是要保存的数据(中间件干的)
- 将生成的随机字符串返回给客户端浏览器,浏览器保存键值对(sessionid:随机字符串)
# urls.py
url(r'^set_session/', views.set_session)
# views.py
def set_session(request):
request.session['username'] = 'sean'
return HttpResponse('set_session')
获取:request.session.get('key')
- django会在自动取出浏览器的cookie查找sessionid键值对,获取随机字符串
- 拿着该随机字符串去django_session表中对比数据
- 如果对比上了,就讲随机字符串对应的数据获取出来并封装到request.session供用户调用
# urls.py
url(r'^get_session/', views.get_session)
# views.py
def get_session(request):
print(request.session.get('username'))
return HttpResponse('get_session')
django中默认的超时时间为14天