视图就是一个普通的Python函数,它接收从urls哪里的HttpRequest对象,返回的是HTML网页、重定向、404错误,XML文档或图像等任何东西。
一般使用
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("<h1>hello world</h1>")
例子,返回的是一个h1标签。
接收参数
当我们在路由设置参数时,在视图中应该要有对应的形参接收。
# app/urls.py
from django.urls import path
from .views import index, article
app_name = "app01"
urlpatterns = [
# url中有个参数叫article_id,视图函数是article
path("article/<int:article_id>", article, name="article")
]
# app/views.py
from django.http import HttpResponse
# Create your views here.
def article(request, article_id):
return HttpResponse(f"article的id是{article_id}")
返回
视图函数返回各种数据,一般只需用到django.shortcuts
下面的函数即可,该模块,为我们提供了很多快捷方便的类和方法,它们都很重要,使用频率很高。
HTML页面
使用django.shortcuts.render
函数。
from django.shortcuts import render
def index(request):
return render(request, "app01/index.html")
render(request, template_name, context=None, content_type=None, status=None, using=None)
必需参数:
- request:视图函数处理的当前请求,封装了请求头的所有数据,其实就是视图参数request。
- template_name:要使用的模板的完整名称或者模板名称的列表。如果是一个列表,将使用其中能够查找到的第一个模板。
可选参数:
- context:添加到模板上下文的一个数据字典。默认是一个空字典。可以将认可需要提供给模板的数据以字典的格式添加进去。这里有个小技巧,使用Python内置的locals()方法,可以方便地将函数作用域内的所有变量一次性添加进去。
- content_type:用于生成的文档的MIME类型。 默认为DEFAULT_CONTENT_TYPE设置的值,也就是'text/html'。
- status:响应的状态代码。 默认为200。
- using:用于加载模板使用的模板引擎的NAME。
template_name一般是在app/templates/app/下的html文件。多写一个"app"目的是区分不同的app中的同名html,因为假如settins的TEMPLATES中的APP_DIRS为True时,django会在app/templates下寻找html文件,所以要加上一层app名字。
使用render本质就是:
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), content_type='application/xhtml+xml')
使用locals()
作为render的context参数,可以将视图中的全部局部变量给template。
Json数据
django返回json数据的方法有很多,下面介绍几种方法:
-
使用
django.http.HttpRespone
库+json
库
直接使用json库序列化数据,然后使用HttpRespone返回数据,指定请求头Content-Type=application/json。
序列化非 常用数据类型时,可以指定编码器(一个继承json.JSONEncoder的类,重写default方法)。from django.http import HttpResponse import json def index(request): data = { "name": "lczmx", "age": 22, } return HttpResponse(json.dumps(data), content_type="application/json")
-
使用
django.http.HttpRespone
库+django.core.serializers
库
此方法可以很好的序列化QuerySet对象。def index(request): questions = Question.objects.all() data = serializers.serialize("json", queryset=questions) return HttpResponse(data, content_type="application/json")
serializers.serialize
的第一个参数是格式,第二个参数是queryset数据。
更多见官方文档:序列化 Django 对象 -
使用
django.http.JsonResponse
库class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs)
JsonResponse是一个 HttpResponse 子类,帮助创建一个 JSON 编码的响应。它继承了它的超类的大部分行为,但有一些不同:
其默认的 Content-Type 头设置为 application/json。- 第一个参数 data 应该是 dict 实例。如果 safe 参数设置为 False (见下文),它可以是任何 JSON 可序列化的对象。
- encoder,默认为 django.core.serializers.json.DjangoJSONEncoder,将用于序列化数据。。
- safe 布尔参数默认为 True。如果它被设置为 False,任何对象都可以被传递到序列化中(否则只允许 dict 实例)。如果 safe 为 True,而第一个参数是一个非 dict 对象,则会引发一个 TypeError。
- json_dumps_params 参数是一个关键字参数的字典,用来传递给 json.dumps() 调用,用于生成响应。
from django.http import JsonResponse def index(request): data = { "name": "lczmx", "age": 22, } return JsonResponse(data)
跳转页面
跳转页面使用redirect进行页面跳转。
redirect(to, args, permanent=False, *kwargs)
根据传递进来的url参数,返回HttpResponseRedirect。
参数to可以是:
- 一个模型实例:将调用模型的get_absolute_url()函数,反向解析出目的url;
- URL的name名称:可能带有参数:reverse()将用于反向解析url;
- 一个绝对的或相对的URL:将原封不动的作为重定向的目标位置。
默认情况下是临时重定向,如果设置permanent=True将永久重定向。
例子:
# urls.py
from django.urls import path
from .views import index, article
app_name = "app01"
urlpatterns = [
path("index/", index, name="index"),
path("article/<int:article_id>", article, name="article")
]
# views.py
from django.http import HttpResponse
from django.shortcuts import redirect
# Create your views here.
def index(request):
return HttpResponse("index page")
def article(request, article_id):
if article_id > 123:
return redirect("app01:index")
return HttpResponse(f"article的id是{article_id}")
错误信息
django实现了一些状态码以4和5开头的HttpRespone子类,点击查看。
假如没有我们需要的,可以自定义响应类,只需要继承HttpRespone类,然后指定status_code
属性的值即可。
from http import HTTPStatus
from django.http import HttpResponse
class HttpResponseNoContent(HttpResponse):
status_code = HTTPStatus.NO_CONTENT
404
为了方便,django提供了django.http.Http404
异常类,在视图中主动抛出这个异常时,Django 会捕捉到它并且返回标准的错误页面,连同 HTTP 错误代码 404 。
from django.http import Http404
def article(request, article_id):
if article_id > 123:
raise Http404("page not fund")
return HttpResponse(f"article的id是{article_id}")
虽然可以返回404页面,但是这是django内置的404页面,在实际使用中,需要换成我们自己的。
需要注意的是,要使用自定义的404html,需要将settings文件中的DEBUG设为False。
由于当你raise了Http404后:
django会做一下动作:
- django.conf.urls.handler404,这相当于一个urls文件:
# __init__文件的内容
from django.urls import include, re_path
from django.views import defaults
__all__ = ['handler400', 'handler403', 'handler404', 'handler500', 'include', 'url']
handler400 = defaults.bad_request
handler403 = defaults.permission_denied
handler404 = defaults.page_not_found
handler500 = defaults.server_error
# ...
- 使用
django.views.defaults.page_not_found()
视图 - 判断是否自定义了404.html,
- 如果有(查找的是template,和一般的一样),输出该HTML文件
- 如果没有,输出默认的404提示信息
所以我们要自定以404页面有两个方法:
- 使用默认的
django.views.defaults.page_not_found()
视图,然后再自己在template中创建404.html文件。 - 自定义处理404的视图
第一种方法:
-
在template创建一个404页面
注意要配置好settings.TEMPLATES的DIRS或APP_DIRS。 -
使用静态的页面或使用django的模板引擎展示更多详细信息。
简化django的page_not_found视图,发现其主要做了以下内容:template.render(context = { 'request_path': quote(request.path), 'exception': exception.args[0]}, request) # quote为urllib.parse.quote # 作用是对url进行url编码
因此我们可以在404.html中利用模板引擎拿到context。其中:request_path是你当前的url,exception是异常类的信息,比如这样定义404.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>PageNotFound</title> </head> <body> <h1>404页面</h1> <p>不存在的链接:{{ request_path }}</p> <p>提示:{{ exception }}</p> </body> </html>
第二种方法:
步骤:
- 在根urls中,定义handler404,使其的值为自己处理404的视图
- 完成404视图的编写
比如:
# 根urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("app01/", include("app01.urls")),
]
handler404 = "app01.views.page_not_found"
# 看有些博客用的是
# handler404 = views.page_not_found # handler404直接等于一个视图函数
# 但官方的例子是使用字符串的
# views.py
@requires_csrf_token
def page_not_found(request, exception):
# ....
# ....
return render(request, 'errors/404.html')
注意:一定要在根定义handler404,否则会老是用默认的!!!(我用的是django3.0.1,其他版本django不知道)
关于其他诸如400、403等页面,其自定义方法也是这样,不过是把handler的视图
或状态码.html
换成对应的。对于前种方法,官方给了一些的例子:自定义报错视图
HttpRequest对象
关于HttRequest的各种属性见官方文档:HttpRequest。
一些常用的(表中的request指定的HttpRequest对象):
属性 | 说明 |
---|---|
request.scheme | 协议http or https |
request.path | 返回完整路径,形如:"/app/index" |
request.path_info | 路径的路径信息部分 |
request.method | HTTP 方法,大写 |
request.GET | get参数,属于QueryDict对象 |
request.POST | post参数, 属于QueryDict对象 |
request.COOKIES | 一个包含所有 cookies 的字典。键和值是字符串。 |
request.FILES | 一个类似字典的对象,包含所有上传的文件 |
request.headers | 请求中的所有HTTP头,是一个不区分大小写的类似字典的对象 |
取参数
这里的取参数指的是取get或者post的参数,由于request.GET和request.POST对属于QueryDict对象,所以先了解一下django.http.request.QueryDict
对象:
- QueryDict是对http请求数据的封装
- QueryDIct是dict的子类,所以很多使用方法和字典一样
- 一般只需要使用get方法即可(和字典的一样用),其他方法点击这里查看。
所以取get和post参数:
def t(request):
if request.method == "GET":
print(request.GET.get("name"))
print(request.GET.get("age"))
elif request.method == "POST":
print(request.POST.get("name"))
print(request.POST.get("age"))
return HttpResponse("ok!")
注意使用POST方式提交时,要在form表单中加上{% csrf_token %}标签,至于使用ajax提交post请求,见后文大标题
HttpRespone对象
HttpResponse对象则是你想要返回给浏览器的数据的封装。
我们编写的每个视图都要实例化、填充和返回一个HttpResponse对象。也就是函数的return值。
HttpRespone可以传递一个一个string、bytes或者memoryview或可迭代对象。
常用属性
属性 | 说明 |
---|---|
content | 响应的内容。bytes类型。 |
charset | 编码的字符集。 如果没指定,将会从content_type中解析出来。 |
status_code | 响应的状态码,比如200。 |
常用方法
方法 | 说明 |
---|---|
has_header(header) | 检查头部中是否有给定的名称(不区分大小写),返回True或 False。 |
setdefault(header, value) | 当该头字段不存在时,设置一个头部 |
delete_cookie() | 删除一个Cookie,参数见下文 |
更多点击这里查看
设置与删除响应头字段
from django.http import HttpResponse
def index(request):
response = HttpResponse()
# 通过中括号设置值
response["name"] = "lczmx"
response["age"] = 20
response.setdefault("age", 22)
# 使用del删除头字段
# key不存在时,不会报错
del response["name"]
return response
cookie与session
HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。为了给客户端们一个通行证,W3C组织提出了Cookie。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务 器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
由于Cookie存在不安全、大小有限制、受制于同源策略等缺点,所以除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
下面的内容需要注意方法的对象是respone还是request
Cookie
设置Cookie
使用HttpResponse对象的set_cookie方法设置一个 cookie。参数与 Python 标准库中的 Morsel cookie 对象相同:
HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=False, httponly=False, samesite=None)
- key是cookie的键
- value是cookie的值
- max_age是cookie的生存周期
以秒为单位。如果设为None,浏览器开启期间保持,关闭后一同删除。 - expires是到期时间
格式为 "Wdy, DD-Mon-YY HH:MM:SS GMT" 的字符串,或者是 UTC 的 datetime.datetime 对象。
如果 expires 是一个 datetime 对象,将计算 max_age。
如:datetime.datetime.utcnow() + datetime.timedelta(days=7) - domain用于设置跨域的Cookie
例如domain=".lawrence.com"将设置一个www.lawrence.com、blogs.lawrence.com和calendars.lawrence.com都可读的Cookie。 否则,Cookie将只能被设置它的域读取。 - secure是否支持https,为True时支持https
- httponl,为True时阻止客户端的js代码访问cookie
注意cookie不要超过 4096 字节
获取Cookie
使用HttpRequest.COOKIES对象的get方法:
如:
request.Cookie.get("key")
删除Cookie
HttpResponse.delete_cookie(key, path='/', domain=None, samesite=None)
删除给定键的 cookie。如果键不存在,则静默失败。
- key是键
- path和domain应该与set_cookie()中使用的值相同,否则Cookie不会删掉
例子
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.urls import reverse
def auth_login(req_func):
"""
用于检验登录信息的装饰器
:param req_func:
:return:
"""
def wap(*args, **kwargs):
req = args[0]
# 获取cookie
if req.COOKIES.get("username") != "lczmx":
url = reverse("app01:login") + "?next=%s" % req.path
return redirect(url)
return req_func(*args, **kwargs)
return wap
@auth_login
def index(request):
return render(request, "app01/index.html")
def login(request):
next_url = request.GET.get("next")
if request.method == "GET":
return render(request, "app01/login.html", {"next_url": next_url})
elif request.method == "POST":
username = request.POST.get("username")
pwd = request.POST.get("password")
if username == "lczmx" and pwd == "123456": # 假装验证了
ret = redirect(next_url)
# 设置cookie
ret.set_cookie("username", "lczmx")
else:
ret = HttpResponse("验证失败")
return ret
def logout(request):
ret = HttpResponse("success")
# 删除cookie
ret.delete_cookie("username")
return ret
Session
由于django的session依赖中间件:SessionMiddleware,所以要用session的时候不要去掉 MIDDLEWARE中的django.contrib.sessions.middleware.SessionMiddleware
session操作都要使用到HttpRequest.session对象,这是一个可读可写的,类似字典的对象,代表当前会话。
设置Session
request.session["name"] = "lczmx"
获取Session
request.session.get("lczmx")
删除Session
del request.session["lczmx"]
[更多关于session的方法【包括session的保存方式】(https://docs.djangoproject.com/zh-hans/3.2/topics/http/sessions/#using-sessions-in-views)
设置session的保存时间
request.set_expiry(value)
为会话设置过期时间。你可以传递很多不同值:
- 如果 value 是整型,会话将在闲置数秒后过期。比如,调用 request.session.set_expiry(300) 会使得会话在5分钟后过期。
- 如果 value 是一个 datetime 或 timedelta 对象,会话将在指定的 date/time 过期。注意,如果你正在使用 PickleSerializer ,那么 datetime 和 timedelta 的值只能序列化。
- 如果 value 是 0 ,则当浏览器关闭后,用户会话 cookie 将过期。
- 如果 value 是 None ,会话会恢复为全局会话过期策略。
出于过期目的,读取会话不被视为活动。会话过期时间会在会话最后一次修改后开始计算。
django内置的装饰器
部分内容转载于这里:常用装饰器应用场景及正确使用方法
django.views.decorators.http
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
pass
一些类似的装饰器:
require_GET:装饰器可以要求视图只接受 GET 方法。
require_POST:装饰器可以要求视图只接受 POST 方法。
require_safe:装饰器可以要求视图只接收 GET 和 HEAD 方法。这些方法通常被认为是安全的,因为它们除了检索请求的资源外,没有特别的操作。
django.contrib.auth.decorators
权限验证
-
@login_required
@login_required是Django最常用的一个装饰器。其作用是在执行视图函数前先检查用户是否通过登录身份验证,并将未登录的用户重定向到指定的登录url。其中login_url是可选参数。如果不设置,默认login_url是settings.py里设置的LOGIN_URL。from django.contrib.auth.decorators import login_required @login_required(login_url='/accounts/login/') def my_view(request): ...
@login_required还可以一个可选参数是redirect_field_name, 默认值是'next'。
from django.contrib.auth.decorators import login_required @login_required(redirect_field_name='my_redirect_field') def my_view(request): ...
注意:
login_required装饰器不会检查用户是否是is_active状态。如果你想进一步限制登录验证成功的用户对某些视图函数的访问,你需要使用更强大的
@user_passes_test
装饰器。 -
@user_passes_test
@user_passes_test
装饰器的作用是对登录用户对象的信息进行判断,只有通过测试(返回值为True)的用户才能访问视图函数。不符合条件的用户会被跳转到指定的登录url。@user_passes_test
装饰器有一个必选参数,即对用户对象信息进行判断的函数。该函数必需接收user对象为参数、返回一个布尔值。其与login_required类似,@user_passes_test还有两个可选参数(login_url和redirect_field_name),这里就不多讲了。user_passes_test(func[,login_url=None, redirect_field_name=REDIRECT_FIELD_NAME])
下例中
@user_passes_test
装饰器对用户的email地址结尾进行判断,会把未通过测试的用户会定向到登录url。试想一个匿名用户来访问,她没有email地址,显然不能通过测试,登录后再来吧。from django.contrib.auth.decorators import user_passes_test def email_check(user): return user.email.endswith('@example.com') @user_passes_test(email_check) def my_view(request): ...
如果需要加可选参数,可以按如下方式使用。
@user_passes_test(email_check, login_url='/login/'): def my_view(request): ...
注意:
@user_passes_test
不会自动的检查用户是否是匿名用户,但是@user_passes_test
装饰器还是可以起到两层校验的作用。一来检查用户是否登录,二来检查用户是否符合某些条件,无需重复使用@login_required
装饰器。我们如果只允许
is_active
的登录用户访问某些视图,我们现在可以使用@user_passes_test装饰器轻松地解决这个问题,如下所示:from django.contrib.auth.decorators import user_passes_test @user_passes_test(lambda u: u.is_active) def my_view(request): ...
-
@permission_required
@permission_required
装饰器的作用是检查用户用户是否有特定权限,第一个参数perm是权限名,为必选, 第二个参数login_url为可选。permission_required(perm[, login_url=None, raise_exception=False])
下例检查用户是否有polls.can_vote的权限,没有的话定向至login_url。如果你设置了raise_exception=True, 会直接返回403无权限的错误,而不会跳转到登录页面。那么问题来了,我们需要先使用@login_required来验证用户是否登录,再使用
@permission_required
装饰器来查看登录用户是否具有相关权限吗? 答案是不需要。如果一个匿名用户来访问这个视图,显然该用户没有相关权限,会自动定向至登录页面。from django.contrib.auth.decorators import permission_required @permission_required('polls.can_vote', login_url='/login/') def my_view(request): ...
django.views.decorators.cache
这个模块下的装饰器适用于缓存的,缓存是Django装饰器很重要的一个应用场景。下面我们来看几个主要的缓存装饰器。注意: 使用以下装饰器的前提是你已经对缓存进行了相关设置。
-
@cache_page
该装饰器可以接收缓存的时间作为参数,比如下例缓存页面15分钟。
from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ...
-
@cache_control
通常用户将会面对两种缓存: 他或她自己的浏览器缓存(私有缓存)以及他或她的提供者缓存(公共缓存)。 公共缓存由多个用户使用,而受其它人的控制。 这就产生了你不想遇到的敏感数据的问题,比如说你的银行账号被存储在公众缓存中。 因此,Web 应用程序需要以某种方式告诉缓存那些数据是私有的,哪些是公共的。
cache_control
装饰器可以解决这个问题。from django.views.decorators.cache import cache_control @cache_control(private=True) def my_view(request): ...
该修饰器负责在后台发送相应的 HTTP 头部。还有一些其他方法可以控制缓存参数。 例如, HTTP 允许应用程序执行如下操作:
-
定义页面可以被缓存的最大时间。
-
指定某个缓存是否总是检查较新版本,仅当无更新时才传递所缓存内容。
在 Django 中,可使用
cache_control
视图修饰器指定这些缓存参数。 在下例中,cache_control
告诉缓存对每次访问都重新验证缓存并在最长 3600 秒内保存所缓存版本。from django.views.decorators.cache import cache_control @cache_control(must_revalidate=True, max_age=3600) def my_view(request): ...
在
cache_control
中,任何合法的Cache-Control HTTP 指令都是有效的。下面是完整列表:public=True private=True no_cache=True no_transform=True must_revalidate=True proxy_revalidate=True max_age=num_seconds s_maxage=num_seconds
-
-
@vary_on_headers
缺省情况下,Django 的缓存系统使用所请求的路径(如blog/article/1)来创建其缓存键。这意味着不同用户请求同样路径都会得到同样的缓存版本,不考虑客户端user-agent, cookie和语言配置的不同, 除非你使用Vary头部通知缓存机制需要考虑请求头里的cookie和语言的不同。
要在 Django 完成这项工作,可使用便利的vary_on_headers
视图装饰器。例如下面代码告诉Django读取缓存数据时需要同时考虑User-Agent和Cookie的不同。与此类似的装饰器还有@vary_on_cookie
。from django.views.decorators.vary import vary_on_headers @vary_on_headers('User-Agent', 'Cookie') def my_view(request): ...
-
@never_cache
如果你想用头部完全禁掉缓存, 你可以使用
@never_cache
装饰器。如果你不在视图中使用缓存,服务器端是肯定不会缓存的,然而用户的客户端如浏览器还是会缓存一些数据,这时你可以使用never_cache
禁用掉客户端的缓存。from django.views.decorators.cache import never_cache @never_cache def myview(request): # ...
其它常用装饰器
@method_decorator
前面的案例中,我们的装饰器都是直接使用在函数视图上的。如果需要在基于类的视图上使用装饰器,我们需要使用到@method_decorator
这个装饰器, 它的作用是将类伪装成函数方法。@method_decorator
第一个参数一般是需要使用的装饰器名。
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
常用自定义装饰器
关于一些自定义装饰器,见:详解Django中六个常用的自定义装饰器
使用多重装饰器
你可以在一个函数或基于类的视图上使用多重装饰器,但一定要考虑装饰器执行的先后顺序。比如下例中会先执行@never_cache
, 再执行@login_required
。
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
@method_decorator(never_cache, name='dispatch')
@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
class ProtectedView(TemplateView):
template_name = 'secret.html'
上例等同于:
decorators = [never_cache, login_required]
@method_decorator(decorators, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
上传及下载文件
见:https://docs.djangoproject.com/zh-hans/3.2/topics/http/file-uploads/
FBV及CBV
FBV是指以函数的形式定义视图,而CBV是指以类的形式定义视图。
二者各有优点,FBV胜在灵活、简便,而CBV在某些场景中可以极大地降低代码复用。
要使用类视图,主要有一下几个要点:
- views.py中自定义一个继承
django.views.View
的类 - 为自定义类添加请求方法的方法,即get请求就添加get方法
- 在urls.py中,指定path的第二个参数为类名.as_view()
# views.py
from django.views import View
class CView(View):
def get(self, request):
return render(request, "app01/index.html")
# urls.py
from .views import CView
app_name = "app01"
urlpatterns = [
path("cview/", CView.as_view(), name="cview"),
]
除了django.views.View
外,django还有一些内置的类视图,点击查看:内置基于类的视图 API。