Title

forms组件的介绍以及cookie和session介绍

一.新手示例引导

需求:在页面做一个输入用户名和密码的input框,用户输入后数据提交到后台进行校验,并且用户名中不能包含 ‘帅’ 以及密码不能为空,提示信息分别为 ‘你能不能有点笔数’‘密码不能为空啊兄得’

逻辑思路:

  • 渲染页面:手写获取用户信息的前端代码

  • 逻辑校验:获取前端发送回来的用户信息并进行逻辑校验

  • 展示信息:将校验后需要展示的信息发送到前端进行渲染

代码如下

 1 def register(request):
 2     # 提前建立好一个空字典,可以往前端发送
 3     error_dict = {'username':'','password':''}
 4     if request.method == 'POST':
 5         username = request.POST.get("username")
 6         password = request.POST.get("password")
 7         if '' in username:
 8             # 提示报错信息,将信息添加到字典中
 9             error_dict['username'] = '不符合社会主义核心价值观'
10         if not password:
11             # 提示报错信息
12             error_dict['password'] = '密码不能为空 你个DSB'
13     return render(request,'register.html',locals())
 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Title</title>
 6     <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
 7     <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
 8     <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
 9 </head>
10 <body>
11     <!--form表单-->
12 <form action="" method="post">
13     <p>username:
14         <input type="text" name="username">
15         <!--接收报错信息-->
16         <span style="color: red">{{ error_dict.username }}</span>
17     </p>
18     <p>password:
19         <input type="text" name="password">
20         <span style="color: red">{{ error_dict.password }}</span>
21     </p>
22     <input type="submit">
23 </form>
24 </body>

实现效果:

二.如何校验数据

先补充一个小知识点:

之前已经了解过如何实现快速创建测试环境设置如何在Django中单独测试某一个py文件

现在再介绍另一个方法

总结:

  • 先创建一个类

1 from django import forms
2 class MyRegForm(forms.Form):
3      # 用户名最少3位最多8位
4      username = forms.CharField(max_length=8,min_length=3)
5      password = forms.CharField(max_length=8,min_length=3)
6      # email字段必须填写符合邮箱格式的数据
7      email = forms.EmailField()
  • 如何校验数据

 1 # 1.传入待校验的数据  用自己写的类 传入字典格式的待校验的数据
 2 form_obj =views.MyRegForm({'username':'jason','password':'12','email':'123456'})
 3  4 # 2.判断数据是否符合校验规则
 5 form_obj.is_valid()  # 该方法只有在所有的数据全部符合校验规则才会返回True
 6     # False
 7     
 8 # 3.如何获取校验之后通过的数据
 9 form_obj.cleaned_data
10     #{'username': 'jason'}
11     
12 # 4.如何获取校验失败及失败的原因
13 form_obj.errors
14 # 返回的是一个字典,里面的values传的的错误原因
15 {'password': ['Ensure this value has at least 3 characters (it has 2).'],'email': ['Enter a valid email address.']}
16
 1 # 5.注意 forms组件默认所有的字段都必须传值 也就意味着传少了是肯定不行的 而传多了则没有任何关系 只校验类里面写的字段 多传的直接忽略了
 2 form_obj = views.MyRegForm({'username':'jason','password':'123456'})
 3 form_obj.is_valid()
 4  5 # Out[12]: False
 6         
 7 form_obj.errors
 8 # 表示必须传值
 9 Out[18]: {'email': ['This field is required.']}
10     
11 # 多传一组为定义的数据    
12 form_obj = views.MyRegForm({'username':'jason','password':'123456',"email":'123@qq.com',"hobby":'hahahaha'})
13 # 判断是否合肥
14 form_obj.is_valid()
15 # 因为只会校验已经定义的数据,未定义的即使传过来也没有意义,并不会进行校验
16 Out[14]: True
17         
18 form_obj.cleaned_data
19 Out[15]: {'username': 'jason', 'password': '123456', 'email': '123@qq.com'}
20     
21 form_obj.errors
22 # 当全部数据都合法的时候,错误信息为一个空字典
23 Out[16]: {}

三.渲染页面的方式

  • 先写一个类

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()
  • 第一种渲染方式(多个p标签)

1 def reg(request):
2     # 1 先生成一个空的类对象
3     form_obj = MyRegForm()
4     # 2 直接将该对象传给前端页面
5     return render(request, 'reg.html', locals())
<body>
{#<p>第一种渲染方式:多个p标签  本地测试方便 封装程度太高了 不便于扩展</p>#}
{{ form_obj.as_p }}
# 下面两种类似
<!--{{ form_obj.as_p }}-->
<!--{{ form_obj.as_p }}-->
</body>
</html>

 

 

总结:forms组件只帮你渲染获取用户输入(输入 选择 下拉 文件)的标签 不渲染按钮和form表单标签,渲染出来的每一个input提示信息都是类中字段首字母大写

但是由于封装程度太高,不便于扩展,多用于本地测试

  • 第二种渲染方式(渲染方式: 扩展性较高 书写较为繁琐)

1 <p>第二种渲染方式</p>
2 <!--通过.label方式可以拿到这个文本,这样文本想放在哪个标签内就放在哪个标签内-->
3 <!--.username.id_for_label就可以拿到username的id值-->
4     <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>
5         {{ form_obj.username }}
6         {{ form_obj.password.label }}{{ form_obj.password }}
7         {{ form_obj.email.label }}{{ form_obj.email }}

  • 第三种渲染方式

1 {% for form in form_obj %}
2 <!--也可以拿到类属性名,方法可以参照上面哪一种-->    
3     <p> {{ form.label }}{{ form }}</p>
4 {% endfor %}

四.如何展示校验信息

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()
​
​
def reg(request):
    # 1 先生成一个空的类对象
    # 注意这个form_obj必须和下面那个form_obj是同名的,这样才能保证后面接收到数据
    form_obj = MyRegForm()
    if request.method == 'POST':
        # 3 获取用户数据并交给forms组件校验,在校验数据中我们知道需要传一个字典,并request.POST就是一个字典,因此可以直接传入校验
        form_obj = MyRegForm(request.POST)
        # 4 判断是否合法,获取校验结果
        if form_obj.is_valid():
            return HttpResponse('数据没问题')
            # return render(request, 'reg.html', locals())
        else:
            # 5 获取校验失败的字段和提示信息
            print(form_obj.errors)
    # 2 直接将该对象传给前端页面
    return render(request,'reg.html',locals())
<form action="" method="post" novalidate>
    {% for form in form_obj %}
    <p>
        {{ form.label }}{{ form }}
        <!--直接将错误信息传过来-->
        <span>{{ form.errors }}</span>
    </p>
    {% endfor %}
    <input type="submit">
</form>

 

缺点:前端校验非常脆弱,可被随时修改,因此数据的校验前后端都需要,尤其是后端的校验更需要严谨

补充:如何取消浏览器自动帮我们校验的功能

# form表单取消前端浏览器自动校验功能 
<form action="" method="post" novalidate>

如何解决上面这个问题

1 # 固定写法,在获取的容器数据后面加个索引0的方法
2 <span>{{ form.errors.0 }}</span>
3 # 注意,即时传过来的容器数据是个空,也不会报错,放心使用

五.forms组件中常用的参数

 1 from django import forms
 2 # 这个模块可以不导,导入后面写widget参数为有提示信息
 3 from django.forms import widgets
 4 class MyRegForm(forms.Form):
 5     # 用户名最少3位最多8位
 6     username = forms.CharField(max_length=8,min_length=3,label='用户名',
 7                                error_messages={
 8                                    'max_length':"用户名最长8位",
 9                                    'min_length':"用户名最短3位",
10                                    'required':"用户名不能为空"
11                                },required=False,initial='jason',
12                                # 第一个widget表示参数,第二个widget表示forms里面的属性
13                                widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'}),
14                                )
15     password = forms.CharField(max_length=8,min_length=3,label='密码',
16                                # widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
17                                )
18     confirm_password = forms.CharField(max_length=8,min_length=3,label='确认密码',
19                                # widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
20                                )
21     # email字段必须填写符合邮箱格式的数据
22     email = forms.EmailField(label='邮箱',error_messages={
23         'required':'邮箱必填',
24         'invalid':'邮箱格式不正确'
25     })
26

总结:

 1 max_length=8                                                     表示最长字符长度要求为8
 2  3 min_length=3                                                     表示最短字符长度要求为3
 4  5 label='用户名'                                        修改前面表示类属性的username为用户名
 6  7 error_messages={
 8 'max_length':"用户名最长8位",
 9 'min_length':"用户名最短3位",
10 'required':"用户名不能为空" }                              表示报错提示信息,可用字典表示多个
11     
12 required=False                                 False表示可以不填(默认Ture必须要填入数据)
13 14 initial='jason'                                                表示设置默认数据为jason
15 16 widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})    表示设置input框的type属性,默认type属性都是TextInput,比如密码类可以修改为PasswordInput,这样就是密文形式了,attrs可以控制标签属性
17 18

六.钩子函数

1)全局钩子函数(针对多个字段)

示例:

  • 后端代码

 1 from django import forms
 2 from django.forms import widgets
 3 from django.core.validators import RegexValidator
 4 class MyRegForm(forms.Form):
 5     # 用户名最少3位最多8位
 6     username = forms.CharField(max_length=8,min_length=3,label='用户名',
 7                                error_messages={
 8                                    'max_length':"用户名最长8位",
 9                                    'min_length':"用户名最短3位",
10                                    'required':"用户名不能为空"
11                                },required=False,initial='jason',
12                                )
13     password = forms.CharField(max_length=8,min_length=3,label='密码',
14                                )
15     confirm_password = forms.CharField(max_length=8,min_length=3,label='确认密码',
16                                )
17     email = forms.EmailField(label='邮箱',error_messages={
18         'required':'邮箱必填',
19         'invalid':'邮箱格式不正确'
20     })
21     
22      # 全局钩子,规定写法,
23     def clean(self):
24         # 校验密码和确认密码是否一致,这个self指的就是下面的form_obj
25         password = self.cleaned_data.get('password')
26         confirm_password = self.cleaned_data.get('confirm_password')
27         if not password == confirm_password:
28             # 添加报错提示信息在confirm_password后面,内容为两次密码不一致
29             self.add_error('confirm_password','两次密码不一致')
30         # 全局钩子拿多个字段出来比较,用完要return回去    
31         return self.cleaned_data
32     
33 def reg(request):
34     # 1 先生成一个空的类对象
35     form_obj = MyRegForm()
36     if request.method == 'POST':
37         # 3 获取用户数据并交给forms组件校验  request.POST
38         form_obj = MyRegForm(request.POST)
39         # 4 获取校验结果
40         if form_obj.is_valid():
41             return HttpResponse('数据没问题')
42         else:
43             # 5 获取校验失败的字段和提示信息
44             print(form_obj.errors)
45     # 2 直接将该对象传给前端页面
46     return render(request,'reg.html',locals())
  • 前端代码

<body>
    <form action="" method="post" novalidate >
    {% for form in form_obj %}
    <p>
        {{ form.label }}{{ form }}
        <span>{{ form.errors.0}}</span>
    </p>
    {% endfor %}
    <input type="submit">
</form>
</body>

注意:若输入的数据有问题,不会跳转或者刷新页面,若数据没有问题,可以绑定一个跳转到其他页面的方法

2)局部钩子函数(针对单个字段)

# 局部钩子,规定写法,直接选择某个类属性名
    def clean_username(self):
        # 校验密码和确认密码是否一致,这个self指的就是下面的form_obj
        username = self.cleaned_data.get('username') 
        if "" in username
            # 添加报错提示信息在confirm_password后面,内容为两次密码不一致
            self.add_error('username','也不照照镜子')
        # 全局钩子拿多个字段出来比较,用完直接return回去    
        return username

补充知识点:

  • 正则校验

 from django.core.validators import RegexValidator
    # validators表示使用正则校验,并且能使用多个正则
    phone = forms.CharField(
            validators=[
                RegexValidator(r'^[0-9]+$', '请输入数字'),
                RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
            ]
        )
# 多个校验补充知识点
# 涉及到选择的基本都是ChoiceField,依靠forms.widgets.属性名的方式来确定用那种校验方式
# 单选
 gender = forms.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )
​
  #下拉框单选  
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
   
# 下拉框多选MultipleChoiceField
    hobby1 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
  
# 
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
​

七.Cookie和Session简介

1).cookie

Cookie的由来

大家都知道HTTP协议是无状态的。

无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。

一句有意思的话来描述就是人生若只如初见,对服务器来说,每次的请求都是全新的。

状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。

什么是Cookie

Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

Cookie的原理

cookie的工作原理是:由服务器产生内容,当你第一次登陆成功之后 服务端给你返回了一个随机字符串 保存客户端浏览器上, 之后再次朝服务端发请求 只需要携带该随机字符串,服务端就能够识别当前用户身份

查看Cookie

我们使用Chrome浏览器,打开开发者工具。

使用cookie

保存在客户端浏览器上的键值对
    return HttpResponse('...')
    
    return render(...)
    
    return redirect(...)
    
    # 操作cookie需要以操作对象形式
    obj = HttpResponse('...')
    return obj
    
    obj1 = render(...)
    return obj1
    
    obj2 = redirect(...)
​
    设置cookie
        obj.set_cookie()
    获取cookie
        request.COOKIES.get()
    删除cookie
        obj.delete_cookie()

举例

 1 # 装饰器模板
 2 # 装饰器名称空间的修复
 3 from functools import wraps
 4  5 def login_auth(func):
 6     @wraps(func)
 7     # 可以先把request接收,剩下的由args他们接收
 8     def inner(request,*args,**kwargs):
 9         # print('request.path_info:',request.path_info)
10         # request.path_info: /home/  只能拿到路径部分,拿不到路径后面的参数
11 12         # print('request.get_full_path():',request.get_full_path())
13         # request.get_full_path(): /home/?username=jason&password=123 能拿到路径和路径后面的参数
14         # 执行被装饰函数之前你想去的下一个
15         target_url = request.path_info
16         # 如果得到cookies里面的username
17         if request.COOKIES.get('username'):
18             res = func(request,*args,**kwargs)
19             return res
20         else:
21             return redirect('/login/?next=%s'%target_url)
22     return inner
23 24 25 # 登陆页面
26 def login(request):
27     if request.method == 'POST':
28         
29         username = request.POST.get('username')
30         password = request.POST.get('password')
31         # 示例用的数据
32         if username == 'jason' and password == '123':
33             #下面两步可以用get一步解决
34             # target_url = request.GET.get("next",'/home/')
35             
36             # 拿到用户访问页面之前想要访问的页面url
37             target_url = request.GET.get("next")
38             # 判断是否存在
39             if target_url:
40                 # 保存用户登录状态
41                 obj = redirect(target_url)
42             else:
43                 obj = redirect('/home/')
44                 
45             # 用set_cookie设置cookie,里面必须有两个参数,key和value(不能是汉字),max_age=3表示cookie保存时间为3秒
46             obj.set_cookie('username','jason666',max_age=3)
47             
48             return obj
49     return render(request,'login.html')
50 51 52 # 首页
53 @login_auth
54 def home(request):
55     # 下面的部分可以通过装饰器代替
56     # 校验浏览器是否有对应的cookie
57     # if request.COOKIES.get('username'):
58     #     print(request.COOKIES)
59     #     return HttpResponse("我是home页面 只有登录的用户才能访问")
60     # else:
61     #     return redirect('/login/')
62     return HttpResponse("我是home页面 只有登录的用户才能访问")
63 64 65 @login_auth
66 def index(request):
67     return HttpResponse('我是index页面 只有登录之后的用户才能看')
68 69 70 @login_auth
71 def demo(request):
72     return HttpResponse('我是demo页面 只有登录之后的用户才能看')
73 74 75 @login_auth
76 def logout(request):
77     obj = HttpResponse('注销了')
78     obj.delete_cookie('username')
79     return obj
80
1 <!--login.html-->
2 3 <body>
4 <form action="" method="post">
5     <p>username:<input type="text" name="username"></p>
6     <p>password:<input type="text" name="password"></p>
7     <input type="submit">
8 </form>
9 </body>

2).Session

Session的由来

Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。

问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。

我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。

另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。

Session使用示例

session 保存在服务端上的键值对

设置
1 request.session['key'] = value
2     """
3     1.django内部会自动生成一个随机字符串
4     2.去django_session表中存储数据 键就是随机字符串 值是要保存的数据(中间件干的)
5     3.将生成好的随机字符串返回给客户端浏览器   浏览器保存键值对
6         sessionid  随机字符串
7     """
   
获取
1 request.session.get('key')
2     """
3     1.django会自动取浏览器的cookie查找sessionid键值对 获取随机字符串
4     2.拿着该随机字符串取django_session表中比对数据
5     3.如果比对上了 就将随机字符串对应的数据获取出来并封装到request.session供用户调用
6     """

1 # 创建session示例
2 def set_session(request):
3     request.session['name'] = 'egon'
4     return HttpResponse("set_session")

1 # 获取session
2 def get_session(request):
3     # print(request.session)  # <django.contrib.sessions.backends.db.SessionStore object at 0x00000259F72683C8>
4     print(request.session.get('name'))
5     # egon
6     return HttpResponse("get_session")

补充:

# 设置session超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。
​
# 删除当前会话的所有Session数据
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie。  推荐使用
request.session.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。
​
session是保存在服务端

流程解析

Django中的Session配置

 1 Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。
 2 
 3 1. 数据库Session
 4 SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
 5  6 2. 缓存Session
 7 SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
 8 SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 9 10 3. 文件Session
11 SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
12 SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 
13 14 4. 缓存+数据库
15 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
16 17 5. 加密Cookie Session
18 SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
19 20 其他公用设置项:
21 SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
22 SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
23 SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
24 SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
25 SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
26 SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
27 SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
28 SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)
29 30 Django中Session相关设置

 

 

posted @ 2020-01-14 00:38  Mr江  阅读(241)  评论(0编辑  收藏  举报