【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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容