Django

利用HTTP协议向服务器传参的几种途径、响应、Cookie、Session、类视图、中间件

注意:

  1>Django中的QueryDict对象

      django 中有一个 QueryDict 对象,这个对象一般用来存储浏览器传递过来的参数,我们可以默认把他看成是一个字典

      但是它和一般的字典的不同之处在于:

          ■ QueryDict 这个字典可以用来处理一个键带有多个值得情况,即: 一键 ===> 多值

      QueryDict 这个字典支持 get( ) 方法的使用:

          ■ 这个位置需要区分是一键一值还是一键多值的不同情况:

              如果是一键一值:

QueryDict.get( key ) 获取的就是当前对应的值(value)

              如果是一键多值:

QueryDict.get( key ) 获取的就是最后一个值

              QueryDict 还有一个所有值得方法:

QueryDict.getlist( key ) 获取这个键对应的所有值.

  例如:

      get( ):

根据键获取值

如果一个键同时拥有多个值将获取最后一个值

如果键不存在则返回None值,可以设置默认值进行后续处理

# 默认值一般是default,可以省略

QueryDict.get('键',默认值)

# 或可写为:

QueryDict['键']

      getlist( ):

根据键获取值,值以列表返回,可以获取指定键的所有值

如果键不存在则返回空列表[],可以设置默认值进行后续处理

dict.getlist('键',默认值)

  2>Django中CSRF保护的开关

      Django默认开启了CSRF防护,会对POST、PUT、PATCH、DELETE请求方式进行CSRF防护验证

      在使用postman测试时可以关闭CSRF防护机制

      关闭CSRF防护方法为在settings.py文件中注释掉CSRF中间件,如:

  3>Django中的request.GET和request.POST

      查询字符串不区分请求方式,即假使客户端进行POST方式的请求,依然可以通过request.GET获取请求中的查询字符串数据。

      request.GET 指的不是发送ajax用的get方法, 而是指从url的查询字符串中获取参数

      request.POST 指的也不是ajax用的post方法,而是我们从请求体中获取的参数

      通过上面大家就能明白: GET 和 POST 这里指的都是获取参数的位置, 而不是我们以前说的get请求和post请求

      如果是从url中(通过查询字符串)传递参数,使用GET(代表的是传递参数的位置),和请求方法无关

      如果是从请求体中传递参数使用POST

  4>postman请求体传递参数的4个选项

      form-data多元素表单

      x-www-form-urlencoded 正常表单

      raw (非表单)可以选择传输数据的数据类型

      binary二进制

1.利用HTTP协议向服务器传参的几种途径

<1>flask框架

  1>通过URL的特定部分传参

      提取URL的特定部分,如/weather/2018,可以在服务器端的路由中用正则转换器截取

# flask后端接收数据:

  # 使用正则转换器

  # /user/weather/2018

  @app.route('/user/weather/<re: userid>')

  def index(userid):

print(userid) # 2018

      return '%s' % userid

  2>通过查询字符串(query string)传递参数,url中?后形如key1=value1&key2=value2

      获取参数的方式

request.args.get('key1')

  3>请求体(body)中发送的数据,比如表单数据、json、xml

      获取参数的方式

request.json.get('key')

  4>通过http请求报文的请求头(header)传递参数。

      获取参数的方式

request.headers.get('content-type')

<2>Django

  1>通过URL的特定部分传递参数

      获取参数的方式:视图函数传参

          ■ 在定义路由URL时,可以使用正则表达式提取参数的方法从URL中获取请求参数,Django会将提取的参数直接传递到视图的传入参数中。

          ■ 未命名参数按定义顺序传递(相当于位置参数), 如

  url(r'^weather/([a-z]+)/(\d{4})/$', views.weather),

  def weather(request, city, year):

      print('city=%s' % city) # beijing

      print('year=%s' % year) # 2008

      return HttpResponse('OK')

  命名参数按名字传递(相当于关键字参数),如

  url(r'^weather/(?P<city>[a-z]+)/(?P<year>\d{4})/$', views.weather),

  def weather(request, year, city):

      print('city=%s' % city)

      print('year=%s' % year)

      return HttpResponse('OK')

  2>通过请求路径url中的查询字符串传递参数(形如?k1=v1&k2=v2)

      获取参数的方式:request.GET.get('key')

          ■ 可以通过request.GET属性获取,这个方法返回QueryDict对象。

          ■ QueryDict对象类似于一个字典, 所以我们可以通过get() 来获取 key 值所对应的 value 值

a = request.GET.get('a')

    b = request.GET.get('b')

    alist = request.GET.getlist('a')

  3>通过请求体传递参数

      请求体数据格式不固定,可以是表单类型字符串,可以是JSON字符串,可以是XML字符串,应区别对待。

      可以发送请求体数据的请求方式有 POST、PUT、PATCH、DELETE。

      请求体—表单类型 (Form Data)

          ■ 获取参数的方式:request.POST.get('key')

          ■ 前端发送的表单类型的请求体数据,可以通过request.POST属性获取,返回QueryDict对象。

          ■ QueryDict对象类似于一个字典, 所以我们可以通过get() 来获取 key 值所对应的 value 值

a = request.POST.get('a')

b = request.POST.get('b')

alist = request.POST.getlist('a')

  注意:

      如果是表单数据, 发送的请求不但要求body中是表单的键值对

      也要求请求头中content-type的类型是application/x-www-form-urlencoded

      如果请求中的不是这样写的类型, 上面的request.POST.get( )方法也不可以使用.

      如图所示: (这两部分都有才可以使用上面的request.POST.get( )方法进行请求)

      请求体——非表单类型 (Non-Form Data)

          ■ 非表单类型的请求体数据,Django无法自动解析

          ■ 可以通过request.body属性获取最原始的请求体数据,自己按照请求体格式(JSON、XML等)进行解析。

          ■ 其中: request.body返回bytes类型。

    json_bytes = request.body

    json_str = json_bytes.decode()

    #python3.6及以上版本中, json.loads()方法可以接收str和bytes类型

    #但是python3.5以及以下版本中, json.loads()方法只能接收str,

    #所以我们的版本如果是3.5以及以下 需要将bytes类型解码为str类型

    req_data = json.loads(json_str)

    print(req_data['a'])

    print(req_data['b'])

  4>通过请求头传递参数

      我们可以通过request.META属性获取请求头headers中的数据

      request.META为字典类型。

      注意请求头的用法区别,key的表现形式不同

          ■ 我们通过request.META获取的时候,需要使用如下所示的用法:

          ■ CONTENT_LENGTH – The length of the request body (as a string).

          ■ CONTENT_TYPE – The MIME type of the request body.

          ■ HTTP_ACCEPT – Acceptable content types for the response.

          ■ HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.

          ■ HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.

          ■ HTTP_HOST – The HTTP Host header sent by the client.

          ■ HTTP_REFERER – The referring page, if any.

          ■ HTTP_USER_AGENT – The client’s user-agent string.

          ■ QUERY_STRING – The query string, as a single (unparsed) string.

          ■ REMOTE_ADDR – The IP address of the client.

          ■ REMOTE_HOST – The hostname of the client.

          ■ REMOTE_USER – The user authenticated by the Web server, if any.

          ■ REQUEST_METHOD – A string such as "GET" or "POST".

          ■ SERVER_NAME – The hostname of the server.

          ■ SERVER_PORT – The port of the server (as a string).

request.META['CONTENT_TYPE']

<3>其他常用的HttpRequest对象属性

  1>method:一个字符串,表示请求使用的HTTP方法,常用值包括:'GET'、'POST'。

  2>user:请求的用户对象。

  3>path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。

  4>encoding:一个字符串,表示提交的数据的编码方式。

      如果为None则表示使用浏览器的默认设置,一般为utf-8。

      这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。

  5>FILES:一个类似于字典的对象,包含所有的上传文件。

2.响应

  视图在接收请求并处理后,必须返回HttpResponse对象或子对象。

  HttpRequest对象由Django创建,HttpResponse对象由开发人员创建。

<1>HttpResponse类

  1>HttpResponse的使用方式

      可以从 django.http里面导入HttpResponse

      from django.http import HttpResponse

HttpResponse(

    content=响应体,

    content_type=响应体数据类型,

    status=状态码

)

      通过上式我们知道HttpResponse里面有对应的一些参数可以修改:

          ■ content:表示返回的内容。

          ■ status_code:返回的HTTP响应状态码。

          ■ content_type:指定返回数据的的MIME类型。

      例如:

# 定义一个新的视图函数

def demo_response(request):

    # 定义一个json字符串

    s = '{"name": "python"}'

    # 返回一个HttpResponse响应对象

    return HttpResponse(s, content-type="application/json", status=400)

  2>HttpResponse特别的使用方式:

      我们如果需要在响应头添加自定义的键值对内容,可以把HttpResponse对象当做字典进行响应头键值对的设置:

response = HttpResponse()

# 自定义响应头Itcast, 值为Python

response['Itcast'] = 'Python'

示例:

from django.http import HttpResponse

def demo_view(request):

    return HttpResponse('itcast python', status=400)

    或者

    response = HttpResponse('itcast python')

    response.status_code = 400

    response['Itcast'] = 'Python'

    return response

<2>HttpResponse子类

  Django提供了一系列HttpResponse的子类,可以快速设置状态码

  这个状态码可以从 Django.http 里面导入,例如:

  from django.http import HttpResponseNotFound

      HttpResponseRedirect 301

      HttpResponsePermanentRedirect 302

      HttpResponseNotModified 304

      HttpResponseBadRequest 400

      HttpResponseNotFound 404

      HttpResponseForbidden 403

      HttpResponseNotAllowed 405

      HttpResponseGone 410

      HttpResponseServerError 500

  使用的演示:

<3>JsonResponse

  如果我们要返回json字符串, 那么我们可以使用 JsonResponse 来帮助我们快速的构建json字符串,进行返回.

  JsonResponse 能够帮助我们自动把字典转成json字符串类型, 并且还不用自己设置响应头中contentType字段

  1>JsonResponse的作用

      帮助我们将数据转换为json字符串

      设置响应头Content-Type为 application/json

  2>JsonResponse的使用

# 导入JsonResponse

from django.http import JsonResponse

def demo_view(request):

    # 直接返回JsonResponse这个对象,并且里面可以直接传入参数

    return JsonResponse({'city': 'beijing', 'subject': 'python'})

<4>redirect重定向

  建议redirect(重定向)和我们前面学习的reverse(反解析)搭配使用.

      return redirect(reverse('reqrespspace:getresponse_name'))

          ■ reverse('spacename:name')

  尽量不要把路由写死. 有利于我们更改开发代码.

from django.shortcuts import redirect

def demo_view(request):

    return redirect('/index.html')

3.Cookie

  Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用。

  Cookie,有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。

  Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。

  Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。

  Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等。服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。

  Cookies最典型记住用户名。

<1>Cookie的特点

  Cookie以键值对的格式进行信息的存储。

  Cookie基于域名安全,不同域名的Cookie是不能互相访问的

      如访问taobao.com时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到taobao.com写的Cookie信息。

  当浏览器请求某网站时,会将浏览器存储的跟网站相关的所有Cookie信息提交给网站服务器。

<2>设置Cookie

  可以通过HttpResponse对象中的set_cookie方法来设置cookie。

HttpResponse.set_cookie(cookie名, value=cookie值, max_age=cookie有效期)

      max_age 单位为秒,默认为None。

      如果是临时cookie,可将max_age设置为None。

  示例:

def demo_view(request):

    response = HttpResponse('ok')

    response.set_cookie('0001', 'python1')  # 临时cookie

    response.set_cookie('0002', value='python2', max_age=3600)  # 有效期一小时

    return response

<3>获取Cookie

  可以通过HttpRequest对象的COOKIES属性来读取本次请求携带的cookie值。request.COOKIES为字典类型。

  request.COOKIES.get('key')

def demo_view(request):

    cookie1 = request.COOKIES.get('0001')

    print(cookie1)

    return HttpResponse('OK')

4.Session

<1>启用Session

  1>Session简介:

      在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。注意 会话状态仅在支持 cookie 的浏览器中保留。

  2>Session的理解:

      广义来讲: session是一种会话机制, 用于记录多次http请求之间的关系,关系就是状态数据,比如登录状态.

      狭义来讲: session是一种会话数据, 记录的状态数据, 比如登录之后记录的user_id等

  3>Django项目默认启用Session

      可以在settings.py文件的中间件选项中查看,如图所示

      如需禁用session,将上图中的session中间件注释掉即可。

<2>存储方式

  在settings.py文件中,可以设置session数据的存储方式.

  另外session可以保存在数据库、本地缓存( 程序的运行内存中, 全局变量)、文件、redis等 。

  1>数据库

      存储在数据库中,如下设置可以写到settings中,也可以不写,这是默认存储方式。

# 如果是存放数据库, 一般以db结尾

SESSION_ENGINE='django.contrib.sessions.backends.db'

      如果存储在数据库中,需要在项INSTALLED_APPS中安装Session应用。

      数据库中的表如图所示

      表结构如下

      由表结构可知,操作Session包括三个数据:键,值,过期时间。

  2>本地缓存

      存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。

# 如果是存放在本地缓存, 一般以cache结尾

SESSION_ENGINE='django.contrib.sessions.backends.cache'

      其中,本地缓存会出现问题: 因为是存放在本地的内存中,所以会出现在脱机情况下出现的跨机访问问题:

          ■ 我们这里可以看到: 有两台服务器存储的有session数据, 前面由nginx负责管理访问机制,有可能现在的访问方式是轮询形式, 那么意味着第一次用户进入的是上面的服务器,进行了登录操作,我们把他的登录状态保存到了服务器1里面, 随后用户有进行了其他操作, 然后有登陆进入这个服务器,这次轮询到了服务器2里面,但是这里面没有保存登录状态,这样就会造成用户第二次登录.所以会造成用户跨机的问题.

          ■ 但是如果我们使用redis就不会出现这样的情况,因为无论是哪一个服务器,最终存储的数据都保存到了redis中 :

  3>混合存储

      优先从本机内存中存取,如果没有则从数据库中存取。

# 如果是存放数据库,一般以cached_db结尾

SESSION_ENGINE='django.contrib.sessions.backends.cached_db'

  4>Redis

      django-redis 中文文档: http://django-redis-chs.readthedocs.io/zh_CN/latest/#cache-backend

      在redis中保存session,需要引入第三方扩展,我们可以使用django-redis来解决。

      安装扩展

pip install django-redis

      在settings.py文件中做如下设置

CACHES = {

    "default": {

        "BACKEND": "django_redis.cache.RedisCache",

        # 定义django中redis的位置

        "LOCATION": "redis://127.0.0.1:6379/0",

        "OPTIONS": {

            # django使用redis的默认客户端来进行操作.

            "CLIENT_CLASS": "django_redis.client.DefaultClient",

        }

    }

}

# 我们定义一个cache(本地缓存来存储信息,cache指定的是redis)

SESSION_ENGINE = "django.contrib.sessions.backends.cache"

# 本地的session使用的本地缓存名称是'default', 这个名称就是上面我们配置的caches的名

# 称"default"

SESSION_CACHE_ALIAS = "default"

      添加代码,查看是否能够存储到redis中去:

urls.py:

# url的配置:

from django.conf.urls import url

from . import views

urlpatterns = [

    # 保存session数据

    url(r'^set_session', views.set_session),

    # 获取session数据

    url(r'^get_session', views.get_session),

]

Views.py:

# 定义设置session的视图函数

def set_session(request):

    request.session['one'] = '1'

    request.session['two'] = '2'

    request.session['three'] = '3'

    return HttpResponse('保存session数据成功')

# 定义获取session的视图函数

def get_session(request):

      one = request.session.get('one')

      two = request.session.get('two')

      three = request.session.get('three')

      text = 'one=%s, two=%s, three=%s' % (one,two,three)

      return HttpResponse(text)

      打开redis来查看存储的信息:

打开redis:

redis-server

查看信息: (在新窗口中)

redis-cli

select 1

keys *

<3>Session操作

  通过HttpRequest对象的session属性进行会话的读写操作。

  1>设置session

      以键值对的格式写session

      request.session['键']=值

例如: request.session['one'] = '1'

  2>获取session

      根据键读取值

      request.session.get('键')

例如: one = request.session.get('one')

  3>清除所有session,在存储中删除值部分

request.session.clear()

  4>清除session数据,在存储中删除session的整条数据。

request.session.flush()

  5>删除session中的指定键及值,在存储中只删除某个键及对应的值。

del request.session['键']

  6>设置session的有效期

request.session.set_expiry(value)

      如果value是一个整数,session将在value秒没有活动后过期。

      如果value为0,那么用户session的Cookie将在用户的浏览器关闭时过期。

      如果value为None,那么session有效期将采用系统默认值,默认为两周,可以通过在settings.py中设置SESSION_COOKIE_AGE来设置全局默认值。其中 SESSION_COOKIE_AGE的单位是以秒为单位的.

5.类视图

<1>类视图

  1>视图函数

      以函数的方式定义的视图称为函数视图,即我们常说的视图函数

  2>视图函数的缺点

      视图函数遭遇不同方式分别请求( 例如 get 和 post ),并且需要做不同处理时,我们如果在一个函数中编写不同的业务逻辑,代码可读性和复用性都不好

  3>类视图

      使用类来定义的视图,称为类视图

      类视图中的方法通常以请求方式命名

      在Django中给我们提供了类视图的概念,即可以使用类来定义一个视图,解决了视图函数的缺点

<2>类视图使用

  1>如果想要使用类视图需要如下几步:

      定义类视图, 且类视图继承自View

          ■ 使用: from django.views.generic import View

          ■ 或者是: from django.views.generic.base import View

      定义路由,路由的第二个参数需要是一个函数,所以我们会调用系统的 as_view() 方法

urlpatterns = [

    # 类视图:注册

    # url(路径, 执行的函数)

    # url的第二个参数需要是一个函数

    # 我们这里如果传入: views.RegisterView 会发现这个是一个类, 不是一个函数,

    # 所以我们需要调用系统给我们提供的 as_view() 方法

    url(r'^register/$', views.RegisterView.as_view()),

]

      使用浏览器访问我们定义的路由, 查看结果

  2>使用类视图可以将视图对应的不同请求方式以类中的不同方法来区别定义

如下所示:

# 导入类视图的父类View

from django.views.generic import View

class RegisterView(View):

    """类视图:处理注册"""

    def get(self, request):

        """处理GET请求,返回注册页面"""

        return render(request, 'register.html')

    def post(self, request):

        """处理POST请求,实现注册逻辑"""

        return HttpResponse('这里实现注册逻辑')

  3>类视图的好处:

      代码可读性好

      类视图相对于函数视图有更高的复用性

      如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可

  4>注意:

      如果我们在类视图函数中没有定义方法, 但是我们请求了. 会报405找不到请求方法的错误.

          ■ 例如: 类视图中没有定义get方法, 但是我们使用get方法进行了请求, 那么会报405的错误: 找不到对应的请求方法.

      在类视图中定义的get或者是post都是对象方法, 第一个参数都是self.

      第二个参数一般情况下都是 request对象. 其他的参数依次往后面写就可以.

      我们在使用类视图的时候, 需要在路由位置进行设置, 设置的第二个参数需要是一个函数, 所以我们这里调用了类以后, 后面需要调用 as_view( ) 函数.

<3>类视图原理

  为什么我们定义url的时候, 调用 as_view() 函数,就可以达到结果, 如果不调用就会报错

  as_view()的原理

      由源码可看出as_view()的作用是返回真正的视图函数

    @classonlymethod

    def as_view(cls, **initkwargs):

        ...省略代码...

        def view(request, *args, **kwargs):

            # 这里的cls是as_view这个函数接收的第一个参数,也就是调用当前函数的类

      # 得到调用的类了之后, 创建类的对象: self

            self = cls(**initkwargs)

            if hasattr(self, 'get') and not hasattr(self, 'head'):

                self.head = self.get

            # 给当前这个类,添加对应的属性, 如下所示:

            self.request = request

            self.args = args

            self.kwargs = kwargs

            # 调用dispatch方法,按照不同请求方式调用不同请求方法

            return self.dispatch(request, *args, **kwargs)

        ...省略代码...

        # 返回真正的函数视图

        return view

    # dispatch本身是分发的意思,这里指的是函数的名字.

    def dispatch(self, request, *args, **kwargs):

        # self.http_method_names指的是我们的类视图中方法的名字

              # 这里把所有方法的名字都存放在了http_methods_names中

              # 我们会把当前请求的方式转为小写,然后判断是否在列表中存在.

        if request.method.lower() in self.http_method_names:

            """

    如果在里面, 则进入这里

    这里的getattr作用是获取当前对象的属性.

    下面的参数为:

    self :  类视图对象

    request.method.lower() : 请求方法的小写. 例如: 'get' 或 'post'

    http_method_not_allowed : 后续处理方式(不允许请求)

    下面代码整体的意思: 根据类视图对象, 获取当前类视图中对应名称的方法

    如果获取到, 则把方法返回给handle, 否则不允许访问.

    """

  handler = getattr(self, request.method.lower(), self.http_method_not_allowed)

else:

          # 如果类视图中如果没有的话, 则进入这里, 表明不允许进行请求.

            # 我们会把不允许请求这个字段返回给handle.

          handler = self.http_method_not_allowed

      # 最终返回handle(handle里面要么包含可以访问的方法, 要么就是不允许访问的字段)

      return handler(request, *args, **kwargs)

<4>类视图使用装饰器

  先定义一个为函数视图准备的装饰器(在设计装饰器时基本都以函数视图作为考虑的被装饰对象),以及一个要被装饰的类视图

#  定义一个装饰器

def my_decorator(func):

    # wrapper函数必然会接收一个request对象,因为传入进来的func这个函数肯定会带这个参数

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了')

        print('请求路径%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

# 我们定义的类视图

class DemoView(View):

    # 我们给get方法添加上装饰器, 然后执行.

    @my_decorator

    def get(self, request):

        print('get方法')

        return HttpResponse('ok')

    # 类视图里面的对象方法: post方法

    def post(self, request):

        print('post方法')

        return HttpResponse('ok')

  把一个修饰函数的装饰器作用在类上的几种方法

  1>在URL配置中装饰

      url配置中使用 as_view( ) 函数返回一个view( ) 函数

      换句话说在调用类视图之前,必然会调用 view( ) 这个函数,所以把装饰器作用到 view( ) 函数上

# 导入views.py视图文件

from . import views

urlpatterns = [

    # 我们在路由部分, 把定义好的装饰器添加到当前的函数上

    # 这里需要注意: as_view() 会返回一个 view() 函数

    # 所以我们把装饰器添加到view()函数上

    url(r'^demo/$', views.my_decorate(views.DemoView.as_view()))

]

      这种方式的弊端

          ■ 此种方式最简单,但因装饰行为被放置到了url配置中,单看视图的时候无法知道此视图还被添加了装饰器,不利于代码的完整性,不建议使用。

          ■ 此种方式会为类视图中的所有请求方法都加上装饰器行为(因为是在视图入口处,分发请求方式前)。

  2>在类视图中使用装饰器

      因为装饰器的内函数第一个参数是request,而类视图里面的函数第一个参数是self,第二个参数才是request。所以我们不能够直接给类视图中的函数添加装饰器,否则传入的参数会出现错误

      解决方法:

  由上面的案例我们可知: 我们定义的装饰器不能够直接使用到类视图的方法上.

  所以我们需要把我们定义的装饰器进行转化, 转化为能够被类视图中函数使用的装饰器

  转化的方法需要导入:

  from django.utils.decorators import method_decorator

  导入进来之后, 我们需要把自定义的装饰器用这个方法包裹住转化, 例如:

  @method_decorator(自定义装饰器)

      第一种解决方式:

  在类视图中使用为函数视图准备的装饰器时,不能直接添加装饰器

  需要使用method_decorator将其转换为适用于类视图方法的装饰器。

from django.views.generic import View

# 导入转换的装饰器方法:

from django.utils.decorators import method_decorator

# 为特定请求方法添加装饰器

class DemoView(View):

    # 使用转换的方法将装饰器转化:

    @method_decorator(my_decorator)

    def get(self, request):

        print('get方法')

        return HttpResponse('ok')

    def post(self, request):

        print('post方法')

        return HttpResponse('ok')

  问题:

      虽然上面的方式可以解决类视图添加装饰器问题, 但是我们这种是给单个函数添加的, 而不是类视图中的所有函数

      第二种解决方式:

  重写dispatch方法,给dispatch方法加装饰器

  使用method_decorator将其转换为适用于类视图方法的装饰器

from django.views.generic import View

from django.http import HttpResponse

from django.utils.decorators import method_decorator

# 自定义的装饰器方法

def my_decorator(func):

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了')

        print('请求的路径:%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

# 类视图

class DemoView(View):

    # 重写父类的dispatch方法, 因为这个方法被 as_view() 中的 view() 调用

      # 所以我们对这个方法添加装饰器, 也就相当于对整个类视图的方法添加装饰器.

      @method_decorator(my_decorator)

    def dispatch(self, request, *args, **kwargs):

        # 重写父类的这个方法我们不会修改它的任何参数, 所以我们直接调用父类的这个方法即可

        # 它里面的参数我们也不动它, 直接还传递过去.

        return super().dispatch(request, *args, **kwargs)

    def get(self, request):

        print('get')

        return HttpResponse('getfunc ok')

    def post(self, request):

        print('post')

        return HttpResponse('postfunc ok')

      第三种解决方式:

          ■ method_decorator( )方法直接装饰到类上去,使当前的视图类中的某一个函数添加装饰器方法

from django.views.generic import View

from django.http import HttpResponse

from django.utils.decorators import method_decorator

def my_decorator(func):

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了')

        print('请求的路径:%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

"""

类视图

给类视图增加上@method_decorator方法

增加上之后,并不能够给其中的某一个函数增加上装饰器

所以我们需要给method_decator配置第二个参数

第二个参数就是类中某一个函数的名称,意味着给当前这个函数增加上装饰器

"""

@method_decorator(my_decorator, name='get')

class DemoView(View):

    @method_decorator(my_decorator)

    def dispatch(self, request, *args, **kwargs):

        return super().dispatch(request, *args, **kwargs)

    def get(self, request):

        print('get')

        return HttpResponse('getfunc ok')

    def post(self, request):

        print('post')

        return HttpResponse('postfunc ok')

      第四种解决方式:

          ■ 使用@method_decorator在类视图的位置给'dispatch'方法添加装饰器

from django.views.generic import View

from django.http import HttpResponse

from django.utils.decorators import method_decorator

def my_decorator(func):

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了')

        print('请求的路径:%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

# 类视图

# 因为我们可以直接给dispatch方法添加装饰器,意味着, 我们内部不用重写dispatch方法了.

@method_decorator(my_decorator, name='dispatch')

class DemoView(View):

    def get(self, request):

        print('get')

        return HttpResponse('getfunc ok')

    def post(self, request):

        print('post')

        return HttpResponse('postfunc ok')

  3>method_decorator装饰器的作用

      为函数视图准备的装饰器,其被调用时,第一个参数用于接收request对象

def my_decorate(func):

    def wrapper(request, *args, **kwargs):  # 第一个参数request对象

        ...代码省略...

        return func(request, *args, **kwargs)

    return wrapper

      而类视图中请求方法被调用时,传入的第一个参数不是request对象,而是self 视图对象本身,第二个位置参数才是request对象

class DemoView(View):

    def dispatch(self, request, *args, **kwargs):

        ...代码省略...

    def get(self, request):

        ...代码省略...

      所以如果直接将用于函数视图的装饰器装饰类视图方法,会导致参数传递出现问题。

      method_decorator装饰器的作用是为函数视图装饰器补充第一个self参数,以适配类视图方法。

  4>类视图使用装饰器的最终方法

      将装饰器本身改为可以适配类视图方法

def my_decorator(func):

    def wrapper(self, request, *args, **kwargs):  # 此处增加了self

        print('自定义装饰器被调用了')

        print('请求路径%s' % request.path)

        return func(self, request, *args, **kwargs)  # 此处增加了self

    return wrapper

<5>构造Mixin扩展类

  1>给类视图所有方法添加装饰器有两种

      修改装饰器接收到的参数

          ■ 即把装饰器的第一个参数修改为self,使类中所有的方法都可以直接添加上装饰器

      修改类视图中所有函数都会调用的 as_view() 方法

          ■ 或者是 dispatch方法, 因为dispatch方法在 as_view() 方法内部,故我们这里不讨论dispatch

  2>使用类扩展的形式给当前的类视图添加装饰器

      也就是说,把装饰器加在类扩展中,在类视图的父类中重写as_view()方法,并继承自View类的as_view()方法

      View类的as_view()方法返回了view(),给view()添加装饰器

      那么我们可以继续思考

类视图 —————> 继承自View类 (包含有as_view函数)

      如果我们在整个继承的过程中添加一步, 例如:

类视图 ———> 继承自额外扩展的类 ———> 继承自View类(包含有as_view函数)

      在额外扩展的类中重写 as_view( ) 方法,并且对 as_view( ) 方法添加装饰器,类视图中的所有方法都会被装饰器装饰

          ■ 第一步:创建一个扩展类,在扩展类中重写 as_view 方法,并且给父类传过来的view方法添加装饰器

          ■ 第二步:让类视图继承自扩展类

      第一步代码:

# 定义一个新的扩展类,让该类继承自View类

class BaseView(View):

    # 在扩展类中,重写View类的 as_view 方法, 并且对该方法添加装饰器

    @classmethod

    def as_view(cls, *args, **kwargs):

        # 重写之后, 不对该方法做其他额外操作,所以我们重新调用父类的该方法

        view = super().as_view(*args, **kwargs)

        # 对父类传过来的view方法添加装饰器

        view = my_decorator(view)

        return view

      第二步代码:

# 让我们的类视图继承自扩展类

class DemoView(BaseView):

    def get(self, request):

        print('get')

        return HttpResponse('get func')

    def post(self, request):

        print('post')

        return HttpResponse('post func')

  结论:

      经过中间一层额外扩展类的装饰过滤,我们原来的DemoView中的所有视图方法是能够经过装饰器的

  3>使用多个类扩展的形式给当前的类视图添加多个装饰器

      那么我们可以继续思考:

类视图 —————> 继承自View类 (包含有as_view函数)

类视图 ———> 继承自额外扩展的类1 ---> 继承自额外扩展的类2 ———> 继承自View类(包含有as_view函数)

      延伸:

我们定义两个扩展类, 并且重写两次 as_view 方法, 来看看会发生什么 :

from django.views.generic import View

from django.http import HttpResponse

from django.utils.decorators import method_decorator

# 定义的第一个装饰器:

def my_decorator(func):

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了')

        print('请求的路径:%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

# 定义的第二个装饰器:

def my_decorator2(func):

    def wrapper(request, *args, **kwargs):

        print('自定义装饰器被调用了22222')

        print('请求的路径:%s' % request.path)

        return func(request, *args, **kwargs)

    return wrapper

# 额外增加的第一个扩展类

class BaseView(View):

    # 第一次重写父类中的as_view方法

    @classmethod

    def as_view(cls, *args, **kwargs):

        view = super().as_view(*args, **kwargs)

        # 对获取的view第一次添加装饰器

        view = my_decorator(view)

        return view

# 额外增加的第二个扩展类

class Base2View(View):

    # 第二次重写父类中的as_view方法

    @classmethod

    def as_view(cls, **initkwargs):

        view = super().as_view(**initkwargs)

        # 对获取的view进行第二次添加装饰器

        view = my_decorator2(view)

        return view

# 我们定义的类视图, 继承自两个额外增加的类

class DemoView(BaseView, Base2View):

    # 类视图中的get方法

    def get(self, request):

        print('get')

        return HttpResponse('get func')

    # 类视图中的post方法

    def post(self, request):

        print('post')

        return HttpResponse('post func')

  那么我们来看一下同时调用两个装饰器是怎样实现的:

  说明:

      如果两个扩展类的父类相同:则两个父类都会调用

      如果两个扩展类的父类不同:则只会调用第一个父类

  综上,我们可以把代码变成这个样子:

      为什么BaseView类和Base2View类都继承自object还能重写as_view()?

          ■ 当程序运行到BaseView类的view = super().as_view(*args, **kwargs)时,会在父类中查找as_view(),如果没找到会去Base2View类中以及父类中查找,如果还没有会去View类中查找,再没有就会报错了。

# 第一个扩展类, 让他继承自object

class BaseView(object):

    @classmethod

    def as_view(cls, *args, **kwargs):

        view = super().as_view(*args, **kwargs)

        view = my_decorator(view)

        return view

# 第二个扩展类,让他继承自object

class Base2View(object):

    @classmethod

    def as_view(cls, *args, **kwargs):

        view = super().as_view(*args, **kwargs)

        view = my_decorator2(view)

        return view

# 类视图, 让他除了继承自这两个父类外, 最后继承View类.

class DemoView(BaseView, Base2View, View):

    def get(self, request):

        print('get方法')

        return HttpResponse('ok')

    def post(self, request):

        print('post方法')

        return HttpResponse('ok')

  说明:

      因为都是继承自object,所以扩展类中的super.as_view都会去找其他的父类依次执行,最终都会执行到View这个类这里,所以肯定会执行View中的as_view方法

      使用Mixin扩展类,也会为类视图的所有请求方法都添加装饰行为。

6.中间件

  中间件类似请求钩子

  Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性。

  我们可以使用中间件,在Django处理视图的不同阶段对输入或输出进行干预。

<1>中间件的定义方法

  1>定义中间件的步骤

      在一个子应用中创建一个中间件文件,例如: middleware(别的名字也可以)

          ■ 在中间件文件中定义一个中间件工厂函数,然后返回一个可以调用的中间件。

          ■ 中间件工厂函数需要接收一个可以调用的get_response对象。这个函数接收的参数只能是get_response

          ■ 返回的中间件也是一个可以被调用的对象,并且像视图一样需要接收一个request对象参数,返回一个response对象。

      在setting.py文件的MIDDLEWARE部分注册添加

      在调用视图时,便会调用中间件了

  2>中间件模板

     

def simple_middleware(get_response):

    # 此处编写的代码仅在Django第一次配置和初始化的时候执行一次。

    def middleware(request):

        # 此处编写的代码会在每个请求处理视图前被调用。

        response = get_response(request)

        # 此处编写的代码会在每个请求处理视图之后被调用。

        return response

    return middleware

  3>定义中间件的案例

      在子应用users中新建一个中间件文件middleware.py

          ■ 在中间件文件中定义一个中间件工厂函数

def my_middleware(get_response):

    print('init 被调用')

    def middleware(request):

        print('before request 被调用')

        response = get_response(request)

        print('after response 被调用')

        return response

    return middleware

      定义好中间件后,需要在工程同名应用下的settings.py文件中添加注册中间件

          ■ 子应用名.中间件文件名.中间件工厂函数名

          ■ 'users.middleware.my_middleware'

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',

    'users.middleware.my_middleware',  # 添加中间件

]

      定义一个视图进行测试

def demo_view(request):

    print('view 视图被调用')

    return HttpResponse('OK')

  注意:Django运行在调试模式下,中间件init部分有可能被调用两次,关闭调试模式只会运行一次

<2>多个中间件的执行顺序

  在请求视图被处理前,中间件由上至下依次执行

  在请求视图被处理后,中间件由下至上依次执行

  1>定义两个中间件

def my_middleware(get_response):

    print('init 被调用')

    def middleware(request):

        print('before request 被调用')

        response = get_response(request)

        print('after response 被调用')

        return response

    return middleware

def my_middleware2(get_response):

    print('init2 被调用')

    def middleware(request):

        print('before request 2 被调用')

        response = get_response(request)

        print('after response 2 被调用')

        return response

    return middleware

  2>注册添加两个中间件

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',

    'users.middleware.my_middleware',  # 添加

    'users.middleware.my_middleware2',  # 添加

]

  3>执行结果

init2 被调用

init 被调用

before request 被调用

before request 2 被调用

view 视图被调用

after response 2 被调用

after response 被调用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Refer to: www.threemeal.com/blog/12/ 中间件 中间件是一个钩子框架,它们可以介...
    兰山小亭阅读 16,468评论 9 165
  • Web框架之Django: (1)简介: Django是一个由Python写成开源的重量级Web应用框架,采用MT...
    老肖阅读 3,045评论 0 18
  • 模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚...
    riverstation阅读 2,062评论 0 8
  • 切换到创建项目的目录 cd C:\Users\admin\Desktop\DjangoProject创建名为pr...
    在努力中阅读 3,259评论 2 3
  • 经过对django的初步学习,我们已经对后台的基本流程以及django的运作有了一定的了解,但是这还不足够,dja...
    coder_ben阅读 3,823评论 8 34