第03章 视图层
视图基础
本章要求
- urlconfs
- 视图函数
- 快捷
- 装饰器
视图
视图一般都写在app的view.py中。并且视图的第一个参数永远都是request(一个HttpRequest对象)对象。这个对象存储了请求过来的所有信息,包括携带的参数刀诼一些头部贪睡等。在视图中,一般是完成逻辑相关操作。比如这个请求是添加一篇博客,那么可以通过request来接收这些数据,然后存储到数据库中,最后再把执行的结果返回给浏览器(体现在python中,这些代码就是python的函数)。视图函数的反回结果必须是HttpResponse对象或者子类的对象。
示例代码:
from django.http import HttpResponsedef 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_nameapp_name='user'urlpatterns = [path('admin/', admins.site.urls),path('login/', views.login, name='login'),]#重定向或者反转时reverse("user:login")
实例命名空间
实例开发中,可能会这样设计,同一个app下有多个实例。可以使用多个url映射同一个app。所以这就会产生一个问题。以后在做反转时,如果使用应用命名字音,那么就会发生混乱。为了避免这个瓿。我们可以使用实例命名空间
# urls.pyurlpatterns = [path('admin/', admins.site.urls),# 同一个app下有两个实例,path('cms1/', include('cms.urls')),path('cms2/', include('cms.urls')),]# views.pydef index(reqeust):username= request.GET.get('username')if username:return HttpResponse('cms首页')else:# 访问同一个app时,url会在两个实例间跳转return redirect(reverse("cms:login")
解决方式:
使用include()的参数namespace参数来指定实例命名空间
# urls.pyurlpatterns = [path('admin/', admins.site.urls),# 同一个app下有两个实例,访问同一个app时,url会在两个实例间跳转path('cms1/', include('cms.urls', namespace='cms1')),path('cms2/', include('cms.urls', namespace='cms2')),]# cms/views.pydef index(reqeust):username= request.GET.get('username')if username:return HttpResponse('cms首页')else:# 访问同一个app时,url会在两个实例间跳转current_namespace = request.resolver_match.namespacereturn 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函数的映射。```pythonfrom django.urls import pathfrom . import viewsurlpatterns = [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, redirectdef 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 headersUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36X-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/htmltext/plaintext/csstext/javascriptmultipart/form-data:文件提交‘application/json:json传输application/xml
- 设置请求头:
response['X-Access-Token']='xxxxx'
常用方法:
set_cookiedelete_cookiewrite:HttpResponse是一个类似于文件的对象,可以用来写入数据到数据体中
示例代码
from django.http import HttpResponse""" baidu Response HeadersBdpagetype: 2Bdqid: 0xacb0ff4b00074fd9Cache-Control: privateContent-Encoding: gzipContent-Type: text/html;charset=utf-8Date: Tue, 01 May 2018 04:52:42 GMTExpires: Tue, 01 May 2018 04:52:42 GMTServer: BWS/1.1X-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 Viewclas 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 jsonfrom django.http import HttpResponse, JsonResponse# 传统方式 - 返回 jsondef 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 dictdef 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 falsedef 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 HttpResponsefrom django.template import loaderimport csv# 方式1 -手工定义csvdef 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) # 使用模板渲染csvreturn 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, StreamingHttpResponseimport csvdef 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 responsedef 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 Templateviewclass 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=Articletemplate_name='article_list.html'paginate_by=10context_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 contextdef get_queryset(self):return Article.objects.filter(id_lte=89)
