Django forms组件

前戏

"""
写一个注册功能
    获取用户名和密码 利用form表单提交数据
    在后端判断用户名和密码是否符合一定的条件
        用户名中不能含有***
        密码不能少于三位
    
    如果不符合条件 需要将提示信息展示到前端页面
"""

前端:
            <form action="" method="post">
                <p>username:
                    <input type="text" name="username">
                    <span style="color: red">{{ back_dic.username }}</span>
                </p>

                <p>password:
                    <input type="text" name="password">
                    <span style="color: red">{{ back_dic.password }}</span>
                </p>
                <input type="submit" class="btn btn-info">
            </form>
            
           
后端:
# form组件前戏
def ab_form(request):
    back_dic = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if '***' in username:
            back_dic['username'] = '不符合社会主义核心价值观'
        if len(password) < 3:
            back_dic['password'] = '不能太短 不好'

    """
    无论是get请求还是post请求
    页面都能够获取到字典 只不顾get请求来的时候 字典 值都是空的
    而post请求来之后 字典可能有值
    """

    return render(request,'ab_form.html',locals())


"""
1、手动书写前端获取用户数据的html代码    渲染html代码
2、后端对用户数据进行校验                校验数据
3、对不符合要求的数据进行前端提示         展示提示信息
"""

forms组件

"""
能够完成的信息
    1、渲染html代码
    2、校验数据
    3、展示提示信息    
"""

为什么数据校验非要去后端 不能在前端利用js直接完成呢?
    数据校验前端可有可无
    但是后端必须要有!!!
    
     因为前端的校验是弱不禁风的 你可以直接修改
    或者利用爬虫程序绕过前端页面直接朝后端提交数据
    
    购物网站为例
        选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验
        
        实际是获取到用户选择的所有的商品的主键值
        然后在后端查询出所有的商品的价格 再次计算一次
        如果跟前端一致 那么完成支付如果不一致直接拒绝

基本使用

"""
from django import forms

class MyForm(forms.Form):
    # username字符串类型最小3位 最大8位
    username = forms.CharField(min_length=3,max_length=8)
    # password字符串类型最小3位 最大8位
    password = forms.CharField(min_length=3,max_length=8)
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField()
"""

forms组件如何检验数据

"""
测试环境的准备 可以自己拷贝代码准备
其实在pycharm里面已经帮你准备了一个测试环境
pycharm左下角:python console
"""

from app01 import views
# 1、将待校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':12,'email':123})


# 2、判断数据是否合法 注意:该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
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、校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':123,'email':'123@qq.com','hobby':'style'})
form_obj.is_valid()
True


# 6、校验数据 默认情况下 类里面所有的字段都必须被传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
False
form_obj.errors
{'email': ['This field is required.']}


"""
也就意味着校验数据的时候 默认情况下数据可以多传 但是绝不可以少传
"""

渲染标签

"""
forms组件只会自动帮你渲染获取用户输入的标签(input select redio checkbox)
不能帮你渲染提交按钮
"""
def index(requset):
    # 1、先产生一个空对象
    form_obj =MyForm()
    # 2、直接将该空对象传递给html页面
    return render(requset,'index.html',locals())

# 前端利用空对象做操作
<form action="" method="post">
    <p>第一种渲染方式:代码书写极少 封程度太高了 不便于后续扩展 一般情况下只在本地测试使用</p>
    {{ form_obj.as_p }}
    {{ form_obj.as_ul }}    
    {{ form_obj.as_table }}

    
    <p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用</p>
    <p>{{ form_obj.username.label }} {{ form_obj.username }}</p>
    <p>{{ form_obj.password.label }} {{ form_obj.password }}</p>
    <p>{{ form_obj.email.label }} {{ form_obj.email }}</p>

    
    <p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也很高</p>
    {% for form in form_obj %}
       <p>{{ form.label }} {{ form }}</p>
    {% endfor %}
</form>

"""
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可
    username = forms.CharField(min_length=3,max_length=8,label='用户名')

"""

展示错误提示信息

"""
浏览器会自动帮你校验数据 但是前端的校验弱不禁风
如何让浏览器不做校验
    <form action="" method="post" novalidate>

"""
后端:
def index(requset):
    # 1、先产生一个空对象
    form_obj =MyForm()

    if requset.method == 'POST':
        # 获取用户数据并且校验
        """
        1、数据获取繁琐
        2、校验数据需要构造成字典的格式传入才行
        ps:但是request.POST可以看成就是一个字典
        """
        # 3、校验数据
        form_obj = MyForm(requset.POST)
        # 4、判断数据是否合法
        if form_obj.is_valid():
            # 5.1、如果合法 操作数据库 存储数据
            return HttpResponse('OK')
        # 5.2、不合法 有错误

    # 2、直接将该空对象传递给html页面
    return render(requset,'index.html',locals())

前端:
    {% for form in form_obj %}
       <p>
           {{ form.label }} {{ form }}
            <span style="color: red">{{ form.errors.0 }}</span> <!--form.errors.0 只拿列表第一个错误信息-->
       </p>
    {% endfor %}
    <input type="submit" class="btn btn-info">
    
   
"""
1、必备的条件 get请求和post传给html页面对象变量名必须一样
2、form组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改 更加的人性化
"""

自定义提示信息(error_messages)

error_messages的使用

"""
class MyForm(forms.Form):
    # username字符串类型最小3位 最大8位
    username = forms.CharField(min_length=3,
                               max_length=8,
                               label='用户名:',
                               error_messages={
                                   'min_length':'用户名最少3位',
                                   'max_length':'用户名最大3位',
                                   'required':'用户名不能为空',
                               }
                               )
    # password字符串类型最小3位 最大8位
    password = forms.CharField(min_length=3,max_length=8,label='密码:',
                               error_messages={
                                   'min_length': '用户名最少3位',
                                   'max_length': '用户名最大3位',
                                   'required': '用户名不能为空',
                               }
                               )
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label='邮箱:',
                             error_messages={
                                 'invalid':'邮箱格式不正确',
                                 'required':'邮箱不能为空'
                             }
                             )


"""

钩子函数(HOOK)

"""
在特定的节点自动触发 完成相应操作

钩子函数在form组件中就类似于第二道关卡,能够让我们自定义校验规则

在froms组件中有两类钩子
    1、局部钩子
        当你需要给'单'个字段增加校验规则的时候可以使用
    2、全局钩子
        当你需要给'多'个字段增加校验规则的时候可以使用
"""

# 实际案例

# 1、校验用户名中不能含有666            只是校验username字段  局部钩子

# 2、校验密码和确认密码是否一致          password confirm两个字段  全局钩子


"""
在类里面书写方法
"""
    # 钩子函数
    # 局部钩子
    def clean_username(self):
        # 获取到用户名
        username = self.cleaned_data.get('username')
        if '666' in username:
            # 提示前端展示错误信息
            self.add_error('username','光喊666是不行滴~')
        # 将钩子函数钩取出来的数据再放回去
        return username

    # 全局钩子
    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

forms组件其他参数及补充知识点

详情参见:https://www.cnblogs.com/Dominic-Ji/p/9240365.html


label  给字段起字段名
errors_messages 自定义报错信息
initial='jason'  默认值
required=False  控制字段是否必填

"""
1、字段没有样式
2、针对不同类型的input如何修改
    text
    password
    date
    radio
    checkbok
"""
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2','username':'jason'})
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
widget=forms.widgets.EmailInput()
# 多个属性值的话 直接空格隔开即可

# 第一道关卡:还支持正则校验
 validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),
                    RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
                    ],

其他类型渲染

# radio
    gender = forms.fields.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

# select
hobby = forms.ChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
    label="爱好",
    initial=3,
    widget=forms.widgets.Select())

forms组件源码

"""
切入点
    form obj.is_vaild()
"""

def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors
        # 如果is_vaild要返回True的话 
        # 那么self.is_bound要为True
        # self.errors要为False
        
self.is_bound = data is not None or files is not None
# 只要你传值了 肯定为True


 @property
def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors
    
# forms组件所有的功能都基本出自于该方法
def full_clean(self):
        self._clean_fields()  # 校验字段 + 局部钩子
        self._clean_form()  # 全局钩子
        self._post_clean()

cookie与session

"""
发展史
    1、网站都没有保存用户功能的需求 所有用户访问返回的救国都是一样的
        eg:新闻、博客、文章...
        
    2、出现了一些需要保护用户信息的网站
        eg:淘宝、支付宝、京东...
        
        以登录功能为例:如果不保存用户登录状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码(你觉得这样的网站你还想用吗???)
        当用户第一次登录成功之后 将用户的用户名密码返回给用户浏览器 让用户浏览器保存在本地 之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动校验
        早先这种方式具有非常大的隐患
        
优化:
    当用户登录成功之后 服务端产生一个随机字符串(在服务端保存数据,用k,v键值对的形式) 交由客户端浏览器保存
    随机字符串1:用户1相关信息
    随机字符串2:用户2相关信息
    随机字符串3:用户3相关信息
    
    之后访问服务端的时候,都带着该随机字符串  服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息
    

但是如果你截获到了该随机字符串 那么你就可以冒充当前用户 其实还是有安全隐患的

你要知道在web领域 没有绝对的安全 也没有绝对的不安全
""""""
发展史
    1、网站都没有保存用户功能的需求 所有用户访问返回的救国都是一样的
        eg:新闻、博客、文章...
        
    2、出现了一些需要保护用户信息的网站
        eg:淘宝、支付宝、京东...
        
        以登录功能为例:如果不保存用户登录状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码(你觉得这样的网站你还想用吗???)
        当用户第一次登录成功之后 将用户的用户名密码返回给用户浏览器 让用户浏览器保存在本地 之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动校验
        早先这种方式具有非常大的隐患
        
优化:
    当用户登录成功之后 服务端产生一个随机字符串(在服务端保存数据,用k,v键值对的形式) 交由客户端浏览器保存
    随机字符串1:用户1相关信息
    随机字符串2:用户2相关信息
    随机字符串3:用户3相关信息
    
    之后访问服务端的时候,都带着该随机字符串  服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息
    

但是如果你截获到了该随机字符串 那么你就可以冒充当前用户 其实还是有安全隐患的

你要知道在web领域 没有绝对的安全 也没有绝对的不安全
"""

cookie

"""
服务端保存在客户端浏览器上的信息都可以称之为cookie
它的表现形式一般都是k:v键值对(可以有多个)
"""

session

"""
数据是保存在服务端的
并且它的表现形式一般也是k:v键值对(可以有多个)
"""

token

"""
session虽然数据是保存在服务端的
但是禁不住数据量大

服务端不再保存数据
    登录成功之后 将一段用户信息进行加密处理(加密算法只有你公司的开发知道)
    将加密之后的结果拼接在信息后面 整体返回给浏览器保存
    浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
    跟浏览器尾部的密文进行比对
"""

jwt认证

"""
三段信息
(后期会讲)
"""

总结

"""
    1、cookie就是保存在客户端浏览器上的信息
    2、session就是保存在服务端上的信息
    3、session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)
"""

cookie操作

# 虽然cookie是服务端告诉客户端浏览器需要保存内容
# 但是客户端浏览器可以选择拒绝保存  如果禁止了 那么 只要是需要记录用户状态的网站登录功能都无法使用了

# 视图函数的返回值
return HttpResponse()
return render()
return redirect()

obj1 = HttpResponse()
# 操作cookie
return obj1

obj2 = render()
# 操作cookie
return obj2

obj3 = redirect()
# 操作cookie
return obj3
# 如果你想要操作cookie,你就不得不利用obj对象



"""
设置cookie
    obj.set_cookie(key,value)

获取cookie
    request.COOKIES.get(key)
    
在设置cookie的时候可以添加一个超时时间
                obj.set_cookie('username','jason666',max_age=5,expires=3)
    
    max_age
    expires
        两者都是设置超时时间的 并且都是以秒为单位
        需要注意的是 针对IE浏览器需要使用expires
        
主动删除cookie(注销功能)
@login_auth
def logout(request):
    obj = redirect('/login/')
    obj.delete_cookie('username')
    return obj
"""

cookie实现登录功能

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

# 校验用户是否登录的装饰器
"""
用户如果在没有登录的情况下想访问一个需要登录的页面
那么先跳转到登录页面 当用户输入正确的用户名和密码之后
应该跳转到用户之前想要访问的页面去 而不是直接写死
"""
def login_auth(func):
    def inner(request,*args,**kwargs):
        # print(request.path_info)  # 获取用户输入的url
        # print(request.get_full_path())  # 获取用户输入的url以及url后面的参数

        # 获取到用户上一次想要访问的url
        target_url = request.get_full_path()

        if request.COOKIES.get('username'):
            res = func(request,*args,**kwargs)
            return res
        else:
            return redirect('/login/?next=%s' %target_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':

            # 获取用户上一次想要访问的url
            target_url = request.GET.get('next')  # 这个结果可能是None
            if target_url:
                obj = redirect(target_url)
            else:
                # 保存用户登录状态
                obj = redirect('/home/')

            # 让浏览器记录cookie数据
            obj.set_cookie('username','jason666')
            """
            浏览器不单单会帮你存
            而且后面每次访问你的时候还会带着它过来
            """

            # 跳转到一个需要用户登录之后才能看到的页面
            return obj
    return render(request,'login.html')

@login_auth
def home(request):
    # 获取cookie信息 判断你有没有
    # if request.COOKIES.get('username') == 'jason666':
    #     return HttpResponse('我是home页面 只要登录的用户才能进来哦')
    # # 没有登录应该跳转 到登录页面
    # return redirect('/login/')
    return HttpResponse('我是home页面 只要登录的用户才能进来哦')


@login_auth
def index(request):
    return HttpResponse('我是index页面 只要登录的用户才能进来哦')



@login_auth
def func(request):
    return HttpResponse('我是func页面 只要登录的用户才能进来哦')

session实现登录功能

https://www.cnblogs.com/Dominic-Ji/p/10718365.html

"""
session数据是保存在服务端的(存?) 给客户端返回的是一个随机字符串
    sessionid:随机字符串
    
1、在默认的情况下操作session的时候需要django默认的一张django_session表
    数据库迁移命令
        django会自己创建很多表 django_session就是其中的一张

django默认session的过期时间是14天
    但是你也可以人为的修改它

设置session
    request.session['key'] = value
    
获取session
    request.session.get('key')
    
设置过期时间
    request.session.set_expiry()
        括号内可以放四种类型的参数
            1、整数       多少秒
            2、日期对象    到指定日期就失效
            3、0          一旦当前浏览器关闭 立刻失效
            4、不写        失效时间就取决于django内部全局session默认的时间
            
    
清除session
    request.session.delete()  # 只删服务端的 客户端的不删
    requset.session.flush()  # 浏览器和服务端都清空(推荐使用)
    

session是保存在服务端的 但是session的保存位置可以有多种选择
    1、MySQL
    2、文件
    3、redis
    4、memcache
    ...
"""



# 设置session
def set_session(request):
    request.session['hobby'] = 'girl'
    """
    内部发生看哪些事情   
        1、django内部会自动帮你生成一个随机字符串
        2、django内部自动将随机字符串和对应的数据储存到django_session表中
            2.1、现在内存中产生操作数据的缓存
            2.2、在响应结果django中间件的时候才真正的操作数据库
        3、将产生的随机字符串返回给客户端浏览器保存
    """
    return HttpResponse('heiheihei')


# 获取session
def get_session(request):
    print(request.session.get('hobby'))
    """
    内部发生了那些事情
        1、自动从浏览器请求中获取sessionid对应的随机字符串
        2、拿着该随机字符串去django_session表中查找对应的数据
        3、
            如果比对上了 则将对应的数据取出并以字段的形式封装到request.session中
            如果比对不上,则request.session.get()返回的是None
    """
    return HttpResponse('哈哈哈')


"""
django_session表中的数据条数是取决于浏览器的
    同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
    (当session过期的时候可能会出现多条数据对应一个浏览器 
     但是该现象不会持续很久 内部会自动识别过期的数据清除 你也可以通过代码清除)
    
    主要是为了节省服务端数据库资源
"""

# 利用session实现登录验证
原文地址:https://www.cnblogs.com/ZhZhang12138/p/14875623.html