常使民无知、无欲,使夫智者不敢为也。为无为,则无不治
——老子《道德经》
首先,要说这是Django基础部分的最后一个章节了。后面会详细讲解Django每一部分的使用方式和API了
本节内容
- 内置视图处理对象的定义
- 内置视图处理对象的使用
- 源代码分析
1. 内置视图处理对象的定义
对于我们视图处理函数,从最原始的HttpResponse()
来进行页面数据的输出,到render()
函数进行模板页面的操作,已经是可以满足我们的需求了,但是Django赶脚还不够,不够~所以提供了常规的一些页面视图对象对页面模板和模板中要展示的数据进行了封装处理。
常规情况下,对应页面中要展示数据列表的视图,通过继承Django的django.views.generic.ListView
对象来实现,页面中要展示数据信息的视图,通过继承Django的django.views.generic.DetailView
进行实现。
我们改造polls/views.py
视图模块如下:
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.views import generic
from django.urls import reverse
from .models import Question
# 封装:定义展示应用首页的处理类
class IndexView(generic.ListView):
# 模板路径
template_name = "index.html"
# 指定变量名称,保存查询到的数据
context_object_name = "question_list"
# 列表页面,需要查询对应的数据集合
def get_queryset(self):
return Question.objects.order_by("-pub_date")
# 封装:定义展示问题详细信息的处理类
class DetailView(generic.DetailView):
# 指定要展示的数据类型
model = Question
# 指定展示的模板页面
template_name = "details.html"
# 封装:定义展示结果的视图处理类
class ResultView(generic.DetailView):
model = Question
template_name = "results.html"
# 定义投票结果
def vote(request, question_id):
# 获取查询的问题对象
question = get_object_or_404(Question, pk=question_id)
print(request.POST)
try:
select_choice = question.choice_set.get(pk=request.POST['choice'])
except Exception as e:
print(e)
return render(request, "details.html",
{
"question": question,
"error_message": "你的问题还没有发布解决方案"
})
else:
# 投票数量增加1
select_choice.votes += 1
# 保存到数据库
select_choice.save()
# 代码中使用配置的方式跳转到指定的路由
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
上述代码中,对于展示问题列表的首页,继承了
generic.ListView
内置类,通过template_name
指定了HTML视图,通过context_object_name
指定了保存数据的变量,在get_queryset()
函数中定义了数据的查询方式
问题详细信息页面和问题解决方案的结果展示页面,继承了
generic.DetailView
内置类,通过model
指定了展示的数据的类型,template_name
指定了HTML视图的名称
vote函数暂时不做任何处理,这是一个包含了业务逻辑处理的视图函数
2. 视图对象的使用
经过上述的改造,我们可以看到视图处理函数已经变得非常的简洁,但是存在这样一个问题,IndexView
处理类中的数据,我们可以看懂是通过get_queryset()
函数进行查询的,然后自动复制给变量question_list
,那么后面的DetailView
和ResultView
中的数据是怎么来的呢?
DetailView
和ResultView
中的要查询的数据,同样也是通过主键编号进行查询的,默认的变量名称是pk
,所以在路由中要进行如下的修改配置,才可以正常使用
改造polls/urls.py
路由模块
from django.conf.urls import url
from . import views
app_name = "polls"
urlpatterns = [
# url(r"^$", views.index, name="index"),
# url(r"^(?P<question_id>\d+)$", views.detail, name="detail"),
# url(r"^(?P<question_id>\d+)/results/$", views.results, name="results"),
url(r"^$", views.IndexView.as_view(), name="index"),
url(r"^(?P<pk>\d+)/$", views.DetailView.as_view(), name="detail"),
url(r"^(?P<pk>\d+)/results/$", views.ResultView.as_view(), name="results"),
url(r"^(?P<question_id>\d+)/vote/$", views.vote, name="vote"),
]
这里路由配置的过程中,通过
as_view()
函数,会自动对目标视图类进行封装,将视图处理类中的template_name
模板进行加载,然后渲染由model
或者context_object_name
指定的数据。
2.1. 项目测试
重启项目,打开网页进行访问
3. 源码分析
此时,会有个问题困扰着我们,为什么要继承视图处理类,为什么要继承不同的处理类ListView
和DetailView
?
这个问题就对了,我们看看Django源代码
首先查看ListView
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
"""
Render some list of objects, set by `self.model` or `self.queryset`.
`self.queryset` can actually be any iterable of items, not just a queryset.
"""
......
class MultipleObjectMixin(ContextMixin):
"""
A mixin for views manipulating multiple objects.
"""
allow_empty = True
queryset = None
model = None
paginate_by = None
paginate_orphans = 0
context_object_name = None
paginator_class = Paginator
page_kwarg = 'page'
ordering = None
......
def get_queryset(self):
"""
Return the list of items for this view.
The return value must be an iterable and may be an instance of
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
"""
......
我们可以从上述代码中看到,继承自ListView视图处理类的类型,已经在内部封装好了分页操作功能,这也是我们项目中经常用到的技术呢,非常棒吧,我们继承了这个视图处理对象,就可以在后续的项目中,通过重写它的属性和方法来快捷的完成数据展示的分页功能了。
接下来,我们继续看DetailView
是怎么操作的吧
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
"""
Render a "detail" view of an object.
By default this is a model instance looked up from `self.queryset`, but the
view will support display of *any* object by overriding `self.get_object()`.
"""
......
class SingleObjectMixin(ContextMixin):
"""
Provides the ability to retrieve a single object for further manipulation.
"""
model = None
queryset = None
slug_field = 'slug'
context_object_name = None
slug_url_kwarg = 'slug'
pk_url_kwarg = 'pk'
query_pk_and_slug = False
......
我们可以在源代码中,看到各种操作的一些信息,官方注释用于使用对象数据来渲染详细信息页面的视图对象,继承的父类中,也定义了各种属性字段用于控制单个对象数据的展示操作
以上两个视图类,都间接继承了View
内置类,我们看一下源代码
# 基础视图对象工具类,用于进行视图的渲染操作,包含了as_view()处理函数
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
......
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
for key in initkwargs:
......
# 用于进行视图模板和数据混合渲染的工具类,可以看到包含了template_name字段
class TemplateResponseMixin(object):
"""
A mixin that can be used to render a template.
"""
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
......
# 用于封装基础模板渲染页面视图的工具类
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
A view that renders a template. This view will also pass into the context
any keyword arguments passed by the URLconf.
"""
......
# 用于请求最终重定向的视图工具类
class RedirectView(View):
"""
A view that provides a redirect on any GET request.
"""
......
从基础文件
view.py
中,我们可以看到,各种视图模板的基础处理操作和视图类型的封装都有了简洁的定义,我们需要做的就是在项目中,继承这些Django已经封装好的对象,快捷的完成项目的开发。