クラスベースのビューでのミックスインの使用—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/topics/class-based-views/mixins
移動先:案内検索

クラスベースのビューでミックスインを使用する

注意

これは高度なトピックです。 これらの手法を検討する前に、 Djangoのクラスベースビューの実用的な知識をお勧めします。


Djangoの組み込みのクラスベースのビューは多くの機能を提供しますが、その一部は個別に使用することをお勧めします。 たとえば、HTTP応答を作成するためにテンプレートをレンダリングするビューを作成したいが、 TemplateView を使用することはできません。 おそらく、POSTでのみテンプレートをレンダリングする必要があり、GETはまったく別のことを行います。 TemplateResponse を直接使用することもできますが、これによりコードが重複する可能性があります。

このため、Djangoは、より個別の機能を提供する多数のミックスインも提供しています。 たとえば、テンプレートレンダリングは、 TemplateResponseMixin にカプセル化されています。 Djangoリファレンスドキュメントには、すべてのミックスインの完全なドキュメントが含まれています。

コンテキストとテンプレートの応答

クラスベースのビューでテンプレートを操作するための一貫したインターフェイスを提供するのに役立つ2つの中央ミックスインが提供されています。

TemplateResponseMixin

TemplateResponse を返すすべての組み込みビューは、TemplateResponseMixinが提供する render_to_response()メソッドを呼び出します。 ほとんどの場合、これは自動的に呼び出されます(たとえば、 TemplateViewDetailView の両方によって実装される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 、およびモデル固有のビュー CreateViewUpdateViewDeleteView )に関連するミックスインもあります。日付ベースの一般的なビュー。 これらは mixinリファレンスドキュメントでカバーされています。

DetailView:単一のDjangoオブジェクトを操作する

オブジェクトの詳細を表示するには、基本的に2つのことを行う必要があります。オブジェクトを検索してから、適切なテンプレートを使用して TemplateResponse を作成し、そのオブジェクトをコンテキストとして作成する必要があります。

オブジェクトを取得するために、 DetailViewSingleObjectMixin に依存します。このメソッドは、リクエストのURLに基づいてオブジェクトを特定する get_object()メソッドを提供します( URLConfで宣言されているpkおよびslugキーワード引数の場合、ビューの model 属性または queryset のいずれかからオブジェクトを検索します。 ]属性(提供されている場合)。 SingleObjectMixinは、 get_context_data()もオーバーライドします。これは、テンプレートレンダリングのコンテキストデータを提供するために、すべてのDjangoの組み込みクラスベースビューで使用されます。

次に、 TemplateResponse を作成するために、 DetailViewSingleObjectTemplateResponseMixin を使用します。その上。 実際にはかなり洗練されたオプションのセットを提供しますが、ほとんどの人が使用する主なオプションは<app_label>/<model_name>_detail.htmlです。 _detailの部分は、サブクラスの template_name_suffix を別のものに設定することで変更できます。 (たとえば、汎用編集ビューは、ビューの作成と更新に_formを使用し、ビューの削除に_confirm_deleteを使用します。)


ListView:多くのDjangoオブジェクトを操作する

オブジェクトのリストはほぼ同じパターンに従います。オブジェクトの(ページ付けされている可能性がある)リスト(通常は QuerySet )が必要です。次に、それを使用して適切なテンプレートで TemplateResponse を作成する必要があります。オブジェクトのリスト。

オブジェクトを取得するために、 ListViewMultipleObjectMixin を使用します。これは、 get_queryset()paginate_queryset()の両方を提供します。 SingleObjectMixin とは異なり、使用するクエリセットを特定するためにURLの一部をキーオフする必要がないため、デフォルトでは queryset または model 属性が使用されます。ビュークラスで。 ここで get_queryset()をオーバーライドする一般的な理由は、現在のユーザーに応じてオブジェクトを動的に変更したり、ブログの将来の投稿を除外したりするためです。

MultipleObjectMixin は、 get_context_data()もオーバーライドして、ページ付けに適切なコンテキスト変数を含めます(ページ付けが無効になっている場合はダミーを提供します)。 object_listがキーワード引数として渡され、 ListView がそれを調整することに依存しています。

TemplateResponse を作成するには、 ListViewMultipleObjectTemplateResponseMixin を使用します。 上記の 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モデルで説明します。

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 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に簡単にフックできます。

urls.py

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つの方法は、 ListViewSingleObjectMixin と組み合わせて、ページ付けされた書籍のリストのクエリセットが単一のオブジェクトとして見つかった出版社にぶら下がることができるようにすることです。 これを行うには、2つの異なるクエリセットが必要です。

Book ListView で使用するクエリセット
リストしたい本のPublisherにアクセスできるので、get_queryset()をオーバーライドし、Publisher逆外部キーマネージャーを使用します。
Publisher get_object()で使用するクエリセット
正しいPublisherオブジェクトをフェッチするには、get_object()のデフォルトの実装に依存します。 ただし、queryset引数を明示的に渡す必要があります。そうしないと、get_object()のデフォルトの実装がget_queryset()を呼び出し、オーバーライドしてBookオブジェクトを返す代わりになります。 Publisherのもの。

ノート

get_context_data()について慎重に考える必要があります。 SingleObjectMixinListView はどちらも、設定されている場合はcontext_object_nameの値でコンテキストデータに格納されるため、代わりにPublisherを明示的に確認します。 ]はコンテキストデータにあります。 ListView は、super()と呼ぶことを忘れない限り、適切なpage_objpaginatorを追加します。


これで、新しい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.objectget()内に設定して、後でget_context_data()およびget_queryset()で再び使用できるようにする方法に注目してください。 template_nameを設定しない場合、テンプレートはデフォルトで通常の ListView の選択になります。この場合、本のリストであるため"books/book_list.html"になります。 ListViewSingleObjectMixin について何も知らないため、このビューが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 は、それらの機能が必要な場合に使用できます。 上に示したように、少し注意すれば、SingleObjectMixinListView と組み合わせることもできます。 ただし、そうしようとすると状況はますます複雑になります。大まかな目安は次のとおりです。

ヒント

各ビューは、一般的なクラスベースのビューのグループ( detail、listediting 、およびdate)のいずれかからのミックスインまたはビューのみを使用する必要があります。 たとえば、 TemplateView (組み込みビュー)を MultipleObjectMixin (一般的なリスト)と組み合わせるのは問題ありませんが、SingleObjectMixin(一般的な詳細)の組み合わせで問題が発生する可能性があります。 MultipleObjectMixin(一般的なリスト)を使用します。


より洗練されたものにしようとするとどうなるかを示すために、より単純なソリューションがある場合に読みやすさと保守性を犠牲にする例を示します。 まず、 DetailViewFormMixin を組み合わせて、Django Form を同じURLにPOSTできるようにするという素朴な試みを見てみましょう。 ' DetailView を使用してオブジェクトを表示しています。

FormMixinとDetailViewの使用

ViewSingleObjectMixin を一緒に使用した以前の例を思い出してください。 特定の著者に対するユーザーの関心を記録していました。 なぜ彼らが彼らを好きなのかというメッセージを彼らに残させたいと今言ってください。 繰り返しになりますが、これをリレーショナルデータベースに保存するのではなく、ここでは気にしない、より難解なものに保存するとします。

この時点で、 Form にアクセスして、ユーザーのブラウザーからDjangoに送信される情報をカプセル化するのは自然なことです。 また、 REST に多額の投資を行っているため、ユーザーからのメッセージをキャプチャする場合と同じURLを使用して作成者を表示するとします。 そのためにAuthorDetailViewを書き直してみましょう。

GETの処理は DetailView から保持しますが、 Form をコンテキストデータに追加して、テンプレートでレンダリングできるようにする必要があります。 また、 FormMixin からフォーム処理を取り込み、POSTでフォームが適切に呼び出されるようにコードを記述します。

ノート

FormMixin を使用し、 DetailViewFormView (すでに適切な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()を提供する必要があります。


より良い解決策

FormMixinDetailView の間の微妙な相互作用の数は、物事を管理する能力をすでにテストしています。 この種のクラスを自分で作成することはまずありません。

この場合、post()メソッドを自分で記述し、 DetailView を唯一の汎用機能として維持できますが、 Form 処理コードの記述には多くの重複が伴います。

あるいは、フォームを処理するための別個のビューを用意することは、上記のアプローチよりも作業が少なくて済みます。これにより、 DetailView とは別の FormView を問題なく使用できます。


別のより良い解決策

ここで実際にやろうとしているのは、同じURLから2つの異なるクラスベースのビューを使用することです。 では、なぜそれだけをしないのですか? ここでは非常に明確な区分があります。GETリクエストは DetailViewForm がコンテキストデータに追加されている)と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

その場合、AuthorInterestFormFormView ですが、 SingleObjectMixin を取り込む必要があります。これにより、話している作成者を見つけることができ、設定することを忘れないでください。 template_nameは、フォームエラーがAuthorDetailViewGETで使用しているものと同じテンプレートをレンダリングすることを保証します。

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

ノート

DjangoモデルとクエリセットをJSONに正しく変換する方法の詳細については、 SerializingDjangoオブジェクトのドキュメントをご覧ください。


このミックスインは、 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つでミックスインを使用することもできます。 JSONResponseMixinBaseDetailView と混合することで 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ヘッダー。 JSONResponseMixinSingleObjectTemplateResponseMixin の両方を組み合わせ、 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)の呼び出しは、 TemplateResponseMixinrender_to_response()実装を呼び出すことになります。