视图层view layer
转载:刘江的django教程
环境:Django2.0 + Python3.6 +Win 7 + Pycharm
视图层是真正的后端,是我们的主营业务
视图层主要包括以下几个方面:
- urlconf(URL路由)
- 视图函数
- 快捷方式
- 装饰器
- 请求与响应RequestAndResponse
- 类视图
- 文件上传
- csv和pdf生成
- 内置中间件
URL是Web服务的入口,用户通过浏览器发送过来的任何请求,都是发送到一个指定的url地址,然后被响应。
在Django项目中编写路由,就是向外暴露我们接受哪些url请求,除此之外的任何url都不被处理,也没有返回。
Django奉行DRY主义,提倡使用简洁优雅的url,让你随心所欲设计你的url,不受框架的束缚。我感觉这有点跟python的性格有点像,追求的是随心所欲,简洁优雅。
一:urls.py
url路由在django项目中的体现就是urls.py文件了,这个文件可以有很多个,但是绝对不能在同一目录下,实际提倡的是在根目录文件夹下有根urls.py文件,各个app下分别有自己的一个urls.py。这样做的好处是可以解耦合,让url路径变的很清晰。
当我们第一次打开根路径下项目文件夹中的urls.py文件时,显示的应该是
from django.contrib import admin
from django.urls import path
urlpatterns = [
path(r'^admin/', admin.site.urls), ]
我第一次看到的时候就有一个疑问,咦?在写路径的时候不的以url开头吗,怎么变成了path,其实这都无所谓,只是版本的更迭导致,不过它向老版本兼容,所以也就不用担心了。如果你跟我一样有强迫症,那好吧,把path换成url,并且导入from django.urls import urls
django如何处理请求:
当用户请求一个页面时,django根据下面的逻辑执行操作
1、决定要使用的根urlconf模块。通常,这是在settings.py 中设置的ROOT_URLCONF的值,但是如果传入的HttpRequest对象具有urlconf属性,则其值将被用于替代ROOT_CONF设置,也就是说,你可以自定义项目的入口url是哪个文件夹,只需要修改root_conf 的值就可以了。
2、加载该模块并寻找可用的urlpatterns,它是django.conf.urls.url()实例的一个列表。
3、以此匹配每个url模式,在与请求的url想匹配的第一个模式停下来。也就是说,url匹配是从上往下的短路操作,所以url在列表中的位置非常关键。
tip:什么是短路操作,就是说请求从你编写的urlpatterns中从上往下一个一个匹配,如果第一个url就匹配成功了,那么就直接进去views层,而不再往下继续匹配。
4、导入并调用匹配行中给定的视图,该视图是一个简单的python函数,也称为视图,视图将获得如下参数:
1:一个HttpRequest实例对象
2:如果匹配的正则表达式返回了没有命名的组,那么正则表达式匹配的内容将作为位置参数提供给视图
3:关键字参数由正则表达式匹配的命名组组成,但是可以被django.conf.urls.url()的可选参数kwargs覆盖
5、如果没有匹配到正则表达式,或者过程中抛出异常,将会调用一个适当的错误处理视图比如404,400,403
举个例子: |
|
urlpatterns中的每条正则表达式在第一次访问时被自动编译,因此匹配速度非常快,
注意:
- 跟正则表达式取值一样,若要从url中捕获一个值,只需要在它周围放置一对圆括号
- 不需要添加前导的反斜杠
- 每个正则表达式前面的‘r’是可选的,但是建议是加上,它告诉python这个字符串是原始的,字符串中任何字符都不应该转义
根据上面的urlconf我们来搞点事情:
/articles/2005/03/
将匹配列表中的第三个模式。Django将调用函数views.month_archive(request, '2005', '03')
。/articles/2005/3/
不匹配任何URL模式,因为列表中的第三个模式要求月份是两个数字。/articles/2003/
将匹配列表中的第一个模式不是第二个,因为模式按顺序从上往下匹配,第一个会首先被匹配。Django会调用函数views.special_case_2003(request)
/articles/2003
不匹配任何一个模式,因为每个模式都要求URL以一个斜杠结尾。/articles/2003/03/03/
将匹配最后一个模式。Django将调用函数views.article_detail(request, '2003', '03', '03')
。
命名组:
命名组是一个很好用的东西!很多时候,我们需要获取url中的一段片段来作为参数,传递给处理请求的视图。
上面的示例使用简单的,没有命名的正则表达式组(通过圆括号获取值),并以位置参数的形式传递给视图。
很多时候我们会使用命名的正则表达式组来捕获url中的值,并以关键字参数传递给视图
在python的正则表达式中,命名组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。
将上面的urlconf使用命名组重写:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。 像这样:
/articles/2005/03/
请求将调用views.month_archive(request, year='2005', month='03')
函数,而不是views.month_archive(request, '2005', '03')
。/articles/2003/03/03/
请求将调用函数views.article_detail(request, year='2003', month='03', day='03')
。
urlconf 匹配请求中的哪些部分呢?
请求的url被看做是一个普通的python字符串,urlconf在其中查找并匹配,进行匹配时将不包括GET或者POST请求方式的参数以及域名
例如,在https://www.example.com/myapp/的请求中,URLconf将查找myapp/。
在https://www.example.com/myapp/?page=3的请求中,URLconf也将查找myapp/。
URLconf不检查使用何种HTTP请求方法,所有请求方法POST、GET、HEAD等都将路由到同一个URL的同一个视图。在视图中,才根据具体请求方法的不同,进行不同的处理。
url中捕获的参数为字符串类型
每个捕获的参数都作为一个普通的python字符串传递给视图,即便被捕获的是例如‘100’之类的数字,但实际上是个字符串‘100’,例如:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
传递给views.year_archive()
的year参数将是一个字符串,不是整数,即使[0-9]{4}
只匹配整数字符串。
指定视图参数的默认值
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
...
在上面的例子中,两个URL模式指向同一个视图views.page
。但是第一个模式不会从URL中捕获任何值。 如果第一个模式匹配,page()函数将使用num参数的默认值"1"。 如果第二个模式匹配,page()将使用捕获的num值。
自定义错误页面
当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。它们分别位于:
- handler400 —— django.conf.urls.handler400。
- handler403 —— django.conf.urls.handler403。
- handler404 —— django.conf.urls.handler404。
- handler500 —— django.conf.urls.handler500。
这些值可以在根URLconf中设置。在其它app中的二级URLconf中设置这些变量无效。
Django有内置的HTML模版,用于返回错误页面给用户,但是这些403,404页面实在丑陋,通常我们都自定义错误页面。
首先,在根URLconf中额外增加下面的条目:
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error
然后在,views.py文件中增加四个处理视图:
def page_not_found(request):
return render(request, '404.html')
def page_error(request):
return render(request, '500.html')
def permission_denied(request):
return render(request, '403.html')
def bad_request(request):
return render(request, '400.html')
再根据自己的需求,创建404.html、400.html等四个页面文件,就可以了。
上面我们讲完了根目录下的urls.py,接下来讲一下路由转发:
二:路由转发
通常,我们会在每个app里,各自创建一个urls.py路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应app的urls.py模块中,
from django.conf.urls import include, url
from
urlpatterns = [
# ... 忽略 ...
url(r'^captcha/', include('captcha.urls'))
# ... 忽略 ...
]
路由转发使用的是include()方法,需要提前导入,它的参数是转发目的地路径的字符串,路径以圆点分割。
注意,这个例子中的正则表达式没有包含$(字符串结束匹配符),但是包含一个末尾的斜杠。 每当Django 遇到include()(来自django.conf.urls.include())时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。
再看下面的URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/permissions/$', views.permissions),
]
上面的路由写得不好,我们可以改进它,只需要声明共同的路径前缀一次,并将后面的部分分组转发:
from django.conf.urls import include, url
from . import views
urlpatterns = [
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/', include([
url(r'^history/$', views.history),
url(r'^edit/$', views.edit),
url(r'^discuss/$', views.discuss),
url(r'^permissions/$', views.permissions),
])),
]
捕获参数:
被转发的URLconf会收到来自父URLconf捕获的所有参数,看下面的例子:
# In settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.blog.index),
url(r'^archive/$', views.blog.archive),
]
在上面的例子中,捕获的"username"变量将被传递给include()指向的URLconf,再进一步传递给对应的视图。
嵌套参数
正则表达式允许嵌套参数,Django将解析他们并传递给视图,当反查时,Django将尝试填满所有外围捕获的参数,并忽略嵌套捕获的参数,考虑下面的url模式,它带有一个可选的page参数:
from django.conf.urls import url
urlpatterns = [
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
]
blog_articles视图需要最外层捕获的参数来反查,在这个例子中comments或者没有参数,而page-2/可以不带参数或者用一个page_number值来反查
pass: 没看懂什么是嵌套参数
向视图传递额外的参数
URLconfs具有一个钩子(hook),允许你传递一个Python字典作为额外的关键字参数给视图函数。
像这样:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
在上面的例子中,对于/blog/2005/
请求,Django将调用views.year_archive(request, year='2005', foo='bar')
。理论上,你可以在这个字典里传递任何你想要的传递的东西。但是要注意,URL模式捕获的命名关键字参数和在字典中传递的额外参数有可能具有相同的名称,这会发生冲突,要避免。
传递额外的参数给include()
类似上面,也可以传递额外的参数给include()。参数会传递给include指向的urlconf中的每一行。
例如,下面两种URLconf配置方式在功能上完全相同:
配置一:
# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('inner'), {'blogid': 3}),
]
# inner.py
from django.conf.urls import url
from mysite import views
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]
配置二:
# main.py
from django.conf.urls import include, url
from mysite import views
urlpatterns = [
url(r'^blog/', include('inner')),
]
# inner.py
from django.conf.urls import url
urlpatterns = [
url(r'^archive/$', views.archive, {'blogid': 3}),
url(r'^about/$', views.about, {'blogid': 3}),
]
注意,只有当你确定被include的URLconf中的每个视图都接收你传递给它们的额外的参数时才有意义,否则其中一个以上视图不接收该参数都将导致错误异常。
三:URL反向解析和命名空间
反向解析url
在实际django项目中, 经常需要获取某条URL,为生成的内容配置URL链接。
比如,我要在页面上展示一列文章列表,每个条目都是个超级链接,点击就进入该文章的详细页面。
现在我们的urlconf是这么配置的:^post/(?P<id>\d+)
。
在前端中,这就需要为HTML的<a>
标签的href属性提供一个诸如http://www.xxx.com/post/3
的值。其中的域名部分,Django会帮你自动添加无须关心,我们关注的是post/3
。
此时,一定不能硬编码URL为post/3
,那样费时、不可伸缩,而且容易出错。试想,如果哪天,因为某种原因,需要将urlconf中的正则改成^entry/(?P<id>\d+)
,为了让链接正常工作,必须修改对应的herf属性值,于是你去项目里将所有的post/3
都改成entry/3
吗?显然这是不行的!
我们需要一种安全、可靠、自适应的机制,当修改URLconf中的代码后,无需在项目源码中大范围搜索、替换失效的硬编码URL。
为了解决这个问题,Django提供了一种解决方案,只需在URL中提供一个name参数,并赋值一个你自定义的、好记的、直观的字符串。
通过这个name参数,可以反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。
在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:
-
在模板语言中:使用
url
模板标签。(也就是写前端网页时) -
在Python代码中:使用
reverse()
函数。(也就是写视图函数等情况时) -
在更高层的与处理Django模型实例相关的代码中:使用
get_absolute_url()
方法。(也就是在模型model中)
Django内置的快捷方法
Django在django.shortcuts
模块中,为我们提供了很多快捷方便的类和方法,它们都很重要,使用频率很高。
1. render()
render(request, template_name, context=None, content_type=None, status=None, using=None)[source]
结合一个给定的模板和一个给定的上下文字典,返回一个渲染后的HttpResponse对象。
必需参数:
- request:视图函数处理的当前请求,封装了请求头的所有数据,其实就是视图参数request。
- template_name:要使用的模板的完整名称或者模板名称的列表。如果是一个列表,将使用其中能够查找到的第一个模板。
可选参数:
- context:添加到模板上下文的一个数据字典。默认是一个空字典。可以将认可需要提供给模板的数据以字典的格式添加进去。这里有个小技巧,使用Python内置的locals()方法,可以方便的将函数作用于内的所有变量一次性添加。
- content_type:用于生成的文档的MIME类型。 默认为
DEFAULT_CONTENT_TYPE
设置的值。 - status:响应的状态代码。 默认为200。
- using:用于加载模板使用的模板引擎的NAME。
redirect()
redirect(to, permanent=False, args, *kwargs)[source]
根据传递进来的url参数,返回HttpResponseRedirect。
参数to可以是:
- 一个模型:将调用模型的
get_absolute_url()
函数,反向解析出目的url; - 视图名称:可能带有参数:reverse()将用于反向解析url;
- 一个绝对的或相对的URL:将原封不动的作为重定向的目标位置。
默认情况下是临时重定向,如果设置permanent=True
将永久重定向。
范例:
1.调用对象的get_absolute_url()
方法来重定向URL:
from django.shortcuts import redirect
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object)
2.传递视图名,使用reverse()方法反向解析url:
def my_view(request):
...
return redirect('some-view-name', foo='bar')
- 重定向到硬编码的URL:
def my_view(request):
...
return redirect('/some/url/')
- 重定向到一个完整的URL:
def my_view(request):
...
return redirect('https://example.com/')
所有上述形式都接受permanent参数;如果设置为True,将返回永久重定向:
def my_view(request):
...
object = MyModel.objects.get(...)
return redirect(object, permanent=True)
4. get_object_or_404()
get_object_or_404(klass, args, *kwargs)[source]
这个方法,非常有用,请一定熟记。常用于查询某个对象,找到了则进行下一步处理,如果未找到则给用户返回404页面。
在后台,Django其实是调用了模型管理器的get()方法,只会返回一个对象。不同的是,如果get()发生异常,会引发Http404异常,从而返回404页面,而不是模型的DoesNotExist异常。
必需参数:
- klass:要获取的对象的Model类名或者Queryset等;
**kwargs
:查询的参数,格式应该可以被get()接受。
范例:
1.从MyModel中使用主键1来获取对象:
from django.shortcuts import get_object_or_404
def my_view(request):
my_object = get_object_or_404(MyModel, pk=1)
这个示例等同于:
from django.http import Http404
def my_view(request):
try:
my_object = MyModel.objects.get(pk=1)
except MyModel.DoesNotExist:
raise Http404("No MyModel matches the given query.")
2.除了传递Model名称,还可以传递一个QuerySet实例:
queryset = Book.objects.filter(title__startswith='M')
get_object_or_404(queryset, pk=1)
上面的示例不够简洁,因为它等同于:
get_object_or_404(Book, title__startswith='M', pk=1)
但是如果你的queryset来自其它地方,它就会很有用了。
3.还可以使用Manager。 如果你自定义了管理器,这将很有用:
get_object_or_404(Book.dahl_objects, title='Matilda')
4.还可以使用related managers:
author = Author.objects.get(name='Roald Dahl')
get_object_or_404(author.book_set, title='Matilda')
与get()一样,如果找到多个对象将引发一个MultipleObjectsReturned异常。
5. get_list_or_404()
get_list_or_404(klass, args, *kwargs)[source]
这其实就是get_object_or_404
多值获取版本。
在后台,返回一个给定模型管理器上filter()的结果,并将结果映射为一个列表,如果结果为空则弹出Http404异常。
必需参数:
- klass:获取该列表的一个Model、Manager或QuerySet实例。
**kwargs
:查询的参数,格式应该可以被filter()接受。
范例:
下面的示例从MyModel中获取所有发布出来的对象:
from django.shortcuts import get_list_or_404
def my_view(request):
my_objects = get_list_or_404(MyModel, published=True)
这个示例等同于:
from django.http import Http404
def my_view(request):
my_objects = list(MyModel.objects.filter(published=True))
if not my_objects:
raise Http404("No MyModel matches the given query.")
HttpRequest对象
每当一个用户请求发送过来,Django将HTTP数据包中的相关内容,打包成为一个HttpRequest对象,并传递给每个视图函数作为第一位置参数,也就是request,供我们调用。
HttpRequest对象中包含了非常多的重要的信息和数据,应该熟练掌握它。
类定义:class HttpRequest[source]
HttpResponse对象
类定义:class HttpResponse[source]
HttpResponse类定义在django.http模块中。
HttpRequest对象由Django自动创建,而HttpResponse对象则由程序员手动创建.
我们编写的每个视图都要实例化、填充和返回一个HttpResponse对象。也就是函数的return值。