八、Django细碎知识点

杂碎知识点

1、Ajax

  1. 特点:异步提交,局部刷新

  2. 例子:当我们在浏览淘宝商品的时候,浏览器不会将我们搜索到的所有商品信息展示到浏览器上,而是我们在滑动的过程中展示给我们。在这个过程中,页面没有刷新,我们也没有在提交数据,只是数据局部刷新。

  3. 朝服务端发送请求的方式

    • 浏览器地址栏直接输入url:GET请求
    • a标签href属性:GET请求
    • from表单的method属性:GET/POST请求
    • ajax:GET/POST请求
  4. 注意:Ajax不是编程语言,而是一种使用现有标准的新方法

  5. 优点:是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容渲染到浏览器中。(用户体体验极好)

  6. 小🌰

    """
    要求:
    	页面上有三个input框和一个提交按钮
    	在前两个矿中输入数字 点击提交按钮 朝后端发送请求
    	在后端计算出结果 再返回给前端 动态展示到第三个框中
    	(整个过程页面不准刷新 也不能在前端计算)
    """
    
    <input type="text" id="d1"> +
    <input type="text" id="d2"> =
    <input type="text" id="d3">
    <p>
        <button id="btn">点我</button>
    </p>
    
    <script>
        // 先给按钮绑定一个点击事件
        $('#btn').click(function () {
            // 朝后端发送ajax请求
            $.ajax({
                // 1.指定朝哪个后端发送ajax请求
                url:'', // 不写就是朝当前地址提交
                // 2.请求方式
                type:'post',  // 不指定默认就是get 都是小写
                // 3.数据
                {#data:{'username':'jason','password':123},#}
                data:{'i1':$('#d1').val(),'i2':$('#d2').val()},
                // 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果
                success:function (args) {
                    {#alert(args)  // 通过DOM操作动态渲染到第三个input里面#}
                    {#$('#d3').val(args)#}
                    console.log(typeof args)
    
                }
            })
        })
    </script>
    
                    
    """
    如果后端使用HttpResponse返回数据 回调函数不会自动帮我们反序列化
    如果后端使用JsonResponse返回数据 回调函数会自动帮我们反序列化
    
    针对HttpResponse不会反序列化解决方法
    	1.自己在前端用JSON.parse()反序列化
    	2.在ajax里配置dataType='json'参数即可
    """
    

2、前后端传输数据的编码格式

  • 前端可以朝后端发送post请求的方式
    • form表单
    • ajax请求
  • 前后端传输数据的编码格式
    • urlencoded
    • formdata
    • json

①、form表单

"""
编码格式
	urlencoded
	formdata
	json
"""

"""
1.默认的编码格式是urlencoded
	数据格式:username=jason&password=123
	django后端针对符合urlencoded编码格式的数据都会自动帮我们解析到request.POST中,也就是说,凡是符合urlencoded编码格式的数据全部去request.POST中取
	username=jason&password=123			>>>>>> request.POST
	
2.修改编码格式为formdata
	针对普通的键值对数据(理解为username:jason&password:123)还是会把数据解析到request.POST中。而文件数据会解析到request.FILES中

3.修改编码格式为json
	form表单是没有办法发送json格式数据的
"""

②、ajax请求

"""
编码格式
	urlencoded
	formdata
	json
"""

"""
1.默认编码格式是urlencoded
	数据格式:username=jason&password=123
	django后端针对符合urlencoded编码格式的数据都会自动帮我们解析到request.POST中,也就是说,凡是符合urlencoded编码格式的数据全部去request.POST中取
	username=jason&password=123			>>>>>> request.POST
	
2.修改为formdata格式
	...
	
3.修改为json格式
	...
"""

3、ajax发送json格式数据

"""
前后端传输数据的时候一定能够要确保编码格式跟数据真正的格式是一致的

{"username":"jason","password":123}
	在request.POST中一定能够找不到,因为不符合urlencoded编码格式
	
	在Django中针对json格式的数据 不会做任何操作 只能自己处理
	
request对象方法补充
	request.is_ajax():判断当前请求是否为ajax请求 返回布尔值
"""


# 前端
<script>
	$('#d1').click(function() {
        $.ajax({
            url:'',
            type:'post',
            data:JSON.stringify({'username':'jason','password':123}),
            contentType:'application/json', # 指定编码格式
            success:function () {
                
            }
        })
        
    })
</script>


# 后端
import json
def ab_json(request):
    if request.is_ajax(): # 判断是否请求为ajax请求
        print(request.POST)
        print(request.FILES)
        print(request.body) # 获取前端传来的json数据 是二进制格式的 需要自己反序列化
        
        # 反序列化方式1
        json_bytes = request.body
        json_str = json_bytes.decode('utf-8')
        json_dict = json.loads(json_str)  # 字典类型
        
        # 反序列化方式2  json.loads内部会自动帮我们用utf-8解码
        json_bytes = request.body
        json_dict = json.loads(json_bytes) # 字典类型
        
        
        
"""
注意
	1.contentType参数必须指定成:application/json
	2.前端发送的数据必须是json格式的数据
	3.Django后端不会帮我们吹json格式的数据,需要我们自己request.body中获取并且手动处理
"""

4、ajax发送文件

"""
ajax发送文件需要借助js内置对象FormData
"""

# 前端
<script>
	//点击安妮朝后端发送普通键值对和文件数据
    $('#d4').on('click',function() {
        // 1.需要先利用FormData内置对象
        let formData = new FormData();
        // 2.添加普通键值对
        formData.append('username',$('#d1').val())
        formData.append('password',$('#d2').val())
        // 3.添加文件对象
        formData.append('myfile',$('#d3')[0].files[0]) //此时需要将jQuery对象转换成js对象
        // 4.将对象基于ajax发送给后端
        $.ajax({
            url:'',
            type:'post',
            data:formData,  //直接将对象放在data后面即可
            
            // ajax发送文件必须要指定两个参数
            contentType:false,  //不需要任何编码 Django后端能够自动识别formData对象
            processData:false,  //告诉浏览器不要对你的数据进行任何处理
            success:function (args) {
                
            }
        })
    })
</script>

# 后端
def ab_file(request):
    if request.is_ajax:
        if request.method == 'POST':
            print(request.POST)  # 那普通的键值对
            print(request.FILES) # 拿文件数据
    return render(request,'ab_file.html')
    
    
"""
总结:
	1.需要利用内值得FormData对象
		添加普通键值对
            formData.append('username',$('#d1').val())
            formData.append('password',$('#d2').val())
        添加文件对象
            formData.append('myfile',$('#d3')[0].files[0])
            
    2.指定两个关键性的参数
    	contentType:false,  //不需要任何编码 Django后端能够自动识别formData对象
        processData:false,  //告诉浏览器不要对你的数据进行任何处理
        
    3.Django后端会自动识别formData对象并且能够将内部的普通键值对数据自动解析封装到request.POST中,将文件数据解析封装到request.FILES中
"""

5、Django自带的序列化组件

"""
需求:在前端获取到后端数据库中的数据 并且要列表套字典格式的
	[{},{},{},{},{}...]
"""

import django
from django.http import JsonResponse
from django.core import serializers

def ab_ser(request):
    user_queryset = models.User.objects.all()
    
    # 序列化方式1 手动将数据处理成列表套字典格式
    user_list = []
    for user_obj in user_queryset:
        tem = {
            'pk':user_obj.pk,
            'username':user_obj.username,
            'password':user_obj.password,
            'gender':user_obj.get_gender_display()
        }
        user_list.append(tem)
    return JsonResponse(user_list,safe=False)
	
    """
	手动处理数据样式
	[
 {"pk": 1, "username": "jason", "password": 25, "gender": "male"}, 
 {"pk": 2, "username": "egon", "password": 31, "gender": "female"},
 {"pk": 3, "username": "kevin", "password": 32, "gender": "others"}, 
 {"pk": 4, "username": "tank", "password": 40, "gender": 4}
 ]
	"""

    
	# 序列化方式2 利用模块自动处理数据
    from django.core import serializers
    res = serializers.serialize('json',user_queryset)
    # 自动将数据编程json格式的字符串
    return HttpResponse(res)
	
    """
	自动处理数据样式
	[
{   "model": "app01.user", 
    "pk": 1, 
    "fields": {"username": "jason", "password": 25, "gender": 1}}, 
    
{   "model": "app01.user", 
    "pk": 2, 
    "fields": {"username": "egon", "password": 31, "gender": 2}}, 
    
{   "model": "app01.user", 
    "pk": 3, 
    "fields": {"username": "kevin", "password": 32, "gender": 3}},
     
{   "model": "app01.user", 
    "pk": 4, 
    "fields": {"username": "tank", "password": 40, "gender": 4}}
	]
	"""

6、ajax结合sweetalert

<!--前端-->
<style>
        div.sweet-alert h2{
            padding-top: 10px;
        }
</style>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
{% load static %}
<link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}">
<script src="{% static 'dist/sweetalert.min.js' %}"></script>

<div class="container-fluid">
    <h2 class="text-center">用户展示</h2>
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <table class="table table-hover table-condensed">
                <thead>
                    <tr>
                        <td>ID</td>
                        <td>name</td>
                        <td>gender</td>
                        <td>age</td>
                        <td>hobby</td>
                        <td>action</td>
                    </tr>
                </thead>
                <tbody>
                    {% for user_obj in user_queryset %}
                    <tr>
                        <th>{{ forloop.counter }}</th>
                        <th>{{ user_obj.name }}</th>
                        <th>{{ user_obj.get_gender_display }}</th>
                        <th>{{ user_obj.age }}</th>
                        <th>{{ user_obj.hobby }}</th>
                        <th>
                            <button class="btn btn-primary btn-xs">修改</button>
                            <button class="btn btn-danger btn-xs del" delete_id="{{ user_obj.pk }}">删除</button>
                        </th>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

<!--script代码-->
<script>
        $('.del').on('click',function () {
            let $currentBtn = $(this)
            swal({
              title: "你确定要删吗?",
              text: "有可能要跑路喔",
              type: "warning",
              showCancelButton: true,
              confirmButtonClass: "btn-danger",
              confirmButtonText: "是的,老子就要删",
              cancelButtonText: "算了,算了",
              closeOnConfirm: false,
              closeOnCancel: false,
              showLoaderOnConfirm: true  // 显示时间间隙
            },
            function(isConfirm) {
              if (isConfirm) {
                  $.ajax({ // 将要删除的对象的pk值传给后端,用ajax实现
                      url: '/user/deldte/',
                      type: 'post',
                      data:{'delete_id': $currentBtn.attr('delete_id')},
                      success:function (args) {
                        if(args.code === 1000){
                            swal("是个猛男", args.msg, "success");
                            $currentBtn.parent().parent().remove()
                        }else{
                            swal('完了','出现未知错误','info')
                        }
                      }
                  })

              } else {
                swal("怂逼", "别说我认识你", "error");
              }
            });
        })
    </script>


<!--后端-->
import time
def user_delete(request):
    back_dic = {'code':1000,'msg':''} // 手动写一个字典,到前端变成自定义对象,可以用点点的方式操作
    if request.is_ajax():
        if request.method == 'POST':
            time.sleep(3)
            models.User.objects.filter(pk=int(request.POST.get('delete_id'))).delete()
            back_dic['msg'] = '赶紧跑路吧'
            return JsonResponse(back_dic)

7、批量插入

def ab_insert(request):
    # 方式一:效率太低 不用
    # 1.先给Book表插入10000条数据
    for i in range(10000):
        models.Book.objects.create(title='第{}本书'.format(i))
    # 2.在将所有书籍查询数据展示到前端也页面
    book_queryset = modexls.Book.objects.all()
    
    # 方式二:直接利用Django提供的方法
    # 1.先生成10000个book_obj对象
    book_list = []
    for i in range(10000):
        book_obj = models.Book(title='第{}本书'.format(i))
        book_list.append(book_obj)
    # 2.给Book表插入10000条数据
    models.Book.objects.bult_create(book_list)
    # 3.再将所有书籍查询数据展示到前端页面
    return render(request,'ab_insert.html',locals())

8、分页器

  1. 自制分页器

    """
    分页器在写之前先思考:我们大概需要哪些参数,一页展示多少条数据,总共有多少条数据,每页的起始位置,每页的结束位置...
    """
    
    # 先获取所有的数据对象
    book_queryset = models.Book.objects.all()
    # 获取get请求提供的参数数据 如果是直接访问还没点击按钮则访问的是主页 此时展示第一页
    current_page = request.GET.get('page',1) # 如果获取不到当前页码 就展示第一页
    # 数据类型的转换 前端返回的数据默认都是字符串格式
    try:
        # 因为前端传来的数据是字符类型
        current_page = int(current_page)
    except Exception:
        # 如果传来的是非数字就直接转换成1
        current_page = 1
    # 每页展示多少条数据
    per_page_num = 10
    # 起始位置
    start_page = (current_page - 1) * per_page_num
    # 终止位置
    end_page = current_page * per_page_num
    
    # 计算出到底有多少条数据
    all_page = book_list.count()
    # divmod内置函数 传两个值 返回商和余数
    page_count,more = div(all_page,per_page_num)
    # 如果有余数就需要再加一页展示数据 总页数+1
    if more:
        page_count+=1
    # 设置一个变量来存放我们书写的HTML代码
    page_html = ''
    xxx = current_page
    # 为了防止出现负数页码,这里处理 如果页码数小于6就不做运算
    if current_page < 6:
        current_page = 6
    # 每页展示11个分页器
    for i in range(current_page-5,current_page+6):
        if xxx == i:
            # 将分页器的展示页的标签做成活动态的
            # if判断是做选中页码高亮显示
            page_html = '<li calss="active"><a href="?page={}">{}</a></li>'.format(i,i)
        else:
            page_html = '<li><a href="?page={}">{}</a></li>'.format(i,i)
    book_queryset = book_list[start_page:end_page]
    return render(request,'ab_insert.html',locals())
    
  2. 分页器

    class Pagination(object):
        def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
            """
            封装分页相关数据
            :param current_page: 当前页
            :param all_count:    数据库中的数据总条数
            :param per_page_num: 每页显示的数据条数
            :param pager_count:  最多显示的页码个数
            """
            try:
                current_page = int(current_page)
            except Exception as e:
                current_page = 1
    
            if current_page < 1:
                current_page = 1
    
            self.current_page = current_page
    
            self.all_count = all_count
            self.per_page_num = per_page_num
    
            # 总页码
            all_pager, tmp = divmod(all_count, per_page_num)
            if tmp:
                all_pager += 1
            self.all_pager = all_pager
    
            self.pager_count = pager_count
            self.pager_count_half = int((pager_count - 1) / 2)
    
        @property
        def start(self):
            return (self.current_page - 1) * self.per_page_num
    
        @property
        def end(self):
            return self.current_page * self.per_page_num
    
        def page_html(self):
            # 如果总页码 < 11个:
            if self.all_pager <= self.pager_count:
                pager_start = 1
                pager_end = self.all_pager + 1
            # 总页码  > 11
            else:
                # 当前页如果<=页面上最多显示11/2个页码
                if self.current_page <= self.pager_count_half:
                    pager_start = 1
                    pager_end = self.pager_count + 1
    
                # 当前页大于5
                else:
                    # 页码翻到最后
                    if (self.current_page + self.pager_count_half) > self.all_pager:
                        pager_end = self.all_pager + 1
                        pager_start = self.all_pager - self.pager_count + 1
                    else:
                        pager_start = self.current_page - self.pager_count_half
                        pager_end = self.current_page + self.pager_count_half + 1
    
            page_html_list = []
            # 添加前面的nav和ul标签
            page_html_list.append('''
                        <nav aria-label='Page navigation>'
                        <ul class='pagination'>
                    ''')
            first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
            page_html_list.append(first_page)
    
            if self.current_page <= 1:
                prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
            else:
                prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
    
            page_html_list.append(prev_page)
    
            for i in range(pager_start, pager_end):
                if i == self.current_page:
                    temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
                else:
                    temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
                page_html_list.append(temp)
    
            if self.current_page >= self.all_pager:
                next_page = '<li class="disabled"><a href="#">下一页</a></li>'
            else:
                next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
            page_html_list.append(next_page)
    
            last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
            page_html_list.append(last_page)
            # 尾部添加标签
            page_html_list.append('''
                                               </nav>
                                               </ul>
                                           ''')
            return ''.join(page_html_list)
    
  3. 封装分页器的使用

    """
    在Django中一些第三方组件我们可以单独建立一个utils文件夹存放 utils文件夹可以建在项目的根目录下 也可以建在应用里面
    """
    
    # 后端
    book_queryset = models.Book.objects.all()
    current_page = request.GET.get('page',1)
    all_count = book_queryset.count()
    # 1.传值产生对象
    page_obj = Pagination(current_page=current_page,all_count=all_count)
    # 2.直接对总数据进行切片操作
    page_queryset = book_queryset[page_obj.start:page_obj.end]
    # 3.将page_queryset传递到前端页面
    
    # 前端
    {% for book_obj in page_queryset %}
    	<p>{{ book_obj.title }}</p>
        <nav aria-label="Page navigation"></nav>
    {% endfor %}
    {#利用自定义分页器直接显示分页器样式#}
    {{ page_obj.oage_html|safe }}
    

9、forms组件

①、前戏

"""
要求:
	用我们现在所学到的知识完成
	1.判断用户名密码是否符合规范
	2.用户名不能是含有紧瓶梅
	3.密码不能少于三位
"""

"""实现"""

# 后端
def ab_form(request):
    back_dict = {'username':'','password':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if '紧瓶梅' in username:
            back_dict['username'] = '不符合社会主义核心价值观呀'
        if len(password) < 3:
            back_dict['password'] = '太短不好呀'
     """
     无论是post请求还是get请求
     页面都是能够获取到字典 只不过get请求来的时候 字典的值都是空的
     而post请求来的时候 字典可能有值
     """
     return render(request,'ab_from.html',locals())

# 前端
<from action="" method="post">
	<p>username:
    	<input type="text" name="username">
        <span style="color:res;">{{ back_dict.username }}</span>
    </p>
    <p>password:
    	<input type="text" name="password">
        <span style="color:res;">{{ back_dict.password }}</span>
    </p>
    <input type="submit" class="btn btn-info">
</from>


"""
总结
	完成该项要求需要三步
	1.手动书写前端获取用户数据的html代码		渲染html代码
	2.后端对用户数据进行校验				   校验数据
	3.对不符合要求的数据进行前端提示            展示提示信息
	
froms组件全部可以完成
	1.渲染html代码
	2.校验数据
	3.展示提示信息
	
为什么数据一定要去后端校验 不能在前端利用js校验完成呢?
	数据校验在前端可有可无 安全系数低
	但是后端数据校验必须要有
	

"""

②、基本使用

from django import froms

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

③、基本方法

"""
测试环境的配置
	1.在test.py中配置
	2.pycharm自带一个测试环境:python console
"""

# 1 实例化from对象 这里的数据传多了不报错 但是传少了就会报错 默认每个字段都是非空
from_obj = views.MyFrom({'username':'jason','password':12,'email':'123@qq.com'})

# 2 判断数据是否合法 只有在全部字段都合法的情况下才会返回True 其余情况下都会返回False
from_obj.is_valid()
False

# 3 查看所有通过校验的数据
from_obj.cleaned_data
{'username':'jason','email':'123@qq.com'}

# 查看所有没有通过校验的数据及原因
from_obj.errors
{'password':['Ensure this value has at least 3 characters (it has 2).']}

④、校验数据

from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})
# 2 判断数据是否合法		注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
False
# 3 查看所有校验通过的数据
form_obj.cleaned_data
{'username': 'jason', 'password': '123'}
# 4 查看所有不符合校验规则以及不符合的原因
form_obj.errors
{
  'email': ['Enter a valid email address.']
}
# 5 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid()
True
# 6 校验数据 默认情况下 类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid()
False
"""
也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传
"""

⑤、渲染标签

"""
froms组件只会帮我们渲染获取用户输入的标签(input、select、radio、checkbox),不会帮我们渲染提交按钮
"""
# 后端
def ab_from(request):
    # 1.先产生一个空对象
    from_obj = MyFrom()
    # 2.直接将该对象传递给html页面
    return render(request,'ab_from.html',locals())

# 前端
<p>第一种渲染方式:代码书写极少,封装程度太高 不便于后期扩展 一般情况下只在本地测试</p>
{{ from_obj.as_p }} //以p标签展示
{{ from_obj.as_uk }} //以ul标签展示
{{ from_obj.as_table }} //以table标签展示

<p>第一种渲染方式:可扩展性强 但是需要书写太多代码 一般情况下不用</p>
<p>{{ from_obj.username.label }}:{{ from_obj.username }}</p>
<p>{{ from_obj.password.label }}:{{ from_obj.password }}</p>
<p>{{ from_obj.email.label }}:{{ from_obj.email }}</p>
    
<p>第三种方式渲染(推荐使用):代码书写简单 可扩展性高</p>
{% for from in from_obj %}
<p>{{ from.label }}:{{ from }}</p>
{% endfor %}

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

⑥、展示提示信息

"""
浏览器会自动帮我们校验数据 但是前端的校验安全系数太低 建议关闭
	<from action="" method="" novalidate>
"""

# 后端
def ab_from(request):
    # 先产生一个空对象
    from_obj = From()
    if request.method == 'POST':
        # 获取用户数据并且校验
        """
        1.数据获取繁琐
        2.校验数据需要构建成字典的格式传入空对象才行
        3.但是request.POST可以看成一个字典        
        """
        # 3.校验数据
        from_obj = From(request.POST)
        # 4.判断数据是否合法
        if from_obj.is_alid():
            # 5.如果合法 操作数据库存储数据
            return HttpResponse('OK')
        # 5.如果不合法 在前端处理展示错误信息
    # 6.将from_obj对象传给html页面
    return render(request,'ab_from.html',locals())


# 前端
{% for from in from_obj %}
	<p>
		{{ from.label }}:{{ from }}
        <span style="color:red;">{{ from.errors.0 }}</span>
	</p>
{% endfor %}


# 针对错误信息提示还可以自己定制 配置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':'用户名最大8位',
                                   'required':"用户名不能为空"
                               }
                               )
    # password字符串类型最小3位最大8位
    password = forms.CharField(min_length=3,max_length=8,label='密码',
                               error_messages={
                                   'min_length': '密码最少3位',
                                   'max_length': '密码最大8位',
                                   'required': "密码不能为空"
                               }
                               )
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'invalid':'邮箱格式不正确',
                                 'required': "邮箱不能为空"
                             }
                             )

    
    
"""
总结:
	1.必备条件 get请求和post请求传给html页面对象变量名必须一样 和前戏中的back_dict一样
	2.froms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改 更加人性化
"""

⑦、钩子函数

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

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

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

"""
实际案例
1.校验用户名中不能含有666			  一个字段		  局部钩子
2.校验密码和确认密码是否一致			两个字段		全局钩子
"""

# 钩子函数 在类中书写

# 局部钩子
def clean_usename(self):
    # 获取到已经校验通过的username数据
    username = self.cleaned_data.get('username')
    if '666' in username:
        # 添加不通过钩子函数校验的username的错误信息
        self.add_errors('username','光喊666是没用的')
     # 将钩子函数勾出去的数据再放回去
     return username
    
# 全局钩子
def clean(self):
    password = self.cleaned_data.get('password')
    confirm_password = se;f.cleaned_data.get('confirm_password')
    if not passwors == confirm_password:
        self.add_errors('confirm_password','两次密码不一样')
    # 将钩子函数勾出去的数据再放回去
    return self.cleaned_data

⑧、froms组件其他参数及标签渲染

  1. froms组件其他参数

    """
    label 字段名
    error_messages 自定义错误提示信息 以字段存放对应每个报错信息
    initial input框中的默认值
    requested 该字段在实例化对象时是否可以为空(不填)
    froms.widgets 修改字段标签样式 标签属性
    vaildators 正则校验数据
    """
    
    froms.widgets参数
    from django.forms import widgets
    widgets = froms.widgets.PasswordInput({'class':'from-control'})
    -----------------------------------------------------
    vaildators参数
    from django.core.validators import RegexValidator
    user = fields.CharField(
            validators=[
                RegexValidator(r'^[0-9]+$', '请输入数字'),
            	RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
            ],
        )
    
    """
    针对校验数据的方式
    	1.最简单的(第一道关卡):max_length、min_length
    	2.正则校验(第一道关卡):vaildators
    	3.钩子函数校验(第二道关卡)
    """
    
  2. 标签渲染

    # 单选radio
    gender = forms.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()
        )
    --------------------------------------------------------------------
    # 单选checkbox
    keep = forms.ChoiceField(
            label="是否记住密码",
            initial="checked",
            widget=forms.widgets.CheckboxInput()
        )
    # 多选checkbox
    hobby = forms.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.CheckboxSelectMultiple()
        )
    
  3. froms组件源码

    # from组件的切入点是is_valid()
    
     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_valid要返回True的话 那么self.is_bound要为True self.errors要为Flase
            
    self.is_bound = data is not None or files is not None
    # 可以发现is_bound只要传值了就是true,所以直接看errors就行了
    
    
    @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
    
    self._errors = None
    # _errors在定义的时候默认为None,所以这里必定会执行full_clean
    
    
        def full_clean(self):
            """
            Cleans all of self.data and populates self._errors and
            self.cleaned_data.
            """
            # 定义一个存放错误信息的对象
            self._errors = ErrorDict()
            # is_bound 只要传值了就不为空
            if not self.is_bound:  # Stop further processing.
                return
            # 定义一个存放正确数据的字典
            self.cleaned_data = {}
            # If the form is permitted to be empty, and none of the form data has
            # changed from the initial data, short circuit any validation.
            # empty_permittedm默认为空,所以这里不会执行
            if self.empty_permitted and not self.has_changed():
                return
    		
            # 这三条是form组件的核心
            self._clean_fields() # 内部是校验数据和局部钩子
            self._clean_form()	# 内部是全局钩子
            self._post_clean()
    

10、Cookie与Session

①、Cookie与Session简介

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


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

cookie
	服务端保存在客户端浏览器上的信息都可以称之为cookie
    它的表现形式一般都是kv键值对(可以有多个)
   
session
	数据是保存在服务端的并且它的表现形式一般都是kv键值对(可以有多个)
    
token
	session虽然数据是保存在服务端的 但是禁不住数据量大 服务端不再保存数据
    登录成功后 将一段用户信息进行加密处理(加密算法各个公司都不一样)
    将加密之后的结果拼接在信息后面 整体返回给浏览器保存
    浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法
    跟浏览器尾部的密文进行比对
    
jwt认证


"""
总结:
  	1.cookie就是保存在客户端浏览器上的信息
    2.session就是保存在服务端上的信息
    3.session是基于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的具体操作
"""
设置cookie 可以设置多个
	obj.set_cookie(key,value)
	obj.set_signed_cookie(key,value,salt='盐') 加盐操作
		obj.set_cookie('username','jason')
		obj.set_cookie('password','123')
获取cookie
	request.COOKIES.get(key)
	request.get_signed_cookie(key) 获取加盐cookie
		request.COOKIES.get('username')
		request.COOKIES.get('password')
设置cookie失效时间
	obj.set_cookie('username','jason',max_age=3,expires=3)
	
	max_age 针对现在主流的浏览器都有效 以秒为单位
	expires 针对IE浏览器使用 以秒为单位
	
删除cookie 注销功能
	obj.delete_cookie('username')
"""

利用cookie实现登录功能
# 注册功能
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        target_url = request.GET.get('next_url')
        if username == 'jason' and password == '123':
            obj = redirect(target_url)
            obj.set_cookie('key','value')
            return obj
    return render(request,'login.html')
  
# 装饰器功能
def login_auto(func):
    def inner(request,*args,**kwargs):
        target_url = request.get_full_path()
        if request.COOKIES.get('key'):
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/?next_url={}'.format(target_url))
    return inner

@login_auto
def index(request):
    return HttpResponse('知道你付钱了 index')

@login_auto
def func(request):
    return HttpResponse('知道你付钱了 func')

# 注销功能 要在登录之后才能执行
@login_auto
def logout(request):
    obj = redirect('/login/')
    obj.delete_cookie('key')
    return obj

③、Session操作

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

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

session的具体操作
"""
设置session
	request.session[key] = value
		request.session['username'] = 'jason'
获取session
	request.session.get('key')
		request.session.get('username')
设置session失效时间
	request.session.set_expiry()
	括号内的参数类型
		1.整型          		多少秒
		2.日期对象			   到指定日期实效
		3.0					 当前浏览器窗口关闭就实效
		4.不写				实效时间就是django默认的14天
清除session
	request.session.delete()    只删除服务端的 客户端的不删
	request.session.flush()     浏览器和服务器都清空
"""

session是保存在服务器的 但是session的保存位置可以有多种选择
	1.MySQL
    2.文件
    3.redis
    4.memcache
    ...
    
django_session表中的数据条数是取决于浏览器的
	同一个计算机上(IP地址)同一个浏览器只会有一条数据生效
	(当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除 你也可以通过代码清除)
	
	主要是为了节省服务端数据库资源
    

request.session['username'] = 'jason'
	"""
	内部发生那些事
		1.django内部会自动帮你生成一个随机字符串
		2.django内部会自动将随机字符串和对应的数据存储起到django_session表中
			2.1先在内部产生操作数据的缓存
			2.2在响应结果django中间件的时候才是真正的操作数据库
		3.将产生的随机字符串返回给客户端浏览器保存
	"""
request.session.get('username')
	"""
	内部发送了那些事
        1.自动从浏览器请求中获取sessionid对应的随机字符串
        2.拿着该随机字符串去django_session表中查找对应的数据
        3.如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中
          如果比对不上 则request.session.get()返回的是None
	"""
    

利用session实现登录功能
# 注册功能
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        target_url = request.GET.get('next_url')
        if username == 'jason' and password == '123':
            request.session['key'] = 'value'
            request.session.set_expiry(3) # 设置session失效时间
            return redirect(target_url)
    return render(request,'login.html')
  
# 装饰器功能
def login_auto(func):
    def inner(request,*args,**kwargs):
        target_url = request.get_full_path() # 获取当前的请求url后缀
        if request.session.get('key') == 'value':
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/?next_url={}'.format(target_url)) # 带着此url后缀访问login
    return inner

@login_auto
def index(request):
    return HttpResponse('知道你付钱了 index!')

@login_auto
def home(request):
    return HttpResponse('知道你付钱了 home!')

# 注销功能 要在登录之后才能执行
def logout(request):
    request.session.flush()
    return redirect('/login/')

11、CBV添加装饰器

from django.views import View
from django.utils.decorators import method_decorator

"""
CBV中django不建议直接给类的方法加装饰器
无论该装饰器能不能正常使用 都不建议加
"""

@method_decorator(login_auto,name='get') # 方式2(可以添加多个针对不同方法加不同的装饰器)
@method_decorator(logout_auto,name='post')
class MyLogin(View):
    @method_decorator(login) # 方式3:它会直接作用于当前类里面的所有的方法
    def dispatch(self,request,*args,**kwargs):
    	return super().dispatch(request,*args,**kwargs)    
    @method_decorator(login_auto) # 方式1 指名道姓
    def get(self,request):
        return HttpResponse('get请求')
    def post(self,request):
        return HttpResponse('post请求')

12、django中间件

①、django中间件

  1. django中间件是django的门户 请求响应都要经过它(参考django请求生命周期流程图)

    • 请求来的时候需要先经过中间件才能到达真正的django后端
    • 相应走的时候也要经过中间件才能将数据返回给浏览器
  2. django自带7个中间件

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    	]
    
  3. 研究django中间件代码规律

    class SessionMiddleware(MiddlewareMixin):
        def process_request(self, request):
            session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
            request.session = self.SessionStore(session_key)
        def process_response(self, request, response):
            return response
          
    class CsrfViewMiddleware(MiddlewareMixin):
      	def process_request(self, request):
            csrf_token = self._get_token(request)
            if csrf_token is not None:
                # Use same token next time.
                request.META['CSRF_COOKIE'] = csrf_token
        def process_view(self, request, callback, callback_args, callback_kwargs):
            return self._accept(request)
    
        def process_response(self, request, response):
            return response
          
    class AuthenticationMiddleware(MiddlewareMixin):
        def process_request(self, request):
            request.user = SimpleLazyObject(lambda: get_user(request))
    
  4. 总结 django支持程序员自定义中间件并且暴露了5个自定义的方法

    • 必须掌握
      • process_request
      • process_response
    • 了解即可
      • process_view
      • process_template_response
      • process_exception

②、如何自定义中间件

  1. 步骤

    • 在项目名或者应用名下创建一个任意名称的文件夹

    • 在该文件夹下创建一个任意名称的py文件

    • 在该py文件内需要书写类(这个类必须继承MiddlewareMixin)

      • 在这个类里面就可以自定义5个方法
      • 这5个方法并不是全部都要书写 用几个就写几个
    • 需要将类的路径以字符串的形式注册到配置文件中才会生效

      MIDDLEWARE = [
          'django.middleware.security.SecurityMiddleware',
          'django.contrib.sessions.middleware.SessionMiddleware',
          'django.middleware.common.CommonMiddleware',
          'django.middleware.csrf.CsrfViewMiddleware',
          'django.contrib.auth.middleware.AuthenticationMiddleware',
          'django.contrib.messages.middleware.MessageMiddleware',
          'django.middleware.clickjacking.XFrameOptionsMiddleware',
          '你自己写的中间件的路径1',
          '你自己写的中间件的路径2',
          '你自己写的中间件的路径3',
      	]
      
  2. 五个方法详解

    • process_request

      • 请求来的时候需要经过每个中间件里面的process_request方法 结果顺序是按照配置文件中注册的中间件从上往下的顺序依次执行

      • 如果中间件里面没有定义该方法 那么直接跳过执行下一个中间件

      • 如果该方法返回了HttpResponse对象 那么请求将不再继续往后执行 而是直接沿路返回(校验失败不允许访问...)

      • process_request方法是用来做全局相关的所有限制功能

        from django.utils.deprecation import MiddlewareMixin
        class MyMiddleware1(MiddlewareMixin):
            def process_request(self,request):
                print('我是第一个自定义中间件里面的process_request方法')
        
    • process_response

      • 响应走的时候需要结果每一个中间件里面的process_response方法,该方法有两个额外的参数request,response

      • 该方法必须返回一个HttpResponse对象

        • 默认返回的就是形参response
        • 也可以自己返回自己的数据
      • 顺序是按照配置文件中注册了的中间件从下往上依次经过,如果你没有定义的话 直接跳过执行下一个

        from django.utils.deprecation import MiddlewareMixin
        class MyMiddleware1(MiddlewareMixin):
            def process_response(self,request,response):
                print('我是第一个自定义中间件里面的process_response方法')
        
      """
      研究如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况
      		是其他情况
      			就是会直接走同级别的process_reponse返回
      		
      		flask框架也有一个中间件但是它的规律
      			只要返回数据了就必须经过所有中间件里面的类似于rocess_reponse方法
      """
      
    • process_view

      • 路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该方法

      • 顺序是按照配置文件中注册的中间件从上往下的顺序依次执行

        from django.utils.deprecation import MiddlewareMixin
        class MyMiddleware1(MiddlewareMixin):
            def process_view(self,request,view_name,*args,**kwargs):
                print(view_name,args,kwargs)
                print('我是第一个自定义中间件里面的process_view')
        
    • process_template_response

      • 回的HttpResponse对象有render属性的时候才会触发

      • 顺序是按照配置文件中注册了的中间件从下往上依次经过

        from django.utils.deprecation import MiddlewareMixin
        class MyMiddleware1(MiddlewareMixin):
            def process_template_response(self,request,response):
                print('我是第一个自定义中间件里面的process_template_response')
                return response
            
        # 视图函数也要书写代码才会执行process_template_response方法
        def index(request):
            print('我是视图函数index')
            obj = HttpResponse('index')
        
            def render():
                print('内部的render')
                return HttpResponse("O98K")
            obj.render = render
            return obj
        
    • process_exception

      • 当视图函数中出现异常的情况下触发

      • 顺序是按照配置文件中注册了的中间件从下往上依次经过

        from django.utils.deprecation import MiddlewareMixin
        class MyMiddleware1(MiddlewareMixin):
            def process_exception(self,request,exception):
                print('我是第一个中间件里面的process_exception')
                print(exception)
        

③、csrf跨站请求伪造

"""
钓鱼网站
	我搭建一个跟正规网站一模一样的界面(中国银行)
	用户不小心进入到我们的网站,用户给某个人转账
	转账的操作确实是提交给中国银行的系统,用户的钱也确实是减少了
	但是唯一不同的是转账的账户不是用户想要转的账户
"""

"""
内部原理
	我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
	然后我们在内部隐藏一个已经写好name和value的input框

如何规避上述问题
	csrf跨站请求伪造校验
		网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识
		当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行	
"""

④、post请求如何符合csrf校验

# from表单请求如何符合校验
<from action="" method="post">
	{% csrf_token %}
    <p>username:<input type="text" name="name"></p>
    <p>target_user:<input type="text" name="arget_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</from>
<button id="d1" name="提交"></button>


# ajax如何符合请求
<script>
    $('#d1').click(function () {
        $.ajax({
            url:'',
            type:'post',
            // 方式一:利用标签获取页面上的验证码
            data:{'username':'hz','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
            // 方式二:利用模版语言便捷写法
            data:{'username':'hz','csrfmiddlewaretoken':{{ csrf_token }},

            data:{'username':'hz'},
            success:function () {

            }
        })
    })
</script>

# 方式1 利用标签查找获取页面上的随机字符串
data:{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()}
# 方式2 利用模板语法提供的快捷书写
data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}
# 方式3 通用方式直接拷贝js代码并应用到html页面上即可
data:{"username":'jason'}
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

⑤、csrf相关装饰器

"""
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不需要校验
"""

"""
csrf_protect 需要校验
	针对csrf_protect符合我们之前所学的CBV装饰器的三种玩法
csrf_exempt 忽略校验
	针对csrf_exempt只能给dispatch方法才有效
"""

from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator

# @csrf_exempt
# @csrf_protect
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print('%s给%s转了%s元'%(username,target_user,money))
    return render(request,'transfer.html')



from django.views import View

# @method_decorator(csrf_protect,name='post')  # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
    # @method_decorator(csrf_protect)  # 针对csrf_protect 第三种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第三种方式可以
    def dispatch(self, request, *args, **kwargs):
        return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)

    def get(self,request):
        return HttpResponse('get')

    # @method_decorator(csrf_protect)  # 针对csrf_protect 第一种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第一种方式不可以
    def post(self,request):
        return HttpResponse('post')

⑥、django重要思想

  1. importlib模块

    # 模块:importlib
    import importlib
    res = 'myfile.b'
    ret = importlib.import_module(res)  # from myfile import b
    # 该方法最小只能到py文件名
    print(ret)
    
  2. 思想

    # 创建一个包里面存有我们要使用的类
    
    # mymid.qq
    class QQ():
        def __init__(self):
            pass
    
        def send(self,msg):
            print(f'qq:{msg}')
    
    # mymid.wechat
    class Wechat():
        def __init__(self):
            pass
    
        def send(self,msg):
            print(f'wechat:{msg}')
            
    # 创建一个settings去存储这些类的路径
    # settings.py
    MYMID_DIR = [
        'mymid.qq.QQ',
        'mymid.wechat.Wechat',
    ]
    
    # 重点!!!!!!
    # mymid.__init__.py
    import importlib
    import settings
    
    def send_all(msg):
        for mymid in settings.MYMID_DIR:
            # 1 先用切片的方式获取模块路径和具体类名
            module_path,cls_name = mymid.rsplit('.',maxsplit=1) # ['mymid.qq','QQ']
            # print(module_path,cls_name)
            # 2 通过importlib模块以字符串的形式导入指定模块
            res = importlib.import_module(module_path) # from mymid import qq
            # 3 通过反射的方法获取类名
            cls = getattr(res,cls_name) # 获取QQ这个类
            obj = cls() # 生成一个QQ的对象
            # 4 利用鸭子类型直接调用send方法
            obj.send(msg) # 加括号执行这个send方法
            
            
    # start.py启动文件
    import mymid
    mymid.send_all('吃饭了')
    

⑦、钓鱼网站制作

# 正规网站html代码
<h1>我是正儿八经的网站</h1>
<form action="" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>target_user:<input type="text" name="target_user"></p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

# 钓鱼网站html代码
<h1>我是钓鱼网站</h1>
<form action="http://127.0.0.1:8000/index/" method="post">
    <p>username:<input type="text" name="username"></p>
    <p>
        target_user:<input type="text" name="target_user" value="jason" style="display: none;">
                    <input type="text">
    </p>
    <p>money:<input type="text" name="money"></p>
    <input type="submit">
</form>

13、Auth模块

①、Auth模块简介

"""
其实我们在创建好一个django项目之后直接执行数据库迁移命令会自动生成很多张表
	django_session
	auth_user
django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且还必须是管理员用户才能进入

创建超级用户(管理员)
	python manage.py createsuperuser
	
依赖于auth_user表完成用户相关的所有功能
	用户注册、用户登录、登录校验、用户注销....
"""

②、方法总结

  1. auth方法

    from django.contrib import auth
    
    user_obj = auth.authenticate(request,username=username,password=password) # 校验用户名密码 参数:request,用户名,密码
    auth.login(request,user_obj) # 保存用户状态 参数:request,用户对象
    auth.logout(request) # 退出登录 参数:request
    
  2. request方法

    request.user # 用户对象
    request.user.is_authenticated() # 判断用户是否登录
    request.user.check_password(old_password) # 检验旧密码是否正确 参数:旧密码
    request.user.set_password(new_password)  # 仅仅是在修改对象的属性
    request.user.save()	# 真正修改密码并保存
    
  3. User方法

    from django.contrib.auth.models import User
    	
    User.objects.create(username=username,password=password)  # 写入数据  不能用create 密码没有加密处理
    # 创建普通用户
    User.objects.create_user(username=username,password=password)
    # 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
    User.objects.create_superuser(username=username,email='123@qq.com',password=password)
    
# 1.比对用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
# 括号内必须同时传入用户名和密码
print(user_obj) # 用户对象 jason 数据不符合则返回None
print(user_obj.password) # jason
print(user_obj.password) # 密文

# 2.保存当前用户状态
auth.login(request,user_obj) # 类似于request.session[key] = user_obj
# 主要执行了该方法 你就可以在任何地方通过request.user获取到当前登录的用户对象

# 3.判断当前用户是否登录
request.user.is_authenticated()

# 4.获取当前用户
request.user

# 5.校验用户是否登录装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置
LOGIN_URL = '/login/'
	1.如果全局和局部都有 该听谁的?
    	局部 > 全局
    2.局部和全局那个好?
    	去哪聚的好处在于无需重复写代码 但是跳转的uemain很单一
        局部的好处在于不同的视图函数在用户没有登录的情况下可以跳转到不同的页面
        
# 6.对比原密码
request.user.check_password(old_password)

# 7.修改密码
request.user.set_password(new_password) # 仅仅在修改对象属性
request.user.save() # 这一步才是真正操作数据库

# 8.注销
auth.logout(request)

# 9.注册
# auth_user表内写入数据
User.objects.create(username=username,password=password) # 写入数据不能用create 因为密码没有加密处理 后期会出错
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令行创建爱你则可以不用填
User.objects.create_superuser(username=username,password=password)

③、Auth具体使用

from django.contrib import auth

********************************* 注册 *********************************
from django.contrib.auth.models import User
def register(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        confirm_password = request.POST.get('confirm_password')
        if password == confirm_password:
            User.objects.create_user(username=username,password=password) # 普通用户注册
            return redirect('/index/')
    return render(request,'register.html')

********************************* 登录 *********************************
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username') # 获取用户名
        password = request.POST.get('password') # 获取密码门
        user_obj = auth.authenticate(request,username=username,password=password) # 校验用户名和密码
        if user_obj:
            auth.login(request,user_obj) # 保存用户状态
            return redirect('/home/')
        return HttpResponse('用户名或者密码错误')
    return render(request,'login.html')


********************************* 修改密码 *********************************
from django.contrib.auth.decorators import login_required
@login_required
def change_password(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        if new_password == confirm_password:
            if request.user.check_password(old_password):
                request.user.set_password(new_password)
                request.user.save()
                return redirect('/index/')
    return render(request,'change_password.html',locals())


********************************* 退出 *********************************
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def logout(request):
    auth.logout(request)
    return redirect('/login/')

********************************* 装饰器 *********************************
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def index(request):
    print(request.user)
    print(request.user.is_authenticated())
    return HttpResponse('index')

④、如何扩展auth_user表

from django.db import models
from django.contrib.auth.models import User,AbstractUser

# 第一种:一对一关系 不推荐
class UserInfo(models.Model):
    phone = models.BigIntegerField()
    user = models.OneToOneField(to="USer")
    
# 第二种:面向对象的继承
class UserInfo(AbstractUser):
    phone = models.BigIntegerField()
"""
如果继承了AbstractUser
那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了
而UserInfo表就会出现auth_user所有的字段外加自己扩展的字段
这么做的好处在于能够直接点击你自己的创建的表,更加快捷地完成操作及扩展

前提:
	1.在继承之前没有执行过数据库迁移命令
		auth_user没有被创建出来,如果当前数据库已经创建了,那么你就重新换一个库
	2.继承的类里面不要覆盖原来的AbstractUser里面的额字段名
		表里面的字段都不要动,只扩展额外的字段即可
	3.需要在配置文件中告诉django不要用哪个UserInfo替代auth_user
		AUTH_USER_MODEL = 'app01.UserInfo'
							应用名.表名

如果自己写的表替代了auth_user那么
auth模块的功能照常使用呢,使用的页面由原来的auth_user变成了UserInfo
"""

posted @ 2021-01-26 15:42  今天捡到一百块钱  阅读(79)  评论(0编辑  收藏  举报