阅读(2966) (3)

基于类的视图简介

2020-06-13 11:11:41 更新

基于类的视图简介

基于类的视图提供了一种将视图实现为Python对象而非函数的替代方法。它们不能替代基于功能的视图,但是与基于功能的视图相比具有某些区别和优势:

  • 与特定HTTP方法(GET,POST等)相关的代码组织可以通过单独的方法而不是条件分支来解决。
  • 诸如mixin(多重继承)之类的面向对象技术可用于将代码分解为可重用的组件。

通用视图,基于类的视图和基于类的通用视图的关系和历史记录

开始时只有视图函数协定,Django将您的函数传递给,HttpRequest并期望将 传递给HttpResponse。这就是Django提供的功能。

早期就认识到在视图开发中发现了常见的习惯用法和模式。引入了基于函数的通用视图,以抽象化这些模式并简化常见情况下的视图开发。

基于函数的通用视图的问题在于,尽管它们很好地涵盖了简单的情况,但无法扩展或自定义某些配置选项之外的视图,从而限制了它们在许多实际应用程序中的用途。

创建基于类的通用视图的目的与基于函数的通用视图相同,以使视图开发更加容易。但是,通过使用mixins来实现解决方案的方式提供了一个工具包,该工具包使得基于类的通用视图比基于功能的对应视图更具可扩展性和灵活性。

如果您过去曾经尝试过基于函数的通用视图,但发现缺少这些功能,则不应将基于类的通用视图视为基于类的等效视图,而应将其视为解决通用视图旨在解决的原始问题的全新方法。解决。

Django用于构建基于类的泛型视图的基类和mixin工具包的构建具有最大的灵活性,因此,它们具有默认方法实现和属性形式的许多钩子,您可能不会在最简单的用法中关注它们案件。例如,实现不是form_class使用get_form方法的基于类的属性,而是使用了一种方法,该get_form_class方法调用一种方法,该方法在其默认实现中返回form_class类的属性。这为您提供了几个选项,用于指定从属性到完全动态,可调用的钩子使用哪种形式。对于简单情况,这些选项似乎增加了空心的复杂性,但是如果没有这些选项,则会限制更高级的设计。

使用基于类的意见

从本质上讲,基于类的视图使您可以使用不同的类实例方法来响应不同的HTTP请求方法,而不是使用单个视图函数中的有条件分支代码。

因此,GET在视图函数中用于处理HTTP的代码如下所示:

from django.http import HttpResponse

def my_view(request):
  if request.method == 'GET':
      # <view logic>
      return HttpResponse('result')

在基于类的视图中,这将变为:

from django.http import HttpResponse
from django.views import View

class MyView(View):
  def get(self, request):
      # <view logic>
      return HttpResponse('result')

因为Django的URL解析器希望将请求和关联的参数发送给可调用的函数而不是类,所以基于类的视图具有一个 as_view()class方法,该类方法返回一个函数,该请求可以在请求到达与关联模式匹配的URL时被调用。该函数创建该类的实例,调用 setup()以初始化其属性,然后调用其dispatch()方法。 dispatch查看该请求以确定它是否为GET, POST等,并将请求转发给匹配的方法(如果已定义),否则将其引发HttpResponseNotAllowed:

# urls.py
from django.urls import path
from myapp.views import MyView

urlpatterns = [
  path('about/', MyView.as_view()),
]

值得注意的是,您的方法返回的内容与您从基于函数的视图返回的内容相同,即的某种形式 HttpResponse。这意味着 http快捷方式或 TemplateResponse对象可在基于类的视图中有效使用。

尽管最小的基于类的视图不需要任何类属性即可执行其工作,但是类属性在许多基于类的设计中很有用,并且有两种配置或设置类属性的方法。

第一种是子类化和覆盖子类中的属性和方法的标准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"

另一个选择是将类属性配置为as_view()URLconf中的调用的关键字参数 :

urlpatterns = [
  path('about/', GreetingView.as_view(greeting="G'day")),
]

注意:在为分配给它的每个请求实例化您的类时,通过as_view()导入点设置的类属性 在导入URL时仅配置一次。

使用混入

Mixins是多重继承的一种形式,可以将多个父类的行为和属性进行组合。

例如,在基于通用类的视图中,有一个mixin, TemplateResponseMixin其主要目的是定义method render_to_response()。当与View 基类的行为组合时,结果是一个TemplateView 类,该类会将请求分派到适当的匹配方法(View基类中定义的行为),并且具有 render_to_response() 使用 template_name 属性返回TemplateResponse 对象(行为)的方法。 )中定义TemplateResponseMixin。

Mixins是在多个类之间重用代码的绝佳方法,但是它们会带来一些成本。您的代码散布在mixin中的次数越多,读取子类并了解其确切操作的难度就越大,而如果您正在子类化具有深层继承树。

还要注意,您只能从一个通用视图继承-也就是说,只有一个父类可以继承,View其余(如果有)应该是mixins。尝试从多个继承的类中进行继承View-例如,尝试使用列表顶部的表单并组合ProcessFormView和 ListView-将无法按预期工作。

使用基于类的视图处理表单

处理表单的基于函数的基本视图可能如下所示:

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})

这是一个最小的情况,但是您可以看到您可以通过覆盖任何类属性(例如form_class,通过URLconf配置,或子类化并覆盖一个或多个方法(或两者)!)来定制此视图 。 。

装饰基于类的视图

基于类的视图的扩展不仅限于使用混合。您也可以使用装饰器。由于基于类的视图不是函数,因此根据您正在使用as_view()还是创建子类来装饰它们的工作方式有所不同。

在URLconf中装饰

您可以通过装饰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())),
]

此方法基于每个实例应用装饰器。如果要装饰视图的每个实例,则需要采用其他方法。

装饰类

要修饰基于类的视图的每个实例,您需要修饰类定义本身。为此,您可以将装饰器应用于dispatch()类的 方法。

类上的方法与独立函数并不完全相同,因此您不能仅将函数装饰器应用于该方法–您需要首先将其转换为方法装饰器。所述method_decorator装饰来转换函数装饰成方法装饰,使得它可以在一个实例方法中。例如:

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)

或者,更简洁地说,您可以代替装饰类,并将要装饰的方法的名称作为关键字参数传递name:

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

如果您在多个地方使用了一组通用装饰器,则可以定义一个装饰器列表或元组,然后使用它而不是method_decorator()多次调用 。这两个类是等效的:

decorators = [never_cache, login_required]

@method_decorator(decorators, 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'

装饰者将按照传递给装饰者的顺序处理请求。在示例中,never_cache()将在之前处理请求 login_required()。

在此示例中,的每个实例都ProtectedView将具有登录保护。这些示例使用login_required,但是通过使用可以获得相同的行为 LoginRequiredMixin。

注意:method_decorator将*args和**kwargs 作为参数传递给类中经过修饰的方法。如果您的方法不接受一组兼容的参数,它将引发 TypeError异常。

详情参考: https://docs.djangoproject.com/en/3.0/