Django-深度分析Django基于类的视图(2)(翻译)

来源:互联网 发布:人工智能对政治的影响 编辑:程序博客网 时间:2024/05/29 14:31

摘要

在上一篇中,我介绍了Django基于类的视图背后的原理以及为什么它比基于函数的视图强大。我还介绍了Django的通用视图
ListView。这是整个系列的第二部分,我希望谈谈通用视图中第二个最常使用视图,DetailView,并且谈谈关于如何定制
queryset和相应的参数。最后,我会介绍其他一些基于类的视图,这些视图可以让你构建更加复杂的页面。然而,为了完全理
解DetailView,你首先需要掌握两个基本概念,查询集(queryset)和视图参数。因此,对那些喜欢边练边学的同学们说声抱歉
了,这次我们的话题还是要从纯编程的话题开始。

查询集或者叫信息抽取的艺术

Django其中最重要的部分就是ORM(Object Relational Mapper),它能让你像访问一个Python对象集合一样地访问底层的数
据库。你已经知道,Django为你提供了很多的工具来简化构造一个DB查询。那就是管理器(也就是model的.objects属性,
其中一例)以及查询方法(get(),filter()以及其他一些好朋友)。请注意了,事情可能比你预想的更加的神奇。当你使用某个管理器中的某个方法时,你会得到一个结果,也就是QuerySet。此物大多数时间是当作一个列表使用的,但是可不仅
仅如此。推荐阅读官方文档了解详细信息。我这里像强调的是,一直到你做个某个动作访问了实际内容之前,QuerySet是不会被真的投入计算的(evaluated,比如分片
(slicing)或者对其进行遍历(iterating)。这意味着,我们可以创建QuerySet,将它们传递给函数,对其进行存储,甚至
于对其进行程序性的构建或者做元编程,却不会对数据库做实际的操作。如果你把QuerySet想象成一堆食谱,我估计你离真相就不
远了:他们只是一堆对象,而这些对象只是保存了你打算如何获取你感兴趣的数据的方法(而不是实际的获取数据)。实际地获取数
据则是此游戏的另外一部分的事情了。这种某件事的定义和其执行之间的分离叫做懒计算(lazy evaluation)。还是先给你个小例子告诉你为什么QuerySet的懒计算是如此重要。
[...]def get_oldest_three(queryset):    return queryset.order_by['id'][0:2]old_books = get_oldest_three(Book.objects.all())old_hardcover_books = \    get_oldest_three(Book.objects.filter('type=Book.HARDCOVER'))


可见,get_oldest_three()方法只是对输入的QuerySet(也可以是其他任何类型)进行过滤;它只是简单滴对对象进行排序,然后获得前三个插入数据库的记录。这里很重要的一点是,我们使用QuerySet就像在使用纯‘算法’那样,或者像使用某个过程的描述。当我们创建old_books变量时,我们只是对get_oldest_three()方法说:“嘿,我打算用这个方法把感兴趣的数据抽取出来。能不能请你帮忙对它完善一下并最后把实际的数据返回给我?”

作为如此灵活的对象,QuerySet是通用视图很重要的一部分。因此,了解他们并为接下来的盛宴做好准备吧。


为了灵活:参数化的视图


URL就是我们网站或者服务的API。这一点对通过页面浏览的用户而言或多或少的比较明显,但是从程序猿的角度看,URL更像一个计算机系统的入口。因此,他们跟某个库的API没什么区别:在这里,静态页面就像常量,或者像总是返回相同值(比如配置参数)的函数,而动态页面则更像那些处理输入数据(参数)然后返回一个结果的函数。

因此,Web URL是可以接收参数的,并且我们的底层视图也是可以接收参数的。基本上,通过HTTP,你有三种方法将参数从用户的浏览器传输到你的服务器。第一个方法叫做查询字符串(query string),就是通过某个通用的语法将参数直接放入URL中。第二个方法是把参数存储在HTTP的请求体里,也就是POST请求经常做的事儿。我们会在以后的贴子里讨论表单。

第一个方法有个巨大的缺陷:大多时候URL太长了(有时候太长了),因此很难将其作为一个真正的API使用。为了缓解这种情况,出现了清洁URL(clean URL),这正是Django本身使用的方式(当然,如果你喜欢,你还是可以坚持使用查询字符串的方法。)

现在你知道了,通过正则表达式的解析,你可以搜集包含在URL中的参数;而我们所要探究的是基于类的视图是怎么接收和处理这些参数的。

在前面的贴子中,我们已经讨论了as_view()方法,它如何实例化类并返回dispatch()的结果。

    @classonlymethod    def as_view(cls, **initkwargs):        """        Main entry point for a request-response process.        """        # sanitize keyword arguments        for key in initkwargs:            if key in cls.http_method_names:                raise TypeError("You tried to pass in the %s method name as a "                                "keyword argument to %s(). Don't do that."                                % (key, cls.__name__))            if not hasattr(cls, key):                raise TypeError("%s() received an invalid keyword %r. as_view "                                "only accepts arguments that are already "                                "attributes of the class." % (cls.__name__, key))        def view(request, *args, **kwargs):            self = cls(**initkwargs)            if hasattr(self, 'get') and not hasattr(self, 'head'):                self.head = self.get            self.request = request            self.args = args            self.kwargs = kwargs            return self.dispatch(request, *args, **kwargs)        # take name and docstring from class        update_wrapper(view, cls, updated=())        # and possible attributes set by decorators        # like csrf_exempt from dispatch        update_wrapper(view, cls.dispatch, assigned=())        return view
现在让我们看看view()包裹(wrapper)函数实际上对实例化的类做了些什么。不出所料,它接收由URLconf传来的request,args和kwargs参数并将它们转换成很多具有相同名字的类属性。

这意味着,在我们CBV代码的每一处,我们都可以通过简单滴读取self.request,self.args和self.kwargs来访问这些原始的调用参数。其中,*args和**kwargs是一些未命名和已命名的值,他们来自URLconf的正则表达式。


细节

给出对象列表后,接下来一个最有用的事情就是给出有关对象的详细信息(细节)。很明显,任何一个电子商务网站的大部分页面就是用来列出产品并显示产品的详细信息,而一个博客也是由一个或多个页面构成的,同时需要显示博客的列表以及博客的每个页面。因此呢,创建一个细节视图来处理我们数据库的内容还是非常值得学习滴。

为了帮助我们完成这项任务,Django提供了DetailView,实际上,功能如其名,它就是用来处理我们从数据库获取的信息的。ListView的基本行为就是获取某个给定model的所有对象的列表,而DetailView则是获取单个对象。那它是怎么知道要抽取的是哪个对象呢?
当HTTP请求进来,我们调用dispatch(),它唯一要做的事情就是查看method属性,针对这些HttpRequest对象,这些属性包含了当前HTTP 动词(verb)的名字(比如,‘GET’);然后,dispatch()寻找类里具有这一动词小写名字的方法(比如‘GET’-->get())。找到后,此方法被调用,调用参数与dispatch()一样,也就是request本身,*args和**kwargs。

DetailView并没有实体,其所有的东西都是从两个类那里继承的;而第二个类BaseDetailView实现了get()方法。

def get(self, request, *args, **kwargs):    self.object = self.get_object()    context = self.get_context_data(object=self.object)    return self.render_to_response(context)
如你所见,此方法通过调用self.get_object()提取出了它将展示的单个对象。然后,调用self.get_context_data()(我们在前一个帖子中碰到过)和最后那个大家熟悉的self.render_to_response(),它是与著名的Django函数等价的类的版本。方法self.get_object()由BaseDetailView的父类SingleObjectMixin提供:对于我们今天的话题而言,这是其代码中最重要的部分。

def get_object(self, queryset=None):    if queryset is None:        queryset = self.get_queryset()    pk = self.kwargs.get(self.pk_url_kwarg, None)    if pk is not None:        queryset = queryset.filter(pk=pk)    obj = queryset.get()    return obj
警告:上面函数中,为了利于阅读,我删除了很多代码行;请查看源代码看完整的实现。

这段代码显示出DetailView从哪里获得的查询集;而get_queryset()方法由SingleObjectMixin本身提供,并且如果存在self.queryset的话,基本上就是将其返回,否者就把指定model的所有对象都返回(如ListView所做如出一辙)。此queryset被filter()进行提炼,最后经过get()的处理。在这里,get()并没有操心各种错误情况以及抛出相应的异常(我是这么认为滴)。

filter()中使用的pk直接来自self.kwargs,因此也就是直接来自URL。总体来说,由于这是视图的核心概念,我希望对这部分进行仔细讲解。

我们的DetailView是由URLconf调用的,后者提供了一个正则表达式对URL进行解析,比如url(r'^(?P<pk>\d+)/$'。这一正则表达式的作用是抽取一个参数并将其命名为pk,从而视图的kwargs将会包含pk作为键并将出现在URL中的实际值作为键值。例如,URL 123/将会解析成{'pk': 123}。DetailView的缺省行为是搜寻pk键并用它对查询集进行过滤,因为self.pk_url_kwarg就是‘pk’。

因此,如果我们希望更改此参数的名字,很简单,我们只要定义我们类的pk_url_kwarg并提供一个正则表达式来用新名字抽取主键就可以了。比如,url(r'^(?P<key>\d+)/$',就是用名字key进行抽取的,因此,我们在我们的类里定义pk_url_kwarg = 'key'就好了。

从这一快速探索里,我们了解到,一个继承了DetailView的类:

  • 提供了包含object键的上下文,此object键初始化为某个单个对象
  • 必须设置一个model类属性,以便知道要抽取什么对象。
  • 可以设置queryset类属性,对某个对象所属的对象集合进行精炼。
  • 必须从某个URL中调用,此URL包含了一个正则表达式可以将被搜索对象的主键提取为pk
  • 可以通过设置pk_url_kwarg类属性,为主键使用一个不同的名字

下面代码演示了DetailView的基本使用方法。

class BookDetail(DetailView):    model = Bookurlpatterns = patterns('',    url(r'^(?P<pk>\d+)/$',        BookDetail.as_view(),        name='detail'),    )
此视图从Book model中抽取了一个对象;正则表达式被配置成使用标准的pk名字。

如前一个帖子中ListView所示,任何CBV都是用get_context_data()来返回上下文字典给渲染引擎的。因此,继承自DetailView的视图可以遵循下面同样的模式将数据加到上下文中。

class BookDetail(DetailView):    model = Book    def get_context_data(self, **kwargs):        context = super(BookDetail, self).get_context_data(**kwargs)        context['similar'] = get_similar_books(self.object)        return contexturlpatterns = patterns('',    url(r'^(?P<pk>\d+)/$',        BookDetail.as_view(),        name='detail'),    )
如前所书,你可以通过self.object访问此对象。在上面的例子中,self.object被传给了我们在代码中实现的某个服务函数。

使用基础视图

有时候,在处理某些复杂的页面时,Django提供的这些通用显示视图反而不是很适合。这种情况通常在你需要重载方法(以防止视图运行其标准行为)时更加明显。举个例子,你想要显示多个对象的详细信息,这时,DetailView或许很快就显出力不从心了,因为它的创建只是为一个对象服务。

如果这些情形无法用通用显示视图解决,你可以考虑其他的类:RedirectView,TemplateView和View。

这里不打算详细描述这些视图;我只想简单地指出它们的某些特别之处。

View是目前为止我们的老朋友了。我们在讨论as_view()和dispatch()时就遇到过它。它是最通用的视图类,因此可以被用来执行一些特别的任务,比如不通过模版对页面进行渲染(例如在AJAX技术中,返回JSON数据)。

TemplateView是从模版中渲染页面的最好选择,同时,就上下文内容而言还能保持某种自由度。这个可能是你以后除了ListVIew和DetailView之外使用最多的视图。基本上,你只需要继承他并定义get_context_data()方法就行了。从源代码中可见,TemplateView只是应答了GET请求。

RedirectView,类如其名,是用来重定向一个请求的。其重定向的机制非常的简单:其get()方法将HttpResposeRedirect返回给由url类属性定义的URL。当通过HTTP方法而不是通过GET(也就是HEAD,POST,OPTIONS,DELETE和PUT)被调用时,这个类表现出非常有意思的行为模式:它将方法“转换”到GET只需要简单地从相应的方法(head(),post()等等)中调用get()就可以了。在下一个帖子中,我们将演示如何利用这一简单的技术来为用户展示一个预先填好的表单。


基于日期的视图

Django提供了其他一些基于类的视图用来简化处理由日期提取或者排序的对象。作为一个程序猿,你知道,有时候处理日期经常有点小尴尬;vews/generic/dates.py中的视图目标就是来帮助你降服基于时间的对象的。任何包含一个日期的对象(比如文章发表的日期,消息记录的日期,等等)都可以用这些视图来处理。

记住,基于日期的视图也是CBV,因此他们也是基于View的。就像ListView和DetailView一样。因此,除了他们在日期上的特殊性之外,他们的行为都是一样的(执行get_context_data(),get(),dispatch()等等)


结论

此贴中我们深度探讨了DetailView,并快速地讨论了其余的基础视图和基于日期的视图。下一帖子,我们将进入丰富多彩(陌生的)的表单世界。











阅读全文
0 0
原创粉丝点击