Django 视图函数

Django 视图函数

Django 中的视图是 MTV 架构模式中的 V 层,主要处理客户端的请求并生成响应数据返回。它其实类似于 MVC 架构模式中的 C 层,用于处理项目的业务逻辑部分。Django 的视图函数就是 Django 项目中专门处理对应 path 的 python 函数,这也是整个项目开发中最核心的业务处理部分。当然,在 Django 中处理 path 路径的不只有视图函数(FBV),还有视图类(CBV)。

1. Django 视图中的 FBV 和 CBV

FBV 全称是 function base views, 即在视图中是使用函数来对应处理请求。如下示例:

# 在django中会对跨域请求做拦截处理,这里使用@csrf_exempt注解表示不作处理,主要是针对POST请求@csrf_exemptdef hello_world(request, *args, **kwargs):if request.method == 'GET':return HttpResponse('Hello, get request', content_type=text/plain)elif request.method == 'POST':return HttpResponse('Hello, post request', content_type=text/plain)return HttpResponse(Hello, world., content_type=text/plain)urlpatterns = [path('admin/', admin.site.urls),path('hello/', hello_world),]

注意: 视图函数可以接受 request 参数,用于存放浏览器传递过来的所有数据。它是 WSGIRequest 类的一个实例,而 WSGIRequest 类又继承自 HttpRequest 。我们通过 request 这个实例可以拿到客户端 http 请求的方法,请求路径、传递的参数以及上传的文件等等。

CBV 全称是 class base views 就是在视图中使用类处理请求。在 Django 中加入了 CBV 的模式,让我们用类去处理响应,这样做有以下几个好处:

  • 可以使用面向对象的技术,比如 Mixin(多继承),这样可以有效复用增删改查数据库的代码;

  • 在视图类中我们定义如 get 方法就能处理对应的 GET 请求,这样无需使用 if 再进行判断,一方面提高了代码的可读性,另一方面也规范了 web api 的接口设计。

示例代码:

from django.http import HttpResponsefrom django.views import Viewclass HelloView(View):def get(self, request, *args, **kwargs):  return HttpResponse('get\n')  def post(self, request, *args, **kwargs):  return HttpResponse('post\n')def put(self, request, *args, **kwargs):  return HttpResponse('put\n')def delete(self, request, *args, **kwargs):  return HttpResponse('delete\n')# 注意,给CBV加上@csrf_exempt注解,需要加到dispatch方法上,后续会详解介绍这个函数的作用@csrf_exemptdef dispatch(self, request, *args, **kwargs):return super(HelloView, self).dispatch(request, *args, **kwargs)urlpatterns = [path('admin/', admin.site.urls),path('hello/', HelloView.as_view()),]

将包含这个视图层的 django demo 工程启动后,请求 /hello/ 路径:

[root@server ~]# curl http://127.0.0.1:8881/hello/get[root@server ~]# curl -XPOST http://127.0.0.1:8881/hello/post[root@server ~]# curl -XPUT http://127.0.0.1:8881/hello/put[root@server ~]# curl -XDELETE http://127.0.0.1:8881/hello/delete

可以看到,这样封装的视图类对应的 web api 接口具有良好的 restful 风格,而且代码可读性也非常好。 后面我们会深入学习这种视图模式以及 Django 内部封装的各种 view 类。

2. Django 中的 HttpRequest 类

上面我们初步接触到了 HttpRequest 类,现在来详细介绍下这个类及其相关属性和方法。当 URLconf 文件匹配到客户端的请求路径后,会调用对应的 FBV 或者 CBV,并将 HttpRequest 类的实例作为第一个参数传入对应的处理函数中。那么这个 HttpRequest 类有哪些常用的属性和方法呢?

常用属性

  • HttpRequest.scheme:请求的协议,一般为 http 或者 https;

  • HttpRequest.body:请求主体;

  • HttpRequest.path: 所请求 URL 的完整路径,即去掉协议,主机地址和端口后的路径;

  • HttpRequest.method:客户端 HTTP 请求方法,如 GET、POST、PUT、DELETE等;

  • HttpRequest.GET: 返回一个 querydict 对象,该对象包含了所有的 HTTP 请求中 GET 请求的参数;

  • HttpRequest.POST: 返回一个 querydict 对象,该对象包含了所有的 HTTP 请求中 POST 请求的参数;

  • HttpRequest.COOKIES:返回一个包含了所有 cookies 的字典;

  • HttpRequest.FILES:返回一个包含所有文件对象的字典。

常用方法

  • HttpRequest.get_host():返回客户端发起请求的 IP + 端口;

  • HttpRequest.get_port():返回客户端请求端口;

  • HttpRequest.get_full_path():返回请求的完整路径,包括 “?” 后面所带参数;

  • HttpRequest.get_raw_uri():返回完整的 uri 地址,包括了协议、主机和端口以及完整请求路径;

  • HttpRequest.build_absolute_uri():通过 request 实例中的地址和变量生成绝对的 uri 地址。

示例代码

# 省略了import内容def hello_world(request, *args, **kwargs):request_info = request_info += request.scheme={}\n.format(request.scheme)request_info += request.body={}\n.format(request.body)request_info += request.path={}\n.format(request.path)request_info += request.method={}\n.format(request.method)request_info += request.GET={}\n.format(request.GET)request_info += request.FILES={}\n.format(request.FILES)request_info += request.get_host={}\n.format(request.get_host())request_info += request.get_port={}\n.format(request.get_port())request_info += request.get_full_path={}\n.format(request.get_full_path())request_info += request.get_raw_uri={}\n.format(request.get_raw_uri())request_info += request.build_absolute_uri={}\n.format(request.build_absolute_uri())return HttpResponse(request_info, content_type=text/plain)urlpatterns = [path('admin/', admin.site.urls),path('hello/', hello_world),]

我们启动 Django 服务后,我们使用 curl 命令发送 HTTP 请求如下:

# 准备一个新的文件
[root@server ~]# cat upload_file.txt 
upload file test
[root@server ~]# curl -XPOST http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz -F 'data={name: join, age: 28}' -F file=@/root/upload_file.txt
request.scheme=http
request.body=b'------------------------------c28860e155fe\r\nContent-Disposition: form-data; name=data\r\n\r\n{name: join, age: 28}\r\n------------------------------c28860e155fe\r\nContent-Disposition: form-data; name=file; filename=upload_file.txt\r\nContent-Type: text/plain\r\n\r\nupload file test\n\r\n------------------------------c28860e155fe--\r\n'
request.path=/hello/
request.method=POST
request.GET=<QueryDict: {'a': ['xxx', 'yyy'], 'b': ['zzz']}>
request.FILES=<MultiValueDict: {'file': [<InMemoryUploadedFile: upload_file.txt (text/plain)>]}>
request.get_host=127.0.0.1:8881
request.get_port=8881
request.get_full_path=/hello/?a=xxx&a=yyy&b=zzz
request.get_raw_uri=http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz
request.build_absolute_uri=http://127.0.0.1:8881/hello/?a=xxx&a=yyy&b=zzz

通过测试结果可以更容易理解 HttpRequest 类属性和方法的含义。其中,上述 curl 请求中 -F 表示带表单数据。

3. Django 视图函数的返回值

对于视图函数的返回值,往往有如下几种方式:

3.1 直接返回字符串

直接返回字符串是非常常见的一种方式,不过我们需要将字符串作为参数传到 HttpResponse 类中实例化后返回:

def hello_world(request, *args, **kwargs):return HttpResponse('要返回的字符串')

Tips:HttpResponse:是 Django 中封装的用于返回响应的类 。

3.2 返回 json 类型

视图函数直接返回 json 数据是在微服务架构中常见的套路。这里 Django 程序只提供后端数据并不提供静态资源。针对返回 json 数据,在 Django 中专门定义了一个 JsonResponse 类用来生成 json 数据。它实际上是继承自 HttpResponse 类:

# django/http/response.py# 忽略其他内容class JsonResponse(HttpResponse):
    忽略注释部分内容
    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs):if safe and not isinstance(data, dict):raise TypeError('In order to allow non-dict objects to be serialized set the ''safe parameter to False.')if json_dumps_params is None:json_dumps_params = {}kwargs.setdefault('content_type', 'application/json')data = json.dumps(data, cls=encoder, **json_dumps_params)super().__init__(content=data, **kwargs)

JsonResponse 类的使用和 HttpResponse 类一样简单,我们只需要把字典数据传给 JsonResponse 类进行实例化即可。但是字典数据中存在中文时候,会出现乱码,我们只需要在实例化 JsonResponse 时,多传入一个参数即可:

# 在页面上会出现乱码def hello_world(request, *args, **kwargs):data = {'code': , content: 返回中文字符串, err_msg: }return JsonResponse(data)# 经过处理后的JsonResponsedef hello_world(request, *args, **kwargs):data = {'code': , content: 返回中文字符串, err_msg: }return JsonResponse(data, json_dumps_params={'ensure_ascii': False})

请求结果:

# 第一个不处理的 JsonResponse 返回[root@server ~]# curl http://127.0.0.1:8881/hello/{code: 0, content: \u8fd4\u56de\u4e2d\u6587\u5b57\u7b26\u4e32, err_msg: }# 使用第二个数据处理后的HttpResponse返回[root@server ~]# curl http://127.0.0.1:8881/hello/{code: 0, content: 返回中文字符串, err_msg: }

另外一种比较好的方式是,仿照 JsonResponse 类,定义一个支持返回包含中文 json 数据的 Response 类:

# 忽略导入模块# 将原来支持的json_dumps_params参数固定写死成{'ensure_ascii':False}class JsonResponseCn(HttpResponse):
    忽略注释部分内容
    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):if safe and not isinstance(data, dict):raise TypeError('In order to allow non-dict objects to be serialized set the ''safe parameter to False.')kwargs.setdefault('content_type', 'application/json')data = json.dumps(data, cls=encoder, **{'ensure_ascii':False})super().__init__(content=data, **kwargs)

这样处理后,我们在和原来一样使用 JsonResponseCn 类来返回 json 数据即可,不用考虑返回的字典数据中是否包含中文字符:

def hello_world(request, *args, **kwargs):data = {'code': , content: 返回中文字符串, err_msg: }return JsonResponseCn(data)

3.3 调用 render 函数返回

HTML 文本或者模板文件。其实,通过查看 Django 的源代码,可以看到 render 函数会调用 loader.render_to_string() 方法将 html 文件转成 string,然后作为 content 参数传递给 HttpResponse 类进行实例化:

# django/shortcuts.pydef render(request, template_name, context=None, content_type=None, status=None, using=None):
    Return a HttpResponse whose content is filled with the result of calling
    django.template.loader.render_to_string() with the passed arguments.
    content = loader.render_to_string(template_name, context, request, using=using)return HttpResponse(content, content_type, status)

它的用法如下:

# 第一步,在django工程中准备一个静态文件,放到templates目录下(django-manual) [root@server first_django_app]# cat templates/index.html <!DOCTYPE html><html lang=en><head><meta charset=UTF-8><title>Title</title></head><body><h1>这是首页</h1></body></html># 第二步,在first_django_app/setting.py文件,指定静态资源的目录(django-manual) [root@server first_django_app]# cat first_django_app/settings.py...TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates',# 指定项目目录下的templates目录'DIRS': [os.path.join(BASE_DIR, 'templates')],'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},]...# 第三步,添加视图函数以及URLconf,位置first_django_app/urls.pydef index(request, *args, **kwargs):return render(request, index.html)urlpatterns = [path('admin/', admin.site.urls),path('index/', index),]

就这样一个简单的配置,我们请求 /index/ 路径时,会返回 index.html 文件内容:

[root@server ~]# curl http://127.0.0.1:8881/index/<!DOCTYPE html><html lang=en><head><meta charset=UTF-8><title>Title</title></head><body><h1>这是首页</h1></body></html>

另一方面,index.html 还可以是一个模板文件,我们通过 render 函数最后会将该模板文件转成一个完整的 HTML 文件,返回给客户端:

# index.html
(django-manual) [root@server first_django_app]# cat templates/index.html 
<!DOCTYPE html><html lang=en><head><meta charset=UTF-8><title>Title</title></head><body><h1>{{ title }}</h1><p>{{ content }}</p></body></html># 修改 urls.py 中的视图函数
(django-manual) [root@server first_django_app]# cat first_django_app/urls.py
...

def index(request, *args, **kwargs):
    return render(request, index.html, {title:首页, content: 这是正文})
    
...

最后请求结果可以看到完整的 HTML 文本:

[root@server ~]# curl http://127.0.0.1:8881/index/
<!DOCTYPE html>
<html lang=en>
<head>
    <meta charset=UTF-8>
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p>这是正文</p>
</body>
</html>

3.4 调用 redirect 函数

实现重定向功能,它的常用形式如下:

from django.shortcuts import redirect# 指定完整的网址def redirect_view1(request):# 忽略其他return redirect(http://www.baidu.com)# 指定内部path路径def redirect_view2(request):# 忽略其他
  return redirect(/index/)def redirect_view3(request):# 忽略其他return redirect(reverse('blog:article_list'))def redirect_view4(request):# 忽略其他return redirect('some-view-name', foo='bar')

4. 给视图加装饰器

在 Django 工程中,由于视图有两种:FBV 和 CBV,因此视图加装饰器的情况也分为两种:给视图函数加装饰器和给视图类加装饰器。由于视图函数是普通的 Python 函数,因此给视图函数加装饰器和给普通函数加装饰器方式一致。下面代码中我们给视图函数 index 加了一个简单的装饰器,用于在执行视图函数前和后各打印一段字符:

from django.shortcuts import render, HttpResponse, redirectdef wrapper(f):def innser(*args, **kwargs):print('before')ret = f(*args, **kwargs)print('after')return retreturn innser
 
@wrapperdef index(request):
  return render(request, 'index.html')

由于类中的方法与普通函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。Django 中提供了 method_decorator 装饰器用于将函数装饰器转换为方法装饰器:

# 定义函数装饰器def wrapper(f):def innser(*args, **kwargs):print('before')ret = f(*args, **kwargs)print('after')return retreturn innser# 另一个代码文件中使用wrapper装饰器from django.views import Viewfrom django.utils.decorators import method_decorator

@method_decorator(wrapper, name='get')class HelloView(View):# @method_decorator(wrapper)def get(self, request, *args, **kwargs):  print('get')return HttpResponse('get\n')  def post(self, request, *args, **kwargs):print('post')return HttpResponse('post\n')def put(self, request, *args, **kwargs): print('put')return HttpResponse('put\n')def delete(self, request, *args, **kwargs): print('delete')return HttpResponse('delete\n')
    @csrf_exempt# @method_decorator(wrapper)def dispatch(self, request, *args, **kwargs):return super(HelloView, self).dispatch(request, *args, **kwargs)

对于给 View 类添加装饰器,我们有如下几种性质:

  • method_decorator 装饰器直接添加到 View 类上。第一个参数指定需要添加的装饰器,如 wrapper,name 参数可以选择将装饰器 wrapper 作用到 View 类中的哪个函数。给类添加装饰器时,必须要指定    name 参数,不然运行 Django 服务时会报错

  • method_decorator 装饰器可以直接作用于 View 类中的函数上。如果是在 get、post 这类 HTTP 请求方法的函数上,则装饰器只会作用于调用该函数的 http 请求上;

  • 如果 method_decorator 装饰器作用于 dispatch 函数上。由于对于视图中所有的 HTTP 请求都会先调用 dispatch 方法找到对应请求方法的函数,然后再执行。所以这样对应视图内的所有 HTTP 请求方式都先经过装饰器函数处理;

5. 小结

本小节中,我们介绍了视图的两种形式 FBV 和 CBV,并进行了简单的说明。接下来我们详细描述了 Django 中的 HttpRequest 和 HttpResponse 对象,并进行了代码说明。最后我们介绍了给 FBV 和 CBV 加上装饰器的方法。