Python入门自学进阶-Web框架——19Django其他相关知识

Posted kaoa000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——19Django其他相关知识相关的知识,希望对你有一定的参考价值。

与Django相关的其他一些知识点。

中间件、CSRF、缓存、信号、BootStrap(模板)——响应式+模板

一、中间件

在所有的web框架中都有。

 Django中中间件,在settings.py中:

 定义了一个MIDDLEWARE列表,其中定义了使用的中间件,列表是有序的。

每个中间件都是一个类,请求时,是按照列表顺序,依次执行类中的某个方法,如果某个中间件没有通过,就直接返回客户端,即请求都没有到达URLS匹配就返回了。如果请求到达views函数,处理后,在返回给客户端时,是反序依次执行中间件的另一个方法,即返回时也反序穿过所有中间件。

查看一下CsrfViewMiddleware:

是一个类,继承了MiddlewareMixin父类,然后定义了一些方法

而MiddlewareMixin父类如下:

 中间件中执行的方法,名称是固定的,即Django在执行中间件时,会默认去执行这些方法。

我们自定义自己的中间件,需要继承MiddlewareMixin类,如下

from django.utils.deprecation import MiddlewareMixin

class MidTestA(MiddlewareMixin):
    pass

class MidTestB(MiddlewareMixin):
    pass

在settings.py中:

 这样,我们自定义的中间件就开发、部署完成,只不过这两个中间件什么也没做。

我们要自定义的方法,就像CsrfViewMiddleware中的一样,主要是process_request()和process_response(),一个处理请求,一个处理响应。

from django.utils.deprecation import MiddlewareMixin

class MidTestA(MiddlewareMixin):
    def process_request(self,request):
        print("A----------》process_request")

    def process_response(self,request,response):
        print("A-------->process_response")
        return response         # 处理响应的,必须有返回值,即将响应response返回

class MidTestB(MiddlewareMixin):
    def process_request(self, request):
        print("B----------》process_request")

    def process_response(self, request, response):
        print("B-------->process_response")
        return response

这里要注意process_response必须有返回,将response返回。

执行结果:执行的顺序很明显。

 这里的参数request和response就是请求时的请求request和响应的response,可以对返回的信息进行再次处理。

那process_request能否返回呢?可以返回,但是这就意味着请求在这个中间件没有通过。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MidTestA(MiddlewareMixin):
    def process_request(self,request):
        print("A----------》process_request")
        return HttpResponse('中间件Aprocess_request返回了')

    def process_response(self,request,response):
        print("A-------->process_response")
        return response         # 处理响应的,必须有返回值,即将响应response返回

class MidTestB(MiddlewareMixin):
    def process_request(self, request):
        print("B----------》process_request")

    def process_response(self, request, response):
        print("B-------->process_response")
        return response

class MidTestC(MiddlewareMixin):
    def process_request(self, request):
        print("C----------》process_request")

    def process_response(self, request, response):
        print("C-------->process_response")
        return response

再次执行:前端

后台打印:

 可以看出,执行A的process_request返回后,又从A的process_response执行返回的序列。

修改一下,在B中返回:

 哪些中间件执行了process_request,有返回时,执行过的中间件也会反序执行process_response。

所以,整个生命周期图修改如下:

 实际上,还有一个方法: process_view(self, request, callback, callback_args, callback_kwargs)

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MidTestA(MiddlewareMixin):
    def process_request(self,request):
        print("A----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('A-------->process_view')

    def process_response(self,request,response):
        print("A-------->process_response")
        return response         # 处理响应的,必须有返回值,即将响应response返回

class MidTestB(MiddlewareMixin):
    def process_request(self, request):
        print("B----------》process_request")
        # return HttpResponse('中间件Bprocess_request返回了')

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('B-------->process_view')

    def process_response(self, request, response):
        print("B-------->process_response")
        return response

class MidTestC(MiddlewareMixin):
    def process_request(self, request):
        print("C----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('C-------->process_view')

    def process_response(self, request, response):
        print("C-------->process_response")
        return response

运行的结果:

 先执行的是ABC中间件的process_request,然后又回去重新执行ABC的process_view,然后到了view函数,最后执行CBA的process_response。

 打印一下process_view的参数:print(callback,callback_args,callback_kwargs)

<function test at 0x000000000BAAAEA0> () ,可见,callback就是函数test。

如果在B的process_request就返回了,运行如下:

 没有执行process_view,也就是process_view是在执行views函数前,即已经通过中间件的process_request后才执行的。

将urls中如下设置:path('test/<int:nid>/',views.test),

views中的test改为:
def test(req,nid):
     print("views")
     return HttpResponse("TEST")

再次运行,打印print(callback,callback_args,callback_kwargs)

<function test at 0x000000000BABAEA0> () 'nid': 21

将urls中如下设置:re_path('test/(\\d+)/',views.test),

<function test at 0x000000000BAB76A8> ('21',)

通过这里,我们应该能够得出,请求到了urls,进行了路由匹配,知道了对应的视图函数后,才开始执行process_view,这时已经知道了相应的视图函数,以及参数。

还有一个方法:process_exception(),即异常处理

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MidTestA(MiddlewareMixin):
    def process_request(self,request):
        print("A----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print(callback,callback_args,callback_kwargs)
        print('A-------->process_view')

    def process_response(self,request,response):
        print("A-------->process_response")
        return response         # 处理响应的,必须有返回值,即将响应response返回

    def process_exception(self,request,exception):
        print('A---------->process_exception')

class MidTestB(MiddlewareMixin):
    def process_request(self, request):
        print("B----------》process_request")
        # return HttpResponse('中间件Bprocess_request返回了')

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('B-------->process_view')

    def process_response(self, request, response):
        print("B-------->process_response")
        return response

    def process_exception(self,request,exception):
        print('B---------->process_exception')

class MidTestC(MiddlewareMixin):
    def process_request(self, request):
        print("C----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('C-------->process_view')

    def process_response(self, request, response):
        print("C-------->process_response")
        return response

    def process_exception(self,request,exception):
        print('C---------->process_exception')

执行结果:

没有执行process_exception,因为没有异常出现,修改test函数:

def test(req,nid):
    print("views")
    int('asc')
    return HttpResponse("TEST")

 int(‘asc’)会出现异常的,这时看运行结果:

 视图函数test执行后,执行了CBA的process_exception,然后又执行了CBA的process_response。

如果B的process_exception进行返回,如下:

    def process_exception(self,request,exception):
        print('B---------->process_exception')
        return HttpResponse('B---Process_exception-----return')

 再执行,结果不会报错了:前端

 后台打印:

 process_exception执行了C和B,A没有执行,因为B返回了,即B处理了,A就不执行,没有异常了。异常处理后,又回到CBA执行process_response。

另一个方法:process_template_response()

这个方法是当视图函数返回的结果对象中含有render方法时,这个方法才会执行:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse

class MidTestA(MiddlewareMixin):
    def process_request(self,request):
        print("A----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print(callback,callback_args,callback_kwargs)
        print('A-------->process_view')

    def process_response(self,request,response):
        print("A-------->process_response")
        return response         # 处理响应的,必须有返回值,即将响应response返回

    def process_exception(self,request,exception):
        print('A---------->process_exception')

    def process_template_response(self,request,response):
        print('A---------->process_template_response')

class MidTestB(MiddlewareMixin):
    def process_request(self, request):
        print("B----------》process_request")
        # return HttpResponse('中间件Bprocess_request返回了')

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('B-------->process_view')

    def process_response(self, request, response):
        print("B-------->process_response")
        return response

    def process_exception(self,request,exception):
        print('B---------->process_exception')
        # return HttpResponse('B---Process_exception-----return')

    def process_template_response(self,request,response):
        print('B---------->process_template_response')

class MidTestC(MiddlewareMixin):
    def process_request(self, request):
        print("C----------》process_request")

    def process_view(self,request,callback,callback_args,callback_kwargs):
        # 这个方法的参数,callback就是视图函数,这里就是views.test
        print('C-------->process_view')

    def process_response(self, request, response):
        print("C-------->process_response")
        return response

    def process_exception(self,request,exception):
        print('C---------->process_exception')

    def process_template_response(self,request,response):
        print('C---------->process_template_response')

视图函数修改一下:

class Foo():
    def __init__(self,req,html):
        self.req = req
        self.html = html

    def render(self):
        return render(self.req,self.html)

def test(req,nid):
    print("views")
    # int('asc')
    # return HttpResponse("TEST")
    return Foo(req,"test.html")

这时,视图函数test返回的是Foo对象,因为Foo对象中有render方法,中间件的process_template_response就会执行,执行结果:

 可以看到,C的process_template_response执行了,但是前端显示

 说的是C的process_template_response没有返回一个HttpResponse对象。修改一下C的这个函数

def process_template_response(self,request,response):
    print('C---------->process_template_response')
    print(request,';;',response)
    return response

 response是Foo对象,它返回后,错误就出在了B

 BA继续返回:都增加return response

结果:

前端显示test.html内容。 

中间件主要用于全局操作,对于所有用户请求都要处理,适合使用中间件,如过滤器,每个请求都要对请求头进行判断,还有就是缓存等。

二、CSRF

通过form提交:后台必须是render();前端form中增加% csrf_token %

Ajax提交:cookie中提取随机字符串 csrftoken对象的值;设置请求头(要知道Django中的请求头的名字):cookie中取到的值。

通过form提交,在没有增加% csrf_token%和增加后的响应头:

增加% csrf_token %

可以看到,响应头增加set-Cookie键,其值是csrftoken=rcgd1J7aEzjuFsovfiLJdvjC0jYsPGAe2IQOlNyzj0urN3Mj85uEQFqA82h9gViE; expires=Wed, 12 Jul 2023 01:52:27 GMT; Max-Age=31449600; Path=/; SameSite=Lax

说明要在客户端设置Cookie,其内容是csrftoken=随机字符串,再次进行请求时,即POST请求时,这时的请求头中增加了Cookie键。

 而在前端页面上,生成了一个隐藏标签:

 其name=csrfmiddlewaretoken,其value值随机字符串。Django就是通过这个键值对来验证是否通过csrf验证。这个value的值每次请求都不一样。

Ajax提交:Ajax提交,一种方法是在标头中增加一个键值对,其中键是X-CSRFTOKEN,值是cookie中的csrf_token的值,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        <input type="text" name="username" />
        % csrf_token %
        <input type="submit" value="Form提交">
        <input type="button" id="btn" value="Ajax提交">
    </form>
<script src="/static/jquery-3.6.0.js"></script>
<script src="/static/jquery.cookie.js"></script>
<script>
    $(function () 
        $('#btn').click(function () 
            $.ajax(
                url: '/login/',
                type: "POST",
                data: username:'root',
                headers: 'X-CSRFTOKEN': $.cookie('csrftoken') ,
                success: function (arg) 
                    console.log(arg)

                
            );

        )
    );
</script>
</body>
</html>

这里主要是要注意headers的设置方法,在headers中设置的字典,在提交时,在请求头中进行了设置:

 提交到服务器后,服务器端会将请求的信息以及其他一些信息保存到request中,即WSGIRequest类对象中。在pycharm中可以通过debug模式查看。

def login(req):
    from django.conf import settings   # 这是最全的settings设置,我们写的settings.py只是一部分,重复的会覆写这个settings
    print(settings.CSRF_HEADER_NAME)
    print(req.method)
    return render(req,'login.html')

在print(req.method)前打上中断点,然后以debug运行:ajax提交

 可以看到,此时login函数参数是POST '/login/'的request,这个request包含了很多内容:

 打印的settings.CSRF_HEADER_NAME是HTTP_X_CSRFTOKEN,在请求头中,即headers中设置时去掉HTTP_,就是他的键,后台数据组装到request中是,headers中的键都加上HTTP_,并将键中的减号也替换成下划线。这时就是通过头部的这个键将cookie中的csrftoken值带过去,进行验证。

还有一种方法,是直接在数据中添加数据:

data: username:'root',csrfmiddlewaretoken:' csrf_token ',

这时的请求头:

 没有X-CSRFTOKEN,但是在载荷中:

 验证依然通过。

WSGIRequest对象中不单保存了客户端请求头的信息,还有一些服务器本机的信息,需要认真研究,尤其是其中META,感觉有很多都是重复在存储,只是存在对象不同的地方。

如果有多个Ajax提交,那么每个提交设置时都要设置headers:
 headers: 'X-CSRFTOKEN': $.cookie('csrftoken') ,

可以通过$.ajaxSetup统一设置:

<script>
    $(function () 
        $.ajaxSetup(
                beforeSend: function (xhr) 
                    xhr.setRequestHeader("X-CSRFToken",$.cookie('csrftoken'))
                
            );
        $('#btn').click(function () 
            $.ajax(
                url: '/login/',
                type: "POST",
                #data: username:'root',csrfmiddlewaretoken:' csrf_token ',#
                data: username:'root',
                #headers: 'X-CSRFTOKEN': $.cookie('csrftoken') ,#
                success: function (arg) 
                    console.log(arg)
                
            );
        );
    );
</script>

================================================

WSGIRequest对象

Django在接收到http请求之后,会根据http请求携带的参数以及报文信息创建一个WSGIRequest对象,并且作为视图函数第一个参数传给视图函数。这个参数就是django视图函数的第一个参数,通常写成request。在这个对象上我们可以找到客户端上传上来的所有信息。这个对象的完整路径是django.core.handlers.wsgi.WSGIRequest

http请求的url详解:

在了解WSGIRequest对象的属性和方法之前,我们先了解一下url的组成,通常来说url的完整组成如下,[]为可选:

protocol ://hostname[:port]/path/[;parameters][?query]#fragment

  • protocol: 网络协议,常用的协议有http/https/ftp等
  • hostname: 主机地址,可以是域名,也可以是IP地址
  • port: 端口 http协议默认端口是:80端口,在浏览器中默认会隐藏不显示
  • path:路径 网络资源在服务器中的指定路径
  • parameter: 参数 如果要向服务器传入参数,在这部分输入
  • query: 查询字符串 如果需要从服务器那里查询内容,在这里编辑
  • fragment:片段 网页中可能会分为不同的片段,如果想访问网页后直接到达指定位置,可以在这部分设置

WSGIRequest对象常用属性:

WSGIRequest对象上大部分的属性都是只读的。因为这些属性是从客户端上传上来的,没必要做任何的修改,在django视图中使用时,视图函数的第一个参数参数request就是WSGIRequest对象。以下将对一些常用的属性进行讲解:

  1. path:资源在服务器的完整“路径”,但不包含域名和参数,在url中也是path的内容。比如http://www.baidu.com/xxx/yyy/,那么path就是/xxx/yyy/
  2. method:代表当前请求的http方法。比如是GET、POST、delete或者是put等方法
  3. GET:一个django.http.request.QueryDict对象。操作起来类似于字典。这个属性中包含了所有以?xxx=xxx的方式上传上来的参数。
  4. POST:也是一个django.http.request.QueryDict对象。这个属性中包含了所有以POST方式上传上来的参数。
  5. FILES:也是一个django.http.request.QueryDict对象。这个属性中包含了所有上传的文件。
  6. COOKIES:一个标准的Python字典,包含所有的cookie,键值对都是字符串类型。
  7. session:一个类似于字典的对象。用来操作服务器的session
  8. user:user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。它的值是一个 setting.py 里面AUTH_USER_MODEL 字段所定义的类的对象,表示当前登录的用户。如果用户当前没有登录,user 将设为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。

  9. META:存储的客户端发送上来的所有header信息,下面是这些常用的header信息:

    1.   CONTENT_LENGTH:请求的正文的长度(是一个字符串)。

    2.   CONTENT_TYPE:请求的正文的MIME类型。
    3.   HTTP_ACCEPT:响应可接收的Content-Type。
    4.   HTTP_ACCEPT_ENCODING:响应可接收的编码,用于告知服务器客户端所能够处理的编码方式和相对优先级。
    5.   HTTP_ACCEPT_LANGUAGE: 响应可接收的语言。
    6.   HTTP_HOST:客户端发送的HOST值。
    7.   HTTP_REFERER:在访问这个页面上一个页面的url。
    8.   QUERY_STRING:单个字符串形式的查询字符串(未解析过的形式)。
    9.   TE:设置传输实体的编码格式,表示请求发起者愿意接收的Transfer-Encoding类型(传输过程中的编码格式,代理服务器之间)
    10.   REMOTE_ADDR:客户端的IP地址。如果服务器使用了nginx做反向代理或者负载均衡,那么这个值返回的是127.0.0.1,这时候可以使用HTTP_X_FORWARDED_FOR来获取,所以获取ip地址的代码片段如下:
        if request.META.has_key('HTTP_X_FORWARDED_FOR'):  
            ip =  request.META['HTTP_X_FORWARDED_FOR']  
        else:  
            ip = request.META['REMOTE_ADDR']
      
    11.   REMOTE_HOST:客户端的主机名。
    12.   REQUEST_METHOD:请求方法。一个字符串类似于GET或者POST
    13.   SERVER_NAME:服务器域名。
    14.   SERVER_PORT:服务器端口号,是一个字符串类型。

WSGIRequest对象常用方法:

  1. is_secure():是否是采用https协议。
  2. is_ajax():是否采用ajax发送的请求。原理就是判断请求头中是否存在X-Requested-With:XMLHttpRequest
  3. get_host():服务器的域名。如果在访问的时候还有端口号,那么会加上端口号,在url中就是hostname+port。比如www.baidu.com:9000
  4. get_full_path():返回完整的path。如果有查询字符串,还会加上查询字符串,在url中就是path以及其后面的所有。比如/music/bands/?print=True
  5. get_raw_uri():获取请求的完整url

QueryDict对象:

我们平时用的request.GET、request.POST和request.FILES都是QueryDict对象,这个对象继承自dict,因此用法跟dict相差无几。其中用得比较多的是get方法和getlist方法。

  1. get方法:用来获取指定key的值,如果没有这个key,那么会返回None
  2. getlist方法:如果浏览器上传上来的key对应的值有多个,如果使用get取值,那么你只能取出最后面一个值,如果你想取到所有的值,那么就需要通过getlist这个方法获取。

CSRF有全站使用csrf_token,和部分使用,全站使用,就是启用了CSRF中间件,即所有程序都使用CSRF,因为所有请求来到后都需要经过中间件,而部分使用,是禁用了CSRF中间件,但是部分函数又要求使用CSRF,这时可以使用装饰器。

两个装饰器:@csrf_protect 和 @csrf_exempt

@csrf_protect :为当前函数强制设置防跨域请求伪造功能,即便settings中没有设置CSRF全局中间件

 @csrf_exempt:取消当前函数防跨域伪造请求功能,即便settings中设置了CSRF全局中间件

引入:from Django.views.decorators.csrf import csrf_exempt,csrf_protect

以上是关于Python入门自学进阶-Web框架——19Django其他相关知识的主要内容,如果未能解决你的问题,请参考以下文章

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——20Django其他相关知识2

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置

Python入门自学进阶-Web框架——21DjangoAdmin项目应用