クラスベースのビューでのミックスインの使用—Djangoドキュメント
クラスベースのビューでミックスインを使用する
Djangoの組み込みのクラスベースのビューは多くの機能を提供しますが、その一部は個別に使用することをお勧めします。 たとえば、HTTP応答を作成するためにテンプレートをレンダリングするビューを作成したいが、 TemplateView を使用することはできません。 おそらく、POST
でのみテンプレートをレンダリングする必要があり、GET
はまったく別のことを行います。 TemplateResponse を直接使用することもできますが、これによりコードが重複する可能性があります。
このため、Djangoは、より個別の機能を提供する多数のミックスインも提供しています。 たとえば、テンプレートレンダリングは、 TemplateResponseMixin にカプセル化されています。 Djangoリファレンスドキュメントには、すべてのミックスインの完全なドキュメントが含まれています。
コンテキストとテンプレートの応答
クラスベースのビューでテンプレートを操作するための一貫したインターフェイスを提供するのに役立つ2つの中央ミックスインが提供されています。
TemplateResponseMixin
TemplateResponse を返すすべての組み込みビューは、
TemplateResponseMixin
が提供する render_to_response()メソッドを呼び出します。 ほとんどの場合、これは自動的に呼び出されます(たとえば、 TemplateView と DetailView の両方によって実装されるget()
メソッドによって呼び出されます)。 同様に、オーバーライドする必要はほとんどありませんが、Djangoテンプレートを介してレンダリングされていないものを応答で返す場合は、オーバーライドする必要があります。 この例については、 JSONResponseMixinの例を参照してください。render_to_response()
自体が get_template_names()を呼び出します。これは、デフォルトでクラスベースのビューで template_name を検索します。 他の2つのミックスイン( SingleObjectTemplateResponseMixin および MultipleObjectTemplateResponseMixin )はこれをオーバーライドして、実際のオブジェクトを処理するときに、より柔軟なデフォルトを提供します。ContextMixin
テンプレートのレンダリング(上記の
TemplateResponseMixin
を含む)など、コンテキストデータを必要とするすべての組み込みビューは、 get_context_data()を呼び出して、キーワード引数として確実にデータを渡す必要があります。 。get_context_data()
は辞書を返します。ContextMixin
では、キーワード引数を返しますが、これをオーバーライドして、辞書にメンバーを追加するのが一般的です。 extra_context 属性を使用することもできます。
Djangoの一般的なクラスベースのビューを構築する
Djangoの2つの汎用クラスベースビューが、個別の機能を提供するミックスインからどのように構築されているかを見てみましょう。 オブジェクトの「詳細」ビューをレンダリングする DetailView と、通常はクエリセットからオブジェクトのリストをレンダリングし、オプションでそれらをページングする ListView を検討します。 これにより、4つのミックスインが紹介されます。これらのミックスインは、単一のDjangoオブジェクトまたは複数のオブジェクトを操作するときに便利な機能を提供します。
汎用編集ビュー( FormView 、およびモデル固有のビュー CreateView 、 UpdateView 、 DeleteView )に関連するミックスインもあります。日付ベースの一般的なビュー。 これらは mixinリファレンスドキュメントでカバーされています。
DetailView:単一のDjangoオブジェクトを操作する
オブジェクトの詳細を表示するには、基本的に2つのことを行う必要があります。オブジェクトを検索してから、適切なテンプレートを使用して TemplateResponse を作成し、そのオブジェクトをコンテキストとして作成する必要があります。
オブジェクトを取得するために、 DetailView は SingleObjectMixin に依存します。このメソッドは、リクエストのURLに基づいてオブジェクトを特定する get_object()メソッドを提供します( URLConfで宣言されているpk
およびslug
キーワード引数の場合、ビューの model 属性または queryset のいずれかからオブジェクトを検索します。 ]属性(提供されている場合)。 SingleObjectMixin
は、 get_context_data()もオーバーライドします。これは、テンプレートレンダリングのコンテキストデータを提供するために、すべてのDjangoの組み込みクラスベースビューで使用されます。
次に、 TemplateResponse を作成するために、 DetailView は SingleObjectTemplateResponseMixin を使用します。その上。 実際にはかなり洗練されたオプションのセットを提供しますが、ほとんどの人が使用する主なオプションは<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()もオーバーライドして、ページ付けに適切なコンテキスト変数を含めます(ページ付けが無効になっている場合はダミーを提供します)。 object_list
がキーワード引数として渡され、 ListView がそれを調整することに依存しています。
TemplateResponse を作成するには、 ListView は MultipleObjectTemplateResponseMixin を使用します。 上記の SingleObjectTemplateResponseMixin と同様に、これはget_template_names()
をオーバーライドして、さまざまなオプションを提供します。最も一般的に使用されるのは、<app_label>/<model_name>_list.html
と[X288X ] の部分は、 template_name_suffix 属性から再び取得されます。 (日付ベースの汎用ビューは、_archive
、_archive_year
などのサフィックスを使用して、さまざまな特殊な日付ベースのリストビューに異なるテンプレートを使用します。)
Djangoのクラスベースのビューミックスインを使用する
これで、Djangoの汎用クラスベースビューが提供されたミックスインをどのように使用するかを見てきました。それらを組み合わせることができる他の方法を見てみましょう。 これらを組み込みのクラスベースのビューまたは他の一般的なクラスベースのビューと組み合わせる予定ですが、Djangoが提供するよりも、解決できるまれな問題がいくつかあります。
警告
すべてのミックスインを一緒に使用できるわけではなく、すべての汎用クラスベースのビューを他のすべてのミックスインで使用できるわけではありません。 ここでは、機能するいくつかの例を示します。 他の機能をまとめたい場合は、使用している異なるクラス間で重複する属性とメソッド間の相互作用、およびメソッド解決順序がメソッドのどのバージョンにどのように影響するかを考慮する必要があります。どのような順序で呼び出されます。
Djangoのクラスベースのビューとクラスベースのビューミックスインのリファレンスドキュメントは、どの属性とメソッドが異なるクラスとミックスインの間で競合を引き起こす可能性があるかを理解するのに役立ちます。
疑わしい場合は、 View または TemplateView をベースにして、おそらく SingleObjectMixin および MultipleObjectMixin を使用する方がよい場合がよくあります。 おそらくより多くのコードを書くことになりますが、後で他の誰かがコードに来ることを明確に理解できる可能性が高く、心配するインタラクションが少なくなると、思考を節約できます。 (もちろん、問題に取り組む方法についてのインスピレーションを得るために、Djangoの一般的なクラスベースのビューの実装にいつでも浸ることができます。)
ビューでSingleObjectMixinを使用する
POST
にのみ応答するクラスベースのビューを記述したい場合は、 View をサブクラス化し、そのサブクラスにpost()
メソッドを記述します。 ただし、URLから識別される特定のオブジェクトで処理を機能させる場合は、 SingleObjectMixin によって提供される機能が必要になります。
これは、汎用クラスベースビューの紹介で使用したAuthor
モデルで説明します。
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 RecordInterestView(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}))
実際には、リレーショナルデータベースではなく、Key-Valueストアに関心を記録したいと思うかもしれないので、そのビットは省略しました。 SingleObjectMixin の使用について心配する必要があるビューの唯一のビットは、self.get_object()
の呼び出しで、関心のある作成者を検索する場所です。 他のすべてはmixinによって私たちのために世話をされます。
これをURLに簡単にフックできます。
from django.urls import path
from books.views import RecordInterestView
urlpatterns = [
#...
path('author/<int:pk>/interest/', RecordInterestView.as_view(), name='author-interest'),
]
pk
という名前のグループに注意してください。 get_object()はAuthor
インスタンスを検索するために使用します。 スラッグ、または SingleObjectMixin の他の機能のいずれかを使用することもできます。
SingleObjectMixinとListViewの使用
ListView は組み込みのページ付けを提供しますが、すべて(外部キーによって)別のオブジェクトにリンクされているオブジェクトのリストをページ付けしたい場合があります。 私たちの出版の例では、特定の出版社によるすべての本をめくりたいと思うかもしれません。
これを行う1つの方法は、 ListView を SingleObjectMixin と組み合わせて、ページ付けされた書籍のリストのクエリセットが単一のオブジェクトとして見つかった出版社にぶら下がることができるようにすることです。 これを行うには、2つの異なるクエリセットが必要です。
Book
ListView で使用するクエリセット- リストしたい本の
Publisher
にアクセスできるので、get_queryset()
をオーバーライドし、Publisher
の逆外部キーマネージャーを使用します。 Publisher
get_object()で使用するクエリセット- 正しい
Publisher
オブジェクトをフェッチするには、get_object()
のデフォルトの実装に依存します。 ただし、queryset
引数を明示的に渡す必要があります。そうしないと、get_object()
のデフォルトの実装がget_queryset()
を呼び出し、オーバーライドしてBook
オブジェクトを返す代わりになります。Publisher
のもの。
ノート
get_context_data()
について慎重に考える必要があります。 SingleObjectMixin と ListView はどちらも、設定されている場合はcontext_object_name
の値でコンテキストデータに格納されるため、代わりにPublisher
を明示的に確認します。 ]はコンテキストデータにあります。 ListView は、super()
と呼ぶことを忘れない限り、適切なpage_obj
とpaginator
を追加します。
これで、新しいPublisherDetailView
を作成できます。
from django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from books.models import Publisher
class PublisherDetailView(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()
self.object
をget()
内に設定して、後でget_context_data()
およびget_queryset()
で再び使用できるようにする方法に注目してください。 template_name
を設定しない場合、テンプレートはデフォルトで通常の ListView の選択になります。この場合、本のリストであるため"books/book_list.html"
になります。 ListView は SingleObjectMixin について何も知らないため、このビューがPublisher
と関係があるという手がかりはありません。
この例では、paginate_by
は意図的に小さいため、ページネーションが機能することを確認するために大量の本を作成する必要はありません。 使用したいテンプレートは次のとおりです。
{% 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 %}
より複雑なものは避けてください
通常、 TemplateResponseMixin および SingleObjectMixin は、それらの機能が必要な場合に使用できます。 上に示したように、少し注意すれば、SingleObjectMixin
を ListView と組み合わせることもできます。 ただし、そうしようとすると状況はますます複雑になります。大まかな目安は次のとおりです。
ヒント
各ビューは、一般的なクラスベースのビューのグループ( detail、list 、 editing 、およびdate)のいずれかからのミックスインまたはビューのみを使用する必要があります。 たとえば、 TemplateView (組み込みビュー)を MultipleObjectMixin (一般的なリスト)と組み合わせるのは問題ありませんが、SingleObjectMixin
(一般的な詳細)の組み合わせで問題が発生する可能性があります。 MultipleObjectMixin
(一般的なリスト)を使用します。
より洗練されたものにしようとするとどうなるかを示すために、より単純なソリューションがある場合に読みやすさと保守性を犠牲にする例を示します。 まず、 DetailView と FormMixin を組み合わせて、Django Form を同じURLにPOST
できるようにするという素朴な試みを見てみましょう。 ' DetailView を使用してオブジェクトを表示しています。
FormMixinとDetailViewの使用
View と SingleObjectMixin を一緒に使用した以前の例を思い出してください。 特定の著者に対するユーザーの関心を記録していました。 なぜ彼らが彼らを好きなのかというメッセージを彼らに残させたいと今言ってください。 繰り返しになりますが、これをリレーショナルデータベースに保存するのではなく、ここでは気にしない、より難解なものに保存するとします。
この時点で、 Form にアクセスして、ユーザーのブラウザーからDjangoに送信される情報をカプセル化するのは自然なことです。 また、 REST に多額の投資を行っているため、ユーザーからのメッセージをキャプチャする場合と同じURLを使用して作成者を表示するとします。 そのためにAuthorDetailView
を書き直してみましょう。
GET
の処理は DetailView から保持しますが、 Form をコンテキストデータに追加して、テンプレートでレンダリングできるようにする必要があります。 また、 FormMixin からフォーム処理を取り込み、POST
でフォームが適切に呼び出されるようにコードを記述します。
ノート
FormMixin を使用し、 DetailView を FormView (すでに適切なpost()
を提供している)と混合しようとするのではなく、post()
を自分で実装します)両方のビューがget()
を実装しているため、事態はさらに混乱します。
新しいAuthorDetailView
は次のようになります。
# 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 AuthorDetailView(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()
は、リダイレクト先の場所を提供します。これは、form_valid()
のデフォルトの実装で使用されます。 前述のように、独自のpost()
を提供する必要があります。
より良い解決策
FormMixin と DetailView の間の微妙な相互作用の数は、物事を管理する能力をすでにテストしています。 この種のクラスを自分で作成することはまずありません。
この場合、post()
メソッドを自分で記述し、 DetailView を唯一の汎用機能として維持できますが、 Form 処理コードの記述には多くの重複が伴います。
あるいは、フォームを処理するための別個のビューを用意することは、上記のアプローチよりも作業が少なくて済みます。これにより、 DetailView とは別の FormView を問題なく使用できます。
別のより良い解決策
ここで実際にやろうとしているのは、同じURLから2つの異なるクラスベースのビューを使用することです。 では、なぜそれだけをしないのですか? ここでは非常に明確な区分があります。GET
リクエストは DetailView ( Form がコンテキストデータに追加されている)とPOST
リクエストを取得する必要があります FormView を取得する必要があります。 まず、これらのビューを設定しましょう。
AuthorDetailView
ビューは、AuthorDetailView を最初に導入したときのとほぼ同じです。 AuthorInterestForm
をテンプレートで使用できるようにするには、独自のget_context_data()
を作成する必要があります。 わかりやすくするために、以前のget_object()
オーバーライドはスキップします。
from django import forms
from django.views.generic import DetailView
from books.models import Author
class AuthorInterestForm(forms.Form):
message = forms.CharField()
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = AuthorInterestForm()
return context
その場合、AuthorInterestForm
は FormView ですが、 SingleObjectMixin を取り込む必要があります。これにより、話している作成者を見つけることができ、設定することを忘れないでください。 template_name
は、フォームエラーがAuthorDetailView
が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 AuthorInterestFormView(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})
最後に、これを新しいAuthorView
ビューにまとめます。 クラスベースのビューで as_view()を呼び出すと、関数ベースのビューとまったく同じように動作するものが得られることはすでにわかっているので、2つのサブビューから選択した時点でそれを実行できます。
AuthorInterestFormView
の動作を別のURLにも表示したいが、別のテンプレートを使用する場合など、URLconfの場合と同じ方法で、キーワード引数を as_view()に渡すことができます。 :
from django.views import View
class AuthorView(View):
def get(self, request, *args, **kwargs):
view = AuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = AuthorInterestFormView.as_view()
return view(request, *args, **kwargs)
このアプローチは、他の一般的なクラスベースのビュー、または View または TemplateView から直接継承する独自のクラスベースのビューでも使用できます。これは、さまざまなビューを可能な限り分離するためです。 。
HTMLだけではありません
クラスベースのビューが優れているのは、同じことを何度もやりたいときです。 APIを作成していて、すべてのビューがレンダリングされたHTMLではなくJSONを返す必要があるとします。
すべてのビューで使用するミックスインクラスを作成して、JSONへの変換を1回処理できます。
たとえば、JSONミックスインは次のようになります。
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
このミックスインは、 render_to_response()と同じシグネチャを持つrender_to_json_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)
同様に、一般的なビューの1つでミックスインを使用することもできます。 JSONResponseMixin
を BaseDetailView と混合することで DetailView の独自のバージョンを作成できます–(テンプレートのレンダリング動作が混合される前の 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 と同じ方法で展開できますが、応答の形式を除いて、まったく同じ動作をします。
本当に冒険したい場合は、HTTPリクエストのプロパティに応じて、 HTMLとJSONの両方のコンテンツを返すことができる DetailView サブクラスを混在させることもできます。クエリ引数またはHTTPヘッダー。 JSONResponseMixin
と 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().render_to_response(context)
Pythonがメソッドのオーバーロードを解決する方法のため、super().render_to_response(context)
の呼び出しは、 TemplateResponseMixin の render_to_response()実装を呼び出すことになります。