2022-06-15
devops
00
请注意,本文编写于 671 天前,最后修改于 671 天前,其中某些信息可能已经过时。

目录

django函数视图
快速安装和配置
视图函数 function-based view
路由+装饰后的函数 inner
路由+原始函数+装饰 inner
Json响应
django类视图
View工作逻辑
URL映射
视图
分析源码

django函数视图

快速安装和配置

shell
# pycharm终端 pip install django==3.2.8 pip install mysqlclient #启动项目 django-admin startproject salary .

配置sql和日志和本地化 salary/settings.py

python
# SQL DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 't39', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '33306', } } # 本地化 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' # 模板静态文件 STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static'] # 日志 import os LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'root': { 'handlers': ['console'], 'level': 'WARNING', }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, }, }

生成django默认的表

bash
# 迁移表 (venv) D:\py_projects\p3901>python manage.py makemigrations (venv) D:\py_projects\p3901>python manage.py migrate # 生成应用,启动app (venv) D:\py_projects\p3901>python manage.py startapp employee

注册 INSTALLED_APPS = ['employee']

完成测试

hello world

配置主URL(一级路由)salary/urls.py

python
from django.contrib import admin from django.urls import path, include # 主路由,一级路由, 根路由配置 urlpatterns = [ path('admin/', admin.site.urls), path('emps/', include('employee.urls')) # restful风格 ]

配置二级路由employee/urls.py

python
from django.urls import path from .views import * # 二级路由, 应用路由 urlpatterns = [ path('', test), # 列表页, path('<int:id>', test_detail), # 详情页, restful ]

employee/views.py

python
from django.shortcuts import render from django.http import HttpRequest, HttpResponse """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # function-based view 第1个参数必须是request对象 # GET HEAD POST PUT PATCH DELETE 都可以,但POST不行,CSRF关了 # 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(**kwargs) return HttpResponse('hello world', status=201) # 详情页 def test_detail(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) # {'id': 1} return HttpResponse('hello world', status=201) # class-based view

由于以上函数可以处理GET, POST, PUT, PATCH, DELETE, 而post方法需要CSRF cookie, 所以先注释salary.settings.py 中的 MIDDLEWARE = ['django.middleware.csrf.CsrfViewMiddleware',]

访问 http://127.0.0.1:8000/ 正常

image-20220507084206510

image-20220507084323287

现在需求GET, POST结果不一样

视图函数 function-based view

视图由函数实现

响应:渲染模板返回HTML, 或直接返回JSON

employee/views.py

python
from django.shortcuts import render from django.http import HttpRequest, HttpResponse """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # function-based view 第1个参数必须是request对象 # GET HEAD POST PUT PATCH DELETE 都可以,但POST不行,CSRF关了 # 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(**kwargs) # 在handler中进行方法判断, 这个不好;更好的是 # /emps/ + methods -> handler if request.method.lower() in ('get' ,'head'): return HttpResponse('这是获取内容', status=200) else: # POST PUT PATCH DELETE return HttpResponse('这是修改或新加的内容返回', status=201) # 详情页 def test_detail(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) # {'id': 1} return HttpResponse('hello world', status=201) # class-based view

在handler中进行方法判断, 这个不好;更好的是 /emps/ + methods -> handler 需要装饰器控制方法

路由+装饰后的函数 inner

from django.views.decorators.http import
  • require_http_methods 带参装饰器
  • require_GET 无参装饰器 相当于 require_http_methods(["GET"]) 返回 decorator
  • require_POST 无参装饰器 相当于 require_http_methods(["POST"]) 返回 decorator
  • require_safe 无参装饰器 相当于 require_http_methods(["GET","HEAD"]) 返回 decorator

代码分析

python
def require_http_methods(request_method_list): def decorator(func): @wraps(func) def inner(request, *args, **kwargs): if request.method not in request_method_list: response = HttpResponseNotAllowed(request_method_list) log_response( 'Method Not Allowed (%s): %s', request.method, request.path, response=response, request=request, ) return response return func(request, *args, **kwargs) return inner return decorator require_GET = require_http_methods(["GET"]) # 返回 decorator, 单参函数; require_POST = require_http_methods(["POST"])

注意:两次调用,生成不同的2个内嵌函数,均是单参函数

所以每一个view函数装饰后,就是inner函数,真正的view函数是闭包func

装饰器的真正作用: 真正的路由变成现在/emps/ -> inner 函数,调用时-> 方法不允许 405 ,方法允许就调用原函数(request,*args,**kwargs)

employee/views.py

python
from django.shortcuts import render from django.http import HttpRequest, HttpResponse from django.views.decorators.http import require_http_methods, require_GET,require_POST,require_safe # require_safe 相当于GET, HEAD """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # function-based view 第1个参数必须是request对象 # GET HEAD POST PUT PATCH DELETE 都可以,但POST不行,CSRF关了 # 列表页 # @require_http_methods(['GET', 'HEAD']) # 等价 test = require_http_methods(['GET', 'HEAD'])(test) @require_GET def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(**kwargs) return HttpResponse('这是获取内容', status=200) # 装饰器等价 # test = require_GET(test) @require_http_methods(['POST', 'PUT', 'PATCH', 'DELETE']) def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(**kwargs) return HttpResponse('这是修改或新加的内容返回', status=201) # 详情页 def test_detail(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) # {'id': 1} return HttpResponse('hello world', status=201) # 装饰器 # class-based view

注意: 装饰后,test已经相当于是inner函数了, 再被urls中导入Inner过去

在urls中导入的是是装饰后的inner

路由+原始函数+装饰 inner

注意以下和以上是一样的结果,因为urls导入的装饰后的inner或直接在url处装饰这个原始函数是一样的效果

employee/urls.py

python
from django.urls import path from .views import * from django.views.decorators.http import require_GET # 二级路由, 应用路由 urlpatterns = [ # path('', test), # 列表页, /emps/ test = require_GET(test) => inner # path('<int:id>', test_detail), # 详情页, restful path('', require_GET(test)) # /emps/ inner ]

employee/views.py

python
from django.shortcuts import render from django.http import HttpRequest, HttpResponse from django.views.decorators.http import require_http_methods, require_GET,require_POST,require_safe # require_safe 相当于GET, HEAD """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # function-based view 第1个参数必须是request对象 # GET HEAD POST PUT PATCH DELETE 都可以,但POST不行,CSRF关了 # 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) return HttpResponse('这是获取内容', status=200) # 详情页 def test_detail(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) # {'id': 1} return HttpResponse('hello world', status=201) # 装饰器 # class-based view

Json响应

from django.http import JsonResponse 此方法可以将python的字典,列表转换为javascript的对象, 数组

employee/urls.py

from django.urls import path from .views import * from django.views.decorators.http import require_GET # 二级路由, 应用路由 urlpatterns = [ # path('', test), # 列表页, /emps/ test = require_GET(test) => inner # path('<int:id>', test_detail), # 详情页, restful path('', require_GET(test)) # /emps/ inner ]

employee/views.py

from django.shortcuts import render from django.http import HttpRequest, HttpResponse from django.http import JsonResponse from django.views.decorators.http import require_http_methods, require_GET, require_POST, require_safe # require_safe 相当于GET, HEAD """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # function-based view 第1个参数必须是request对象 # GET HEAD POST PUT PATCH DELETE 都可以,但POST不行,CSRF关了 # 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) return JsonResponse({'books':[ (1,'python',3), (2,'c++',3) ]}) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象 # 详情页 def test_detail(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) # {'id': 1} return HttpResponse('hello world', status=201) # 装饰器 # class-based view

正常

如果换成

def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) return JsonResponse([ (1,'python',3), (2,'c++',3) ]) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象

会报错 TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False. 因为默认python会转换安全的数据字典,如果使用列表,就认为不安全,需要加一个关键字参数safe=False表示不进行安全检查,写代码的人认为是安全的。

diff
# 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) return JsonResponse([ (1,'python',3), (2,'c++',3) + ],safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象

django类视图

父类(from django.views import View )有流程,子类只需要修改一点方法

https://docs.djangoproject.com/en/4.0/#the-view-layer

Class-based views: Overview | Built-in display views | Built-in editing views | Using mixins | API reference | Flattened index

view介绍: https://docs.djangoproject.com/en/4.0/topics/class-based-views/intro/

View工作逻辑

View是所有View的根基类, 它定义了所有视图类的基本处理规则。as_view()是仅仅类方法(仅类可以调用) , 本质就是一个视图函数。

URL映射

employee/urls.py

python
from django.urls import path from .views import * from django.views.decorators.http import require_GET # 二级路由, 应用路由 urlpatterns = [ # 暴露handler函数,直接是函数 # path('', test), # 列表页, /emps/ test = require_GET(test) => inner # path('<int:id>', test_detail), # 详情页, restful # 暴露handler函数,装饰出来的handler函数 path('', require_GET(test)), # /emps/ 到 test 函数 require_GET(test) = inner == inner(request, *args, **kwargs) # 暴露handler函数, 仅类方法返回的handler函数; as_view在TestView没有覆盖,就是父类View的as_view就去 path('ts/',TestView.as_view()), # /emps/ts/ 到 TestView类 TestView.as_view() = view == view(request, *args, **kwargs) ]

视图

employee/views.py

python
from django.shortcuts import render from django.http import HttpRequest, HttpResponse from django.http import JsonResponse from django.views import View """ request -> wsgi www解析为 environ 对象 wsgi server调用 wsgi.py中的application(environ,start_response) django框架是application, return 可迭代对象 其中路由,请求不同的path, 找到不同的解决问题的handler; handler可以是函数,可以是类 """ # funciton-based view # 列表页 def test(request: HttpRequest, **kwargs): print(request) # <WSGIRequest: GET '/'> print(kwargs) return JsonResponse([ (1, 'python', 3), (2, 'c++', 3) ], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象 # 详情页 # request, **kwargs # class-based view class TestView(View): """ 发请求是 /emps/ts/ -> TestView.as_view() -> view == view(request, *args, **kwargs) 函数 view函数中,由dispatch函数,不同请求方法对应不同函数的映射。 最终由TestView中的 handler处理 未定义的方法, 统一是405 """ def get(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request print(request.content_type) print(kwargs) return HttpResponse('这是视图类TestView的get') def post(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request print(request.content_type) print(kwargs) return HttpResponse('这是视图类TestView的post')

分析源码

urls.py源代码中TestView.as_view()结果是view,这里的view是每次请求返回新的view,还是一个view?

返回1个view, 每一个请求调用 这个同一个view函数 多次。

path('ts/',TestView.as_view()), # /emps/ts/ 到 TestView类 TestView.as_view() = view == view(request, *args, **kwargs) path('ts1/',TestView.as_view()), # /emps/ts/ 到 TestView类 TestView.as_view() = view == view(request, *args, **kwargs) ]

现在是两次调用,所以view函数生成2次

diff
+view = TestView.as_view() urlpatterns = [ # 暴露handler函数,直接是函数 # path('', test), # 列表页, /emps/ test = require_GET(test) => inner # path('<int:id>', test_detail), # 详情页, restful # 暴露handler函数,装饰出来的handler函数 path('', require_GET(test)), # /emps/ 到 test 函数 require_GET(test) = inner == inner(request, *args, **kwargs) # 暴露handler函数, 仅类方法返回的handler函数; as_view在TestView没有覆盖,就是父类View的as_view就去 + path('ts/',view), # /emps/ts/ 到 TestView类 TestView.as_view() = view == view(request, *args, **kwargs) + path('ts1/',view), # /emps/ts/ 到 TestView类 TestView.as_view() = view == view(request, *args, **kwargs) ]

这样也可以

来一个新的请求,最终调用的是view函数

python
@classonlymethod def as_view(cls, **initkwargs): """Main entry point for a request-response process.""" for key in initkwargs: if key in cls.http_method_names: raise TypeError( 'The method name %s is not accepted as a keyword argument ' 'to %s().' % (key, cls.__name__) ) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) self.setup(request, *args, **kwargs) if not hasattr(self, 'request'): raise AttributeError( "%s instance has no 'request' attribute. Did you override " "setup() and forget to call super()?" % cls.__name__ ) return self.dispatch(request, *args, **kwargs)
  1. self = cls(**initkwargs) 这一句cls是, TestView.as_view()调用的类 TestView, 也是as_view传递进来的cls, 是闭包。

    这一句是解构,将initkwargs解构, 变成关键字传参, 实例化TestView 类对象

    /emps/ts -> view(request) -> 上面的view函数。

    第1句相当于 self = TestView() # 实例; view是1个,每一次请求就会调用一次view函数, 在函数中为这个请求生成一个TestView实例, 处理这个request请求。

    一次请求,一次view调用,一个独立的TestView实例。

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

    self是 TestView实例

    dispatch方法,因为自己没有这个方法,就继承父类View的dispatch方法

    python
    # self对应 TestView的实例, request传递来的 def dispatch(self, request, *args, **kwargs): # 当前请求的方法在支持的方法列表中 # get in ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] if request.method.lower() in self.http_method_names: # 反射: 字符串找TestView实例的属性 # get() 方法 handler = self.get 绑定了self的函数 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # self.get(request, *args, **kwargs)

    注意: getattr(self, request.method.lower(), self.http_method_not_allowed) 反射

    先查找TestView实例字典,后查找TestView,父View类字典, 父类没有时,就第3个参数的handler

    python
    def http_method_not_allowed(self, request, *args, **kwargs): logger.warning( 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) return HttpResponseNotAllowed(self._allowed_methods())

    handler(request, *args, **kwargs) 就是我们定义的TestView实例的方法,因为已经绑定了TestView实例

    python
    class TestView(View): """ 发请求是 /emps/ts/ -> TestView.as_view() -> view == view(request, *args, **kwargs) 函数 view函数中,由dispatch,不同请求方法对应不同函数的映射。 最终由TestView中的 handler处理 未定义的方法, 统一是405 """ def get(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request print(request.content_type) print(kwargs) return HttpResponse('这是视图类TestView的get') def post(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request print(request.content_type) print(kwargs) return HttpResponse('这是视图类TestView的post')

本文作者:mykernel

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!