在之前实际写的Django简单项目中,可以发现视图与模板几乎密不可分,模板的数据可交互全部通过视图控制.虽然之前讲了MTV模型,但Django的T和V实际上也不是全部区分开来,通过视图控制和模板语言,数据可以先在视图中处理一部分,再到页面中进行处理并最终展示,可见模板和视图的关系非常紧密.

视图

Django的视图,指的是一种功能,这个功能是接受请求并且返回响应.实现这一功能的就是视图函数(类),一般直接称其为视图.
反过来说,我们编写的视图函数或者说类,为了能在Django内生效,也必须符合Django的要求,即接受请求,返回响应.
所谓接受请求,就是指视图函数(类)的参数至少要包含特殊的request对象.
所谓返回响应,就是指必须要以符合HTTP协议的方式返回响应,响应的内容可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片等等。
视图的代码其实可以写在app目录下的任何位置,目前约定俗成将视图相关代码放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。

虽然之前编写了很多视图函数,这里还需要对视图函数做一个说明:

# 视图函数的例子
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = "It is now %s." % now
    return HttpResponse(html)

视图函数一定要有一个参数,这个参数是HttpRequest对象,其形参的名字可以任意取,不过一般都习惯起成request.在Django调用视图函数的时候,就会给视图函数传递由HTTP请求包装而成的HttpRequest对象作为参数.
视图函数的返回值,在初学Django的时候知道有HttpResponse,render,redirect三大返回方法,这三大返回方法返回的东西本质,都是封装好的HttpResponse对象.
只要编写的函数符合该要求,即接受一个请求,回复一个响应,然后将该函数与urls捆绑在一起,这个函数就成为一个视图函数.
有了视图函数之后,在浏览器向Django请求访问urls链接的时候,Django会将来自浏览器的请求包装成一个HttpRequest对象,将该对象传入对应的视图函数中作为参数,然后等候视图函数返回HTTP响应,再将该响应返回给浏览器.

FBV与CBV

刚才提到了视图函数(类),这就说明除了函数以外,类也可以用作视图.这两种方式分别叫做FBV(Function Based View)和CBV(Class Based View).之前写过的所有视图都是FBV方式的,如果要写CBV的视图,就需要在views.py里定义类,此外在urls里的对应关系会略有不同.
看一个改写自行编写的添加物品类:

class Item_add(View):
    title = "新增物品"

    def get(self, request):
        type_li = models.Types.objects.all().order_by("id")
        return render(request, 'item_add.html', {"items": "items", "title": self.title, "type_list": type_li})

    def post(self, request):
        new_item_name = request.POST.get("item_name_new").strip()
        new_item_type_id = request.POST.get("item_type_id")
        if new_item_name and new_item_type_id:
            try:
                models.Items.objects.create(type_id=new_item_type_id, name=new_item_name).save()
                return redirect("/items/")
            except:
                type_li = models.Types.objects.all().order_by("id")
                return render(request, 'item_add.html',
                              {"items": "items", "title": self.title, "type_list": type_li, "warning": "名字不能重复"})
        else:
            type_li = models.Types.objects.all().order_by("id")
            return render(request, 'item_add.html',
                          {"items": "items", "title": self.title, "type_list": type_li, "warning": "名字或类型不能为空白"})

用类写的一个好处是,不像函数用if来判断post与get方法(猜想还会有其他方法).可以有效的降低耦合程度,让代码更清晰.
之后需要在urls里用类名.as_view()来对应.

url(r'^item_add/', views.Item_add.as_view())

CBV可以有效的使用Python面向对象的各种高级特性,在实际工业级软件开发中,CBV使用较多.像类的装饰器等在实际项目中经常使用.

Request和Response对象

视图的接受请求和返回的响应其实是两个对象,请求被Django封装在Request对象中,而返回的响应则是Response对象.

Request对象的属性和方法

当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象.

官方文档描述了HttpRequest的各种属性,这些属性可以在视图函数内取得并使用:

不可修改的属性
属性 解释
HttpRequest.scheme 代表HTTP的请求方案,通常返回字符串http或者https
HttpRequest.body 以字节形式表示的请求体.在处理非HTTP的报文时候很有用.可以用HttpResponse.read()来操作请求体
HttpRequest.path 表示访问的路径组件,网站名称,端口号,url附加信息均不包括在结果内,仅有路径.path的路径是绝对路径
HttpRequest.path_info 与path的区别是只拿到相对路径部分,推荐用这个替代path便于调试
HttpRequest.method 表示请求的方法,一般为字符串GET和POST,必须使用大写
HttpRequest.GET 类似字典的对象,包含GET请求的全部参数
HttpRequest.POST 一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。如果表单为空,依然会生成QueryDict对象,因此在判断GET还是POST的方法的时候一定要通过method属性判断,而不能判断QueryDict的存在与否
HttpRequest.encoding 不常用,表示解码HTTP字节流的编码方式,一般都是UTF-8了
HttpRequest.COOKIES 存放COOKIES的一个字典,键和值都是字符串
HttpRequest.FILES 一个字典,包含所有的上传文件,之后用get(“name”)的方式(name是input标签的name属性的值)拿到具体文件.要上传文件,Form表单的属性enctype=”multipart/form-data,否则FILES将是一个空字典.
HttpRequest.META 这是包含HTTP请求头部全部信息的字典.具体信息看这里.常用的是取得客户端IP地址和用户等数据.

上述的属性是原始浏览器发过来的属性.HttpRequest对象还有一些有应用和中间件设置上去的属性.这些属性都是在交给视图函数之前设置上去的.

应用和中间件设置的属性
属性 解释
HttpRequest.current_app 代表处理该请求的当前的app,模板语言url tag会将其作为参数.
HttpRequest.urlconf 这个会被当做当前请求的根路径,和路由系统有关.
HttpRequest.user 这是由 AuthenticationMiddleware 中间件添加的属性,用于用户验证.不启用该中间件是没有用的.详请之后会学习
HttpRequest.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。今后会学习.

HttpRequest对象的方法如下:

HttpRequest的方法
方法 解释
HttpRequest.get_host() 从请求头部获取主机地址.根据从HTTP_X_FORWARDED_HOST(如果打开 USE_X_FORWARDED_HOST,默认为False)和 HTTP_HOST 头部信息返回请求的原始主机。如果这两个头部没有提供相应的值,则使用SERVER_NAME 和SERVER_PORT
HttpRequest.get_full_path() 返回 path,如果存在查询字符串会将字符串也一起返回。
HttpRequest.get_port() 返回端口
HttpRequest.is_secure() 如果请求是通过Https发起的,返回True,否则返回False
HttpRequest.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串’XMLHttpRequest’。这个会在今后使用Ajax的时候学习
HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt=”, max_age=None) Cookie相关.返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。

由于目前我们编写的程序,都是无验证无状态的web应用,基本上只是将数据库操作可视化,对于一些高级方法还没有涉及到,会随着在其后建立不同的站点并且上线各种应用验证方式来逐步学习.

Response对象的属性和方法

HttpRequest是Django自行创建并传入到视图函数的,Response对象则必须由我们自行通过内置方法建立.
之前学过的三种返回方式 HttpResponse, redirect, render 都是通过给内置方法传入数据,实例化一个HttpResponse对象并且返回.
之前学习过HTTP协议可以知道,响应也有响应头和响应体.在生成一个HttpResponse对象后可以不急着返回,通过属性和方法对其做操作.
HttpResponse的使用方法:
实例化HttpResponse对象并进行操作:

res = HttpResponse("我是手动设置的响应")
res.write(r"

这是write方法写入的内容

")

上述返回后在屏幕上会把两部分信息都显示,可见write方法是追加写入.
还可以对对象设置响应头的键值:

res['Age'] = 120
del res['Age']

来看看HttpResponse对象的方法和属性:

HttpResponse的属性
属性 解释
HttpResponse.content 获取响应体,以字节形式显示
HttpResponse.charset 返回响应体内字符集的结果
HttpResponse.status_code 返回HTTP状态码.HTTP响应状态码和对应的默认原因字符串都可以在IETF官方网站上的HTTP协议1.1版中找到.
HttpResponse.reason_phrase 返回状态码对应的默认原因字符串,除非被单独设置,否则该部分会随着状态码的设置而自动返回对应的默认原因字符串
HttpResponse.streaming 这个值永远是False,该值的存在是为了给中间件处理流信息所用.不要改动

HttpResponse对象的方法主要是和cookie有关,以后会再学习.官方文档.

JsonResponse对象

JsonResponse是HttpResponse的子类,专门用来生成JSON编码的响应。我们目前写的页面每次都是发送HTTP响应,其实就是将整个页面又传送了一次.在今后学异步的时候,前后端通信经常发送的不是整个页面,而是JSON字符串供页面使用.这个时候虽然可以引入json模块,将dump之后的结果交给HttpResponse,但可以直接引入Django内写好的模块进行响应,例子如下:

from django.http import JsonResponse
response = JsonResponse({'foo': 'bar'})
print(response.content)
b'{"foo": "bar"}'

默认只能传递字典类型,如果要传递非字典类型需要设置一下safe关键字参数。但一般不推荐使用,还是传送标准的Json字符串比较好.
看完了HttpResponse之后,来看看三大返回响应中的render以及redirect

render函数

render 属于Django shortcut functions中的组件之一.在之前的项目中已经知道了render的三个参数分别是request对象,渲染的页面以及传入的数据.现在再来仔细看一下render:

render(request, template_name, context=None, content_type=None, status=None, using=None)

参数详解:

render 函数
参数 解释
request 要响应的request对象
template_name 模板名称,到settings里指定的模板目录去寻找文件
context=None context指一个字典,包含传入给页面的数据,称为上下文字典或者上下文变量.默认为None
content_type=None 响应的MIME方式,默认的None表示text/html也就是标准HTML文档.
status=None HTTP状态码,默认的None表示200即响应成功.
using 加载模板引擎使用的名称.

render函数生成的结果是一个HttpResponse对象.从Django shortcut functions引入的render实际上是为了使用简便,将HttpResponse对象的生成过程以及包含了.如果为了程序的统一性,应该先进行渲染得到结果,最后再用HttpResponse生成响应对象,例如:

from django.shortcuts import render

def my_view(request):
    # 视图的代码写在这里
    return render(request, 'myapp/index.html', {'foo': 'bar'})

这段代码可以改写成先通过模板渲染,然后生成HttpResponse对象的方式:

from django.http import HttpResponse
from django.template import loader

def my_view(request):
    # 视图代码写在这里
    t = loader.get_template('myapp/index.html')
    c = {'foo': 'bar'}
    return HttpResponse(t.render(c, request))

可见这里是调用了模板类的一个方法,载入一个模板,然后对模板采用render方法渲染后得到渲染好的页面,最后用该页面生成HttpResponse对象.
第二种写法更加结构化和标准化,所有的视图函数统一返回由HttpResponse生成的对象.既然Django shortcut functions提供了简单的render方法,也可以使用.

redirect函数

同之前的render函数一样,redirect函数也是由Django shortcut functions模块内提供的”便利”函数:

redirect(to, permanent=False, *args, **kwargs)

redirect函数可以接受三种参数:

  1. 一个ORM模型:将调用模型的get_absolute_url()函数
  2. 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
  3. 一个绝对的或相对的URL,将原封不动的作为重定向的位置。

默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

之前使用redirect只使用了第三种情况,即返回一个绝对或者相对的URL,如果是绝对URL,必须以http(s)开头,否则会被解析为相对当前网站根目录的相对路径.
所谓临时重定向(状态码302)和永久重定向(状态码301)对于普通用户来说功能一样,主要是针对搜索引擎.A页面临时重定向到B页面,那搜索引擎收录的就是A页面。A页面永久重定向到B页面,那搜索引擎收录的就是B页面。
重定向到模型和视图用于应对比较复杂的需求,在今后的学习中遇到再说.

Django的视图架构和使用相比模板要简单很多,不过这是因为业务逻辑本身才是最复杂的部分,所以视图函数相对的限制很少.