【Django】基于类的视图

基于类的视图

Django中的视图是一个可调用对象,它接收一个请求然后返回一个响应。这个可调用对象不仅仅限于函数,Django同时提供一些可以用作视图的类。它们允许你结构化你的视图并且利用继承(inheritance)和混合(mixin)重用代码。后面我们将介绍一些用于简单任务的通用视图,但你可能想要设计自己的可重用视图的结构以适合你的使用场景。
1.基于类的视图简介
2.内建的基于类的通用视图(Built-in class-based generic views);
3.使用基于类的视图处理表单(Form handling with class-based views);
4.使用混合mixin来扩展视图类(Using mixins with class-based views);

基本的示例

Django提供基本的视图类,它们适用于绝大多数的应用。所有的视图类继承自View类,它负责将视图链接到URL、HTTP方法调度和其它简单的功能。RedirectView用于简单的HTTP重定向,TemplateView扩展基类来渲染模板。

在URLconf中的简单用法

使用通用视图最简单的方法是直接在URLconf中创建它们。如果你只是修改基于类的视图的一些简单属性,你可以将它们直接传递给as_view()方法调用。

from django.conf.urls import url
from django.views.generic import TemplateView
urlpatterns = [
  url(r'^about/$', TemplateView.as_view(template_name='about.html')),
]

传递给as_view()的参数将覆盖类中的属性。在这个例子中,我们设置TemplateViewtemplate_name。可以使用类似的方法覆盖RedirectViewurl属性。

子类化通用视图

使用通用视图更有效的方式是继承一个已经存在的视图并在子类中覆盖其属性(例如template_name)或方法(例如get_context_data)以提供新的值或方法。例如,考虑只显示一个模板about.html的视图。Django有一个通用视图TemplateView来做这件事,所以我们可以简单地子类化它,并覆盖模板的名称:

#some_app/views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
    template_name = 'about.html'

然后我们只需要添加这个新的视图到我们的URLconf中。TemplateView是一个类而不是一个函数,所以我们将URL指向类的as_view()方法,它提供一个类似函数的入口指向基于类的视图:

#urls.py
from django.conf.urls import url
from some_app.views import AboutView
urlpatterns = [
  url(r'^about/$', AboutView.as_view()),
]

支持其它HTTP方法

假设有人想通过HTTP访问我们的书库,它使用视图作为API。这个API客户端将随时连接并下载自上次访问以后新出版的数据的数据。如果没有新的书籍,仍然从数据库中获取书籍、渲染一个完整的响应并发送给客户端将是对CPU和带宽的浪费。如果有个API用于查询书籍最新发布的时间将会更好。
我们在URLconf中映射URL到书籍列表视图:

from django.conf.urls import url
from books.views import BookListView
urlpatterns = [
  url(r'^book/$', BookListView.as_view()),
]

下面是这个视图:

from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
      model = Book
      def head(self, *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请求访问,将在响应中返回一个普通而简单的对象列表(使用book_list.html模板)。但如果客户端发出一个HEAD请求,响应将具有一个空的响应体,而Last-Modified头部会指示最新发布的书籍的时间。基于这个信息,客户端可以下载或不下载完整的对象列表。

基于类的视图简介

基于类的视图使用Python对象实现视图,它是除函数视图之外的另外一种方式。它们不替换基于函数的视图,但与基于函数的视图相比具有一定的区别和优势:

  • HTTP方法(GET、POST等)可以有各自的方法,而不用通过条件分支来解决。
  • 面向对象的技术,例如Mixin(多继承multiple inheritance)可以将代码分解成可重用的组件。

通用视图、基于类的视图和基于类的通用视图的关系和历史

开始的时候只有视图函数,Django传递一个HttpRequest给你的函数并期待返回一个HttpResponse。Django曾经提供的就这么些内容。
在早期,我们认识到在视图开发过程中有共同的用法和模式。这是我们引入基于函数的通用视图来抽象这些模式以简化常见情形的视图开发。
基于函数的视图的问题在于,虽然它们很好地覆盖了简单的情形,但是不能扩展或自定义它们,即使是一些简单的配置选项,这让它们在现实应用中受到很多限制。
基于类的通用视图然后应运而生,目的与基于函数的通用视图一样,就是为了使得视图的开发更加容易。然而,它们使用的Mixin方式使得基于类的通用视图比基于函数的视图更加容易扩展和更加灵活。
Django使用基类和Mixin来构建基于类的通用视图,为用户提供了最大的灵活性,它提供了包含钩子的默认方法和属性,但是简单的用法中不需要考虑它们,也可以正常工作。例如,对于属性form_class,其实现使用get_form方法,它调用get_form_class方法,而它的默认实现就是返回类的form_class属性。这给你多种选择来指定具体使用的表单,例如一个属性或者一个完全动态的、可调用的钩子。这些选择似乎白白地增加了简单使用场景的复杂性,但是没有它们更高级的功能就会受到限制。

使用基于类的视图

基于类的视图的核心是允许你用不同的实例方法来响应不同的HTTP请求方法,而不是在一个函数视图中使用条件分支代码来实现。
所以,函数视图中处理HTTP GET的代码看上去像。

  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()类方法用来作为类的可调用入口。该as_view()入口点创建类的一个实例并调用dispatch()方法。dispatch()查看请求是GET还是POST等等,并将请求转发给相应的方法,如果该方法没有定义则引发HttpResponseNotAllowed

    #urls.py
    from django.conf.urls import url
    from myapp.views import MyView
    urlpatterns = [
          url(r'^about/$', MyView.as_view()),
    ]

值得注意的是,方法的返回值与基于函数的视图的返回值完全相同,即HttpResponse的某种形式。这表示在基于类的视图中可以使用http快捷函数和TemplateResponse对象。
虽然基于类的视图的最小实现不需要任何类属性来完成它的功能,但是在许多基于类的设计中类属性非常重要,有两种方式来设置类属性。
第一种方式是Python标准的方式,子类化并在子类中覆盖属性和方法。所以,如果父类有一个greeting属性:

    from django.http import HttpResponse
    from django.view 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 = [
          url(r'^about/$', GreetingView.as_view(greeting="G'day")),
    ]

注意:对于每个请求都会实例化类的一个实例,但是as_view()入口点设置的类属性只有在URL第一次导入时才会配置。

使用Mixin

Mixin是多继承的一种形式,其来自多个父类的行为和属性可以组合在一起。
例如,在通用的基于类的视图中,有一个Mixin叫做TemplateResponseMixin,它的主要目的是定义render_to_response()方法。它与View基类的组合是TemplateView类,这个类可以调度请求给正确的方法(View基类中定义的行为),同时还具有一个render_to_response()方法,该方法使用template_name属性来返回一个TemplateResponse对象(TemplateResponseMixin中定义的行为)。
Mixin是在多个类中重用代码的一种极好的方法,但是需要一些代价。代码在Mixin中越分散,子类越难阅读并知道它的行为。如果你的继承很深,将难以知道应该覆盖哪一个Mixin的行为。
还要注意,只能继承一个通用视图---也就是说,只能有一个父类继承View,其它的父类必须是Mixin。继承多个继承自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():
                  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 = {'value' :'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():
                    return HttpResponseRedirect('/success/')
                return render(request, self.template_name, {'form' : form})

这是一个非常简单的情形,但你可以看到你将有机会自定义这个视图,例如通过URLconf配置覆盖form_class属性或者子类化并覆盖一个和多个方法。

封装as_view()的Mixin

将共同的行为运用于多个类的一种方法是编写一个封装as_view()方法的Mixin。
例如,如果有许多通用视图,它们需要使用login_required()装饰器,你可以这样实现一个Mixin:

    from django.contrib.auth.decorators import login_required
    class LoginRequiredMixin(object):
            @classmethod
            def as_view(cls, **initkwargs):
                  view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
                  return login_required(view)

    class MyView(LoginRequiredMixin, ...);
            #this is a generic view
            ...

装饰基于类的视图

基于类的视图的扩展不仅仅局限于使用Mixin。你还可以使用装饰器。由于基于类的视图不是函数,对它们的装饰取决于你使用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 = [
              url(r'^about/$', login_required(TemplateView.as_view(template_name="secret.html"))),
              url(r'^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(ProtectedView, self).dispatch(*args, **kwargs)

在这个例子中,ProtectedView的每个实例都将有登录保护。
或者,更简单的,你可以这样来装饰类,通过关键字参数name传递需要被装饰的方法名字。

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

如果你有一系列公共的装饰器,需要使用在多出地方,你可以定义一个装饰器列表或者数组来代替多次触发method_decorator。以下两种方式是相等的。

    decorators = [never_cache, login_required]
    @method_decorator(decorator, 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_requried()先处理请求。

基于类的内建的通用视图

编写Web应用是单调的,因为你需要不断的重复某一种模式。Django尝试从模型和模板层面移除一些单调的情况,但是Web开发者已然会在视图层经历这种厌烦。
Django的通用视图被开发用来消除这一痛苦。它们采用在视图开发过程中发现的某些共同的用法和模式,然后把它们抽象出来,以便你能够写更少的代码,快速的实现常见的视图。
我们能够识别一些基础的任务,比如展示对象的一个列表,以及编写代码来展示任何一个对象的列表。此外,涉及到的模型可以作为一个额外的参数传递到URLconf中。
Django使用通用视图完成下列功能:

  • 为单一的对象展示列表和一个详细页面;
  • 在年/月/日归档页面,以及详细页面和'最后发表'页面中,展示以日期为基础的对象;
  • 允许用户创建,更新和删除对象---以授权或者无需授权的方式;

总的来说,这些视图提供了一些简单的接口来完成开发者遇到的大多数的常见任务。

对象的通用视图

TemplateView确实很有用,但是当你需要呈现数据库中的内容时,Django的通用视图才会脱颖而出。因为这是如此常见的任务,Django提供了一大把内置的通用视图,使生成对象的列表和详细视图变得极其容易。
让我们来看一下显示对象的一个列表和一个单独对象的列子。
我们使用下面模型:

    #models.py
    from django.db import models
    class Publisher(models.Model):
          name = models.CharField(max_length=30)
          address = models.CharField(max_length=50)
          city = models.CharField(max_length=60)
          state_province = models.CharField(max_length=30)
          country = models.CharField(max_length=50)
          website = models.URLField()
          class Meta:
            ordering = ['-name']
          def __str__(self):  \#__unicode__on python 2
            return self.name

    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')
          def __str__(self):
              return self.name

    class Book(models.Model):
          title = models.CharField(max_length=100)
          authors = models.ManyToManyField('Author')
          publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
          publication_date = models.DateField() 

现在我们需要定义一个视图:

    #views.py
    from django.views.generic import ListView
    from books.models import Publisher
    class PublisherList(ListView):
          model = Publisher

最后将视图勾到你的url上。
#urls.py
from django.conf.urls import url
from books.views import PublisherList
urlpatterns = [
url(r'^publishers/$', PublisherList.as_view()),
]

上面就是我们所要写的全部Python代码了。我们还需要写一个模板。我们可以通过在视图中添加template_name属性,来明确的告诉视图使用哪个模板。但是如果没有提供明确的模板,Django会根据对象名字推出一个。在这个例子中,推出的模板会是'books/putlisher_list.html''books'部分来自模型所属应用的名字,‘publisher’是模型名字的小写形式。
注意,当DjangoTemplates后端的APP_DIRS选项设置为True时,模板位置是/path/to/project/books/templates/books/publisher_list.html
这个模板将会依据一个上下文来渲染,这个上下文包含一个名为object_list的上下文变量,它包含所有publisher对象的变量。一个非常简单的模板可能看起来像下面这样:

使用‘友好’的模板Context

你可能已经注意到,publisher_list模板将所有Publisher保存在object_list变量中。虽然能够正常工作,但这对模板作者并不‘友好’。
如果你在处理一个模型对象,Django已经为你准备好。当你处理一个普通对象或者QuerySet时,Django能够使用其模型类的小写名称来放入Context。实现方法是,除了默认的object_list,还提供一个包含完全相同数据的变量,例如publisher_list
如果这个变量仍然不能很好的符合要求,你可以手动设置上下文变量的名字。通用视图的context_object_name属性指定要使用的上下文变量:

   #views.py
  from django.views.generic import ListView
  from books.models import Publisher
  class PublisherList(ListView):
        model = Publisher
        context_object_name = 'my_favorite_publishers'

添加额外的上下文

你会经常需要展示一些通用视图不能提供的额外信息。比如,考虑一下在每个Publisher详细页面上显示一个包含所有图书的列表。DetailView通用视图提供了Publisher对象给上下文,但是我们如何在模板中获得附加信息呢?
答案是继承DetailView,并且在get_context_data方法中提供你自己的实现。默认的实现只是简单地给模板添加要展示的对象,但是你可以覆盖它来展示更多信息。

    from django.views.generic import DetailView
    from books.models import Publisher, Book
    class PublisherDetail(DetailView):
          model = Publisher
          def get_context_data(self, **kwargs):
              #Call the base implementation first to get a context
              context =super(PublihserDetail, self).get_context_data(**kwargs)
              #Add in a QuerySet of all the books
              context['book_list'] = Book.objects.all()
              return context

注意:通常来说,get_context_data会将当前类中的上下文数据,合并到父类的上下文数据。要在你自己想要改变上下文的类中保持这一行为,你应该确保调用了父类中的get_context_data。如果没有两个类尝试定义相同的键,它会返回预期的结果。

查看对象的子集

现在让我们来近距离查看一下我们一直在用的model参数。model参数指定视图在哪个数据库模型上进行操作,这适用于所有需要操作一个单独的对象或者一个对象集合的通用视图。然而,model参数并不是唯一能够指明视图要基于哪个对象进行操作的方法---你同样可以使用queryset参数来指定一个对象列表:

    from django.views.generic import DetailView
    from books.models import Publisher
    class PublisherDetail(DetailView):
          context_object_name = 'publisher'
          queryset = Publisher.objects.all()

指定model = Publisher等价于快速声明queryset = Publisher.objects.all()。然而,通过使用queryset来定义一个过滤的对象列表,你可以更加详细的了解哪些对象将会被显示在视图中。
来看一个简单的例子,对图书列表按照出版日期进行排序,并且把最近的放到前面:

    from django.views.generic import ListView
    from books.models import Book
    class BookList(ListView):
          queryset = Book.obejcts.order_by('-publication_date')
          context_object_name = 'book_list'

这是个非常简单的列子,但是它很好的诠释了处理思路。 当然,你通常想做的不仅仅只是对对象列表进行排序。如果你想要展现某个出版商的所有图书列表,你可以使用 同样的手法。

动态过滤

另外一个普遍的需求是,在给定的列表页面中根据URL中的关键字来过滤对象。ListView有一个get_queryset()方法来供我们重写。在之前,它只是返回一个queryset属性值,但是现在我们可以添加更多的逻辑。让这种方式能够工作的关键点在于,当类视图被调用时,各种有用的对象被存储在self上,同request(self.request)一样,其中包含了从URLconf中获取到的位置参数(self.args)和基于名字的关键字参数(self.kwargs)。
这里,我们拥有一个带有一组供捕获的参数的URLconf

    # urls.py
    from django.conf.urls import url
    from books.views import PublisherBookList

    urlpatterns = [
          url(r'^books/([\w-]+)/$', PublisherBookList.as_view()),
    ]

接着,我们编写PublisherBookList视图:

    #views.py
    from django.shortcuts import get_object_or_404
    from django.views.generic import ListView
    from books.models import Book, Publisher
    class PublihserBookList(ListView):
          template_name = 'books/books_by_publisher.html'
          def get_queryset(self):
              self.publisher = get_object_or_404(Publisher, name=self.args[0])
              return Book.objects.filter(publisher=self.publisher)

如你所见,在queryset区域添加更多的逻辑非常容易:如果偶尔们想的话,可以使用self.request.user来过滤当前用户,或者添加其他更加复杂的逻辑。
同时我们可以把出版商添加到上下文中,这样我们就可以在模板中使用它:

    def get_context_data(self, **kwargs):
          # Call the base implementation first to get a context
          context = super(PublisherBookList, self).get_context_data(**kwargs)
          # Add in the publisher
          context['publisher'] = self.publisher
          return context

执行额外的工作

我们将要看一下最后的共同之处,在调用通用视图之前货之后完成一些额外的工作。
想象一下,在我们的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()

通用的DetailView类当然不知道关于这个字段的事情,但是我们可以很容易编写一个自定义的视图,来保持这个字段的更新。
首先,我们需要添加作者详情页的代码配置到URLconf中,指向自定义的视图:

    from django.conf.urls import url
    from books.views import AuthorDetailView
    urlpatterns = [
        #...
        url(r'^authors/(?P<pk>[0-9]+)/$', AuthorDetailView.as_view(), name='author-detail'),
    ]

然后,编写我们新的视图---get_object是用来获取对象的方法---因此,我们简单的重写并封装调用。

from django.views.generic import DetailView
from django.utils import timezone
from books.models import Author
class AuthorDetailView(DetailView):
      queryset = Author.objects.all()
      def get_object(self):
          # Call the superclass
          object = super(AuthorDetailView, self).get_object()
          # Record the last accessed date
          object.last_accessed = timezone.now()
          object.save()
          # Return the object
          return object

这里URLconf使用参数组的名字pk - 这个名字是DetailView用来查找主键的值的默认名称,其中主键用于过滤查询集。

如果你想要调用参数组的其它方法,你可以在视图上设置pk_url_kwarg

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

表单的处理通常有3个步骤:

  • 初始的GET(空白或预填充的表单)
  • 带有非法数据的POST(通常重新显示表单和错误信息)
  • 带有合法数据的POST(处理数据并重定向)

自己实现这些功能经常导致许多重复的样本代码。为了避免这点,Django提供一系列的通用的基于类的视图用于表单的处理。

基本的表单

一个简单的联系人表单:
#forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget = forms.Textarea)
def send_email(self):
pass
可以使用FormView来构造其视图:
#views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
def form_valid(self, form):
#This method is called when valid form data has been POSTED. It should return an HttpResponse.
form.send_email()
return super(ContactView, self).form_valid(form)
注意:

  • FormView类继承TemplateResonseMixin, 所以这里可以使用template_name
  • form_valid()的默认实现只是简单地重定向到success_url

模型表单

通用视图在与模型一起工作时会真正光芒四射。这些通用的视图将自动创建一个ModelForm,只要它们能知道使用哪一个模型类。

  • 如果给出model属性,则使用该模型类;
  • 如果get_object()返回一个对象,则使用该对象的类;
  • 如果给出queryset,则使用该查询集的模型。

模型表单提供一个form_valid()的实现,它自动保存模型。如果你有特殊的需求,可以覆盖它。
你不需要为CreateView和UpdateView提供success_url,如果模型对象的get_absolute_url()存在的话,它们将使用get_absolute_url()
如果你想使用一个自定义的ModelForm,只需要简单地在你的视图上设置form_class
注意:当指定一个自定义的表单类时,你必须指定模型,即使form_class可能是一个ModelForm。
首先我们需要添加get_absolute_url()到Author类中。
#models.py
from django.core.urlresolvers import reverse
from django.db import models
class Author(models.Model):
name = models.CharField(max_lenght=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
然后我们可以使用CreateView及其伙伴来做实际的工作。注意这里我们是如何配置基于类的通用视图的,我们甚至没有必要自己写任何逻辑。
#views.py
from django.view.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']

    class AuthorUpdate(UpdateView):
          model = Author
          fields = ['name']

    class AuthorDelete(DeleteView):
            model = Author
            success_url = reverse_lazy('author-list')

注意:
这里我们必须使用reverse_lazy()而不是reverse,因为在该文件导入时URL还没有加载。
fields属性的工作方式与ModelForm的内部Meta类的fields属性相同。除非你用另外一种方式定义表单类,该属性是必须的,如果没有将引发一个ImproperlyConfigured异常。
如果你同时指定fieldsform_class属性,将引发一个ImproperlyConfigured异常。
最后,我们将这些新的视图放到URLconf中:

    #urls.py
    from django.conf.urls import url
    from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete

    urlpatterns = [
        # ...
        url(r'author/add/$', AuthorCreate.as_view(), name='author-add'),
        url(r'author/(?P<pk>[0-9]+)/$', AuthorUpdate.as_view(), name='author-update'),
        url(r'author/(?P<pk>[0-9]+)/delete/$', AuthorDelete.as_view(), name='author-delete'),
    ]

注意:这些表单继承SingleObjectTemplateResponseMixin,它使用template_name_suffix并基于模型来构造template_name。
在这个例子中:

  • CreateViewUpdateView使用myapp/author_form.html
  • DeleteView使用myapp/author_confirm_delete.html;

如果你希望分开CreateViewUpdateView的模板,你可以设置你的视图类的template_name或者template_name_suffix

模型和request.user

为了追踪使用CreateView创建对象的用户,你可以使用一个自定义的ModelForm来实现这点。首先,向模型添加外键关联:
#models.py
from django.contrib.auth.models import User
from django.db import models
class Author(models.Model):
name = models.CharField(max_lenght=200)
created_by = models.ForeignKey(User, on_delete = models.CASCADE)
在这个视图中,请确保你没有将created_by包含进要编辑的字段列表,并覆盖form_valid()来添加这个用户:
#view.py
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super(AuthorCreate, self).form_valid(form)
注意,你需要使用login_required()来装饰这个视图,或者在form_valid()中处理未认证的用户。

Mixin

Django基于类的视图提供了许多功能,但是你可能只想使用其中的一部分。例如,你想编写一个视图,它渲染模板来响应HTTP,但是你用不了TemplateView;或者你只需要对POST请求渲染一个模板,而GET请求做一些其它的事情。虽然你可以直接使用TemplateResponse,但是这将导致重复的代码。
由于这些原因,Django提供许多Mixin,它们提供更细致的功能。例如,渲染模板封装在TemplateResponseMixin中。

Context和TemplateResponse

在基于类的视图中使用模板具有一致的接口,有两个Mixin起了核心的作用。

TemplateResponseMixin

返回TemplateResponse的每个视图都将调用render_to_response()方法,这个方法由TemplateResponseMixin提供。大部分时候,这个方法会隐式调用(例如,它会被TemplateViewDetailView中实现的get()方法调用);如果你不想通过Django的模板渲染响应,那么你可以覆盖它,虽然通常不需要这样。
render_to_response()本身调用get_template_names(),它默认查找类视图的template_name,其它两个Mixin(SingleObjectTemplateResponseMixin和MultipleObjectTemplateResponseMixin)覆盖了这个方法,以在处理实际的对象时能提供更灵活的默认行为。

ContextMixin

需要Context数据的每个内建视图,例如渲染模板的视图(包括TemplateResponseMixin),都应该以关键字参数调用get_context_data(),以确保它们想要的数据在里面。get_context_data()返回一个字典。在ContextMixin中,它只是简单地返回它的关键字参数,通常会覆盖这个方法来向字典中添加更多的成员。

DetailView:用于单个Django对象

为了显示一个对象的详细信息,我们通常需要做两件事情:查询对象然后利用合适的模板和包含该对象的Context生成TemplateResonse
为了获得对象,DetailView依赖SingleObjectMixin,它提供一个get_object()方法,这个方法基于请求的URL获取对象(它查找URLconf中声明的pkslug关键字参数,然后从视图的model属性或者queryset属性查询对象)。SingleObjectMixin还覆盖get_context_data(),这个方法在Django所有的内建的基于类的试图中都有用到,用来给模板的渲染提供Context数据。
然后,为了生成TemplateResponseDetailView使用SingleObjectTemplateResponseMixin,它扩展自TemplateResponseMixin并覆盖上文讨论过的get_template_name()。实际上,它提供比较复杂的选项集合,但是大部分人用到的主要的一个是<app_label>/<model_name>_detail.html_detail部分可以通过设置子类的template_name_suffix来改变。(例如,通用的编辑视图使用_form来创建和更新视图,用_confirm_delete来删除视图)。

ListView:用于多个Django对象

显示对象的列表和上面的步骤大体相同:我们需要一个对象的列表(可能是分页形式的),这通常是一个QuerySet,然后我们需要利用合适的模板和对象列表生成一个TemplateResponse。
为了获取对象,ListView使用MultipleObjectMixin,它提供get_queryset()和paginate_queryset()两种方法。与SingleObjectMixin不同,不需要根据URL中关键字参数来获得查询集,默认将使用视图类的queryset或model属性。通常需要覆盖get_queryset()以动态获取不同的对象。
MultipleObjectMixin还覆盖get_context_data()以包含合适的Context变量用于分页(如果禁止分页,则提供一些假的)。这个方法依赖传递给它的关键字参数object_list,ListView会负责准备好这个参数。
为了生成TemplateResponse,ListView然后使用MultipleObjectTemplateResponseMixin,与上面的SingleObjectTemplateResponseMinxi类似,它覆盖get_template_names()来提供一系列的选项,而最常用到的是<app_label>/<model_name>_list.html,其中_list部分同样由template_name_suffix属性设置。(基于日期的通用视图使用_archive、_archive_year等等这样的后缀来针对各种基于日期的列表视图使用不同的模板)。

返回HTML以外的内容

基于类的视图在同一件事需要实现多次的时候非常有优势。假设你正在编写API,每个视图应该返回JSON而不是渲染后的HTML
我们可以创建一个Mixin类来处理JSON的转换,并将它用于所有的视图。
例如,一个简单的JSON Mixin可能像这样:

from django.http import JsonResponse
class JSONResponseMixin(object):
      """
      A mixin that can be used to render a JSON response
      """
      def render_to_json_response(self, context, **response_kwargs):
          """
          Returns a JSON response, transforming 'context' to make the payload.
          """
          return JsonResponse(self.get_data(context), **response_kwargs)
      def get_data(self, context):
          """
          Returns an object that will be serialized as JSON by json.dump().
          """
          # Note: This is *EXTREMELY* naive; in reality, you'll need
          # to do much more complex handling to ensure that arbitrary
          # objects -- such as Django model instances or querysets
          # -- can be serialized as JSON.
          return context

注:查看序列化Django对象的文档,其中有如何正确转换Django模型和查询集到JSON的更多信息。
该Mixin提供一个render_to_json_response()方法,它与render_to_response()的参数相同。要使用它,只需要将它与TemplateView组合,并覆盖render_to_response来调用render_to_json_response()。

  from django.views.generic import TemplateView
  class JSONView(JSONResponseMixin, TemplateView):
          def render_to_response(self, context, **response_kwargs):
              return self.render_to_json_response(context, **response_kwargs)

同样地,我们可以将Mixin与某个通用的视图一起使用。我们可以实现自己的DetailView版本,将JSONResponseMixin和django.views.generic.detail.BaseDetialView组合(the DetailView before template rendering behavior has been mixed in):

  from django.views.generic.detail import BaseDetailView
  class JSONDetailView(JSONResponseMixin, BaseDetailView):
        def render_to_response(self, context, **response_kwargs):
            return self.render_to_json_response(context, **response_kwargs)

这个视图可以和其它DetailView一样使用,它们的行为完全相同,除了响应的格式之外。
如果你想更进一步,你可以组合DetailView的子类,它根据HTTP请求的某个属性既能够返回HTML又能够返回JSON内容,例如查询参数或者HTTP头部。这只需要将JSONResponeMixin和SingleObjectTemplateResponseMixin组合,并覆盖render_to_response()的实现以根据用户请求的响应类型进行正确的渲染:

    from django.views.generic.detail import  SingleObjectTemplateResponseMixin
    class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
          def render_to_response(self, context):
              #Look for a 'format=json' GET argument
              if self.request.GET.get('format')  == 'json':
                 return self.render_to_json_response(context)
              else:
                  return super(HybridDetailView, self).render_to_response(context)

序列化Django对象

Django的序列化框架提供了一种机制,将Django模型转换成其他形式。通常这些其他形式是基于文本的,被用来发送Django数据。但是序列化工具是有可能处理任意的形式的,文本或非文本。
如果你只是想从表中获取一些数据到序列化形式,可以使用dumpdata管理命令。

序列化数据

从最高层面来看,序列化数据是一项非常简单的操作。

  from djanog.core import serializers
  data = serializers.serialize("xml", SomeModel.objects.all())

传递给serialize方法的参数有两个:一是序列化目标格式,另外一个是被序列化的对象QuerySet。

django.core.serializers.get_serializer(format)

你也可以直接使用序列化对象。

    XMLSerializer = serializers.get_serializer('xml')
    xml_serializer = XMLSerializer()
    xml_serializer.serialize(queryset)
    data = xml_serializer.getvalue()

如果你想将数据直接序列化为类似文件的对象(其中包含HttpResponse),则次选项非常有用:

  with open('file.xml', 'w') as out:
        xml_serializer.serialize(SomeModel.objects.all(), stream=out)

注意调用未知格式的get_serializer()会产生django.core.serializers.SerializerDoesNotExist异常。

字段子集

如果只想要序列化一部分字段,可以为序列化程序指定字段参数:

  from django.core import serializers
  data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name', 'size'))

在本例中,只要模型的name和size属性会被序列化。

继承模型

如果你定义的模型继承了abstract base class抽象基类,你不需要对它做任何特别的处理。只需对待序列化的对象调用serializers,就能输出该对象完整的序列化对象表述。
但是,如果您有使用multi-talbe inheritance多表继承,则还需要序列化模型的所有父类。这是因为只有在模型本地定义的字段才会被序列化。例如,考虑以下模型:

  class Place(models.Model):
        name = models.CharField(max_lenght=50)
  class Restaurant(Place):
        serves_hot_dogs = models.BooleanField(default=False)

如果你只序列化Restaurant模型:

  data = serializers.serialize('xml', Restaurant.objects.all())

序列化输出的字段将只包含serves_hot_dogs属性。基类的name属性将被忽略。
为了完全序列化Restaurant实例,还需要将Place模型序列化:

  all_objects = list(Restaurant.objects.all()) + list(Place.objects.all())
  data = serializers.serialize('xml', all_objects)

序列化格式

Django支持多种序列化格式,其中一些格式要求安装第三方Python模块。
xml: Serializes to and from a simple XML dialect.
json: Serializes to and from JSON.
yaml: Serializes to YAML. This serializer is only available if PyYAML is installed.

参考

  1. https://docs.djangoproject.com/en/1.11/topics/class-based-views/
  2. https://docs.djangoproject.com/en/1.11/topics/class-based-views/intro/
  3. http://python.usyiyi.cn/translate/django_182/topics/class-based-views/index.html
  4. https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/
  5. http://python.usyiyi.cn/translate/django_182/topics/serialization.html

推荐阅读更多精彩内容