框架第十课---forms组件渲染标签,展示信息,校验补充,参数补充,源码剖析,modelform组件,django中间件

昨日内容回顾

  • ajax补充说明

    形参接收后端返回的各种数据 不再直接影响整个浏览器页面
    dataType:'json'
    
  • 多对多三种创建方式

    1.全自动创建
    2.纯手动创建
    3.半自动创建
    	through
    	through_fields
    
  • django序列化组件

    序列化组件可以简单的看着是帮你把数据处理成最容易实现不同语言交互的形式
    
  • 批量插入数据

    models.Book.objects.bulk_create([数据对象1,数据对象2...])
    
  • 自定义分页器

    1.queryset支持切片操作
    2.定义了分页相关的参数
    	current_page\per_page_num\start_page\end_page
    3.研究参数之间的数学关系
    4.前端提供分页的页码标签
    5.总页码数的确定>>>:divmod
    6.前端渲染固定个数的页码标签(对称美)
    

自定义分页器代码
只需要掌握使用方式即可


* forms组件

```python
1.校验数据
2.渲染标签
3.展示信息

from django import forms
class MyForm(forms.Form):
    username = forms.CharField(min_length=3,max_length=8)
1.使用字典的形式传入待校验的数据
	form_obj = MyForm({'username':'jason'})
2.调用校验方法完成数据的校验
	form_obj.is_valid()  所有的数据全部符合规范结果才是True
3.获取符合校验规则的数据
	form_obj.cleaned_data
4.获取不符合校验规则的字段及错误原因
	form_obj.errors
"""
1.默认所有的字段必须传值
2.多传的数据会直接忽略不影响校验
"""

今日内容概要

  • forms组件渲染标签
  • forms组件展示信息
  • forms组件校验补充
  • forms组件字段补充
  • forms组件参数补充
  • forms组件源码剖析
  • modelform组件
  • django中间件

今日内容详细

forms组件渲染标签

--------------------------------------
from django.shortcuts import render

# Create your views here.

from django import forms

class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8,label='用户名')
    age = forms.IntegerField(min_value=0, max_value=200,label='年龄')
    email = forms.EmailField()

def ab_forms_func(request):
    # 1. 产生一个空对象
    form_obj = MyForm()

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

--------------------------------------
<p>forms组件渲染标签的方式1(封装程度过高 扩展性差 主要用于本地测试):</p>
        {{ form_obj.as_p }}     # p标签包裹input标签

        {{ form_obj.as_ul }}   # ul标签包裹input标签
        {{ form_obj.as_table }}   # table标签包裹input标签

forms组件好用的地方就是,只需要在后端提前把自定义的MyForm()类写好,如果想要获取自定义的类里面所定义的字段数据,只需要用该类产生一个空对象,然后把该空对象传递到html页面上去后,用{{ form_obj.as_p }}  就能自动生成3个input标签,专门用来获取类里面字段所需要的数据,并且该3个input标签外面用p标签包裹着。
这样就不需要自己编写input标签了!!!
forms组件渲染出来的标签展示的文本默认都是类里面定义的字段名首字母大写,如果要想改成中文,就要给类里面的字段再加个参数label='XXX' 给字段起别名了
--------------------------------------------------------
<p>forms组件渲染标签的方式2(封装程度过低 扩展性高 编写麻烦)</p>

      {{ form_obj.username.label }}   # 拿字段名里面label对应的值
      {{ form_obj.username }}   # 相当于input标签
      {{ form_obj.age.label }}
      {{ form_obj.age }}
      {{ form_obj.email.label }}
      {{ form_obj.email }}

这种方法实现了label标签与input标签的解除绑定了,这个时候就可以自定义label标签了,比如label标签把input标签包起来或者和和input标签并排都可自定义了
---------------------------------------------------------
<body>
<p>forms组件渲染标签的方式3(封装程度较高 扩展性高 编写简单 推荐使用)</p>
<form action="" novalidate >
    {% for form in form_obj %}
        <p>
            {{ form.label }}   # 拿字段名里面label对应的值,如果没有拿的就是首字母大写的字段名
            {{ form }}   # 相当于input标签
        </p>
    {% endfor %}
    <input type="submit">
</form>
</body>

# 这种无论你定义的form类里面有多少字段名都会帮你渲染成标签
----------------------------------------------------------

注意事项
forms组件只负责渲染获取用户数据的标签, 也就意味着form标签与按钮都需要自己写!!!
----------------------------------------------------------

前端的校验是弱不禁风的 最终都需要后端来校验 所以我们在使用forms组件的时候可以直接取消前端帮我们的校验
	<form action="" novalidate>
-----------------------------------------------------------

.
.
.
.

forms组件展示信息

后端不同请求返回的forms对象一定要是相同的变量名
def ab_forms_func(request):
    # 1.产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)  # request.POST可以看成是一个字典 直接传给forms类校验 字典中无论有多少键值对都没关系 只校验forms类中有的字段名,对应的键的值
        if form_obj.is_valid():  # 校验数据是否合法
            print(form_obj.cleaned_data)
        else:
            print(form_obj.errors)
    # 2.将该对象传递给html文件
    return render(request, 'formsPage.html', locals())
--------------------------------------

{% for form in form_obj %}
     <p>
         {{ form.label }}
         {{ form }}
         <span>{{ form.errors.0 }}</span>
     </p>
{% endfor %}
--------------------------

{{ form.errors }}
拿当前这个标签框里面获取的数据可能的报错提示  是这种样子的
<ul class="errorlist"><li>username<ul class="errorlist"><li>This field is required.</li></ul></li><li>age<ul class="errorlist"><li>This field is required.</li></ul></li><li>email<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
----------------------------

{{ form.errors.0 }}   拿只拿里面的文本信息!!!

image
.
当前端发的是get请求时,form_obj是空对象,空对象里面是没有form_obj.errors 的,所以前端的span标签就是空的
但是当前端发送的post请求时,form_obj就不是空对象了,form_obj.errors就能点出用户在输入框数据的数据对应不符合校验规则的原因了
image
image
image
.
但此时展示的错误提示被自动包裹成一个标签了,破环页面的样式了,我们希望是只展示出错误的文字出来就行,只需要在errors后面加个.0 就行了!!
{{ form.errors.0 }}
image
这样就和之前我们写的自动提示(在前端的input框后后面定义一个span标签,然后再后端先定义一个空字典,如果获取的数据不符合要求,就把不符合要求的信息放到空字典里面去,然后在前端的span标签里通过字典拿错误的信息的操作)就很像了!!!都是把文本信息放到span标签里面去了!!!
image
.
.
.

针对错误信息的提示可以修改成各国语言!!!

方式1:完全自己自定义内容
	给字段对象添加errors_messages参数!!!
	username = forms.CharField(
                              min_length=3,
                              max_length=8,
                              label='用户名',
                              error_messages={
                           'min_length': '用户名最少三个字符',
                           'max_length': '用户名最多八个字符',
                           'required': '用户名不能为空',
                             "invalid": "格式错误",}
                               )
---------------------------------------------------------
方式2:修改系统语言环境(django是支持切换语言环境的!!!)
	from django.conf import global_settings
	django内部真正的配置文件

django是支持修改语言环境的,改一下LANGUAGE_CODE = 'zh-hans',整个django的系统语言环境全部变成汉文了

global_settings 是settings配置文件的父级
django暴露给我们可以定义的配置,都是来源于global_settings
所以我们settings文件里面的所有配置都来源于global_settings

image
.
image
.
image
.
.
.
.
.

forms组件校验补充(钩子函数重要!!!)

forms组件针对字段数据的校验 提供了三种类型的校验方式(可以一起使用!!)
-------------------------------------------------

第一种类型:直接填写参数		max_length
-------------------------------------------------

第二种类型:使用正则表达式		validators
需要先导入正则校验模块
from django.core.validators import RegexValidator
每个字段都可以加正则,并且还支持对一个字段写多个正则,比如:

class MyForm(forms.Form):
phone = forms.CharField(label='手机号码',
          validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),
          RegexValidator(r'^159[0-9]+$', '数字必须以159开头')]
                        )
RegexValidator(r'正则表达式', '不符合正则表达式后的提示信息')
---------------------------------------------------
上面两种的校验,都是单单某一个特定的条件,但是如果校验是由逻辑的,比如
假如对获取到的用户名,我们想校验用户名是否已经在数据库里面存在了,怎么办?就要写代码来解决了!!!

第三种类型:钩子函数		编写代码自定义校验规则
钩子函数:其实就是相当于一个数据正常的走着,可以通过钩子函数在某一个时间节点,把数据先勾上来,做一些处理,然后再放回去

---------------------------------------------

image
.
.
.

钩子函数


钩子函数>>>钩子函数的触发条件:数据已经经过字段里面的参数校验之后,才会触发钩子函数
也就是说钩子函数相当于最后一到关卡了,可以想象成每一个字段数据都会经历3个关卡

# 第一个关卡是min_length,max_length这种,第二个关卡是正则,第三个关卡就是钩子函数

# 前端的数据传到后端的request.POST里面后转手就作为数据传到我自定义的MyForm()类里面进行校验,并生成对象

只要符合前两个关卡的要求,数据就会被放到MyForm类的对象的cleaned_data里面

--------------------------------------

# 钩子函数>>>:校验的最后一环 是在字段所有的校验参数之后触发

self.add_error('XXX字段', '错误信息')   # 给对应字段添加错误信息

对应到html页面上对应的字段的input框后面的span标签就能拿到对应的错误信息并展现出来!<span>{{ form.errors.0 }}</span>

.
.
.

views.py文件代码:

from django.shortcuts import render

# Create your views here.

from django import forms
from django.core.validators import RegexValidator

class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8, label='用户名')
    password = forms.CharField(min_length=3, max_length=8, label='密码')
    confirm_pwd = forms.CharField(min_length=3, max_length=8, label='确认密码')

    age = forms.IntegerField(min_value=0, max_value=200, label='年龄')
    email = forms.EmailField(label='邮箱')
    phone = forms.CharField(label='手机号码',
                            validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),
                                        RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
                            )
------------------

# 局部钩子:每次只校验一个字段数据   比如校验用户名是否存在
    def clean_username(self):
        # 钩子函数是有权力直接去cleaned_data里面获取数据的!
        username88 = self.cleaned_data.get('username')  # 从cleaned_data里面将username字段的数据勾出来
        # 此时如果和数据库相连,就可以先导入models模块 然后查询该用户名是否存在了
        from app01 import models
        res = models.User.objects.filter(username=username88)
        if res:
            self.add_error('username', '用户名已存在')
        return username88     # 勾出了啥就要返还啥
------------------

# 全局钩子:一次可以校验多个字段数据   比如校验两次密码是否一致
# 一次性将cleaned_data里面所有的数据,全部勾出来
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_pwd = self.cleaned_data.get('confirm_pwd')
        if not password == confirm_pwd:
            self.add_error('confirm_pwd','两次密码不一致')
        return self.cleaned_data  # 最后一次性将cleaned_data里面勾出的所有的数据再返还

    注意钩子函数是定义在form类里面的!!!别写错地方了!!!
-----------------

def ab_forms_func(request):
    # 1. 产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)  # request.POST 是一个字典,传给forms类校验
        if form_obj.is_valid():  # 校验数据是否合法
            print(form_obj.cleaned_data)
        else:
            print(form_obj.errors)

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

-----------------------------------------
models.py  代码
from django.db import models

# Create your models here.

class User(models.Model):
    """用户表"""
    id = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32, verbose_name='用户名')
    password = models.CharField(max_length=32, verbose_name='密码')
    age = models.IntegerField(verbose_name='年龄')
    phone = models.BigIntegerField(verbose_name='电话号码',null=True)
    email = models.CharField(max_length=32, verbose_name='用户名')

    def __str__(self):
        return f'图书对象<*>>>{self.username}'

---------------------------------

.

这样以后不管是用户名也好,密码也好,手机号码也好,只要提前在views.py里面把自定义的Form类写好,将来就可以一劳永逸了!!!
对应的视图函数 里面的代码也是固定的那几行了!!!
所有的校验都是由我们自定义的form类来帮我们完成

.
.
.

forms组件字段补充

forms.CharField(min_length=3, max_length=8, label='用户名',
                error_messages={
                           'min_length': '用户名最短三位',
                           'max_length': '用户名最长八位',
                           'required': '用户名不能为空'},
                widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))


forms.EmailField(label='邮箱',
                 error_messages={
                          'required': '邮箱不能为空',
                          'invalid': '邮箱格式不正确'},
                 widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))


.
.
.

forms组件参数补充

min_length			最小字符
max_length			最大字符
min_value			最小值
max_value			最大值
label				字段注释
error_messages			自定义字段的错误提示信息。
validators			正则校验器
initial='xxx'		给字段添加默认值

required			该字段是否必填    required=False 该字段非必填
那也就是说一个字段里只要写了required=False就说明该字段可以不填,但是只要填了,还是会按照括号里的条件进行校验的!!!

widget		控制生成标签的各项属性(class id type 等都可以用widget来控制)
-----------------------------------
重写错误信息。
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="密码")

.
.
.

forms组件渲染其他不同类型的标签!!!

widget		控制生成标签的各项属性(class id type 等都可以用widget来控制)
-------------------------------------
class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8, label='用户名',initial='哈哈哈')
    password = forms.CharField(min_length=3, max_length=8, label='密码',widget=forms.widgets.PasswordInput()   # 相当于标签里面的type='password'
                               )

widgets.TextInput()
widgets.PasswordInput()
widgets.EmailInput()

---------------------------------------
还支持在括号里面再加attrs{}给标签添加各种属性,这样forms组件渲染的标签就可以具有其他第三方框架所提前编好的各种样式了!!!
----------------------------------------
	widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'username': 'jason'})
----------------------------------------
这些各种标签,不用记,知道到时候到这来复制代码就行!!!
gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

单选框
------------------------
    hobby1 = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好1",
        initial=3,
        widget=forms.widgets.Select()
    )

单选的下拉框
-------------------------
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好2",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

多选的下拉框
--------------------------
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

单选的Checkbox
----------------------------
    hobby3 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好3",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

多选的Checkbox
-----------------------------

.
initial='xxx' 给字段添加默认值
image
image
.
image
image
.
.
.
.

forms组件源码剖析 没事多看看重要!!!

切入口:form_obj.is_valid()
课下自行研究

image
.
image
.
我自己定义的MyForm类继承了Form类,Form类又继承了BaseForm类
MyForm类加括号产生空对象的时候会触发类里面双下init方法,但是MyForm类里面没有,Form类里面也没有,最后触发了父父类BaseForm类里面的双下init方法的运行!!!
所以对象.is_valid()后,最后找到了BaseForm类里面is_valid方法,该方法的返回值是 return self.is_bound and not self.errors
所以当我们用用自定义类加括号产生对象的时候,form_obj = MyForm(request.POST) 会触发父父类里面的双下init方法,将对象当成self,request.POST当成第一个参数传给双下init方法,所以关键字行参data接收到的就是request.POST对应的值,所以对象.is_valid()的返回值return self.is_bound and not self.errors 中此时只要request.POST不是一个空字典 self.is_bound的值就是True了因为
看双下init方法里面的代码只要request.POST不是空字典self.is_bound的值就是True
self.is_bound = data is not None or files is not None
image
.
所以要想form_obj.is_valid()的结果是True,必须要让is_valid函数里面的返回值self.is_bound and not self.errors 两边都为True ,上面也推导了,只要前端传过来的不是个空字典那么self.is_bound一定是True,所以self.errors的结果一定要是False,form_obj.is_valid()的结果才能是True
点errors发现errors是父父类里面方法,(self.errors没加括号只拿到了函数名没有运行errors函数对吗?错!!!在类里面的函数被@property装饰伪装过后,对象调用该函数运行不用加括号了!!!!!!)

    if self._errors is None:
        self.full_clean()
    return self._errors

发现在双下init里面 self.errors = None 所以if条件肯定成立的,所以肯定走self.full_clean() 条件 ,所以肯定会调用运行全部清洗函数
.
image
image
.
image
.
self.fields 拿到类里面所编写的所有的字段名和字段对象
可以看到一开始 cleaned_data = {} 被定义为了一个空字典
当前端传的数据被前两个关卡校验后,校验符合的话放到cleaned_data这个字典里面去,
并且每for循环出一个字段名和字段对象,都会在最后利用面向对象的反射原理hasattr确定该字段有没有对应的钩子函数,如果有,再用getattr去调用运行该字段对象的类里面的钩子函数
注意源码里面将 函数的返回值当作value重新写到cleaned_data字典里面去
所以如果想要保证钩子函数勾的数据重新写到cleaned_data字典里面数据不变,那么钩子函数 的返回值就一定要和勾出来的结果一样!!!
还有为什么我们写局部勾子函数名的时候一定是 clean_对应的字段名 看源码也能看出来了,
因为用反射的时候用的字符串是,'clean
%s' % name 所以钩子函数的函数名只要不这么写,钩子函数是不会正常触发的!!!
注意看源码里面最后将钩子函数的返回值当作值,触发局部钩子函数的字段名作为键,添加到cleaned_data的字典里面去!!
image
image
.
image
.
.
全局钩子,源码里面上来就是对象.clean() 所以我们的全局钩子函数的函数名还就只能叫clean
并且源码里面还用变量名cleaned_data来接收运行全局钩子函数的返回值,所以如果不想 前两道关卡校验合格用变量名cleaned_data指代的数据丢失,那么就要让全局钩子函数的返回值就等于对象.cleaned_data 否则打个比方全局钩子函数里面返回个888,那么源码里面cleaned_data=888,就直接把原来放校验合格数据的字典给顶掉了!!这样其他地方想通过cleaned_data拿合格数据都拿不到了,所以全局钩子函数的返回值必须是self.cleaned_data !!!
image
.
.
.
.

modelform组件

通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。
基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。

"""
我们学习校验性组件的目的 绝大部分是为了数据录入数据库之前的各项审核
forms组件使用的时候需要对照模型类编写代码 不够方便
"""
forms组件的强化版本 更好用更简单更方便!!!

from django import forms
from app01 import models

class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        labels = {
            'username':'用户名',
            'password':'密码',
        }
        widgets = {
            "password": forms.widgets.PasswordInput(attrs={"class": "c1" 'form-control'})
                   }

def ab_mf_func(request):
    modelform_obj = MyModelForm()
    if request.method == 'POST':
        modelform_obj = MyModelForm(request.POST,instance=User_obj)
        if modelform_obj.is_valid():
            modelform_obj.save()# models.UserInfo.objects.create(...)/update(...)
        else:
            print(modelform_obj.errors)
    return render(request, 'modelFormPage.html', locals())


写好modelform后,如果后端想保存数据到数据库的话,只需要用自定义的Mymodelform对象点save()就可以保存了,都不需要用ORM操作了,因为modelform自动和models关联了
modelform对象点save()既可以创建数据到数据库中,也可以修改数据库中数据!!!
只需要在MyModelForm的括号里面多传一个参数instance=用户想改的数据对象
这个时候,modelform_obj.save()就变成了基于用户前端传来的数据修改数据库改数据对象中原有的数据了
-------------------------------------------------
class Meta下常用参数:
model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

.
.
.

django中间件

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
image

django默认有七个中间件,相当于保安  并且还支持用户自定义中间件
中间件主要可以用于:网站访问频率的校验, 用户权限的校验等全局类型的功能需求
-------------------------------------
比如现在想要做用户访问频率的校验(防爬虫用的): 假设我们做了一个用户访问频率的限制,同一个ip一分钟之内,只允许访问5次,如果一分钟内访问的频率超过了5次,就认为是一个爬虫程序,而不是一个人
那这个功能在哪里写比较合适了,就是在中间件里面写,请求以来,就直接看该ip在刚刚过去的1分钟内访问了几次了,如果已经超过5次,就直接让该请求原路滚蛋了,直接在中间件阶段就做完了用户访问频率的校验,这样就节省了django后端的资源消耗!!!
-------------------------------------
用户权限:比如VIP有哪些权限,普通用户有哪些权限,每一次请求里面都带着请求方的信息,所以中间件就可以从请求里拿到请求方到底是谁,然后获取该用户所具有的权限,然后拿着用户 想要访问的网址到用户所具有的权限表里面查看,如果用户有访问该网址的权限正常访问,如果没有该权限,就直接该请求访问网址了,VIP其实就是该账号添加权限,或者添加路由而已!!!这些功能都可以在中间件里面做!!!
-------------------------------------
全局类型的功能都可以用中间件来做!!!
-------------------------------------
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',
]

# 这里的每一个字符串其实都是一个模块文件的路径!!!
-------------------------------------------------------

这七个字符串实际上对应的是中间件程序存储的路径,我们可以顺着路径进去看一下它们的结构:
image
这七个程序都遵循着这样的结构:
继承了MiddlewareMixin类
可能做了一些初始化准备__init__
拥有process_request和process_response函数
process_request需要传入request参数,没返回值
process_response需要传入request和response两个参数,必须返回response
.
.

如何自定义中间件:
	1.创建存储自定义中间件代码的py文件或者目录(如果中间件很多)
	2.参考自带中间件的代码编写类并继承MiddlewareMixin类
	3.在类中编写五个可以自定义的方法
---------------------------------------------------------
五个可以自定义的方法:
	需要掌握的:
process_request
	1.请求来的时候会从上往下依次经过每一个注册了的中间件里面的该方法,如果没有则直接跳过
	2.如果该方法自己返回了HttpResponse对象那么不再往后执行而是直接原路返回!!
-------------------------------
process_response
	1.响应走的时候会从下往上依次经过每一个注册了的中间件里面的该方法,如果没有则直接跳过
	2.该方法有两个行参request和response 并且一定要返回response
	因为:形参response其实接收的就是后端想要返回给前端浏览器的数据, 该方法必须返回该形参, 也可以替换掉后端想发送给浏览器的数据!!!
-------------------------------
	需要了解的:
process_view
process_exception
process_template_response
-------------------------------
自定义中间件一定在配置文件中注册中间件才可以生效!!!!!!
-------------------------------
'''如果在执行process_request方法的时候直接返回了HttpResponse对象那么会原路返回执行process_response 不是执行所有'''

image
.
后端的数据从最下面的中间件开始往上一个一个的过中间件的process_response 函数
此时后端想给前端的数据被变量名response接收,但是当某一个中间件的返回值不在是response时,后端想给前端的数据就被中途截掉了,假设某一个中间件返回个'hahaha',那么下一个中间件就会把'hahaha'当成当成后端的数据继续传给下一个中间件!!!
image
image
.
.
.
.
请求到达中间件之后,先按照正序执行每个注册中间件的process_request方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法(注意不是掉头执行所有的process_response方法),将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。
image
.
.
process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。
image
.
.
了解
process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:
image
.
.
自定义中间件
首先,我们需要一个py文件存放程序,这个位置最好相对固定
image
其次,编写类,导入MiddlewareMixin继承,写好基础的类体函数:
process_request|process_response
注意这几个函数具有的特征。

from django.utils.deprecation import MiddlewareMixin

class MidWare01(MiddlewareMixin):
    def process_request(self, request):
        pass

    def process_response(self, request, response):
        return response

最后,将编写好的类的路径及类名按格式注册到MIDDLEWARE的列表中。
image
.
image
.
.
.

作业

1.forms组件完成用户注册功能
2.modelform组件完成用户注册与编辑功能
3.尝试将中间件柔和到某个项目中(开放问题)
posted @ 2022-12-21 22:15  tengyifan  阅读(35)  评论(0)    收藏  举报