警告: 這是一個(gè)高級(jí)主題。在探索這些技術(shù)之前,建議您對(duì)Django基于類(lèi)的視圖有一定的了解。
Django的內(nèi)置基于類(lèi)的視圖提供了許多功能,但您可能需要單獨(dú)使用其中的一些功能。例如,您可能想編寫(xiě)一個(gè)視圖,該視圖呈現(xiàn)一個(gè)模板以進(jìn)行HTTP響應(yīng),但是您不能使用 TemplateView; 也許您只需要在上渲染模板POST,然后GET完全執(zhí)行其他操作即可。雖然您可以TemplateResponse直接使用 ,但這可能會(huì)導(dǎo)致代碼重復(fù)。
因此,Django還提供了許多混合器,這些混合器提供了更多離散功能。例如,模板呈現(xiàn)封裝在中 TemplateResponseMixin。Django參考文檔包含所有mixins的完整文檔。
提供了兩個(gè)中央mixin,它們有助于在使用基于類(lèi)的視圖中的模板時(shí)提供一致的界面。
讓我們看看Django的兩個(gè)基于類(lèi)的通用視圖是如何通過(guò)提供離散功能的mixin構(gòu)建的。我們將考慮 DetailView,它呈現(xiàn)一個(gè)對(duì)象的“詳細(xì)”視圖,而 ListView,它將呈現(xiàn)一個(gè)對(duì)象列表(通常來(lái)自查詢(xún)集),并可選地對(duì)它們進(jìn)行分頁(yè)。這將向我們介紹四個(gè)mixin,它們?cè)谑褂脝蝹€(gè)Django對(duì)象或多個(gè)對(duì)象時(shí)提供有用的功能。
也有參與通用編輯觀點(diǎn)混入(FormView和特定型號(hào)的意見(jiàn)CreateView, UpdateView和 DeleteView),并在基于日期的通用視圖。這些已在mixin參考文檔中介紹。
要顯示對(duì)象的詳細(xì)信息,我們基本上需要做兩件事:我們需要查找對(duì)象,然后需要TemplateResponse使用合適的模板制作一個(gè) ,并將該對(duì)象作為上下文。
要獲得該對(duì)象,需要DetailView 依賴(lài)SingleObjectMixin,該get_object() 方法提供了一種方法,該 方法可以根據(jù)請(qǐng)求的URL找出對(duì)象(它會(huì)根據(jù)URLConf中的聲明查找pk和slug關(guān)鍵字參數(shù),然后從model視圖的屬性中查找該對(duì)象 ,或提供的 queryset 屬性)。SingleObjectMixin也會(huì)覆蓋 get_context_data(),它在所有Django的所有基于類(lèi)的內(nèi)置視圖中使用,以為模板渲染提供上下文數(shù)據(jù)。
然后TemplateResponse,要 DetailView使用SingleObjectTemplateResponseMixin,則使用 , 如上所討論的,它擴(kuò)展了TemplateResponseMixin,覆蓋 get_template_names()。實(shí)際上,它提供了一組相當(dāng)復(fù)雜的選項(xiàng),但是大多數(shù)人要使用的主要選項(xiàng)是 /_detail.html。該_detail部分可以通過(guò)設(shè)置來(lái)改變 template_name_suffix 的一個(gè)子類(lèi)別的東西。(例如,通用編輯觀點(diǎn)使用_form的創(chuàng)建和更新的意見(jiàn),并 _confirm_delete進(jìn)行刪除的意見(jiàn)。)
對(duì)象列表遵循大致相同的模式:我們需要一個(gè)(可能是分頁(yè)的)對(duì)象列表,通常為 QuerySet,然后我們需要TemplateResponse使用該對(duì)象列表使用合適的模板制作一個(gè) 。
要獲取對(duì)象,請(qǐng)ListView使用 MultipleObjectMixin,同時(shí)提供 get_queryset() 和 paginate_queryset()。與with不同SingleObjectMixin,不需要關(guān)閉URL的一部分來(lái)確定要使用的查詢(xún)集,因此默認(rèn)值使用 view類(lèi)上的querysetor model屬性。覆蓋get_queryset() 此處的常見(jiàn)原因 是動(dòng)態(tài)地改變對(duì)象,例如取決于當(dāng)前用戶(hù)或排除博客的將來(lái)帖子。
MultipleObjectMixin還重寫(xiě) get_context_data()以包括用于分頁(yè)的適當(dāng)上下文變量(如果禁用了分頁(yè),則提供虛擬變量)。它依賴(lài)object_list于作為關(guān)鍵字參數(shù)進(jìn)行傳遞的關(guān)鍵字參數(shù)ListView。
做一個(gè)TemplateResponse, ListView然后使用 MultipleObjectTemplateResponseMixin; 與SingleObjectTemplateResponseMixin 上面的方法一樣,此方法將覆蓋,get_template_names()以提供,最常用的是 ,而該部分再次從 屬性中獲取。(基于日期的通用視圖使用諸如的后綴, 以此類(lèi)推,以便為各種專(zhuān)門(mén)的基于日期的列表視圖使用不同的模板。)a range of options/_list.html``_listtemplate_name_suffix_archive``_archive_year
現(xiàn)在,我們已經(jīng)了解了Django的基于類(lèi)的通用視圖如何使用提供的mixins,讓我們看一下將它們組合在一起的其他方法。當(dāng)然,我們?nèi)詫⑺鼈兣c內(nèi)置的基于類(lèi)的視圖或其他通用的基于類(lèi)的視圖結(jié)合起來(lái),但是您可以解決許多比Django開(kāi)箱即用的罕見(jiàn)問(wèn)題。
警告:并非所有的mixin都可以一起使用,也不是所有基于通用類(lèi)的視圖都可以與所有其他mixin一起使用。在這里,我們提供了一些可行的示例。如果要合并其他功能,則必須考慮使用的不同類(lèi)之間重疊的屬性和方法之間的交互,以及方法解析順序?qū)⑷绾斡绊懸院畏N順序調(diào)用哪些版本的方法。
Django的基于類(lèi)的視圖和基于類(lèi)的視圖mixin的參考文檔將幫助您了解哪些屬性和方法可能會(huì)導(dǎo)致不同的類(lèi)和mixin之間發(fā)生沖突。
如有疑問(wèn),通常最好還是放棄并以 View或?yàn)榛A(chǔ)TemplateView,也許使用 SingleObjectMixin和 MultipleObjectMixin。盡管您可能最終會(huì)寫(xiě)出更多的代碼,但是以后其他人可能更容易理解它,而更少的交互性讓您擔(dān)心,可以節(jié)省一些時(shí)間。(當(dāng)然,您總是可以深入了解Django基于類(lèi)的通用視圖的實(shí)現(xiàn),以獲取有關(guān)如何解決問(wèn)題的靈感。)
如果我們要編寫(xiě)一個(gè)僅對(duì)做出響應(yīng)的基于類(lèi)的視圖,則將創(chuàng)建子POST類(lèi)View并post()在該子類(lèi)中編寫(xiě)一個(gè)方法。但是,如果我們希望我們的處理能夠處理從URL識(shí)別的特定對(duì)象,我們將需要提供的功能 SingleObjectMixin。
我們將通過(guò)在基于通用類(lèi)的視圖簡(jiǎn)介中Author使用的模型來(lái) 演示這一點(diǎn)。
的views.py
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author
?
class RecordInterest(SingleObjectMixin, View):
"""Records the current user's interest in an author."""
model = Author
?
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
?
# Look up the author we're interested in.
self.object = self.get_object()
# Actually record interest somehow here!
?
return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))
實(shí)際上,您可能希望將興趣記錄在鍵值存儲(chǔ)而不是關(guān)系數(shù)據(jù)庫(kù)中,因此我們省略了這一點(diǎn)。唯一需要擔(dān)心使用的視圖 SingleObjectMixin就是我們要查找感興趣的作者的地方,它通過(guò)調(diào)用來(lái)完成 self.get_object()。mixin會(huì)為我們處理其他所有事務(wù)。
我們可以很容易地將其掛接到我們的URL中:
的urls.py
from django.urls import path
from books.views import RecordInterest
?
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterest.as_view(), name='author-interest'),
]
請(qǐng)注意pk命名組,該組 get_object()用于查找Author實(shí)例。您還可以使用slug或的任何其他功能 SingleObjectMixin。
ListView提供內(nèi)置的分頁(yè)功能,但是您可能希望對(duì)所有通過(guò)外鍵鏈接到另一個(gè)對(duì)象的對(duì)象列表進(jìn)行分頁(yè)。在我們的出版示例中,您可能希望對(duì)特定出版商的所有書(shū)籍進(jìn)行分頁(yè)。
一種實(shí)現(xiàn)方法是與結(jié)合ListView使用 SingleObjectMixin,以便分頁(yè)圖書(shū)清單的查詢(xún)集可以脫離作為單個(gè)對(duì)象找到的出版商。為此,我們需要有兩個(gè)不同的查詢(xún)集:
注意:我們必須仔細(xì)考慮get_context_data()。由于SingleObjectMixin和 ListView都會(huì)將事物放置在上下文數(shù)據(jù)中(context_object_name如果已設(shè)置)的值之下,因此我們將明確確保事物在 Publisher上下文數(shù)據(jù)中。ListView 將增加在合適的page_obj和paginator我們提供我們記得打電話(huà)super()。
現(xiàn)在我們可以寫(xiě)一個(gè)新的PublisherDetail:
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
?
class PublisherDetail(SingleObjectMixin, ListView):
paginate_by = 2
template_name = "books/publisher_detail.html"
?
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Publisher.objects.all())
return super().get(request, *args, **kwargs)
?
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['publisher'] = self.object
return context
?
def get_queryset(self):
return self.object.book_set.all()
請(qǐng)注意我們是如何設(shè)置的self.object,get()以便稍后在get_context_data()和中再次使用它get_queryset()。如果您未設(shè)置template_name,則模板將默認(rèn)為正常 ListView選擇,在這種情況下,這是 "books/book_list.html"因?yàn)樗且槐緯?shū)籍清單; ListView一無(wú)所知SingleObjectMixin,因此毫無(wú) 頭緒Publisher。
該paginate_by所以你不必創(chuàng)建大量的書(shū)籍,看到分頁(yè)的工作是在故意例如??!這是您要使用的模板:
{% extends "base.html" %}
?
{% block content %}
<h2>Publisher {{ publisher.name }}</h2>
?
<ol>
{% for book in page_obj %}
<li>{{ book.title }}</li>
{% endfor %}
</ol>
?
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
?
<span class="current">
Page {{ page_obj.number }} of {{ paginator.num_pages }}.
</span>
?
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
一般來(lái)說(shuō),你可以使用 TemplateResponseMixin和 SingleObjectMixin當(dāng)你需要它們的功能。如上所示,稍加注意,您甚至可以SingleObjectMixin與結(jié)合使用 ListView。但是,隨著您嘗試這樣做,事情變得越來(lái)越復(fù)雜,一個(gè)好的經(jīng)驗(yàn)法則是:
暗示:您的每個(gè)視圖都應(yīng)僅使用mixins或一組基于類(lèi)的通用視圖中的視圖:詳細(xì)信息,列表,編輯和日期。例如,將TemplateView(內(nèi)置于視圖中)與 MultipleObjectMixin(通用列表)結(jié)合在一起是很好的選擇 ,但是將SingleObjectMixin(通用細(xì)節(jié))與MultipleObjectMixin(通用列表)結(jié)合起來(lái)可能會(huì)遇到問(wèn)題。
為了顯示當(dāng)您嘗試變得更復(fù)雜時(shí)會(huì)發(fā)生什么,我們展示了一個(gè)示例,該示例在存在更簡(jiǎn)單的解決方案時(shí)會(huì)犧牲可讀性和可維護(hù)性。首先,讓我們看一下與結(jié)合DetailView使用的天真嘗試 , FormMixin以使我們能夠 POST將Django Form帶到與我們使用來(lái)顯示對(duì)象相同的URL DetailView。
回想一下我們先前使用View和 SingleObjectMixin在一起的示例。我們正在記錄用戶(hù)對(duì)特定作者的興趣;現(xiàn)在說(shuō),我們要讓他們留下信息,說(shuō)明他們?yōu)槭裁聪矚g他們。再次,讓我們假設(shè)我們不會(huì)將其存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中,而是存儲(chǔ)在我們不再擔(dān)心的更深?yuàn)W的東西中。
此時(shí),很自然地可以Form將封裝從用戶(hù)瀏覽器發(fā)送到Django的信息。還要說(shuō)我們?cè)赗EST上投入了巨資,因此我們想使用與顯示來(lái)自用戶(hù)的消息相同的URL來(lái)顯示作者。讓我們重寫(xiě)我們的代碼AuthorDetailView。
盡管必須將a添加到上下文數(shù)據(jù)中,以便將其呈現(xiàn)在模板中,但我們將保留GET來(lái)自的處理。我們還希望從中引入表單處理,并編寫(xiě)一些代碼,以便在表單上正確調(diào)用。DetailViewFormFormMixinPOST
注意:我們使用FormMixin并實(shí)現(xiàn) post()自己,而不是嘗試DetailView與之 混合FormView(這post()已經(jīng)提供了合適的方法),因?yàn)檫@兩個(gè)視圖都實(shí)現(xiàn)get(),并且事情會(huì)變得更加混亂。
我們的新AuthorDetail外觀如下所示:
# CAUTION: you almost certainly do not want to do this.
# It is provided as part of a discussion of problems you can
# run into when combining different generic class-based view
# functionality that is not designed to be used together.
?
from django import forms
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
from books.models import Author
?
class AuthorInterestForm(forms.Form):
message = forms.CharField()
?
class AuthorDetail(FormMixin, DetailView):
model = Author
form_class = AuthorInterestForm
?
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
?
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
?
def form_valid(self, form):
# Here, we would record the user's interest using the message
# passed in form.cleaned_data['message']
return super().form_valid(form)
get_success_url()提供重定向到的位置,該位置可用于的默認(rèn)實(shí)現(xiàn)form_valid()。post()如前所述,我們必須提供我們自己的。
之間微妙的相互作用的數(shù)量 FormMixin和DetailView已測(cè)試我們的管理事物的能力。您不太可能想自己編寫(xiě)此類(lèi)。
在這種情況下,盡管編寫(xiě)處理代碼涉及很多重復(fù),但是您可以post()自己編寫(xiě)方法,并保持 DetailView唯一的通用功能 Form。
可替代地,它仍然會(huì)比上面的方法工作少到具有用于處理的形式,其可以使用一個(gè)單獨(dú)的視圖 FormView從不同 DetailView無(wú)顧慮。
我們實(shí)際上在這里試圖做的是使用來(lái)自同一URL的兩個(gè)不同的基于類(lèi)的視圖。那么為什么不這樣做呢?我們?cè)谶@里有一個(gè)非常清楚的劃分:GET請(qǐng)求應(yīng)獲取 DetailView(Form添加到上下文數(shù)據(jù)中),POST請(qǐng)求應(yīng)獲取FormView。讓我們先設(shè)置這些視圖。
該AuthorDisplay視圖與我們首次引入AuthorDetail時(shí)幾乎相同;我們?cè)趯?xiě)我們自己get_context_data(),使 AuthorInterestForm可用的模板。get_object()為了清楚起見(jiàn),我們將跳過(guò)之前的 替代:
from django import forms
from django.views.generic import DetailView
from books.models import Author
?
class AuthorInterestForm(forms.Form):
message = forms.CharField()
?
class AuthorDisplay(DetailView):
model = Author
?
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = AuthorInterestForm()
return context
然后AuthorInterest是a FormView,但是我們必須帶進(jìn)來(lái), SingleObjectMixin以便我們可以找到我們正在談?wù)摰淖髡?,并且我們必須記住進(jìn)行設(shè)置template_name以確保表單錯(cuò)誤將呈現(xiàn)與AuthorDisplayon 相同的模板GET:
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
?
class AuthorInterest(SingleObjectMixin, FormView):
template_name = 'books/author_detail.html'
form_class = AuthorInterestForm
model = Author
?
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
return super().post(request, *args, **kwargs)
?
def get_success_url(self):
return reverse('author-detail', kwargs={'pk': self.object.pk})
最后,我們以一種新的AuthorDetail觀點(diǎn)將它們組合在一起。我們已經(jīng)知道,調(diào)用as_view()基于類(lèi)的視圖會(huì)使我們的行為與基于函數(shù)的視圖完全相同,因此我們可以在兩個(gè)子視圖之間進(jìn)行選擇。
當(dāng)然,您可以通過(guò)傳遞關(guān)鍵字參數(shù)as_view()的方式與在URLconf中傳遞 方式相同,例如,如果您希望該AuthorInterest行為也出現(xiàn)在另一個(gè)URL上,但使用不同的模板:
from django.views import View
?
class AuthorDetail(View):
?
def get(self, request, *args, **kwargs):
view = AuthorDisplay.as_view()
return view(request, *args, **kwargs)
?
def post(self, request, *args, **kwargs):
view = AuthorInterest.as_view()
return view(request, *args, **kwargs)
此方法還可以與任何其他直接從View或繼承的常規(guī)基于類(lèi)的視圖或您自己的基于類(lèi)的視圖一起使用 TemplateView,因?yàn)樗梢允共煌囊晥D盡可能地分開(kāi)。
當(dāng)您想多次執(zhí)行相同的操作時(shí),基于類(lèi)的視圖就會(huì)大放異彩。假設(shè)您正在編寫(xiě)一個(gè)API,并且每個(gè)視圖都應(yīng)返回JSON而不是呈現(xiàn)的HTML。
我們可以創(chuàng)建一個(gè)mixin類(lèi)以在所有視圖中使用,一次處理到JSON的轉(zhuǎn)換。
例如,JSON混合可能看起來(lái)像這樣:
from django.http import JsonResponse class JSONResponseMixin: """ 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.dumps(). """ # 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
注意:請(qǐng)查看序列化Django對(duì)象文檔,以獲取有關(guān)如何正確將Django模型和查詢(xún)集正確轉(zhuǎn)換為JSON的更多信息。
這個(gè)mixin提供了render_to_json_response()一種與簽名相同的方法render_to_response()。要使用它,我們需要將其混入一個(gè)TemplateView例如,并重寫(xiě) render_to_response()以render_to_json_response()代替調(diào)用:
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通過(guò)JSONResponseMixin與django.views.generic.detail.BaseDetailView– 混合來(lái)制作自己的版本(混合 了 DetailView模板渲染之前的行為):
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,并且行為完全相同-除了響應(yīng)的格式。
如果你想成為真正的冒險(xiǎn),你甚至可以混合使用一個(gè) DetailView子類(lèi),它能夠返回兩個(gè) HTML和JSON的內(nèi)容,根據(jù)不同的HTTP請(qǐng)求,的某些屬性,如查詢(xún)參數(shù)或HTTP標(biāo)頭?;旌鲜褂?JSONResponseMixin和和 SingleObjectTemplateResponseMixin,并render_to_response() 根據(jù)用戶(hù)請(qǐng)求的響應(yīng)類(lèi)型覆蓋的實(shí)現(xiàn), 以采用適當(dāng)?shù)某尸F(xiàn)方法:
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().render_to_response(context)
由于Python解決方法重載的方式,對(duì)的調(diào)用 super().render_to_response(context)最終調(diào)用的 render_to_response() 實(shí)現(xiàn)TemplateResponseMixin。
詳情參考: https://docs.djangoproject.com/en/3.0/
更多建議: