Python 学习笔记

通用视图

2018-02-12  本文已影响6人  大爷的二舅

这里又一次是这本书的反复出现的主题:最糟糕的是,Web开发是枯燥而单调的。 到目前为止,我们已经介绍了Django如何试图在模型和模板图层中消除一些单调,但Web开发人员在视图级别也会遇到这种无聊。

Django的通用视图被开发来缓解这种痛苦。

他们采取一些在视图开发中常见的习惯用法和模式,并将它们抽象化,以便您可以快速编写数据的通用视图,而无需编写太多的代码。 我们可以识别某些常见任务,如显示对象列表,编写显示任何对象列表的代码。

然后,所讨论的模型可以作为URLconf的额外参数传递。 Django附带通用显示视图来执行以下操作:

对象的通用视图

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):
        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):
        return self.name

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

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

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

class PublisherList(ListView):
    model = Publisher 

最后,将该视图嵌入您的网址:

# 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 - “书籍”部分来自定义模型的应用名称,而“发布者”位仅仅是模型名称的小写版本。

因此,当(例如)在模板中将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 %}

这就是它的全部。 通用视图的所有很酷的功能都来自更改通用视图上设置的属性。 附录C详细记录了所有通用视图及其选项; 本文档的其余部分将考虑您可能定制和扩展通用视图的一些常用方法。

制作“友好”的模板上下文

您可能已经注意到,我们的示例发布者列表模板将所有发布者存储在名为object_list的变量中。 虽然这很好,但对模板作者来说并不是那么“友好”:他们必须“知道”他们在这里与出版商打交道。

在Django中,如果你正在处理模型对象,这已经为你完成了。 在处理对象或查询集时,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之后),则该类的任何子类也将需要 如果他们想要确保覆盖所有父母,可以在超级后明确地设置它。 如果遇到问题,请查看视图的方法解析顺序。

查看对象的子集

现在让我们仔细看看我们一直在使用的模型参数。 指定视图将操作的数据库模型的模型参数可在所有对单个对象或对象集合进行操作的通用视图中使用。 但是,模型参数并不是指定视图操作对象的唯一方法 - 您也可以使用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来定义过滤的对象列表,您可以更具体地了解在视图中可见的对象。 举一个简单的例子,我们可能要按出版日期排列一个图书清单,最近的一个是:

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'

请注意,除了过滤的查询集之外,我们还使用自定义模板名称。 如果我们不这样做,通用视图将使用与“vanilla”对象列表相同的模板,这可能不是我们想要的。

另外请注意,这不是用于制作特定于发布商的书籍的优雅方式。 如果我们想要添加另一个发布者页面,那么我们需要在URLconf中添加另外几行代码,而不止是添加一些发布者这会变得不合理。 我们将在下一节讨论这个问题。

动态过滤

另一个常见的需求是通过URL中的某个键过滤列表页面中给定的对象。 之前,我们在URLconf中对发布者的名称进行了硬编码,但是如果我们想编写一个显示某个任意发布者的所有书籍的视图,该怎么办?

有用的是,ListView有一个我们可以覆盖的get_queryset()方法。 以前,它刚刚返回queryset属性的值,但现在我们可以添加更多的逻辑。 使这项工作的关键部分是,当调用基于类的视图时,各种有用的东西都存储在自己上; 以及请求(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来过滤使用当前用户或其他更复杂的逻辑。 我们也可以将发布者同时添加到上下文中,因此我们可以在模板中使用它:

# ...

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 ## Performing Extra Work

我们要看的最后一个常见模式涉及在调用通用视图之前或之后做一些额外的工作。 想象一下,我们在我们的作者模型上有一个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的参考资料中找到。

下一步是什么?
在本章中,我们只查看Django附带的几个通用视图,但这里介绍的一般想法非常适合任何通用视图。 附录C详细介绍了所有可用的视图,如果您想充分利用这个强大的功能,建议阅读。

本书结束部分专门介绍模型,模板和视图的高级用法。 以下章节涵盖了现代商业网站中很常见的一系列功能。 我们将从建立互动网站 - 用户管理至关重要。

上一篇 下一篇

猜你喜欢

热点阅读