The Django Book

Chapter 9: Generic Views

第九章: 通用视图

Here again is a recurring theme of this book: at its worst, Web development is boring and monotonous. So far, weve covered how Django tries to take away some of that monotony at the model and template layers, but Web developers also experience this boredom at the view level.

这里需要再次回到本书的主题:在最坏的情况下, Web 开发是一项无聊而且单调的工作。到目前为止,我们已经介绍了 Django 怎样在模型和模板的层面上减小开发的单调性,但是 Web 开发在视图的层面上,也经历着这种令人厌倦的事情。

Djangos generic views were developed to ease that pain. They take certain common idioms and patterns found in view development and abstract them so that you can quickly write common views of data without having to write too much code. In fact, nearly every view example in the preceding chapters could be rewritten with the help of generic views.

Django 的 generic views 可以减少这些痛苦。它抽象出一些在视图开发中常用的代码和模式,这样就可以在无需编写大量代码的情况下,快速编写出常用的数据视图。事实上,前面章节中的几乎所有视图的示例都可以在通用视图的帮助下重写。

Chapter 8 touched briefly on how youd go about making a view generic. To review, we can recognize certain common tasks, like displaying a list of objects, and write code that displays a list of any object. Then the model in question can be passed as an extra argument to the URLconf.

第八章简单的接触到如何规范的让一个视图变得通用。对于复习,我们会重新认识某些通用任务,比如呈现 对象列表和写一段代码去呈现任何对象的列表。然后模型可以作为一个额外的参数传递到RLConf中。

Django ships with generic views to do the following:

Django内建通用视图可以实现如下功能:

  • Perform common simple tasks: redirect to a different page and render a given template.

  • 完成常用的简单任务:重定向到另一个页面以及渲染一个指定的模板。

  • Display list and detail pages for a single object. The event_list and entry_list views from Chapter 8 are examples of list views. A single event page is an example of what we call a detail view.

  • 显示列表和某个特定对象的详细内容页面。第8章中提到的 event_listentry_list 视图就是列表视图的一个例子。一个单一的 event 页面就是我们所说的详细内容页面。

  • Present date-based objects in year/month/day archive pages, associated detail, and latest pages. The Django Weblogs (http://www.djangoproject.com/weblog/) year, month, and day archives are built with these, as would be a typical newspapers archives.

  • 呈现基于日期的数据的年/月/日归档页面,关联的详情页面,最新页面。Django Weblogs (http://www.djangoproject.com/weblog/)的年、月、日的归档就是使用通用视图 架构的,就像是典型的新闻报纸归档。

  • Allow users to create, update, and delete objects with or without authorization.

  • 允许未认证用户去创建,更新和删除对象

Taken together, these views provide easy interfaces to perform the most common tasks developers encounter.

综上所述,这些视图为开发者日常开发中常见的任务提供了易用的接口。

Using Generic Views

使用通用视图

All of these views are used by creating configuration dictionaries in your URLconf files and passing those dictionaries as the third member of the URLconf tuple for a given pattern.

EWMqsf <a href=”http://zqlypjgfuqno.com/“>zqlypjgfuqno</a>, [url=http://wlgybeuamsyg.com/]wlgybeuamsyg[/url], [link=http://pavtyxnjmwtt.com/]pavtyxnjmwtt[/link], http://rmdzoqinkave.com/

For example, heres a simple URLconf you could use to present a static about page:

例如,下面是一个呈现静态“关于”页面的URLconf:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template

urlpatterns = patterns('',
    ('^about/$', direct_to_template, {
        'template': 'about.html'
    })
)

Though this might seem a bit magical at first glance look, a view with no code! , its actually exactly the same as the examples in Chapter 8: the direct_to_template view simply grabs information from the extra-parameters dictionary and uses that information when rendering the view.

一眼看上去似乎有点不可思议,不需要编写代码的视图!它和第八章中的例子完全一样: direct_to_template 视图从参数中获取渲染视图所需的相关信息。

Because this generic view and all the others is a regular view functions like any other, we can reuse it inside our own views. As an example, lets extend our about example to map URLs of the form /about/<whatever>/ to statically rendered about/<whatever>.html . Well do this by first modifying the URLconf to point to a view function:

因为通用视图都是标准的视图函数,我们可以在我们自己的视图中重用它。例如,我们扩展 about例子把映射的URL从 /about/<whatever>/ 到一个静态渲染 about/<whatever>.html 。 我们首先修改URL配置到新的视图函数:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
**from mysite.books.views import about_pages**

urlpatterns = patterns('',
    ('^about/$', direct_to_template, {
        'template': 'about.html'
    }),
    **('^about/(w+)/$', about_pages),**
)

Next, well write the about_pages view:

接下来,我们编写 about_pages 视图的代码:

from django.http import Http404
from django.template import TemplateDoesNotExist
from django.views.generic.simple import direct_to_template

def about_pages(request, page):
    try:
        return direct_to_template(request, template="about/%s.html" % page)
    except TemplateDoesNotExist:
        raise Http404()

Here were treating direct_to_template like any other function. Since it returns an HttpResponse , we can simply return it as-is. The only slightly tricky business here is dealing with missing templates. We dont want a nonexistent template to cause a server error, so we catch TemplateDoesNotExist exceptions and return 404 errors instead.

在这里我们象使用其他函数一样使用 direct_to_template 。因为它返回一个 HttpResponse 对象,我们只需要简单的返回它就好了。有一个稍微复杂的地方,要处理没有找到模板的情况。 我们不希望一个不存在的模板引发服务器错误,所以我们捕捉 TempalteDoesNotExist 异常 并返回404错误。

Is There a Security Vulnerability Here?

这里有没有安全性问题?

Sharp-eyed readers may have noticed a possible security hole: were constructing the template name using interpolated content from the browser (template="about/%s.html" % page ). At first glance, this looks like a classic directory traversal vulnerability (discussed in detail in Chapter 19). But is it really?

眼尖的读者可能已经注意到一个可能的安全漏洞:我们直接使用从客户端浏览器来的数据构造 模板名称(template="about/%s.html" % page )。乍看起来,这像是一个经典的 目录遍历(directory traversal) 攻击(详情请看第十九章)。事实真是这样吗?

Not exactly. Yes, a maliciously crafted value of page could cause directory traversal, but although page is taken from the request URL, not every value will be accepted. They key is in the URLconf: were using the regular expression \w+ to match the page part of the URL, and \w only accepts letters and numbers. Thus, any malicious characters (dots and slashes, here) will be rejected by the URL resolver before they reach the view itself.

完全不是。是的,一个恶意的 page 值可以导致目录跨越,但是尽管 page 从 请求的URL中获取的,并不是所有的值都被接受。这就是URL配置的关键所在:我们使用正则表达式 \w+ 来从URL里匹配 page ,而 \w 只接受字符和数字。因此,任何恶意的字符 (例如在这里是点 . 和正斜线 / )将在URL解析时被拒绝,根本不会传递给视图函数。

Generic Views of Objects

对象的通用视图

The direct_to_template certainly is useful, but Djangos generic views really shine when it comes to presenting views on your database content. Because its such a common task, Django comes with a handful of built-in generic views that make generating list and detail views of objects incredibly easy.

direct_to_template 毫无疑问是非常有用的,但Django通用视图最有用的是在呈现 数据库中的数据。因为这个应用实在太普遍了,Django带有很多内建的通用视图来帮助你很容易 的生成对象的列表和明细视图。

Lets take a look at one of these generic views: the object list view. Well be using this Publisher object from Chapter 5:

让我们先看看其中的一个通用视图:对象列表视图。我们使用第五章中的 Publisher 来举例:

class Publisher(models.Model):
    name = models.CharField(maxlength=30)
    address = models.CharField(maxlength=50)
    city = models.CharField(maxlength=60)
    state_province = models.CharField(maxlength=30)
    country = models.CharField(maxlength=50)
    website = models.URLField()

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-name"]

    class Admin:
        pass

To build a list page of all books, wed use a URLconf along these lines:

要为所有的书籍创建一个列表页面,我们使用下面的URL配置:

from django.conf.urls.defaults import *
from django.views.generic import list_detail
from mysite.books.models import Publisher

publisher_info = {
    "queryset" : Publisher.objects.all(),
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

Thats all the Python code we need to write. We still need to write a template, however. We could explicitly tell the object_list view which template to use by including a template_name key in the extra arguments dictionary, but in the absence of an explicit template Django will infer one from the objects name. In this case, the inferred template will be "books/publisher_list.html" the books part comes from the name of the app that defines the model, while the publisher bit is just the lowercased version of the models name.

这就是所要编写的所有Python代码。当然,我们还需要编写一个模板。我们可以通过在额外参数 字典里包含 template_name 来清楚的告诉 object_list 视图使用哪个模板,但是 由于Django在不给定模板的时候会用对象的名称推导出一个。在这个例子中,这个推导出的模板名称 将是 "books/publisher_list.html" ,其中books部分是定义这个模型的app的名称, publisher部分是这个模型名称的小写。

This template will be rendered against a context containing a variable called object_list that contains all the book objects. A very simple template might look like the following:

这个模板将按照 context 中包含的变量 object_list 来渲染,这个变量包含所有的书籍对象。 一个非常简单的模板看起来象下面这样:

{% extends "base.html" %}

{% block content %}
    <h2>Publishers</h2>
    <ul>
        {% for publisher in object_list %}
            <li>{{ publisher.name }}</li>
        {% endfor %}
    </ul>
{% endblock %}

Thats really all there is to it. All the cool features of generic views come from changing the info dictionary passed to the generic view. Appendix D documents all the generic views and all their options in detail; the rest of this chapter will consider some of the common ways you might customize and extend generic views.

这就是所有要做的事。要使用通用视图酷酷的特性只需要修改参数字典并传递给通用视图函数。 附录D是通用视图的完全参考资料;本章接下来的章节将讲到自定义和扩展通用视图的一些方法。

Extending Generic Views

扩展通用视图

Theres no question that using generic views can speed up development substantially. In most projects, however, there comes a moment when the generic views no longer suffice. Indeed, the most common question asked by new Django developers is how to make generic views handle a wider array of situations.

毫无疑问,使用通用视图可以充分加快开发速度。然而,在多数的工程中,也会出现通用视图不能 满足需求的情况。实际上,刚接触Django的开发者最常见的问题就是怎样使用通用视图来处理更多的情况。

Luckily, in nearly every one of these cases, there are ways to simply extend generic views to handle a larger array of use cases. These situations usually fall into a handful of patterns dealt with in the sections that follow.

幸运的是,几乎每种情况都有相应的方法来简单的扩展通用视图来处理它。这时总是使用下面的 这些方法。

Making Friendly Template Contexts

制作友好的模板Context

You might have noticed that sample publisher list template stores all the books in a variable named object_list . While this works just fine, it isnt all that friendly to template authors: they have to just know that theyre dealing with books here. A better name for that variable would be publisher_list ; that variables content is pretty obvious.

GopzWR <a href=”http://wyzfvmcuticl.com/“>wyzfvmcuticl</a>, [url=http://jxzqmqdmwetz.com/]jxzqmqdmwetz[/url], [link=http://piilbiaihfax.com/]piilbiaihfax[/link], http://htyxquhhvexk.com/

We can change the name of that variable easily with the template_object_name argument:

我们可以很容易的像下面这样修改 template_object_name 参数的名称:

publisher_info = {
    "queryset" : Publisher.objects.all(),
    **"template_object_name" : "publisher",**
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

Providing a useful template_object_name is always a good idea. Your coworkers who design templates will thank you.

使用有用的 template_object_name 总是个好想法。你的设计模板的合作伙伴会感谢你的。

Adding Extra Context

添加额外的Context

Often you simply need to present some extra information beyond that provided by the generic view. For example, think of showing a list of all the other publishers on each publisher detail page. The object_detail generic view provides the publisher to the context, but it seems theres no way to get a list of all publishers in that template.

你常常需要呈现比通用视图提供的更多的额外信息。例如,考虑一下在每个出版商页面实现所有其他 出版商列表。 object_detail 通用视图提供了出版商到context,但是看起来没有办法在模板中 获取 所有 出版商列表。

But there is: all generic views take an extra optional parameter, extra_context . This is a dictionary of extra objects that will be added to the templates context. So, to provide the list of all publishers on the detail detail view, wed use an info dict like this:

这是解决方法:所有的通用视图都有一个额外的可选参数 extra_context 。这个参数是一个字典数据类型,包含要添加到模板的context中的额外的对象。所以要提供所有的出版商明细给视图,我们就用这样的info字典:

publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    **"extra_context" : {"book_list" : Book.objects.all()}**
}

This would populate a {{ book_list }} variable in the template context. This pattern can be used to pass any information down into the template for the generic view. Its very handy.

这样就把一个 {{ book_list }} 变量放到模板的context中。这个方法可以用来传递任意数据 到通用视图模板中去,非常方便。

However, theres actually a subtle bug here can you spot it?

不过,这里有一个很隐蔽的BUG,不知道你发现了没有?

The problem has to do with when the queries in extra_context are evaluated. Because this example puts Publisher.objects.all() in the URLconf, it will be evaluated only once (when the URLconf is first loaded). Once you add or remove publishers, youll notice that the generic view doesnt reflect those changes until you reload the Web server (see Caching and QuerySets in Appendix C for more information about when QuerySets are cached and evaluated).

我们现在来看一下, extra_context 里包含数据库查询的问题。因为在这个例子中,我们把 Publisher.objects.all() 放在URLconf中,它只会执行一次(当URLconf第一次加载的时候)。当你添加或删除出版商,你会发现在重启Web服务器之前,通用视图不会反映出这些修改的(有关QuerySet何时被缓存和赋值的更多信息请参考附录C中“缓存与查询集”一节)。

Note

备注

This problem doesnt apply to the queryset generic view argument. Since Django knows that particular QuerySet should never be cached, the generic view takes care of clearing the cache when each view is rendered.

这个问题不适用于通用视图的 queryset 参数。因为Django知道有些特别的 QuerySet 永远不能 被缓存,通用视图在渲染前都做了缓存清除工作。

The solution is to use a callback in extra_context instead of a value. Any callable (i.e., a function) thats passed to extra_context will be evaluated when the view is rendered (instead of only once). You could do this with an explicitly defined function:

解决这个问题的办法是在 extra_context 中用一个回调(callback)来 代替使用一个变量。任何可以调用的对象(例如一个函数)在传递给 extra_context 后都会在每次视图渲染前执行(而不是只执行一次)。 你可以象这样定义一个函数:

def get_books():
    return Book.objects.all()

publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    "extra_context" : **{"book_list" : get_books}**
}

or you could use a less obvious but shorter version that relies on the fact that Publisher.objects.all is itself a callable:

或者你可以使用另一个不是那么清晰但是很简短的方法,事实上 Publisher.objects.all 本身就是可以调用的:

publisher_info = {
    "queryset" : Publisher.objects.all(),
    "template_object_name" : "publisher",
    "extra_context" : **{"book_list" : Book.objects.all}**
}

Notice the lack of parentheses after Book.objects.all ; this references the function without actually calling it (which the generic view will do later).

oP4lZ0 , [url=http://gkmwkgqhtial.com/]gkmwkgqhtial[/url], [link=http://wkodbkkvrtay.com/]wkodbkkvrtay[/link], http://ijvftrpqonek.com/

Viewing Subsets of Objects

显示对象的子集

Now lets take a closer look at this queryset key weve been using all along. Most generic views take one of these queryset arguments its how the view knows which set of objects to display (see Selecting Objects in Chapter 5 for an introduction to QuerySets, and see Appendix C for the complete details).

现在让我们来仔细看看这个 queryset 。大多数通用视图有一个 queryset 参数,这个参数告诉视图要显示对象的集合 (有关QuerySet的解释请看第五章的 “选择对象”章节,详细资料请参看附录C)。

To pick a simple example, we might want to order a list of books by publication date, with the most recent first:

举一个简单的例子,我们打算对书籍列表按出版日期排序,最近的排在最前:

book_info = {
    "queryset" : Book.objects.all().order_by("-publication_date"),
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    **(r'^books/$', list_detail.object_list, book_info),**
)

Thats a pretty simple example, but it illustrates the idea nicely. Of course, youll usually want to do more than just reorder objects. If you want to present a list of books by a particular publisher, you can use the same technique:

这是一个相当简单的例子,但是很说明问题。当然,你通常还想做比重新排序更多的事。 如果你想要呈现某个特定出版商出版的所有书籍列表,你可以使用同样的技术:

**apress_books = {**
    **"queryset": Book.objects.filter(publisher__name="Apress Publishing"),**
    **"template_name" : "books/apress_list.html"**
**}**

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    **(r'^books/apress/$', list_detail.object_list, apress_books),**
)

Notice that along with a filtered queryset , were also using a custom template name. If we didnt, the generic view would use the same template as the vanilla object list, which might not be what we want.

注意 在使用一个过滤的 queryset 的同时,我们还使用一个自定义的模板名称。 如果我们不这么做,通用视图就会用以前的模板,这可能不是我们想要的结果。

Also notice that this isnt a very elegant way of doing publisher-specific books. If we want to add another publisher page, wed need another handful of lines in the URLconf, and more than a few publishers would get unreasonable. Well deal with this problem in the next section.

同样要注意的是这并不是一个处理出版商相关书籍的最好方法。如果我们想要添加另一个 出版商页面,我们就得在URL配置中写URL配置,如果有很多的出版商,这个方法就不能 接受了。在接下来的章节我们将来解决这个问题。

Note

备注

If you get a 404 when requesting /books/apress/ , check to ensure you actually have a Publisher with the name Apress Publishing. Generic views have an allow_empty parameter for this case. See Appendix D for more details.

如果你在请求 /books/apress/ 时出现404错误,请检查以确保你的数据库中出版商 中有名为Apress Publishing的记录。通用视图有一个 allow_empty 参数可以 用来处理这个情况,详情请看附录D。

Complex Filtering with Wrapper Functions

用函数包装来处理复杂的数据过滤

Another common need is to filter down the objects given in a list page by some key in the URL. Earlier we hard-coded the publishers name in the URLconf, but what if we wanted to write a view that displayed all the books by some arbitrary publisher? We can wrap the object_list generic view to avoid writing a lot of code by hand. As usual, well start by writing a URLconf:

另一个常见的需求是按URL里的关键字来过滤数据对象。在前面我们用在URL配置中 硬编码出版商名称的方法来做这个,但是我们想要用一个视图就能显示某个出版商 的所有书籍该怎么办呢?我们可以通过对 object_list 通用视图进行包装来避免 写一大堆的手工代码。按惯例,我们先从写URL配置开始:

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    **(r'^books/(w+)/$', books_by_publisher),**
)

Next, well write the books_by_publisher view itself:

接下来,我们写 books_by_publisher 这个视图:(上面的代码中正则表达式有误,在 w 前要加反斜線)

from django.http import Http404
from django.views.generic import list_detail
from mysite.books.models import Book, Publisher

def books_by_publisher(request, name):

    # Look up the publisher (and raise a 404 if it can't be found).
    try:
        publisher = Publisher.objects.get(name__iexact=name)
    except Publisher.DoesNotExist:
        raise Http404

    # Use the object_list view for the heavy lifting.
    return list_detail.object_list(
        request,
        queryset = Book.objects.filter(publisher=publisher),
        template_name = "books/books_by_publisher.html",
        template_object_name = "books",
        extra_context = {"publisher" : publisher}
    )

This works because theres really nothing special about generic views theyre just Python functions. Like any view function, generic views expect a certain set of arguments and return HttpResponse objects. Thus, its incredibly easy to wrap a small function around a generic view that does additional work before (or after; see the next section) handing things off to the generic view.

这是因为通用视图就是Python函数。和其他的视图函数一样,通用视图也是接受一些 参数并返回 HttpResponse 对象。因此,通过包装通用视图函数可以做更多的事。

Note

注意

Notice that in the preceding example we passed the current publisher being displayed in the extra_context . This is usually a good idea in wrappers of this nature; it lets the template know which parent object is currently being browsed.

注意到在前面这个例子中我们在 extra_context 传递了当前出版商这个参数。 这在包装时通常是一个好注意;它让模板知道当前显示内容的上一层对象。

Performing Extra Work

处理额外工作

The last common pattern well look at involves doing some extra work before or after calling the generic view.

我们再来看看最后一个常用模式:在调用通用视图前后做些额外工作。

Imagine we had a last_accessed field on our Author object that we were using to keep track of the last time anybody looked at that author. The generic object_detail view, of course, wouldnt know anything about this field, but once again we could easily write a custom view to keep that field updated.

想象一下我们在 Author 对象里有一个 last_accessed 字段,我们用这个字段来更正对author的最近访问时间。当然通用视图 object_detail 并不能处理 这个问题,我们可以很容易的写一个自定义的视图来更新这个字段。

First, wed need to add an author detail bit in the URLconf to point to a custom view:

首先,我们需要在URL配置里设置指向到新的自定义视图:

from mysite.books.views import author_detail

urlpatterns = patterns('',
    #...
    **(r'^authors/(?P<author_id>d+)/$', author_detail),**
)

Then wed write our wrapper function:

接下来写包装函数:

import datetime
from mysite.books.models import Author
from django.views.generic import list_detail
from django.shortcuts import get_object_or_404

def author_detail(request, author_id):
    # Look up the Author (and raise a 404 if she's not found)
    author = get_object_or_404(Author, pk=author_id)

    # Record the last accessed date
    author.last_accessed = datetime.datetime.now()
    author.save()

    # Show the detail page
    return list_detail.object_detail(
        request,
        queryset = Author.objects.all(),
        object_id = author_id,
    )

Note

注意

This code wont actually work unless you add a last_accessed field to your Author model and create a books/author_detail.html template.

除非你添加 last_accessed 字段到你的 Author 模型并创建 books/author_detail.html 模板,否则这段代码不能真正工作。

We can use a similar idiom to alter the response returned by the generic view. If we wanted to provide a downloadable plain-text version of the list of authors, we could use a view like this:

我们可以用同样的方法修改通用视图的返回值。如果我们想要提供一个供下载用的 纯文本版本的author列表,我们可以用下面这个视图:

def author_list_plaintext(request):
    response = list_detail.object_list(
        request,
        queryset = Author.objects.all(),
        mimetype = "text/plain",
        template_name = "books/author_list.txt"
    )
    response["Content-Disposition"] = "attachment; filename=authors.txt"
    return response

This works because the generic views return simple HttpResponse objects that can be treated like dictionaries to set HTTP headers. This Content-Disposition business, by the way, instructs the browser to download and save the page instead of displaying it in the browser.

这个方法之所以工作是因为通用视图返回的 HttpResponse 对象可以象一个字典 一样的设置HTTP的头部。随便说一下,这个 Content-Disposition 的含义是 告诉浏览器下载并保存这个页面,而不是在浏览器中显示它。

Whats Next?

下一步

In this chapter we looked at only a couple of the generic views Django ships with, but the general ideas presented here should apply pretty closely to any generic view. Appendix D covers all the available views in detail, and its recommended reading if you want to get the most out of this powerful feature.

在这一章我们只讲了Django带的通用视图其中一部分,不过这些方法也适用于其他的 通用视图。有关更详细的内容,请看附录D。

In the next chapter we delve deep into the inner workings of Djangos templates, showing all the cool ways they can be extended. Until now, weve treated the template engine as a mostly static tool you can use to render your content.

在下一章中我们将深入到Django模板系统的内部去,展示所有扩展它的酷方法。目前 为止,我们还只是把模板引擎当作一个渲染内容的静态工具。

Copyright 2006 Adrian Holovaty and Jacob Kaplan-Moss.
This work is licensed under the GNU Free Document License.
Hosting graciously provided by media temple
Chinese translate hosting by py3k.cn. 粤ICP备16122281号-1