Django的视图可以分为:
def index(request):
class AboutView(TemplateView):
早期,人们在视图开发中发现了一些常见的习语和句式,也就是重复性代码和工作。于是引入了基于函数的视图来抽象这些模式,便于一般情况下的视图开发。
基于函数的视图的问题在于,尽管它们覆盖了简单的情况,但是除了一些简单的配置选项之外,没有办法扩展或定制它们,限制了它们在许多现实应用中的实用性。
基于类的通用视图与基于函数的视图的目标相同,都是想让视图开发更容易。但由于类视图可以使用MIXIN等一些面向对象的方法和工具包,使得基于类的视图比基于函数的视图更具扩展性和灵活性。
基于类的视图:
两种视图可以实现同样的功能,本质上是一个东西,没有谁好谁坏之分,只是适用场景不同而已:
Django 提供了很多适用于各种应用场景的基本视图类,我们一般不从头自己写起,这些类视图都继承django.views.generic.base.View
类。比如RedirectView
用于 HTTP 重定向,TemplateView
用于渲染模板。
类视图有很多简单的用法,甚至不需要去views.py中编写代码,比如下面的例子:
from django.urls import path from django.views.generic import TemplateView urlpatterns = [ path('about/', TemplateView.as_view(template_name="about.html")), ]
重点关注:
TemplateView
类视图as_view()
方法template_name
参数更通用的使用方法是继承Django提供的各种视图类,所以上面的例子的一般性写法如下:
# some_app/views.py from django.views.generic import TemplateView class AboutView(TemplateView): template_name = "about.html"
但是,由于类不是函数,所以需要在URLconf中使用as_view()
这个类方法将一个基于类的视图转换成函数形式的接口。
# urls.py from django.urls import path from some_app.views import AboutView urlpatterns = [ path('about/', AboutView.as_view()), ]
上面的AboutView视图不涉及模型的访问,比较简单。让我们看一个书籍列表视图的例子:
首先是路由:
from django.urls import path from books.views import BookListView urlpatterns = [ path('books/', BookListView.as_view()), ]
BookListView
视图:
from django.http import HttpResponse from django.views.generic import ListView from books.models import Book class BookListView(ListView): model = Book # 指定模型 def head(self, request, *args, **kwargs): last_book = self.get_queryset().latest('publication_date') response = HttpResponse() # RFC 1123 date format response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT') return response
这个例子中,如果用户通过GET请求数据,那么将正常的返回响应数据(此处省略)。而如果通过HEAD请求,将使用我们写的head方法中的业务逻辑。
本质上来说,基于类的视图允许你使用不同的实例方法响应不同的HTTP 请求,而不是在单个视图函数里使用if/else
代码。
在函数视图里处理 GET
请求的代码像下面这样:
from django.http import HttpResponse def my_view(request): if request.method == 'GET': # <view logic> return HttpResponse('result')
而在类视图中,则通过不同过的实例方法来处理:
rom django.http import HttpResponse from django.views import View class MyView(View): def get(self, request): # <view logic> return HttpResponse('result')
上面的继承关系非常重要,不能从头写一个新类,或者随便自己瞎继承一个类,否则你无法和Django系统勾连。
每个类视图都有一个as_view()
方法,用于在urlconf
中进行dispatch。这个方法会创建一个类视图的实例,并调用它的dispatch()
方法。dispatch方法会在类中查找类似GET\POST之类的类方法,然后与请求request中的HTTP方法匹配。匹配上了就调用对应的代码,匹配不上就弹出异常HttpResponseNotAllowed
,也就是不允许当前的请求类型。
# urls.py from django.urls import path from myapp.views import MyView urlpatterns = [ path('about/', MyView.as_view()), # 注意as_view要加括号进行调用 ]
至于return返回的什么,和函数视图是一样样的。
as_view()
方法可以传递参数,例如:
urlpatterns = [ path('view/', MyView.as_view(size=42)), ]
传递给视图的参数会在视图的每个实例之间共享。这意味着你不应使用列表、字典或任何其他可变对象作为视图的参数。如果你这样做并且共享对象被修改,则会导致一个请求对另外一个请求产生影响,这显然存在极大的风险,完全不可接受。
每个MyView视图的实例都可以使用
self.size
接收传入的参数值42,但该参数必须已经在类中定义了。
基于类的视图不强制你添加任何类属性,当需要的时候,有两种方法来配置或设置类属性。
第一种是继承父类,在子类中重写父类的属性,如下所示:
父类:
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"
注意其中的greeting类属性。
另一种就是简单粗暴的在URLconf路由条目中修改as_view()方法的参数。当然,参数名必须是存在的类属性,你不能随便瞎写!如下所示:
urlpatterns = [ path('about/', GreetingView.as_view(greeting="G'day")), ]
但是这种方式有很大的弊端,那就是虽然每次匹配上了url,你的类视图都会被实例化一次,但是你的URLs却是在被导入的时候才配置一次,也就是as_view()方法的参数的配置只有一次。也就是说这么做,就不能再改了!所以,不要偷懒,使用子类吧。
混入是一种多父类继承的形式,其基础理论知识请参考我的Python教程中多继承相关章节。
在编写子类的时候,一个父类负责类的主体结构、主要行为和主要属性,其它的功能都通过各种MIXin父类提供。
MIXIN是跨多个类重用代码的一个很好的方法,但是它们会带来一些代价。你的代码散布在MIXIN中越多,阅读子类就越难,很难知道它到底在做什么。如果你在对具有多级继承树的子类进行分类,就更难以判断子类的方法到底继承的是哪个先祖,俗称‘家谱乱了’。
需要注意的是你的父类中只有一个类可以继承最顶级的View类,其它的必须以mixin方法混入。
一个用来处理表单的函数视图通常是下面这样的:
from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import MyForm def myview(request): if request.method == "POST": form = MyForm(request.POST) if form.is_valid(): # <process form cleaned data> return HttpResponseRedirect('/success/') else: form = MyForm(initial={'key': 'value'}) return render(request, 'form_template.html', {'form': form})
而如果用类视图来实现,是这样的:
from django.http import HttpResponseRedirect from django.shortcuts import render from django.views import View from .forms import MyForm class MyFormView(View): form_class = MyForm initial = {'key': 'value'} template_name = 'form_template.html' 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(): # <process form cleaned data> return HttpResponseRedirect('/success/') return render(request, self.template_name, {'form': form})
看起来类视图好像比函数视图代码多了很多,复杂了很多,貌似没啥优点啊。但是如果你有多个类似的视图需要编写,那么你就可以发挥子类的继承和复写神操作了,分分钟整出个新的视图来。或者直接在URLConf中修改参数!或者两种操作同时使用!
其实,到现在你应该理解,类视图适用于大量重复性的视图编写工作,在简单的场景下,没几个视图需要编写,或者各个视图差别很大的情况时,还是函数视图更有效!所以,不要认为类视图是多么高大上的东西,人云亦云!
除了mixin,还可以使用装饰器扩展类视图。装饰器的工作方式取决于你是在创建子类还是使用as_view()。
用法一,在URLConf中直接装饰:
from django.contrib.auth.decorators import login_required, permission_required from django.views.generic import TemplateView from .views import VoteView urlpatterns = [ path('about/', login_required(TemplateView.as_view(template_name="secret.html"))), path('vote/', permission_required('polls.can_vote')(VoteView.as_view())), ]
用法二,在类视图中装饰指定的方法:
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.generic import TemplateView class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs)
注意:
method_decorator
这个装饰器的装饰器方法将装饰器运用在类方法上。感觉很绕?其实就是说,我们有很多很多的装饰器,但其中有一些不能直接装饰dispatch这种类方法。那怎么办呢?套层壳!用method_decorator
装饰器包裹起来,假装成一个能用的。有时候,简单地用一下,可以写成下面的精简版:
@method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html'
有时候,可能你需要对一个对象应用多个装饰器,正常做法是:
@method_decorator(never_cache, name='dispatch') @method_decorator(login_required, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html'
为了偷懒,我们可以这么做:
decorators = [never_cache, login_required] @method_decorator(decorators, name='dispatch') class ProtectedView(TemplateView): template_name = 'secret.html'
唯一需要注意地是装饰器是有先后顺序的。上面的例子中,never_cache
就要先于login_required
被调用。
最后,使用method_decorator
有时会导致TypeError
异常,因为参数传递的原因。
打卡