第五章 探究 CBV 视图
5.1 数据显示视图
数据显示视图是将后台的数据展示在网页上,数据主要来自模型,一共定义了4个视图类,分别说明如下:
- RedirectView:用于实现 HTTP 重定向,默认情况下只定义 GET 请求的处理方法。
- TemplateView:视图类的基础视图,可将数据传递给 HTML 模版,默认情况下只定义 GET 请求的处理方法。
- ListView:在 TemplateView 的基础上将数据以列表显示,通常将某个数据表的数据以列表表示。
- DetailView:在 TemplateView 的基础上将数据详细显示,通常获取数据表的单条数据。
5.1.1 重定向视图 RedirectView
视图类 RedirectView 用于实现 HTTP 重定向功能,即网页跳转功能。
视图类 RedirectView 定义了4个属性和8个类方法,分别说明如下:
- permanent:根据属性值的真假来选择重定向方式,若为 True ,则 HTTP 状态码为 301(永久重定向),否则为 302.
- url:代表重定向的路由地址。
- pattern_name:代表重定向的路由命名。如果设置参数 url,则无需设置该参数。
- query_string:是否将当前路由地址的请求参数传递到重定向的路由地址。
- get_redirect_url():根据属性 pattern_name 所指向的路由命名来生成相应的路由地址。
- get():触发 HTTP 的 GET 请求所执行的响应处理。
- 剩余的类方法都是 HTTP 的不同请求方式,它们都由 get() 方法完成响应处理。
由于类具有继承的特性,因此可以对视图类 RedirectView 进行功能扩展。
# App1/views.py 文件
class turnTo(RedirectView):
# 设置属性
permanent = False # 临时重定向
url = None
pattern_name = 'index:index'
query_string = True
# 重写 get_redirect_url
def get_redirect_url(self, *args, **kwargs):
print('This is get_redirect_url')
# super 是一个内置函数,它返回一个代理对象,用于调用父类的方法
return super().get_redirect_url(*args,**kwargs)
# 重写 get
def get(self,request,*args,**kwargs):
print(request.META.get('HTTP_USER_AGENT'))
return super().get(request,*args,**kwargs)
# App1/urls.py
path('turnTo',views.turnTo.as_view(),name='turnTo'),
<h3>Hello World</h3>
<a href="{% url 'index:turnTo' %}?k=1">ToTurn</a>
定义路由时,若使用视图类 turnTo 处理 HTTP 请求,则需要对视图类 turnTo 使用 as_view()方法,这是对视图类进行实例化处理。as_view()方法可在类 View 里找到具体的定义过程。
5.1.2 基础视图 TemplateView
视图类 TemplateView 是所有视图类里最基础的应用视图类,从视图类 TemplateView 的源码来看,它只定义了类方法 get(),该方法分别调用函数方法 get_context_data()和render_to_response(),从而完成 HTTP 请求的响应过程。类方法 get() 所调用的函数方法主要来自父类 TemplateResponseMixin 和 ContextMixin
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
视图类 TemplateView 的 get() 所调用的函数说明如下:
- 视图类 ContextMixin 的 get_count_data() 方法用于获取模版上下文内容,模版上下文是将图里的数据传递到模版文件,在由模版引擎将数据转换成 HTML 网页数据。
- 视图类 TemplateResponseMixin 的 render_to_response() 用于实现响应处理,由响应类TemplateResponse 完成。
在视图类 TemplateResponseMixin 的源码文件里找到视图类 TemplateResponseMixin 的定义过程,该类设置了 4 个属性和两个类方法,具体说明如下:
- template_name:设置模版文件的文件名。
- template_engine:设置解析模版文件的模版引擎。
- response_class:设置 HTTP 请求的响应类,默认值为响应类 TemplateResponse。
- content_type:设置响应内容的数据格式,一般情况下使用默认值即可。
- render_to_response():实现响应处理,由响应类 TemplateResponse 完成。
- get_template_names():获取属性 template_name的值。
# App1/views.py
class index(TemplateView):
template_name = 'index.html'
template_engine = None
content_type = None
extra_context = {'title':'This is GET'}
# 重新定义模版上下文的获取方式
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["value"] = 'I am MyDjango'
return context
# 定义 HTTP 的 POST 的请求处理方法
# 参数 request 代表 HTTP 的请求信息
def post(self,request,*args,**kwargs):
self.extra_context = {'title':'This is POST'}
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
# App1/urls.py
path('',views.index.as_view(),name='index'),
<h3>Hello,{{ title }}</h3>
<div>{{ value }}</div>
<br>
<form action="" method="post">
{% csrf_token %}
<input type="submit" value="Submit">
</form>
上述代码是将网站首页的视图函数 index 改为视图类 index,自定义视图类 index 继承视图类 TemplateView ,并重设了 4 个属性,重写类两个类方法,具体说明如下:
- template_name:将模版文件'index.html'作为网页文件。
- template_engine:设置解析模版文件的模版引擎,默认值为 None,即默认使用配置文件的 TEMPLATES 所设置的模版引擎 BASKEND。
- content_type:设置响应内容的数据格式,默认值为 None,即代表数据格式为 text/html。
- get_context_data():继承并重写视图类 TemplateView的类方法,在变量 context 里新增数据 value。
- extra_context:为模版文件的上下文设置变量值,可将数据转换为网页数据展示在浏览器上。
- post():自定义 POST 请求的处理方法,当触发 POST 请求时,将会重设置属性 extra_context 的值,并调用 get_context_data() 将属性 重新写入,从而实现动态改变模版上下文的数据内容。
5.1.3 列表视图 ListView
已知视图是连接路由和模版的中心枢纽,除此之外,视图还可以连接模型。简单来说,模型是指 Django 通过一定的规则来映射数据库,从而方便 Django 与数据库之间实现数据交互,这个交互过程是在视图里实现的。
由于视图可以与数据库实现数据交互,因此 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.
"""
从视图继承的方式来看,它继承两个不同的父类。视图类 ListView 的底层类是由 TemplateResponseMixin 和 ContextMixin 和 View 组成的,它具有视图类 TemplateView 的所有属性和方法。此外,视图类 ListView 新增了以下属性和方法:
- allow_empty:由 MultipleObjectMixin 定义,在模型查询数据不存在的情况下是否显示页面,若为 False 并且数据不存在,则引发 404 异常,默认值为 True。
- queryset:由 MultipleObjectMixin 定义,代表模型和查询对象,这是对模型对象进行查询操作所生成的查询对象。
- model:由 MultipleObjectMixin 定义,代表模型,模型以类表示,一个模型代表一张数据表。
- paginate_by:由 MultipleObjectMixin 定义,属性值为整数,代表每一页所显示的数据量。
- paginate_orphans:由 MultipleObjectMixin 定义,属性值为整数,默认值为0,代表最后一页可以包含的“溢出”的数据量,防止最后一页的数据量过少。
- context_object_name:由 MultipleObjectMixin 定义,设置模版上下文,即为模版变量进行命名。
- paginator_class:由 MultipleObjectMixin 定义,设置分页的功能类,默认情况下使用内置分页功能。
- ......
# App1/models.py
from django.db import models
# Create your models here.
class PersonInfo(models.Model):
id = models.AutoField(primary_key = True)
name = models.CharField(max_length=20)
age = models.IntegerField()
以上代码是创建一个模型对象,上述代码只是搭建了 PersonInfo 类和数据表 personinfo的映射关系,但在数据库中并没有生成相应的数据表,需要进一步操作在数据库中生成相应的数据表。
打开 PyCharm 的 Terminal 里依次输入数据迁移指令。
1、python manage.py makemigrations
2、python manage.py migrate
当指令执行完成后,数据库中就可以看到新创建的数据表。
<h3>{{ title }}</h3>
<table border="1">
{% for i in personinfo %}
<tr>
<th>{{ i.name }}</th>
<th>{{ i.age }}</th>
</tr>
{% endfor %}
</table>
<br>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="/?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
{% if page_obj.has_next %}
<a href="/?page={{ page_obj.next_page_number }}">下一页</a>
{% endif %}
<br>
<br>
<span class="page-current">
第{{ page_obj.number }}页,
共{{ page_obj.paginator.num_pages }}页。
</span>
</span>
</div>
{% endif %}
class index(ListView):
# 设置模版文件
template_name = 'index.html'
# 设置模型外的数据
extra_context = {'title':'人员信息表'}
# 查询模型 PersonInfo
queryset = PersonInfo.objects.all()
# 每页展示一条数据
paginate_by = 1
# 若不设置,则模版上下文默认为 personinfo_list
context_object_name = 'personinfo'
5.1.4 详细视图
视图类 DetailView 是将数据库某一条数据详细显示在网页上,它与视图类 ListView 存在明显的差异。
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()`.
"""
# App1/views.py
class index(DetailView):
# 设置模版文件
template_name = 'index.html'
# 设置模型外的数据
extra_context = {'title':'人员信息表'}
# 设置模型的查询字段
slug_field = 'age'
# 设置路由的变量名
slug_url_kwarg = 'age'
pk_url_kwarg = 'pk'
# 设置查询模型 PersonInfo
model = PersonInfo
# 属性 queryset 可以做简单的查询操作
queryset = PersonInfo.objects.all()
# 模版上下文的命名
context_object_name = 'personinfo'
# 是否将 pk 和 slug 作为查询条件
# query_pk_and_slug = False
5.2 数据操作视图
数据操作视图是对模型进操作,如增、删、改,从而实现 Django 与数据库的数据交互。数据操作视图有4个视图类,分别是 FormView、CreateView、UpdateView和DeleteView,说明如下:
- FormView:该视图类使用内置的表单功能,通过内置的表单功能实现数据新增。
- CreateView:实现模型的数据新增功能,通过内置的表单功能实现数据新增。
- UpdateView:实现模型的数据修改功能,通过内置的表单功能实现数据修改。
- DeleteView:实现模型的数据删除功能,通过内置的表单功能实现数据删除。
5.2.1 表单视图 FormView
视图类 FormView 是表单在视图里的一种使用方式,表单是搜集用户数据信息的各种表单元素的集合,作用是实现网页上的数据交互,用户在网站输入数据信息,然后提交到网站服务器端进行处理。
视图类 FormView 不仅具有视图类 TemplateView 的所有属性和方法,还新增了以下属性和方法:
- initial:由 FormMixin 定义,设置表单初始化的数据。
- form_class:由 FormMixin 定义,设置表单类。
- success_url:由 FormMixin 定义,设置重定向的路由地址。
- prefix:由 FormMixin 定义,设置表单前缀(即表单在模版的上下文),可在模版里生成表格数据。
- get_initial():由 FormMixin 定义,获取表单初始化的数据。
- ......
新建 form.py 文件
# form.py
from django import forms
from .models import PersonInfo
class PersonInfoForm(forms.ModelForm):
"""
Meta 是一个嵌套在 PersonInfoForm 类中的类,它是 Django 模型表单中的一个特殊类,用于定义表单如何与PersonInfo 模型关联
Meta 类中可以包含多个属性,其中最重要的是 model 和 fields
"""
class Meta:
model = PersonInfo # 指定了与之关联的模型
fields = '__all__' # 定义表单将包含哪些字段
# views.py
class index(FormView):
initial = {'name':'Betty','age':20} # 表单初始化数据
template_name = 'index.html' # 关联模版
success_url = '/result'
form_class = PersonInfoForm
extra_context = {'title':'人员信息表'}
def result(request):
return HttpResponse('Success')
# urls.py
path('',views.index.as_view(),name='index'),
path('result',views.result,name='result'),
<h3>{{ title }}</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="确定">
</form>
对上述代码做进一步的讲解:
- App1 的 form.py 文件里定义了表单类 PersonInfoForm,该表单是根据模型 PersonInfo 定义的模型表单,表单的字段来自模型的字段。
- 路由 index 的请求处理由视图类 FormView 完成,而路由 result 为视图类 index 的属性 success_url 提供路由地址。
- 视图类 index 仅设置了5个属性,属性 extra_context 的值对应模版上下文的 title,属性 form_class 所设置的表单在实例化之后可在模版里使用上下文 form.as_p 生成表格。
5.2.2 新增视图 CreateView
视图类 CreateView 是对模型新增数据的视图类,它是在表单视图类 FormView 的基础上加以封装的。它具有视图类 TemplateView、SingleObjectMixin 和 FormView的所有属性和方法,还新增或重写了以下属性和方法:
-
fields:由 ModelFormMixin 定义,设置模型字段,以列表表示,每个字段代表一个列表元素,可生成表单的数据列表,为用户提供数据输入。
-
get_form_class():由 ModelFormMixin 定义,重写 FormMixin 的方法,根据属性 fields 和 form_class 的组合情况进行判断
-
get_form_kwargs():由 ModelFormMixin 定义,重写 FormMixin 的方法。
-
get_success_url():由 ModelFormMixin 定义,重写 FormMixin 的方法,判断属性 success_url 是否为空,若为空,则从模型的内置方法 get_absolute_url() 获取重定向的路由地址。
-
form_valid():由 ModelFormMixin 定义,重写 FormMixin 的表单验证方法,新增表单数据保存到数据库的功能。
-
template_name_suffix:由 CreateView 定义,设置模版的后缀名,用于设置默认的模版文件。
视图类 CreateView 最大的特点在于 get_form_class() 方法,它是通过判断属性 form_class、fields 和 model,从而实现数据新增操作。其判断方法如下:
- 若fields 和 form_class 都不等于 None ,则抛出异常,提示不允许同时设置 fields 和 form_class 。
- 若 form_class 不等于 None,则返回 form_class。
- 若 form_class 等于 None,则从属性 model、object 和 get_queryset() 获取模型对象。同时,fields不能为 None,根据属性 fields 生成表单对象,由表单内置的 modelform_factory() 方法实现。
综上所述,视图类 CreateView 有两种表单生成方式。第一种是设置属性 form_class,这种方式需要开发者自定义表单对象;第二种是设置属性 model 和 fields,由模型对象和模型字段来生成相应的表单对象,生成的表单字段与模型的字段要求相符,可以减少异常情况发生,无需开发者自定义表单对象。
class index(CreateView):
initial = {'name':'Betty','age':13}
template_name = 'index.html'
success_url = '/result'
# 表单生成方式1
# form_class = PersonInfoForm
# 表单生成方式2
model = PersonInfo
fields = {'name','age'}
extra_context = {'title':'人员信息表'}
视图类 index 只需设置某些类属性即可完成模型数据的新增功能,整个数据新增过程都由视图类 CreateView 完成。
5.2.3 修改视图 UpdateView
视图类 UpdateView 是在视图类 FormView 和视图类 DetailView 的基础上实现的,它首先使用视图类 DetailView 的功能,通过路由变量查询数据表某条数据并显示在网页上,然后在视图类 FormView 的基础上,通过表单方式实现数据修改。
# urls.py
path('<age>.html',views.index.as_view(),name='index')
# views.py
class index(UpdateView):
template_name = 'index.html'
success_url = '/result'
model = PersonInfo
fields = {'name','age'}
# 设置模型的查询字段
slug_field = 'age'
# 设置路由变量名
slug_url_kwarg = 'age'
context_object_name = 'personinfo'
extra_context = {'title':'人员信息表'}
<h3>{{ title }}-{{ presoninfo.name }}</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="确定">
</form>
路由 index 的变量 age 对应视图类 index 的属性 slug_url_kwargs ,用于对模型字段 age 进行数据筛选。筛选结果将会生成表单 form 和 personinfo 对象。两者数据都是来自模型 PersonInfo。
5.2.4 删除视图 DeleteView
视图类 DeleteView 只能删除单挑数据,路由变量为模型主键提供查询范围,因为模型主键具有唯一性,所以通过主键查询能精准到某条数据。查询出来的数据通过 POST 请求实现数据删除。
<h3>{{ title }}-{{ presoninfo.name }}</h3>
<form method="post">
{% csrf_token %}
<div>删除{{ personinfo.name }}</div>
<input type="submit" value="确定">
</form>
#urls.py
path('<pk>.html',views.index.as_view(),name='index')
#views.py
class index(DeleteView):
template_name = 'index.html'
success_url = '/result'
model = PersonInfo
context_object_name = 'personinfo'
extra_context = {'title':'人员信息表'}
在上述的路由文件里的 pk 是一个默认值,代表着数据库的主键。
5.3 日期筛选视图
日期筛选视图是根据模型里的某个日期字段进行数据筛选的,然后将符合结果的数据以一定的形式显示在网页上。一共定义了 7 个日期视图类,说明如下:
-
ArchiveIndexView 是将数据表所有的数据以某个日期字段的降序方式进行排序显示的。
-
YearArchiveView 是在数据表筛选某个日期字段某年的所有数据,默认以升序的方式排序显示,年份的筛选范围由路由变量提供。
-
MonthArchiveView 是在数据表筛选某个日期字段某年某月的所有数据,默认以升序的方式排序显示,年份和月份的筛选范围都由路由变量提供。
-
WeekArchiveView 是在数据表筛选某个日期字段某年某周的所有数据,总周数是将一年的总天数除以7所得的,数据默认以升序的方式排序显示,年份和周数的筛选都是由路由变量提供的。
-
DayArchiveView 是对数据表的某个日期字段精准筛选到某年某月某天,将符合条件的数据以升序的方式排序显示,年份、月份和天数都是由变量提供的。
-
TodayArchiveView 是在视图类 DayArchiveView 的基础上进行封装处理的,它将数据表某个日期字段的筛选条件设为当天时间。符合条件的数据以升序的方式排序显示。
-
DateDetailView 是查询某年某月某日某条数据的详细信息,它在视图类 DetailView 的基础上增加了日期筛选功能,筛选条件主要有年份、月份、天数和某个模型字段,其中某个模型字段必须具有唯一性。
从日期筛选视图类的继承关系得知,它们的继承关系都由一定的相似之处,说明它们的属性和方法在使用上不会存在太大的差异。
5.3.1 月份视图 MonthArchiveView
视图类 MonthArchiveView 新增的属性和方法说明如下:
- template_name_suffix:由 MonthArchiveView 定义,设置模版后缀名,用于设置默认模版文件。
- date_list_period:由 BaseDateView 定义,经 BaseMonthArchiveView 重写,设置日期列表的最小单位,默认为 day。
- get_dated_items():由 BaseDateView 定义,经 BaseMonthArchiveView 重写,根据年份和月份在数据表查询符合条件的数据。
- ......
# views.py
class index(MonthArchiveView): # 继承日期归档的功能
allow_empty = True # 允许视图在没有相关对象的月份也能返回页面
allow_future = True # 允许视图显示未来日期的归档页面。
context_object_name = 'mylist' # 定义了模版上下文中传递给模版的对象的名称
template_name = 'index.html' # 指定视图所使用的模版文件名
model = PersonInfo # 指定视图与之关联的模型
date_field = 'hireDate' # 指定模型中用于归档的日期字段。
queryset = PersonInfo.objects.all() # 指定视图将使用的查询集
year_format = '%Y' # 指定年在 URL 中的格式
month_format = '%m'
paginate_by = 50 # 指定每个页面的条目数
# urls.py
path('<int:year>/<int:month>.html',index.as_view(),name='index')
<ul>
{% for v in mylist %}
<li>{{ v.hireDate }}:{{ v.name }}</li>
{% endfor %}
</ul>
<p>
{% if previous_month %}
Previous Month:{{ previous_month }}
{% endif %}
<br>
{% if next_month %}
Next Month:{{ next_month }}
{% endif %}
</p>
路由 index 在路由地址里设置变量 year 和 month,而且变量的数据类型都是整型,其中路由变量 month 可以设为字符型,不同的数据类型会影响视图类 MonthrchiveView 的属性的值。
5.3.2 周期视图
在一年中,无论是平年还是闰年,一共有 52 周,如果要对数据表的数据生成周报表,就需要根据当前年份的周数来计算相应的日期范围,这样可以大大降低开发效率。
视图类 WeekArchiveView 的继承过程共继承 10 个类,使用方法不做讲述。
本章小结
Django 植入了视图类这一功能,该功能封装了视图开发常使用的代码和模式,可以在无需编写大量代码的情况下,快速完成数据视图的开发,这种以类的形式实现响应与请求处理称为 CBV。
视图类是通过定义和声明类的形式实现的,根据用途划分 3 部分:数据显示视图、数据操作视图和日期筛选视图。