第03章 视图层
视图基础
本章要求
- urlconfs
- 视图函数
- 快捷
- 装饰器
视图
视图一般都写在app的view.py
中。并且视图的第一个参数永远都是request(一个HttpRequest对象)对象
。这个对象存储了请求过来的所有信息,包括携带的参数刀诼一些头部贪睡等。在视图中,一般是完成逻辑相关操作。比如这个请求是添加一篇博客,那么可以通过request来接收这些数据,然后存储到数据库中,最后再把执行的结果返回给浏览器(体现在python中,这些代码就是python的函数)。视图函数的反回结果必须是HttpResponse
对象或者子类的对象。
示例代码:
from django.http import HttpResponse
def book_list(request):
return HttpResponse("书籍列表")
urls模块化
如果项目变得越来越大时,url会以变得越来越多。这里应该使用模块化设计,把每个app自己的相关url要放到自己的app中管理。一般我们会在app中新建一个urls.py文件来存储所有和这个app相关的子url
应该使用include
函数包含子urls.py
,并且这个urls.py
的路径使用相对于项目的路径。
urlpatterns = [
path('admin/', admins.site.urls),
path('book', include('book.urls')),
]
在app/urls.py
中,所有的url匹配也要放在urlpatterns
变量中,否则找不到。
url命名
为什么需要url命名
罚历url是经常变化的,为了解决在视图或者模板中写死url的问题,需要使用url命名
来实现url重定向
或者url反转
来找到url。
如何给一个url指定名称,在path
函数中使用参数name
指定url的名字
urlpatterns = [
path('admin/', admins.site.urls),
path('login/', views.login, name='login'),
]
应用命名空间和实例命名空间
应用命名空间
多个应用app使用了相同的url命名
时,会引起混乱,这里需要给每个应用app也起个名字。
通常,应用程序名称空间应该由包含的模块指定。如果设置了应用程序名称空间,则namespace
可以使用该参数来设置不同的实例名称空间。
# book
# 应用命名空间的变量叫做 app_name
app_name='user'
urlpatterns = [
path('admin/', admins.site.urls),
path('login/', views.login, name='login'),
]
#重定向或者反转时
reverse("user:login")
实例命名空间
实例开发中,可能会这样设计,同一个app下有多个实例。可以使用多个url映射同一个app。所以这就会产生一个问题。以后在做反转时,如果使用应用命名字音,那么就会发生混乱。为了避免这个瓿。我们可以使用实例命名空间
# urls.py
urlpatterns = [
path('admin/', admins.site.urls),
# 同一个app下有两个实例,
path('cms1/', include('cms.urls')),
path('cms2/', include('cms.urls')),
]
# views.py
def index(reqeust):
username= request.GET.get('username')
if username:
return HttpResponse('cms首页')
else:
# 访问同一个app时,url会在两个实例间跳转
return redirect(reverse("cms:login")
解决方式:
使用include()
的参数namespace
参数来指定实例命名空间
# urls.py
urlpatterns = [
path('admin/', admins.site.urls),
# 同一个app下有两个实例,访问同一个app时,url会在两个实例间跳转
path('cms1/', include('cms.urls', namespace='cms1')),
path('cms2/', include('cms.urls', namespace='cms2')),
]
# cms/views.py
def index(reqeust):
username= request.GET.get('username')
if username:
return HttpResponse('cms首页')
else:
# 访问同一个app时,url会在两个实例间跳转
current_namespace = request.resolver_match.namespace
return redirect(reverse("%s:login" % current_namespace)
注意:如果没有指定应用名字空间
app_name
,那么使用实例命名空间会报错
urlpatterns = [
path(‘admin/‘, admins.site.urls),
# 没有指定应用命名空间,直接指定的是实例命名空间
path(‘cms1/‘, include(‘cms.urls’, namespace=’cms1’)),
#### include
[https://docs.djangoproject.com/en/2.0/ref/urls/#include](https://docs.djangoproject.com/en/2.0/ref/urls/#include)
[https://docs.djangoproject.com/en/2.0/topics/http/urls/#namespaces-and-include](https://docs.djangoproject.com/en/2.0/topics/http/urls/#namespaces-and-include)
-
`include(module,namespace = None)`
-
`include(pattern_list)`
-
`include((pattern_list,app_namespace),namespace = None)`
一个函数,它将一个完整的Python导入路径带到另一个应该“包含”在这个地方的URLconf模块。(可选)也可以指定条目将包含到的[应用程序名称空间]和[实例名称空间]。
通常,应用程序名称空间应该由包含的模块指定。如果设置了应用程序名称空间,则`namespace`可以使用该参数来设置不同的实例名称空间。
`include()` 也接受作为参数的一个返回URL模式的迭代器或包含这种迭代器的2元组加上应用程序命名空间的名称。参数:
- **module** - URLconf模块(或模块名称)
- **namespace** - 包含URL条目的实例名称空间
- **pattern_list** - 可重用的`path()`和/或`re_path()`实例。
- **app_namespace** - 包含的URL条目的应用程序名称空间
####URL分发器
django允许你自由地设计你的URL,不受框架束缚。你可以使用python模块`URLconf`来设计URL,它是URL与python函数的映射。
```python
from django.urls import path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
Django 如何处理一个请求
当一个用户请求Django的一个页面时,下面是django系统交定执行哪个python代码的算法:
- 检查根URLconf模块(由项目
/settings.py
中的设置值:ROOT_URLCONF
指定) - 检查
ROOT_URLCONF
指定模块中的变量urlpatterns=[]
。内容为django.urls.path()
和/或者django.urls.re_path()
的实例。 - 依次匹配每个URL模式,在与请求的URL匹配的第一个模式停下来。
- 一旦匹配到URL,Django就导入并调用指定的视图,该视图是一个简单的python函数(或者基于类的视图)。该视图通过以下能参数传递:
- 一个
HttpRequest
实例 - 如果匹配的URL模式没有返回任何命名组,则来自正则表达的匹配作为位置参数提供
- 关键字参数由路径表达式匹配的任何命名部分组成,并由可选KWARGS参数
- 一个
- 如果没有URL匹配,或者发生异常,Django将调用适当的错误片是视图。
QueryDict对象
我们平时的request.GET
和request.POST
都是QueryDict
对象,这个对象继承自dict
,因此用法跟dict相差无几。其中胜利比较多的是get()
和getlist()
方法。
get()
:用来获取指定key值,如果没有这个key,那么会返回Nonegetlist()
: 如果浏览器上传上来的key对应的值有多个,那么就需要通过这个方法获取。
限制请求method
限制请求装饰器
django内置的视图装饰器可以给视图提供一些限制。以下介绍一些常用的内置视图装饰器
django.views.decorators.http.require_http_methods
:这个装饰器需要传递一个允许访问的方法列表。比如只能通过GET访问,示例如下
from django.views.decorators.http import require_http_methods
@require_http_method(['GET'])
def my_view(request):
pass
django.views.decorators.http.require_GET
:这个装饰器相当于require_http_methods(['GET'])
的简写。
重定向
- 永久性重定向301
- 临时性重定向302
from django.shortcuts import reverse, redirect
def profile(request):
if request.GET.get("username"):
return HttpResponse("个人中心页面")
else:
return redirect(reverse("user:login"))
WSGIRequest对象
django在接收到http请求后,会根据http请求携带的参数以及报文信息创建一个WSGIRequest
对象,并且作为视图函数第一个参数传给视图函数。也就是我们经常看到的request
参数。在这个对象上我们可以找到客户端上传上来的所有信息。这个对象的完整路径是django.core.handlers.wsgi.WSGIRequest
WSGIRequest对象常用属性和方法:
WSGIRequest对象上大部分的属性都是只读的。因为这些属性是从客户端上传上来的,没有必要做修改。以下将对一些常用的属性进行说明:
path
:请求服务器完整”路径“,但不包含域名和参数method
:当前请求的 http methodGET
: 一个django.http.request.QueryDict
对象。类似dict,包含了所有参数POST
:一个django.http.request.QueryDict
对象。包含post参数FILES
:一个django.http.request.QueryDict
对象。COOKIES
:一个标准的python字段,包含所有的cokkie键值对,都是str类型session
:类似于dict,用来操作sessionMETA
:存储的客户端发送上来的所有header信息CONTENT_LENGTH
:请求的正文的长度(是一个字符串)。CONTENT_TYPE
:请求的正文的MIME类型。HTTP_ACCEPT
:响应可接收的Content-Type。HTTP_ACCEPT_ENCODING
:响应可接收的编码。HTTP_ACCEPT_LANGUAGE
: 响应可接收的语言。HTTP_HOST
:客户端发送的HOST值。HTTP_REFERER
:在访问这个页面上一个页面的url。QUERY_STRING
:单个字符串形式的查询字符串(未解析过的形式)。REMOTE_ADDR
:客户端的IP地址。REMOTE_HOST
:客户端的主机名。REQUEST_METHOD
:请求方法。一个字符串类似于GET
或者POST
。SERVER_NAME
:服务器域名。SERVER_PORT
:服务器端口号,是一个字符串类型。
WSGIRequest对象常用方法:
is_secure()
:是否是采用https
协议。is_ajax()
:是否采用ajax
发送的请求。原理就是判断请求头中是否存在XMLHttpRequest
。get_host()
:服务器的域名。如果在访问的时候还有端口号,那么会加上端口号。比如www.baidu.com:9000
。get_full_path()
:返回完整的path。如果有查询字符串,还会加上查询字符串。比如/music/bands/?print=True
。
示例代码:
""" baidu request headers
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
X-DevTools-Emulate-Network-Conditions-Client-Id: E7FB183C37ED2035C4C94AD4A29CFF72
"""
QueryDit对象
request.GET.get()
使用
username = request.GET['username']
username = reqeust.GET.get('username')
username = request.GET.get('username', default='guest')
page = reqeust.GET.get('page', default=1)
request.GET.getlist()
使用
tags = request.POST.getlist('tags')
HttpResponse对象
django服务器接收到客户端发送过来的请求后,会将提交上来的这些数据封装为一个 HttpRequest
对象传给视图函数。那些视图函数在片完相关的逻辑后,也需要返回一个响应给浏览器。而这个响应,我们必须返回HttpResponseBase
或者他的子类的对象。
而HttpResponse
则是HttpResponseBase用得最多的子类。下面介绍
常用属性:
content
:返回的内容status_code
:返回的状态码content_type
:返回内容的MIME类型,默认为text/html
。常用的如下:text/html
text/plain
text/css
text/javascript
multipart/form-data
:文件提交‘application/json
:json传输application/xml
- 设置请求头:
response['X-Access-Token']='xxxxx'
常用方法:
set_cookie
delete_cookie
write
:HttpResponse
是一个类似于文件的对象,可以用来写入数据到数据体中
示例代码
from django.http import HttpResponse
""" baidu Response Headers
Bdpagetype: 2
Bdqid: 0xacb0ff4b00074fd9
Cache-Control: private
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Tue, 01 May 2018 04:52:42 GMT
Expires: Tue, 01 May 2018 04:52:42 GMT
Server: BWS/1.1
X-Ua-Compatible: IE=Edge,chrome=1
"""
def index(request):
# response = HttpResponse()
# response.content = '首页'
response = HttpResponse('首页', content_type='text/plain;charset=utf-8') # 效果同上
# response.status_code = 200
# response['PASSWORD'] = '88888888'
return response
类视图
在写视图的时候,django除了使用函数作为视图,也可以使用类作为视图。使用类视图可以使用类的一些特性,比如继承等。
View
django.views.generic.base.View
是主要的类视图,所有的类视图都是继承自他。如果我们写自己的类视图,也可以继承自他。然后再根据当前请求的method,来实现不同的方法。
使用类视图一般分两步:
- 定义类视图
- 映射类视图
下面举例定义一个视图只能使用get的方式来请求:
from django.views import View
# 定义类视图
clas BookDetailView(View):
def get(sekf, request, *args, **kwargs):
return rencer(request, 'detail.html')
定义好类视图后,还应该在urls.py
中进行映射,映射的时候就需要调用View
的类方法as_view()
来进行转换。
urlpatterns = [
path("detail/<book_id>/", views.BookDetailView.as_view(), name='detail'),
]
除了get
方法,View
还支持以下方法['get', 'post', 'put', 'path', 'delete', 'head', 'options', 'trace']
。
如果用户访问了View
没有定义的方法。比如你的类视图只支持get
方法,而出现了post
方法,那么就会把这个请求转发给http_method_not_allowed(request, *args, **kwargs)
from django.views import View
clas BookDetailView(View):
def get(sekf, request, *args, **kwargs):
return HttpResponse("书籍详情页面")
def http_method_not_allow(self,request, *args, **kwargs):
return HttpResponse("您当前采用的method是:%s,本视图只支持使用post请求!" % request.method)
其实不管是get
还是post
请求,都会走dispatch(request, *args, **kwargs)
方法,所以如果实现了这个方法,将能够对所有请求都处理到。
JsonResponse类
用来对象dump成json字符串,然后返回将json字符串封装成Response对象返回给浏览器。并且他的Content-Type
是application/json
。示例:
import json
from django.http import HttpResponse, JsonResponse
# 传统方式 - 返回 json
def josnresponse_view(request):
person = {
'username': 'wangdachui',
'age': 18,
'height': 150,
}
person_str = json.dumps(person)
response = HttpResponse(person_str, content_type='application/json')
return response
# django 封装方式 - 默认内容为 python dict
def josnresponse_view(request):
person = {
'username': 'wangdachui',
'age': 18,
'height': 150,
}
response = JsonResponse(person) # 封装了
return response
# django 封装方式 - 指定参数,传递非dict内容
# in order to allow non-dict objects to be serialized set the safe parameter to false
def josnresponse_view(request):
person = [
{
'username': 'wangdachui',
'age': 18,
'height': 150,
},
{
'username': 'xiaoming',
'age': 22,
'height': 120,
},
]
response = JsonResponse(person, safe=False) # 传递非dict内容
return response
csv文件
生成csv文件
from django.http import HttpResponse
from django.template import loader
import csv
# 方式1 -手工定义csv
def index(request):
response = HttpResponse(content_type="text/csv")
response['Content-Disposition'] = "attachment;filename='person.csv'"
# with open('temp.csv', 'w') as fp:
# csv.writer(fp)
writer = csv.writer(response)
writer.writerow(['username', 'age'])
writer.wirterow(['wangdachui', 18])
reture response
# 方式2 - 使用template生成
def template_csv_view(request):
response = HttpResponse(content_type="text/csv")
response['Content-Disposition'] = "attachment;filename='person.csv'"
context = { # 定义 csv 内容
'rows':[
['username', 'age'],
['wangdachui', 18],
]
}
template = loader.get_template('template_test.txt') # 定义 csv 模板
response.content = template.render(context) # 使用模板渲染csv
return response
# vim template_test.txt
{% for row in rows %}
{{ row.0 }}, {{ row.1 }}
{% endfor %}
关于StreamingHttpResponse
这个类是专门用来处理流数据的。使用在处理一些大型文件的时候,不会因为服务器处理时间垞而到时连接超时。这个类还是继承自HttpResponse,并且中HttpResponse对比有以下几点区别:
- 这个类没有属性
content
,相反是streaming_content
- 这个类的
streaming_content必须是一个可以迭代的对象。
- 这个类没有
write()
方法,如果给这个类的对象写入数据将会报错。
注意:StreamingHttpResponse
会启动一个进程来和客户央保持长连接,所以会很消耗资源。所以如果不是
特殊要求,尽量少用这种方法。
from django.http import HttpResponse, StreamingHttpResponse
import csv
def large_csv_view(request):
response = StreamingHttpResponse(content_type='text/csv')
response['Content-Disposition'] = "attachment;filename='large.csv'"
response.streaming_content = ('useranme, age\n', 'xiongda, 18\n') # 要求是可迭代对象
return response
def large_csv_view(request):
response = StreamingHttpResponse(content_type='text/csv')
response['Content-Disposition'] = "attachment;filename='large.csv'"
rows = ( "Row {}, {}\n".format(row, row) for row in range(0, 65535)
response.streaming_content = rows # 要求是可迭代对象
return response
TemplateView
功能:django.views.generic.base.TemplateView
,这个类视图是专门用来返回模版的。
在这个类中,有两个属性是经常需要用到的,
template-name
,这个属性是用来存储模版的路径,Templateview会自动的渲染这个变量指向的模版。
get_context_data
,这个方法是用来返回上下文数据的,也就是在给模版传的参数的。
示例代码如下:
from django.views.generic.base import Templateview
class HomePageView(TemplateView):
template_name="home.html"
def get_context_data(self,**kwargs):
context=super().get_context_data(**kwargs)
context['username']="小米"
return context
ListView
功能:在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。比如文章列表。在django中可以使用ListView
来帮我们快速实现。
class ArticleListView(Listview):
model=Article
template_name='article_list.html'
paginate_by=10
context_object_name='articles'
ordering='create_time'
page_kwarg='page'
def get_context_data(self, **kwargs):
context=super(ArticlelistView, self).get_context_data(**kwargs)
print(context)
return context
def get_queryset(self):
return Article.objects.filter(id_lte=89)