内置的类通用视图

Web程序是很枯燥的,因为我们总是重复特定的模式。Django尝试在模型层和模板层减少那些枯燥的代码,但是Web开发者在视图层还是会遇到这种麻烦。

Django的通用视图可以减轻那种痛苦。他们在视图开发中提取通用的语法和模式,然后进行抽象,这样就可以快速编写数据的公共视图,而不必编写太多代码。

我们可以识别出特定的任务,比如显示一个对象列表,然后编写可以显示任何对象列表的代码。接着,问题中的模型可以被作为额外的参数传入URLconf

Django自带的通用视图可以做下面的这些工作:

  • 显示单个对象的列表页面和详细页面。如果我们正在创建一个会议管理的程序,那么TalkListViewRegisteredUserListView则是列表视图的样例。一个单独的谈话页面就是我们说的详细视图的样例。
  • 在年、月、日归档页面中显示基于日期的对象,以及相关的详细页面和“最近”页面。
  • 允许用户创建、更新和删除对象-无论是否授权。

总之,这些视图提供了简单的接口,帮助开发者搞定大部分日常任务。

扩展通用视图

毫无疑问,使用通用视图可以大大加快开发速度。然而,在大部分项目中,总会有通用视图不再满足需求的时候。实际上,Django开发新手问的最多的问题就是如何使通用视图应对更广泛的情况。

这是之前为1.3版本重新设计通用视图的原因之一,它们只是视图函数,有许多令人眼花缭乱的选项;现在,我们不用在URLconf文件中进行大量配置,扩展通用视图的推荐方式是子类化他们,然后重载他们的属性和方法。

也就是说,通用视图是有限制的。如果您发现使用通用视图的子类来实现你自己的视图有困难,那么您可能会意识到使用自定义的基于类的视图或者函数视图来编写您需要的代码会更加有效。

在一些第三方应用中有更多的通用视图示例,或者你可以根据需要编写自己的通用视图。

对象的通用视图

TemplateView当然是有用的,但是当显示数据库内容的视图时,Django的通用视图才真正地出类拔萃。因为这是日常任务,所以Django自带一些内置通用视图,来超简单的生成对象的列表和详细视图。

让我们开始看看一些展示对象列表,或单独对象的示例。

我们将使用这些模型:

# models.py
from django.db import models

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

    class Meta:
        ordering = ["-name"]
    
    def __str__(self):  # __unicode__ on Python 2
        return self.name

class Author(models.Model):
    salutation = models.CharField(max_length=10)
    name = models.CharField(max_length=200)
    email = models.EmailField()
    headshot = models.ImageField(upload_to='author_headshots')
    
    def __str__(self):  # __unicode__ on Python 2
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField('Author')
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
    publication_date = models.DateField()

现在我们需要定义一个视图:

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    model = Publisher

最后将那个视图传入你的urls:

# urls.py
from django.conf.urls import url
from books.views import PublisherList

urlpatterns = [
    url(r'^publishers/$', PublisherList.as_view()),
]

这就是我们所需要写的所有Python代码。然而我们仍然需要写一个模板。通过对视图添加一个template_name的属性,我们可以显示的告诉视图使用哪一个模板,但是缺少显示指定模板的话,django会从对象名推断出一个模板名称。在这个例子中,django推断出的模板名将会是books/publisher_list.htmlbooks来自于定义模型的应用名,而publisher是小写的模型名。

笔记

因此,当在TEMPLATES参数中,django模板的APP\_DIRS选项设置成True时,模板的位置会是/path/to/project/books/templates/books/

这个模板将以一个包涵名为object_list变量的上下文为背景被渲染。 一个简单模板如下所示:

{% extends "base.html" %}

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

这就是全部。通用视图所有酷炫的特性来自于改变它的属性设置。通用视图参考手册(generic views reference) 详细列出了所有的通用视图和它的选项;这篇文档剩下的内容将深入一些定制和扩展通用模板的常用方法。

使模板上下文友好

你可能已经注意到,我们的出版社列表模板样例在一个名为object_list的变量中存储所有的出版社。然而这只是可以顺利完成工作,对于模板编辑者并不友好:他们必须要“刚好知道”此时正在处理的是出版社。

好吧,如果你正在处理一个模型对象,当你正在处理一个对象或者查询集的时候,Django能使用模型类的小写名填充上下文。例如除了默认的object_list实体,也提供了publisher_list可以被使用,只不过它们包含一样的数据而已。

如果这仍然不适合你的需要,你可以手工设定上下文变量的名字。在通用视图中设置context_object_name属性,指定上下文变量名以便使用:

# views.py
from django.views.generic import ListView
from books.models import Publisher

class PublisherList(ListView):
    model = Publisher
    context_object_name = 'my_favorite_publishers'

提供一个有用的context_object_name总是一个好主意。负责模板设计的同事将会感谢你的。

添加额外的上下文

通常,您需要简单的显示一些超出通用视图所能提供的额外信息。例如,考虑在每个出版社的详细页面中显示当前出版社图书的列表。 DetailView 通用视图向上下文提供出版社信息,但是我们如何在模板中获得额外的信息呢?

答案是子类化 DetailView 然后提供你自己的get_context_data方法实现。默认实现只是简单的将显示的对象添加到模板中,但您可以重新实现它来发送更多内容到模板中:

from django.views.generic import DetailView
from books.models import Publisher, Book

class PublisherDetail(DetailView):

    model = Publisher

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(PublisherDetail, self).get_context_data(**kwargs)
        # Add in a QuerySet of all the books
        context['book_list'] = Book.objects.all()
        return context

笔记

一般来说,get_context_data方法将所有父类中的上下文数据与当前类合并。为了在你想修改上下文的类中保证这样的方式,你得确保调用父类的get_context_data方法。当没有两个类试图定义相同的键时,这将给期望的结果。但是,如果某个类试图在父类设置后(在调用super方法之后)重写一个键,那么该类的子类们也需要显式地设置它,才能想确保覆盖它们所有的父类。如果您有问题,请查看视图的方法解析顺序。

另一个考虑是类通用视图的上下文数据将会覆盖上下文处理器提供的数据;参看 get_context_data() 方法的示例

查看对象子集

现在,让我们仔细看看我们之前一直在使用的模型参数。模型参数指定视图要操作的数据库模型,它可用于所有对单个对象或对象集合进行操作的通用视图。然而,模型参数并不是指定视图操作对象的唯一方式-你也可以使用查询集参数指定对象列表:

from django.views.generic import DetailView
from books.models import Publisher

class PublisherDetail(DetailView):

    context_object_name = 'publisher'
    queryset = Publisher.objects.all()

指定 model = Publisherqueryset = Publisher.objects.all() 的快捷方式。然而,通过使用查询定义一个过滤列表,你可以使在视图中可见对象,更加明确。(查看 Making queries 获得更多关于 QuerySet 对象的信息, 对于完整的细节也可以看看 class-based views reference ).

举一个简单的例子,我们可能想要根据最近的出版日期排序图书列表:

from django.views.generic import ListView
from books.models import Book

class BookList(ListView):
    queryset = Book.objects.order_by('-publication_date')
    context_object_name = 'book_list'

这是个非常简单的例子,但它很好地阐明了想法。当然,您通常想做的不仅仅是重新排序对象。如果你想显示特定出版社的图书列表,你可以使用相同的技巧:

from django.views.generic import ListView
from books.models import Book

class AcmeBookList(ListView):

    context_object_name = 'book_list'
    queryset = Book.objects.filter(publisher__name='ACME Publishing')
    template_name = 'books/acme_list.html'

注意除了过滤查询集以外,我们还是用了自定义的模板名。如果我们没有那样做,通用视图将使用普通的对象列表名一样名称的模板,这可能不是我们所要的。

还要注意到,这不是处理指定出版社图书的优雅方式。如果我们想添加另一个出版社页面,需要在URLconf中增加一行设置,然后,数量更多的出版社就会觉得不太合理吧。下一节我们来处理这个问题。

笔记

请求/books/acme/时如果你得到一个404错误, 检查你要确保有名字是‘ACME Publishing’的出版社。对于这个例子通用视图有一个允许为空的参数。更多细节请看 class-based-views reference

动态过滤

另一个常用需求是通过URL中的某个键过滤列表页面中的指定的对象。之前我们在URLconf中对出版社进行了硬编码,但是如果我们想写一个视图,显示任意一个出版商的所有书籍呢?

ListView有一个get_queryset()方法,我们可以方便地重写。之前,它仅仅返回查询集属性的值,但现在我们可以添加更多的逻辑。

这项工作的关键是当类视图被调用时,很多有用的信息都被 self 保存着;除了 request(self.request) 还包括通过URLconf捕获的位置参数 (self.args) 和关键字参数 (self.kwargs)

这里,我们URLconf中有一个单独的捕获组:

# urls.py
from django.conf.urls import url
from books.views import PublisherBookList

urlpatterns = [
    url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
]

接下来,我们写PublisherBookList视图本身:

# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher

class PublisherBookList(ListView):

    template_name = 'books/books_by_publisher.html'

    def get_queryset(self):
        self.publisher = get_object_or_404(Publisher, name=self.args[0])
        return Book.objects.filter(publisher=self.publisher)

如你所见,它很容易向查询选择添加更多的逻辑;如果我们需要,可以用 self.request.user 去过滤当前使用的用户, 或者更复杂的逻辑。

同时,我们可以添加publisher到上下文中,让我们可以在模板中使用它:

# ...

def get_context_data(self, **kwargs):
    # Call the base implementation first to get a context
    context = super(PublisherBookList, self).get_context_data(**kwargs)
    # Add in the publisher
    context['publisher'] = self.publisher
    return context

完成额外的工作

最后一个日常模式是,我们会考虑在调用通用视图的前后做一些额外的工作。

设想有一个最近登录(last_accessed)字段在Author模型中,我们使用它来记录别人浏览那个作者信息的最后时间:

# models.py
from django.db import models

    class Author(models.Model):
        salutation = models.CharField(max_length=10)
        name = models.CharField(max_length=200)
        email = models.EmailField()
        headshot = models.ImageField(upload_to='author_headshots')
        last_accessed = models.DateTimeField()

通用详细视图(generic DetailView class)类当然不知道这个字段,但是我们可以再简单地写一个自定义视图来更新那个字段。

首先,我们需要在URLconf中增加一条作者信息的url条目对应自定义视图:

from django.conf.urls import url
from books.views import AuthorDetailView

urlpatterns = [
    #...
    url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
]

然后我们写一个新的视图-get_object是检索对象的方法-然后我们简单重新它,包装自定义的调用:

from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author

class AuthorDetailView(DetailView):

    queryset = Author.objects.all()

    def get_object(self):
        # Call the superclass
        object = super(AuthorDetailView, self).get_object()
        # Record the last accessed date
        object.last_accessed = timezone.now()
        object.save()
        # Return the object
        return object

笔记

这里URLconf使用命名的组pk - 详细视图使用这个默认名寻找用于筛选查询集的主键的值。

如果你想调用别的组,你可以在视图上设置pk_url_kwarg参数。更多信息可以在 DetailView 中找到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容