【Django】视图

本文为 Django 学习笔记的讲解。

运行环境

  • Windows 10
  • Pycharm Community Edition 2020.1.3
  • Django 3.0.8

所有的代码见【Django】系列。

概述

视图的作用是接收并响应 web 请求,其本质是一个 python 中的函数。视图可以对网页和 JSON 数据进行响应,其中网页包括重定向和错误视图(如 404,500, 400);JSON 数据是网页中发出 Ajax 请求的问题。

其响应过程如图:

【Django】视图_第1张图片

url 配置

配置流程

首先制定根级 url 配置文件,在 settings,py 中的 ROOT_URLCONF = 'project.urls',一般默认实现无需修改。

urlpatterns

urlpatterns 是一个 url 实例的列表,里面存 url 对象,实例化时传三个参数,分别为:正则表达式,视图名称,名称(使用反向解析,后面会讲到),include() 表示从下个文件中找视图名称。

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    re_path(r'^', include('myApp.urls')),
]

url 匹配正则的注意事项

如果想从 url 中获取一个值,则需要对正则加小括号,例如在 Django 模板基本操作中,我们通过 (\d+) 获得班级序号。我们通常会在匹配正则最后加反斜杠,则匹配正则前无需重复加反斜杠。正则前加 r 表示字符串不进行转义。

引入其他 url 配置

我们一般在应用中创建 urls.py 文件,定义本应用的 url 配置。因为整个工程不止一个项目,如果全写在 project 下的 urls.py 中,则不方便应用的管理。因此我们需要在 project/urls.py 中使用 include 方法:

from django.contrib import admin
from django.urls import re_path, include

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    re_path(r'^', include('myApp.urls', namespace="myApp")), # 反向代理
]
from django.urls import re_path
from . import views
# app_name = 'myApp' # 要加app_name才能使用反向代理
urlpatterns = [
    re_path(r'^$', views.index, name="index"), # 反向代理
]

匹配过程见 Django 模板基本操作。

URL 的反向解析

如果在视图、模板中使用了硬编码的链接。如点击首页的 a,则生成 url 跳转到下一个页面:

<body>
    <h1>Mike is a good manh1>
    <a href="first/second">aa>
body>

【Django】视图_第2张图片

下个页面路径,如果我们匹配了路径 first/second 则会响应新的页面:

【Django】视图_第3张图片

但当我们修改了 project/urls.py 中的 url 时,我们需要重新写应用中的所有 url 路径,工作量极大。我们可以使用方向解析/代理来解决这个问题,上面代码中的 namespace 的作用就是在 url 配置改变时,动态生成链接的地址。使用 url 模板时经常使用。

视图函数

定义视图

视图的本质在 Django 视图基本操作中已经讲过,就是一个函数。函数的第一个参数是一个 HttpRequest 的实例,是由浏览器发送过来的,浏览器发来的信息全部存在里面,通常写为 request (形参想叫什么都可以)。后面的参数为正则表达式获取的参数。一般将视图放在 views.py 文件下定义。

错误视图

404 视图

找不到网页(即 url 匹配不成功)时返回。404 视图是相对于整个工程的视图,因此需要在 templates 下定义 404.html,且文件名必须为 404。无需配置 url。

<head>
    <meta charset="UTF-8">
    <title>404页面title>
head>
<body>
    <h1>页面丢失h1>
    <h2>{{request_path}}h2> 
body>

想要展示 404 页面,我们还需要配置 settings.py 文件:

DEBUG = False # 如果为True则永远无法调用404

ALLOWED_HOSTS = ['*'] # 允许访问,'*'为任何人可访问

得到结果:

【Django】视图_第4张图片

500 视图

在视图代码中出现错误(服务器代码出错)

400 视图

错误出现在客户操作,如网络爬虫或用户操作修改 cookies。

HttpRequest

概述

服务器接收 http 请求后,Django 会根据报文创建 HttpRequest 对象,在之后调用视图时传递给视图,视图的第一个参数就是 HttpRequest 对象。

属性

以下属性都是字符串形式

  • path:请求的完整路径,不包括域名和端口

  • method:请求的方式,常用的有 GET、POST

  • encoding:表示浏览器提交数据的编码方式,可设置,一般为 utf-8。例如使用 google chrome 打开 www.baidu.com 并按 f12,点击 Network 找到 www.baidu.com 中的 Request Header,就可以找到 Accept-Encoding 字段:

    【Django】视图_第5张图片

  • GET:类似字典的对象,包含了 get 请求的所有参数:

    在这里插入图片描述

  • POST:类似字典的对象,包含了 post 请求的所有参数。

  • FILES:类似字典的对象,包含了所有上传的文件(文件数据)。

  • COOKIES:字典,包含所有的 cookie

  • session:类似字典的对象,表示当前会话

我们可以打印查看以上的这些值:

def attribles(request):
    print(request.path)
    print(request.method)
    print(request.encoding)
    print(request.GET)
    print(request.POST)
    print(request.FILES)
    print(request.COOKIES)
    print(request.session)
    return HttpResponse("attribles")

得到打印结果:

【Django】视图_第6张图片

方法

is_ajax():如果是 XMLHttpRequest 发起的,返回 True。一般返回 JSON 数据

QueryDict 对象

request 对象中的 GEt、POST 都属于 QueryDict 对象。方法有:

  • get():根据键获取值。如 www.sunck.wang/abc?a=1&b=2&c=3 获取 a 的值。
  • getlist():将键的值以列表形式返回。如 www.sunck.wang/abc?a=1&a=2&c=3 获取 a 的值

GET 属性

GET 用来获取浏览器传递过来的数据,这里我们使用两种方式获取 GET 属性。首先输入 http://127.0.0.1:8000/sunck/get1?a=1&b=2&c=3:

# 获取get传递的数据
def get1(request):
    a = request.GET.get('a')
    b = request.GET['b'] # 也可以这样获取
    c = request.GET.get('c')
    return HttpResponse(a + " " + b + " " + c)

得到结果:

【Django】视图_第7张图片

但此时输入 http://127.0.0.1:8000/sunck/get1?a=1&a=2&c=3 提示服务器内部出错,无法获取两个 a:

【Django】视图_第8张图片

第二种输入 http://127.0.0.1:8000/sunck/get1?a=1&a=2&c=3:

def get2(request):
    a = request.GET.getlist('a')
    a1 = a[0]
    a2 = a[1]
    c = request.GET.get('c')
    return HttpResponse(a1 + " " + a2 + " " + c)

得到结果:

在这里插入图片描述

POST 属性

这里使用表单提交实现 post 请求。

首先在 templates/myApp 下创建 register.html 文件,添加表单样式:

<body>
    <form action="register/" method="post">
        姓名:<input type="text" name="name" value=""/>
        <hr/>
        性别:<input type="radio" name="gender" value="1"/><input type="radio" name="gender" value="0"/><hr/>
        年龄:<input type="text" name="age" value=""/>
        <hr/>
        爱好:<input type="checkbox" name="hobby" value="code"/>编程
        <input type="checkbox" name="hobby" value="math"/>数学
        <hr/>
        <input type="submit" value="注册"/>
    form>
body>

然后配置 url:

urlpatterns = [
    re_path(r'^$', views.index, name="index"),
    re_path(r'^attribles/$', views.attribles),
    re_path(r'^get1/$', views.get1),
    re_path(r'^get2/$', views.get2),
    re_path(r'^showregister/$', views.showregister),
    re_path(r'^showregister/register/$', views.register),
]

定义视图:

def showregister(request):
    return render(request, 'myApp/register.html')
def register(request):
    name = request.POST.get("name")
    gender = request.POST.get("gender")
    age = request.POST.get("age")
    hobby = request.POST.getlist("hobby")
    print(name)
    print(gender)
    print(age)
    print(hobby)
    return HttpResponse("post")

此时点击注册会显示 CSRF 禁止:

【Django】视图_第9张图片

我们还需要在 settings.py 中注释这一行代码,关闭 csrf:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware', # 关闭csrf
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

输入表单:

【Django】视图_第10张图片

打印得到结果为:

在这里插入图片描述

HttpResponse 对象

HttpResponse 是给浏览器返回数据的。前面介绍了 HttpRequest 对象是由 django 创建的,而

HttpResponse 则是程序员创建的。

返回用法

返回数据有两种用法,第一种是不调用模板,直接返回数据:

from django.http import HttpResponse
def index(request):
    return HttpResponse("Mike is a good man")

第二种是调用模板,通常使用 render 方法。render 的原型为 render(request, templateName, [context]),其中 request 为请求体对象,templateName 为模板路径, context 为传递给需要渲染在模板上的数据,可缺省。我们可以结合数据和模板,返回完整的 HTML 页面。

def index(request):
    return render(request, 'myApp/index.html')

属性

  • content:返回的内容:
    【Django】视图_第11张图片

我们查看 www.baidu.com 的 Response Headers:

【Django】视图_第12张图片

  • charset:数据的编码格式
  • status_code:响应状态码,有 200,304,404。
  • content-type:指定输出的 MIME 类型(HTML 或 JSON)

我们在这里打印上面的属性:

def showresponse(request):
    res = HttpResponse() # 自己创建对象
    res.content = b'good'
    print(res.content)
    print(res.charset)
    print(res.status_code)
    print(res.content-type)
    return res

得到打印结果:

在这里插入图片描述

方法

  • init:使用页面内容实例化 HttpResponse 对象
  • write(content):以文件的形式写入
  • flush():以文件的形式输出缓冲取
  • set_cookie(key, value='', max_age=None, expires=None):设置 cookie
  • delete_cookie(key):删除 cookie,若不存在则不进行操作。

什么是 cookie?

关于什么是 cookie,我们用一张图来解释它:

【Django】视图_第13张图片

首先浏览器登录,提供账号密码给服务器。服务器去数据库验证账号密码(此处省略 Redis 的过程)并得到验证结果。服务器生成 Token 存入数据库对应用户下,并交给浏览器。浏览器的后续验证将使用 Token(实际是一个字符串) 而不是 cookie,因为 cookie 很容易被截获且长度有限。如果 Token 被截获则不会泄露账号密码。

例如输入 www.baidu.com 得到的 cookie 为,但我们不能使用:

【Django】视图_第14张图片

我们设置一个 cookie:

def cookietest(request):
    res = HttpResponse()
    cookie = res.set_cookie("sunck", "good") # 设置cookie
    return res

查看页面对应的 cookie:

【Django】视图_第15张图片

Headers 中为:

【Django】视图_第16张图片

这里的 cookie 我们还可以通过属性来获取:

def cookietest(request):
    res = HttpResponse()
    cookie = request.COOKIES
    res.write("

" + cookie["sunck"]+"

"
) # 获取sunck对应的cookie并展示 return res

得到结果:

【Django】视图_第17张图片

以后发送 http 请求时都会携带 cookie,只验证 cookie 里面的 token 值。

子类 HttpResponseRedirect

该子类用来实现重定向,完成服务器端的跳转。例如我们输入 www.baidu.com 和 www.baidu.com/index.html 的效果是一样的,这就是重定向的效果。我们希望输入 127.0.0.1:8000/sunck/redirect1 跳转到页面 2:

from django.http import HttpResponseRedirect
def redirect1(request):
    return HttpResponseRedirect('/sunck/redirect2')
def redirect2(request):
    return HttpResponse("重定向")

实现重定向:

在这里插入图片描述

我们再举一个例子,实现与百度类似的功能:

首先配置 url:

urlpatterns = [
    re_path(r'^$', views.index, name="index"),
    re_path(r'index.html', views.index1), # 输入index.html进入index1视图
]

定义视图:

def index1(request): # 重定向到空路径,匹配url后进入index视图
    return HttpResponseRedirect('/sunck')
def index(request):
    return render(request, 'myApp/index.html')

输入 127.0.0.1:8000/sunck/index.html,得到结果与主页相同:

【Django】视图_第18张图片

这样做可以简化代码,大量的操作只需要写一份即可。我们也可以利用缩写简化代码,得到同样的结果:

from django.shortcuts import redirect
def redirect1(request):
    # return HttpResponseRedirect('/sunck/redirect2')
    return redirect('/sunck/redirect2')

to 推荐使用反向解析。

子类 JsonResponse

用来返回 json 数据,一般用于异步请求,如 ajax。如果 is_ajax() 为真,则返回 json 数据。__init__(self, data)data 为 返回的 json 数据,是字典对象。大致过程为:

from django.http import JsonResponse
def redirect2(request):
    if request.is_ajax()
        a = JsonResponse() # 创建JsonResponse对象,参数中传json数据
    return a

其对象的 content-type 为 application/json

状态保持

概述

http 协议是无状态的,每次请求都是一次新的请求,不记得之前的请求。例如我们登录某一网站的主页,右上角会显示未登录,如果仅为 http 请求,这次登录后我们下一次进入网页还是会显示未登录。

客户端和服务器的一次通信就是一次会话。因此如果我们想进行状态保持,需要在客户端或服务端存储有关会话的数据。在客户端和服务端通常分别用 cookie 和 session。使用 cookie,所有的数据存储在客户端,不要存敏感数据;使用session,所有的数据存储在服务端,在客户端用 cookie 存储 session_id,也是一种 key-value 类型。

状态保持在一段时间内跟踪请求者的状态,可跨页面访问当前请求者的数据。例如会员和普通用户在商城中商品显示的价格不同。不同请求者间不会共享此数据,与请求者一一对应。

启用 session

工程会在 settings.py 文件中默认启用 session。

INSTALLED_APPS = [
    'django.contrib.sessions',
]
MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
]

使用 session

启用 session 后,每个 HttpResquest 对象都有一个 session 属性,类似字典的对象。

  • get(key, default=None):根据键获取 session值
  • clear():清空所有会话
  • flush():删除当前会话并删除会话的 cookie

在这里我们实现一个登录界面,实现首页面显示游客,点击登录跳转到登陆页面,输入用户名后返回主界面,显示用户名。首先渲染首页面和登录页面:


<body>
    <h1>欢迎:游客h1> 
    <a href="/sunck/login">登录a>
body>


<body>
    <form action="/sunck/showmain" method="post">
        <input type="text" name="username"/>
        <input type="submit" value="登录">
    form>
body>

视图定义如下:

def main(request):
    return render(request, 'myApp/main.html')
def login(request):
    return render(request, 'myApp/login.html')
def showmain(request): # 重定向到main
    return redirect('/sunck/main')

配置路径:

re_path(r'^main/$', views.main),
re_path(r'^login/$', views.login),
re_path(r'^showmain/$', views.showmain),

此时跳转回 main 时还不能获得用户名,我们还需要获取 session。在 showmain 视图定义中存储 session,在 main 中获取 session 得到 username 传给模板进行显示。

def main(request):
    # 取session
    username = request.session.get('name', "游客")
    return render(request, 'myApp/main.html', {'username': username})
def showmain(request):
    username = request.POST.get('username')
    # 存session
    request.session['name'] = username
    return redirect('/sunck/main')

模板渲染:

<body>
    <h1>欢迎:{{username}}h1> 
    <a href="/sunck/login">登录a>
body>

得到主界面:

【Django】视图_第19张图片

点击登录:

【Django】视图_第20张图片

返回主界面:

【Django】视图_第21张图片

注意:这里容易出 bug,在反复尝试前要清空浏览器的缓存,否则主界面可能会出现显示 None 的情况。

此时我们可以查看数据库中的 session(第条个数据出现了 bug,请忽视),第一列为 session_key ,第二列为 session_data,第三列为 expire_data,即过期时间:

在这里插入图片描述

在线 base64 解码:

【Django】视图_第22张图片

将数据库中的 session_key 字段与浏览器中的 sessionid 比较发现相同:

【Django】视图_第23张图片

在登录后,我们还需要清除 session,清除 session 有很多种方法,下面我们通过代码一一介绍。

先修改模板:

<body>
    <form action="/sunck/showmain/" method="post">
        <input type="text" name="username"/>
        <input type="submit" value="登录">
    form>
body>

再定义视图

from django.contrib.auth import logout
def quit(request):
    # 清除session,三种方法均可,推荐使用logout
    logout(request)
    # request.session.clear()
    # request.session.flush()
    return redirect('/sunck/main')

点击退出,用户名显示为游客:

【Django】视图_第24张图片

设置过期时间

一般网页还有设置 session 过期时间的功能。如果不设置,则默认两星期后过期。

def showmain(request):
    username = request.POST.get('username')
    # 存session
    request.session['name'] = username
    request.session.set_expiry(10) # 设置整数,10s后过期
    return redirect('/sunck/main')

此时数据库中的过期时间为:

在这里插入图片描述

还可以设置为时间对象,到达该时间则过期;设置为 0,关闭浏览器时失效;None 永不过期,一般不使用。

存储 session 的位置

session 存储的位置有以下三种:

  • 默认存储在数据库中:添加 SESSION_ENGINE = "django.contrib.sessions.backends.db"
  • 只存储在本地内存中:如果丢失则无法找回,但比数据库更快,添加 SESSION_ENGINE = "django.contrib.sessions.backends.cache"
  • 数据库+缓存:优先从本地缓存中读取,读取不到再去数据库中获取,类似于计算机组成原理中的 cache,添加 SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"

使用 redis 缓存 session

在 cmd 中使用命令 pip install django-redis-sessions,并在 settings.py 中添加:

SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379 # 默认端口号6379
SESSION_REDIS_DB = 0
SESSION_REDIS_PASSWORD = 'sunck' # 账号
SESSION_REDIS_PREFIX = session

我们的视图部分到这里就结束啦,相信你已经学会文章中有关视图的所有东西了。有问题欢迎与我交流

你可能感兴趣的:(#,Python)