编写Web应用程序可能是单调的,因为我们一次又一次地重复某些模式。Django试图在模型和模板层中消除一些单调,但Web开发人员也在视图级别遇到这种无聊。
Django的通用视图是为了缓解这种痛苦而开发的。它们采用视图开发中的某些常用习语和模式并对其进行抽象,以便您可以快速编写数据的公共视图,而无需编写太多代码。
我们可以识别某些常见任务,例如显示对象列表,以及编写显示任何对象列表的代码。然后,可以将有问题的模型作为额外参数传递给URLconf。
Django附带通用视图来执行以下操作:
- 显示单个对象的列表和详细信息页面。
- 在年/月/日归档页面,相关详细信息和“最新”页面中显示基于日期的对象。
- 允许用户创建,更新和删除对象 - 无论是否授权。
扩展通用视图
- 使用通用视图可以大大加快开发速度。然而,在大多数项目中,通用视图不再足够。实际上,新Django开发人员提出的最常见的问题是如何使通用视图处理更广泛的情况。
- 现在,扩展通用视图的推荐方法不是在URLconf中传递大量配置,而是将它们子类化,并覆盖它们的属性或方法。
- 通用视图将有一个限制。如果您发现自己难以将视图实现为通用视图的子类,那么您可能会发现使用您自己的基于类或功能的视图编写所需的代码会更有效。
对象的通用视图
但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, 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.py
from django.urls import path
from books.views import PublisherList
urlpatterns = [
path('publishers/', PublisherList.as_view()),
]
此模板将针对包含称为object_list包含所有发布者对象的变量的上下文呈现 。
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
制作“友好”的模板上下文
您可能已经注意到,我们的示例发布者列表模板将所有发布者存储在名为的变量中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'
添加额外的上下文
通常,您只需要提供除通用视图提供的信息之外的一些额外信息。例如,考虑在每个发布者详细信息页面上显示所有书籍的列表。该 DetailView通用视图提供了出版商到上下文,但我们如何在模板中获取更多的信息?
DetailView 并提供您自己的get_context_data方法实现。默认实现只是将显示的对象添加到模板中,但您可以覆盖它以发送更多:
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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之后),如果他们想要确保覆盖所有父项, 那么该类的任何子类还需要在super之后显式设置它。
查看对象的子集
现在让我们仔细看看model我们一直在使用的论点。该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()
要选择一个简单的示例,我们可能希望按发布日期订购图书列表,最新的第一个:
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'
动态过滤
另一个常见的需求是通过URL中的某个键过滤列表页面中给出的对象。之前我们在URLconf中对发布者的名称进行了硬编码,但如果我们想编写一个显示某个任意发布者的所有书籍的视图呢?
很方便,ListView有一个get_queryset()我们可以覆盖的 方法。以前,它刚刚返回queryset属性的值 ,但现在我们可以添加更多逻辑。
使这项工作的关键部分是,当调用基于类的视图时,存储各种有用的东西self; 以及request(self.request)这包括根据URLconf捕获self.args的基于位置()和基于名称的(self.kwargs)参数。
# urls.py
from django.urls import path
from books.views import PublisherBookList
urlpatterns = [
path('books/<publisher>/', PublisherBookList.as_view()),
]
# 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.kwargs['publisher'])
return Book.objects.filter(publisher=self.publisher)
我们也可以同时将发布者添加到上下文中,因此我们可以在模板中使用它:
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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()
DetailView当然,泛型类对此字段一无所知,但我们可以再次轻松编写自定义视图以更新该字段。
首先,我们需要在URLconf中添加一个作者详细信息位以指向自定义视图:
from django.urls import path
from books.views import AuthorDetailView
urlpatterns = [
#...
path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
]
然后我们编写新的视图 - get_object是检索对象的方法 - 所以我们简单地覆盖它并包装调用:
from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
此处的URLconf使用命名组pk- 此名称是DetailView用于查找用于过滤查询集的主键值的默认名称。
如果要将该组调用为其他内容,可以pk_url_kwarg 在视图上进行设置。