视图是可调用的,它接收请求并返回响应。这可能不仅仅是一个函数,Django提供了一些可用作视图的类的示例。这些允许您通过利用继承和mixin来构建视图并重用代码。
基于类(class-based
)的视图提供了另一种方法,将视图实现为Python对象而不是函数。 它们不会替换基于函数(function-based
)的视图,但与基于函数的视图相比具有一定的差异和优势:
一般而言,如果要对不同的HTTP请求做出不同的相应的话,function-based views会在单一的函数中采用判断分支的方法,比如:
from django.http import HttpResponse
def index(request):
if request.method == 'GET':
#
return HttpResponse('result')
if request.method == 'POST':
#
return HttpResponse('result')
如果使用基于类的视图:
from django.http import HttpResponse
from django.views import View
class IndexView(View):
def get(self, request):
return HttpResponse('get result')
def post(self, request):
return HttpResponse('post result')
这里IndexView继承了class django.views.generic.base.View
类,它的属性和方法:
http_method_names
:视图支持的HTTP请求方法,默认['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
as_view(**initkwargs)
:返回一个可调用对象,参数为request
返回值是响应:
response = MyView.as_view()(request)
dispatch(request, *args, **kwargs)
:检查HTTP方法并尝试委托给与HTTP方法匹配的方法; 一个GET将被委托给get()
,一个POST到post()
,依此类推。
http_method_not_allowed(request, *args, **kwargs)
:视图接收到不支持的HTTP请求方法时返回的错误,默认HttpResponseNotAllowed
options(request, *args, **kwargs)
:处理HTTP的OPTION请求,返回响应中的ALLOW
首部会包含视图支持的请求方法;
django的URL解析器需要将request和相应的参数传递给一个可调用的函数,而不是一个类。所以class-based view提供一个类方法:as_view()
来解决这个问题,as_view()方法让你可以把类当做函数来调用。as_view创建一个类实例,然后调用它的dispatch
方法,dispatch分析出request是GET、POST或者其他,然后将request匹配给相应的函数,比如将POST请求匹配给post()函数,如果给函数没有定义的话,将引发HttpResponseNotAllowed错误。
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
# path('', views.index, name='index'),
path('', views.IndexView.as_view()),
]
虽然小型的class-based view并不需要依靠类属性来完成它的工作,但是类属性在很多的基于类的设计中都很有用。设置类属性有两个方法。第一个方法是标准的python方法:在子类中重写
类的属性和方法,比如在父类中有greeting
属性,在子类中就可以重写:
from django.http import HttpResponse
from django.views import View
class GreetingView(View):
greeting = "Good Day"
def get(self, request):
return HttpResponse(self.greeting)
class MorningGreetingView(GreetingView):
greeting = "Morning to ya"
另一种方法就是URLconf中将类属性作为参数传递给as_view():
urlpatterns = [
path('about/', GreetingView.as_view(greeting="G'day")),
]
Mixins
是多重继承(multiple inheritance)的一种形式,其中可以组合多个父类的行为和属性。例如,在基于类的通用视图中,有一个名为TemplateResponseMixin的mixin,其主要目的是定义render_to_response()方法。当与View基类的行为结合使用时,结果是一个TemplateView类:它拥有分析request
并作出相应匹配的方法(原本定义在View中的行为),也拥有一个接受一个template_name并返回一个TempalteReponse对象的render_to_response()方法(原本定义在 TemplateResponseMixin中的行为)
Mixins是在多个类中重用代码的绝佳方法,但它们需要一些代价。代码就是使用多重继承的次数越多,读取子类就越难以知道它正在做什么,并且如果你继承了一个具有一个类的东西,那么知道哪些方法可以覆盖哪些方法就更难了另请注意,您只能从一个通用视图继承 - 也就是说,只有一个父类可以从View继承,其余的(如果有的话)应该是mixins。尝试从多个继承自View的类继承 - 例如,尝试在列表顶部使用表单并组合ProcessFormView和ListView - 将无法按预期工作。
使用基于类的视图处理表单, 原视图:
@login_required
def new_topic(request):
"""添加新的主题"""
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = TopicForm(request.POST)
# check whether it's valid:
if form.is_valid():
# 添加数据到数据库
topic = Topic(text=request.POST.get('text'), owner=request.user)
topic.save()
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return redirect('learning_logs:my_topics')
# return HttpResponseRedirect(reverse('learning_logs:my_topics'))
# if a GET (or any other method) we'll create a blank form
else:
form = TopicForm()
return render(request, 'new_topic.html', {'form': form})
使用基于类的视图:
# new_topic 的 基于类的视图
# 装饰一个类
decorators = [never_cache, login_required]
# @method_decorator(login_required, name='dispatch')
# @method_decorator(never_cache, name='dispatch')
@method_decorator(decorators, name='dispatch')
class NewtopicView(View):
form_class = TopicForm
initial = {}
template_name = 'new_topic.html'
# 装饰类的每一个实例
# @method_decorator(login_required)
# def dispatch(self, *args, **kwargs):
# return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
topic = Topic(text=request.POST.get('text'), owner=request.user)
topic.save()
return redirect('learning_logs:my_topics')
return render(request, self.template_name, {'form': form})
使用装饰器装饰类视图的方法和装饰函数的方法不一样,需要method_decorator
来将其进行转化:
path('new_topic/', login_required(views.NewtopicView.as_view()), name='new_topic'),
method_decorator
装饰类的dispatch
方法;decorators
装饰器列表,选择多个装饰器,在调用时会按顺序
执行装饰;在Web应用程序开发过程中可能遇到一些重复的模式。Django的通用视图(generic views)正是为了解决这些枯燥的工作而被开发的。它们采用视图开发中的某些常用习语和模式并对其进行抽象,以便能快速编写数据的公共视图,而无需编写太多代码。
Django附带通用视图来执行以下操作:
TalkListView
和RegisteredUserListView
就是列表视图的示例。单个谈话页面就是我们称之为“细节”视图的一个例子。总之,这些视图提供了简单的界面来执行开发人员遇到的最常见任务。
django.views.generic.base.TemplateView
呈现给定模板,其中包含在URL中捕获的参数的上下文。它继承了这3个类:
它的方法get_context_data()
会返回表示模板上下文的字典。 提供的关键字参数将构成返回的上下文。使用方法:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['number'] = random.randrange(1, 100)
return context
TemplateView当然很有用,但Django的通用视图在呈现数据库内容的视图时确实很有用。 因为它是如此常见的任务,Django附带了一些内置的通用视图,这些视图使得生成对象的列表和详细视图非常容易。
例如,现在需要一个页面显示所有topics:
from django.views.generic import ListView
class Topics(ListView):
template_name = 'topic_list.html'
model = Topic
template_name
指定使用的模板,model
指定使用的模型,在URLconf设置使用基于类的通用视图:
urlpatterns = [
# ...
# path('topics/', views.topics, name='topics'),
path('topics/', views.Topics.as_view(), name='topics'),
]
这就是所有需要Python代码,而在模板中要使用object_list
来指明包含topics的列表:
<ol>
{% for topic in object_list %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic.text }}a><br/>
li>
{% endfor %}
ol>
这就是它的全部内容。通用视图的所有很酷的功能都来自更改通用视图上设置的属性。
若是要进行分页显示,需要添加参数:
class Topics(ListView):
# 指定模板
template_name = 'topic_list.html'
model = Topic
# 每页显示数量
paginate_by = 10
# 最后一页最多显示
paginate_orphans = 15
# 分页排序的标准
ordering = 'date_added'
# 修改默认的object_list名称
context_object_name = 'topic_list'
而且使用Django自带的分页使用的URL模式为:path('topics/page
,或者还是用path('topics/', views.Topics.as_view(), name='topics'),
,用/objects/?page=3
的方式进行查找。
其传递给模板的页面对象为page_obj
具体查看ListView
继承的MultipleObjectMixin
修改模板
{% block content %}
<ol>
{% for topic in topic_list %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic.text }}a><br/>
li>
{% endfor %}
ol>
{# 使用django-bootstrap #}
<div>
{% bootstrap_pagination page_obj url="?page=1" size="small"%}
div>
{% endblock %}
指定model的值model = Topic
,实质上是为了获取queryset
查询的结果集,等价于queryset = Topic.objects.all()
可以直接指定结果集:
class Topics(ListView):
# 指定模板
template_name = 'topic_list.html'
queryset = Topic.objects.order_by('-date_added')
可以继承这个类来构建其它视图:
class MyTopics(Topics):
"""基于类的视图,继承Topics显示自己的topics"""
def get_queryset(self):
"""筛选 作者为当前用户 返回queryset"""
return Topic.objects.filter(owner=self.request.user).order_by('-date_added')
只需要设置queryset
就能显示‘自己’的topics。
DetailView
提供检索单个对象以进行进一步操作的功能。渲染对象的“细节”视图。默认情况下,这是从self.queryset
查找的模型实例,但是view将通过覆盖self.get_object(self, queryset=None):
来支持显示任意对象。
如现在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()
那么在定义好URLconf为基于类的视图后调用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