Django 类视图(三)内置类通用视图

本文是对 Django 官网文档的翻译。

官网链接:https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/

适用 Django 1.11

实现 Web 应用程序可能是单调的,因为总是重复某些模式。 Django 试图在模型层和模板层减少一些单调,但 Web 开发人员在视图层也将体会到无聊。

Django 通用视图希望可以减轻这种痛苦。他们从视图开发过程中中总结一些常用习语和模式,将其抽象出来,以便无需编写太多代码就可以快速实现数据的常用视图。

我们可以识别某些常见任务,例如显示对象列表并编写显示任意对象列表的代码。 然后,将模型以额外参数的形式传入 URLconf 。

Django 发布通用视图来实现以下功能:

  • 展示列表页面和单个对象的详细页面。如果我们创建一个管理会议的应用,TalkListView 和 RegisteredUserListView 是列表视图的例子。一个单独的谈话页面则是我们所说的“详细”视图的例子。

  • 在年/月/日文件页面(相关详细信息)和“最新”页面提供基于日期的对象。

  • 允许授权/未授权的用户来创建、更新和删除对象。

总而言之,这些视图为开发人员遇到的最常见的任务提供方便的接口。

扩展通用视图

使用通用视图毫不疑问会加快开发速度。然而在大多数项目中通用视图并不够用。 事实上,新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将根据对象的名字进行推断。 在上面的例子中,推断的模板将是“books/publisher_list.html” - “books”部分来自定义模型的应用程序的名称,而“publisher”只是模型名称的小写形式。

注意:当 TEMPLATES 中 DjangoTemplates 后端的 APP_DIRS 选项设为 True 时,模板位置应该是/path/to/project/books/templates/books/publisher_list.html。

此模板将对包含所有出版社对象的object_list变量的内容进行渲染。下面是一个非常简单的模板:

{% extends "base.html" %}

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

这真的是所有的工作了。通用视图的所有酷炫功能来自于更改通用视图的属性。 通用视图参考文档详细记录了所有通用视图及其选项; 本文档的其余部分是自定义和扩展通用视图的一些常见方法。

制作“友好地”模板内容


我们可能注意到上面publisher列表模板在object_list中保存了所有的publisher。虽然这样做很好,但这对于模板作者并不是那么“友好”:他们必须“只知道”在这里处理publishers。

如果你正在处理一个模型对象,那么你的工作已经完成了。 当处理对象或queryset时,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通用视图为publisher提供内容,但是如何在模板中获取其他信息呢?

答案是使用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将融合所有父类的内容数据到当前类的内容数据中。要在希望更改内容的类中保留父类行为需要在super类中调用get_context_data。当不存在两个类尝试定义相同的键的情况时,这将给出预期的结果。 但是,如果任何类尝试重写父类设置的键(在调用super之后),如果要确保覆盖所有父类,该类的任何子类也需要在super之后显式设置它。 如果遇到问题,请查看视图的方法解析顺序。另一个考虑因素是,类通用视图的内容数据将重写内容处理器提供的数据,详见get_context_data()提供的例子。

对象子集视图


现在,让我们近距离看一下一直使用的模型参数。指定视图操作数据模型的model参数可用于对单个对象或对对象集合进行操作的所有通用视图。然而,model参数不是指定视图操作对象的唯一方法-也可以使用queryset参数指定对象列表。

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

class PublisherDetail(DetailView):

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

设置 model = Publisher 其实是 queryset=Publisher.objects.all() 的缩写。然而,使用queryset定义对象的过滤列表可以更具体地指定视图中可见的对象(有关QuerySet对象的更多信息请参阅查询,完整详细信息请参阅基类的视图参考)。举个简单的例子,我们可能希望按发布日期订购图书列表,第一个为最新的图书:

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'

请注意,除了queryset我们还使用了自定义模板名称。 如果我们没有定义自定义模板名称,通用视图将使用与“vanilla”对象列表相同的模板(这可能不是我们想要的)。

还要注意,这不是一个优雅的列出出版商专有书籍的方式。 如果我们要添加另一个出版商页面,那么我们在URLconf中添加另外一行,如果有很多出版社那么将很难处理。 我们将在下一节中处理这个问题。

注意:
如果请求 /book/acme/ 时返回 404 ,需要检查是否存在名为“ACME Publishing'的出版商。通用视图有一个 allow_empty 参数用于这种情况,详见类视图参考

动态过滤


另一个常见的需求是通过URL中的某些主键过滤列表页面中给出的对象。 以前我们在URLconf中硬编码出版商的名字,但是如果我们想写一个显示任意出版社的所有书籍的视图呢?

很方便,ListView有一个可以覆盖的get_queryset()方法。 之前,它仅仅返回了queryset属性的值,但现在我们可以添加更多的逻辑。

实现这个工作的关键在于调用类视图时,有用的内容都被存储到self中; 它包括请求(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)

正如我们看到的,为queryset添加更多逻辑非常简单。如果我们愿意,我们将使用self.request.user来过滤当前用户,或者使用更多的复杂逻辑。

我们也可以同时将出版社添加到内容中,这样我们就可以在模板中使用它们:

# ...

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

实现额外工作


最后一个常见的模式是在调用通用视图之前或之后实现一些额外的工作。

想象一下,Author模型中有一个last_accessed字段用来跟踪最近一次查看该作者的时间:

# 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()

当然,通用DetailView类不知道该字段的任何信息,但是我们可以写一个自定义视图来更新这个字段。首先,我们需要在URLconf中添加一个作者详细信息部分来指向自定义视图:

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-这是DetailView用于查找筛选查询集的主键的默认名称。

如果想调用组的其他内容,可以在视图中设置pk_url_kwarg。 详细信息可以在DetailView中找到。

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

推荐阅读更多精彩内容