摘要:
-
form组件
-
cookie组件
-
session组件
一、form组件
- form介绍我们之前在html页面中利用form表单向后端提交数据时候,都需要对用户的输入进行校验,比如校验用户是否输入正确(长度、格式...),如果用户输入的内容有误则需要在相应的位置显示对应的错误信息来提醒用户,提高前端的交互效率。Django form组件就是实现上述功能的一个功能组件。from组件主要功能有:# 生成页面可用的html标签# 对用户条件的数据进行校验# 保留上次输入内容
- 先来看看自己手写注册功能的过程:
# 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})# register.html!DOCTYPE html>
注册页面 -
校验数据
使用form组件实现注册功能:from django import forms# 按照Django form组件的要求自己写一个类class MyForm(forms.Form): name = forms.CharField(label='用户名', max_length=6) pwd = forms.CharField(label='密码', max_length=8, min_length=4)def register(request): form_obj = MyForm() if request.method == 'POST': # 实例化form对象的时候,把post提交过来的数据直接传进去 form_obj = MyForm(request.POST) # 调用form_obj校验数据的方法 if form_obj.is_valid(): # 这里is_valid()如果验证全部通过,则返回True return HttpResponse('注册成功') return render(request, 'register.html', { 'form_obj': form_obj})
这里我们使用PythonConsole来测试:
依次输入:(带#的为结果)from app01 import viewsform_obj = views.MyForm({ 'name': 'hhh', 'pwd': '123'})form_obj.is_valid()#Falseform_obj.errors#{'pwd': ['Ensure this value has at least 4 characters (it has 3).']}form_obj.cleaned_data#{'name': 'hhh'}
## form_obj.is_valid() 校验提交的信息,如果全部通过,则返回True,否则为False
## from_obj.errors 查看错误的信息,结果是一个字典格式,key为错误的字段,value为错误的原因,注意这里面是一个列表,说明原因可以有多个,这里需要说明:所有校验未通过的字段及错误信息提示都在这里放着,以键值对的形式存放。## form_obj.cleaned_data 查看校验通过的数据,这里存放这所有校验通过的字段及其值,以字典形式存放。特别补充:如果:多串字段:不进行校验,所以不会在cleaned_data和errors中 少传字段: 也会校验少传的字段,会在errors中,且该字段描述:{'pwd': ['This field is required.']}所有Form中的字段默认都是要校验,但是可以通过设置required = False来更过。 - 渲染标签
# viewsfrom django import forms# 按照Django form组件的要求自己写一个类class MyForm(forms.Form): name = forms.CharField(max_length=6) pwd = forms.CharField(max_length=8, min_length=4)def register(request): form_obj = MyForm() if request.method == 'POST': # 实例化form对象的时候,把post提交过来的数据直接传进去 form_obj = MyForm(request.POST) # 调用form_obj校验数据的方法 if form_obj.is_valid(): # 这里is_valid()如果验证全部通过,则返回True return HttpResponse('注册成功') return render(request, 'register.html', { 'form_obj': form_obj})
Title 可以看到,前面的Name和Pwd是Django自己渲染加上的,如果想要自定义,可以在MyForm类的对应字段里加上:label='用户名'和label='密码'即可
上面是第一种渲染方式,可以看出可拓展性较差
- 第二种渲染方式:多加几个校验字段
from django import forms# 按照Django form组件的要求自己写一个类class MyForm(forms.Form): name = forms.CharField(max_length=6, label='用户名') pwd = forms.CharField(max_length=8, min_length=4, label='密码') email = forms.EmailField(label='邮箱')
Title 只有input框!
加入input框前的名字:Title 此种方法还是有缺点,比如字段如果有100个,难道要100个全部输吗?所以应该还有更好的方法:
- 第三种渲染方式:
Title 这里提一下:如果想要取消前端校验,可以在form表单中加入:novalidate
这里需要知道: 1、如果Django创建的form表单在提交的时候数据不合法(校验不通过),则会自动保留填写的信息,不会清空。 2、Django创建的表单是没有提交按钮的,需要自己创建。接下来,添加校验不通过时候的提示信息:只需在恰当的位置添加Title 这里,提示错误信息在下面显示,如果想让它在每个字段的input框后面显示可以加个0
Title 继续走向个性化:提示的错误信息是英文,我看不懂,那就改成中文:
修改错误提示信息为自定义信息from django import forms# 按照Django form组件的要求自己写一个类class MyForm(forms.Form): name = forms.CharField(max_length=6, label='用户名', error_messages={ 'max_length': '用户名最大长度为6位', 'required': '用户名必须不能为空' }) pwd = forms.CharField(max_length=8, min_length=4, label='密码', error_messages={ 'max_length': '密码最大长度为8位', 'min_length': '密码最小长度为4位', 'required': '用户名必须不能为空' }) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱必须不能为空', 'invalid': '邮箱格式不合法' })
测试测试:
Title 这里有个小点需要说明:默认google浏览器的表单输入框如果要求最大6位,就只能输入6位最大,再多输入没有反应,这个为了测试方便,可以认为在每次提交前通过右键检查,人为删除限制的html代码
-
常用字段与插件:
这里系统的在补充一些Form类在创建的时候会涉及到的字段和插件,自对用于对用户请求数据的验证,插件用于自动生成htmlinitial
初始值,input框里面的初始值。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值 ) pwd = forms.CharField(min_length=6, label="密码")
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="密码")
PasswordInput给对应字段的input框设置属性,类....
pwd = forms.CharField(max_length=8, min_length=4, label='密码', error_messages={ 'max_length': '密码最大长度为8位', 'min_length': '密码最小长度为4位', 'required': '用户名必须不能为空' }, widget=forms.widgets.PasswordInput(attrs={ 'type': 'password'}))# 将input改成password类型,这样输入的就不是明文了
radioSelect
将下拉式选择框转变成单选形式
gender = forms.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect()
单选Select
class LoginForm(forms.Form): ... hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=3, widget=forms.widgets.Select() )
多选Select
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
单选checkbox
class LoginForm(forms.Form): ... keep = forms.ChoiceField( label="是否记住密码", initial="checked", widget=forms.widgets.CheckboxInput() )
多选checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
choice字段注意事项
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
方式一:
from django.forms import Formfrom django.forms import widgetsfrom django.forms import fields class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import formsfrom django.forms import fieldsfrom django.forms import models as form_model class FInfo(forms.Form): authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
- DjangoForm所有内置字段
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-01TimeField(BaseTemporalField) 格式:11:12DateTimeField(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类型Django Form内置字段
- Hook方法(钩子函数)钩子函数分为:局部钩子和全局钩子这里还需要注意:钩子函数是在对校验通过的字段进行进一步校验。所以需要写在字段下面
局部钩子
我们在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
# 局部钩子 def clean_name(self): name = self.cleaned_data.get('name') if '666' in name: # 用add_error添加错误提示 self.add_error('name', '666不对') return name
全局钩子
我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验。
# 输入两次密码一致性的全局钩子设置两个密码字段 pwd = forms.CharField(max_length=8, min_length=4, label='密码', error_messages={ 'max_length': '密码最大长度为8位', 'min_length': '密码最小长度为4位', 'required': '用户名必须不能为空' }, widget=forms.widgets.PasswordInput(attrs={ 'type': 'password'})) confirm_pwd = forms.CharField(max_length=8, min_length=4, label='密码', error_messages={ 'max_length': '密码最大长度为8位', 'min_length': '密码最小长度为4位', 'required': '用户名必须不能为空' }, widget=forms.widgets.PasswordInput(attrs={ 'type': 'password'}))# 全局钩子: def clean(self): pwd = self.cleaned_data.get('pwd') confirm_pwd = self.cleaned_data.get('confirm_pwd') if pwd != confirm_pwd: self.add_error('confirm_pwd', '密码两次输入不一致') return self.cleaned_data
- 最后说说注册的实际流程:也就是我们通过校验成功后如何将注册信息存入数据库:
def reg(request): # 生成一个空对象 form_obj = MyForm() if request.method == 'POST': print(request.POST) form_obj = MyForm(request.POST) if form_obj.is_valid(): # print(form_obj.cleaned_data) # 这里打散传值需要注意的是模型表字段名要与模型中的字段名相同,不然肯定会报错。 # 所以平时要养成习惯,很多地方需要一样,以便于代码的可读性 # models.User.objects.create(**form_obj.cleaned_data) return render(request,'reg.html',locals())
二、cookie与session
- Cookie介绍
Cookie的由来
大家都知道HTTP协议是无状态的。
无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。
状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。
什么是Cookie
Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
Cookie的原理
cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
查看Cookie
我们使用Chrome浏览器,打开开发者工具。
还可以在这里查看:
Django中操作Cookie
前提:我们在后端views中视图函数的返回参数render(),HttpResponse(),redirect(),通过查看它们的源码发现加括号后世界上返回的是一个对象,也就是我们返回到前端的是一个对象,以前我们是直接返回,现在要用到cookie和session就得在返回前对这个对象进行操作设置Cookie
# viewsdef login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'sgt' and password == '123': # 用户登录校验成功,下一步设置cookie,需要知道用哪一个网页使用cookie obj = redirect('/index/') # 设置cookie obj.set_cookie('name', 'sgt', expires=15) # 过期时间15秒 return obj # 这个obj就是进行操作,设置好cookie的redirect('/index/') return render(request, 'login.html')def index(request): return render(request, 'index.html')# 在login.html登录页面,进行登录,如果用户名和密码为sgt和123,则跳转到inde.html,同时在跳转之前设置cookie,通过在index页面右键检查,查看cookie可以看到cookie也就存在,说明设置成功
当然上面的设置cookie为了演示创建过程,并没有加任何安全措施,在实际使用中我们肯定会对他进行加密处理:做一下更安全性的操作处理:
obj.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
request.COOKIES['key']request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
- default: 默认值
- salt: 加密盐
- max_age: 后台控制过期时间
删除Cookie
def logout(request): obj = redirect("/login/") obj.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值 return rep
# 登录页面视图函数def login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'jason' and password == '123': old_path = request.GET.get('next') if old_path: # 如果原来页面有,则自动跳转 obj = redirect(old_path) else: # 如果是直接进入登录页面,不是从其它页面跳转的,则默认跳到指定主页面去 obj = redirect('/home/') # 用户登录成功 朝浏览器设置一个cookie obj.set_cookie('name','jason',expires=3600) # 这个兼容性更高 # obj.set_cookie('name','jason',max_age=5) return obj return render(request,'login.html')# 登录验证装饰器 from functools import wrapsdef login_auth(func): @wraps(func) def inner(request,*args,**kwargs): # 校验cookie # print(request.get_full_path()) old_path = request.get_full_path() # 拿到原来需要登录的页面路由 if request.COOKIES.get('name'): return func(request,*args,**kwargs) # 如果未登录,跳转到登录页面,同时在路由后面拼接老页面路由地址,用于登录成功自动跳转 return redirect('/login/?next=%s'%old_path) return inner@login_authdef index(request): # # print(request.COOKIES.get('name')) # if request.COOKIES.get('name'): return HttpResponse('我是index页面,只有登录了才能看')@login_authdef home(request): return HttpResponse('我是home页面,只有登录了才能看')@login_authdef xxx(request): return HttpResponse('我是xxx页面,只有登录了才能看')
-
Session介绍
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
-
session基本使用(设置,获取)
# 设置session:request.session['name']='jason'
设置session时候Django做了3件事:
①生成一个随机的字符串②在Django的session表存储随机字符串与数据记录③将随机字符串发送给浏览器# 获取session:request.session.get('name')
-
# Django中session的相关方法:
# 获取、设置、删除Session中数据request.session['k1']request.session.get('k1',None)request.session['k1'] = 123request.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的keyrequest.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失效策略。
# session解析
-
session版验证登录
# session版登录def login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'sgt' and password == '123456': # 设置session request.session['username'] = username # 跳转到登录页面之前的URL next_url = request.GET.get('next') # 这里需要判断一下用户是否是跳转过来登录的还是直接登录的 if next_url: # 如果有则跳转到原页面 return redirect(next_url) else: # 否则默认指定跳转到指定页面 return redirect('/index/') return render(request, 'login.html')# 登录验证装饰器:# 登录验证的过程实际上是基于已登录的状态进行校验的,也就是说,用户登录后,# 会在服务端生成一个cookie存储下来,同时把一份cookie再发给浏览器,# 当用户在某个跳转到某个需要登录次才能访问的页面时候,就会进行登录校验过程# 会将浏览器的cookie发到服务端,和服务端存储的cookie进行匹配,如果匹配到,就说明# 服务端已经记录这这个用户的登录状态,允许访问。from functools import wrapsdef login_auth(func): def inner(request, *args, **kwargs): # 获取到验证登录的当前页面的全部路由 next_url = request.get_full_path() # 如果改用户的session存在,说明已登录 if request.session.get('username'): return func(request, *args, **kwargs) # 发现未登录,则跳转到登录页面,同时路由后面拼接当前页面路由 else: return redirect('/login/?next=%s' % next_url) return inner@login_authdef logout(request): # 删除所有当前请求相关的session request.session.delete() return redirect("/login/")@login_authdef index(request): current_user = request.session.get("user", None) return render(request, "index.html", { "user": current_user})
- FBV加装饰器(前面cookie和session加装饰器都是使用FBV)
- CBV加装饰器:
# 需要导入一个method_decorator模块from django.utils.decorators import method_decorator其它都一样,主要在装饰的方法上有区别:# @method_decorator(login_auth,name='get') # 第二种 name参数必须指定 class MyHome(View): @method_decorator(login_auth) # 第三种 get和post都会被装饰 def dispatch(self, request, *args, **kwargs): super().dispatch(request,*args,**kwargs) # @method_decorator(login_auth) # 第一种 def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post')