組み込みのクラスベースのジェネリックビュー—Djangoドキュメント
組み込みのクラスベースのジェネリックビュー
特定のパターンを何度も繰り返すため、Webアプリケーションの作成は単調になる可能性があります。 Djangoは、モデルレイヤーとテンプレートレイヤーでその単調さの一部を取り除こうとしますが、Web開発者もビューレベルでこの退屈を経験します。
Djangoの一般的なビューは、その痛みを和らげるために開発されました。 これらは、ビュー開発で見られる特定の一般的なイディオムとパターンを採用し、それらを抽象化するため、コードをあまり記述しなくても、データの一般的なビューをすばやく書き込むことができます。
オブジェクトのリストの表示など、特定の一般的なタスクを認識し、任意のオブジェクトのリストを表示するコードを記述できます。 次に、問題のモデルを追加の引数としてURLconfに渡すことができます。
Djangoには、次のことを行うための一般的なビューが付属しています。
- 単一のオブジェクトのリストページと詳細ページを表示します。 会議を管理するアプリケーションを作成する場合、
TalkListView
とRegisteredUserListView
がリストビューの例になります。 単一のトークページは、「詳細」ビューと呼ばれるものの例です。 - 年/月/日のアーカイブページ、関連する詳細、および「最新の」ページに日付ベースのオブジェクトを表示します。
- ユーザーが許可の有無にかかわらず、オブジェクトを作成、更新、および削除できるようにします。
これらのビューを総合すると、開発者が遭遇する最も一般的なタスクを実行するためのインターフェースが提供されます。
ジェネリックビューの拡張
ジェネリックビューを使用すると、開発を大幅にスピードアップできることは間違いありません。 ただし、ほとんどのプロジェクトでは、一般的なビューでは不十分になる瞬間があります。 実際、新しいDjango開発者が尋ねる最も一般的な質問は、ジェネリックビューでさまざまな状況を処理する方法です。
これが、ジェネリックビューが1.3リリース用に再設計された理由の1つです。以前は、それらは途方もない一連のオプションを備えたビュー関数でした。 現在、URLconfで大量の構成を渡すのではなく、ジェネリックビューを拡張するための推奨される方法は、それらをサブクラス化し、それらの属性またはメソッドをオーバーライドすることです。
とはいえ、一般的なビューには制限があります。 ビューを汎用ビューのサブクラスとして実装するのに苦労している場合は、独自のクラスベースまたは機能ビューを使用して、必要なコードだけを記述する方が効果的です。
一般的なビューのその他の例は、一部のサードパーティアプリケーションで利用できます。または、必要に応じて独自のビューを作成することもできます。
オブジェクトの一般的なビュー
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):
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 PublisherListView(ListView):
model = Publisher
最後に、そのビューをURLにフックします。
# urls.py
from django.urls import path
from books.views import PublisherListView
urlpatterns = [
path('publishers/', PublisherListView.as_view()),
]
これが私たちが書く必要のあるすべてのPythonコードです。 ただし、テンプレートを作成する必要があります。 template_name
属性をビューに追加することで、使用するテンプレートをビューに明示的に指示できますが、明示的なテンプレートがない場合、Djangoはオブジェクトの名前からテンプレートを推測します。 この場合、推測されるテンプレートは"books/publisher_list.html"
になります。「books」の部分はモデルを定義するアプリの名前に由来し、「publisher」ビットはモデルの名前の小文字バージョンです。
ノート
したがって、(たとえば)DjangoTemplates
バックエンドのAPP_DIRS
オプションが:setting: `TEMPLATES` でTrueに設定されている場合、テンプレートの場所は次のようになります。/path /to/project/books/templates/books/publisher_list.html
このテンプレートは、すべてのパブリッシャーオブジェクトを含むobject_list
という変数を含むコンテキストに対してレンダリングされます。 テンプレートは次のようになります。
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
それが本当にすべてです。 ジェネリックビューのすべての優れた機能は、ジェネリックビューに設定された属性を変更することから生まれます。 汎用ビューリファレンスには、すべての汎用ビューとそのオプションが詳細に記載されています。 このドキュメントの残りの部分では、汎用ビューをカスタマイズおよび拡張する一般的な方法のいくつかについて検討します。
「フレンドリーな」テンプレートコンテキストの作成
サンプルのパブリッシャーリストテンプレートは、すべてのパブリッシャーをobject_list
という名前の変数に格納していることに気付いたかもしれません。 これは問題なく機能しますが、テンプレートの作成者にとってはそれほど「友好的」ではありません。ここでパブリッシャーと取引していることを「知っている」必要があります。
モデルオブジェクトを扱っている場合、これはすでに行われています。 オブジェクトまたはクエリセットを処理している場合、Djangoは小文字バージョンのモデルクラス名を使用してコンテキストにデータを入力できます。 これは、デフォルトのobject_list
エントリに加えて提供されますが、まったく同じデータが含まれています。 publisher_list
。
それでもうまく一致しない場合は、コンテキスト変数の名前を手動で設定できます。 汎用ビューのcontext_object_name
属性は、使用するコンテキスト変数を指定します。
# views.py
from django.views.generic import ListView
from books.models import Publisher
class PublisherListView(ListView):
model = Publisher
context_object_name = 'my_favorite_publishers'
便利なcontext_object_name
を提供することは常に良い考えです。 テンプレートをデザインする同僚はあなたに感謝します。
コンテキストの追加
多くの場合、一般的なビューによって提供される情報以外の追加情報を提示する必要があります。 たとえば、各出版社の詳細ページにすべての本のリストを表示することを考えてみてください。 DetailView 汎用ビューは、パブリッシャーにコンテキストを提供しますが、そのテンプレートで追加情報を取得するにはどうすればよいですか?
答えは、 DetailView をサブクラス化し、get_context_data
メソッドの独自の実装を提供することです。 デフォルトの実装では、表示されているオブジェクトがテンプレートに追加されますが、それをオーバーライドしてさらに送信することができます。
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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
を呼び出す必要があります。 2つのクラスが同じキーを定義しようとしない場合、これにより期待される結果が得られます。 ただし、親クラスがキーを設定した後(superの呼び出し後)にクラスがキーをオーバーライドしようとした場合、すべての親を確実にオーバーライドする場合は、そのクラスの子もsuperの後に明示的にキーを設定する必要があります。 問題が発生した場合は、ビューのメソッド解決順序を確認してください。
もう1つの考慮事項は、クラスベースの汎用ビューからのコンテキストデータがコンテキストプロセッサによって提供されるデータを上書きすることです。 例については、 get_context_data()を参照してください。
オブジェクトのサブセットの表示
それでは、これまでずっと使用してきたmodel
引数を詳しく見てみましょう。 ビューが操作するデータベースモデルを指定するmodel
引数は、単一のオブジェクトまたはオブジェクトのコレクションを操作するすべての汎用ビューで使用できます。 ただし、model
引数は、ビューが操作するオブジェクトを指定する唯一の方法ではありません。queryset
引数を使用してオブジェクトのリストを指定することもできます。
from django.views.generic import DetailView
from books.models import Publisher
class PublisherDetailView(DetailView):
context_object_name = 'publisher'
queryset = Publisher.objects.all()
model = Publisher
を指定することは、queryset = Publisher.objects.all()
と言うことの省略形です。 ただし、queryset
を使用してオブジェクトのフィルターされたリストを定義することにより、ビューに表示されるオブジェクトをより具体的にすることができます( QuerySetの詳細については、クエリの作成を参照してください)。 オブジェクト、および詳細については、クラスベースのビューリファレンスを参照してください)。
例を挙げれば、出版日ごとに本のリストを並べ替えることができます。最新のものが最初になります。
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
queryset = Book.objects.order_by('-publication_date')
context_object_name = 'book_list'
これはごくわずかな例ですが、アイデアをうまく示しています。 通常、オブジェクトを並べ替えるだけでは不十分です。 特定の出版社の本のリストを提示したい場合は、同じ手法を使用できます。
from django.views.generic import ListView
from books.models import Book
class AcmeBookListView(ListView):
context_object_name = 'book_list'
queryset = Book.objects.filter(publisher__name='ACME Publishing')
template_name = 'books/acme_list.html'
フィルタリングされたqueryset
に加えて、カスタムテンプレート名も使用していることに注意してください。 そうしないと、ジェネリックビューは「バニラ」オブジェクトリストと同じテンプレートを使用することになりますが、これは私たちが望むものではない可能性があります。
また、これは出版社固有の本を作成するための非常にエレガントな方法ではないことにも注意してください。 別の発行者ページを追加する場合は、URLconfにさらに数行が必要になり、多くの発行者が不合理になります。 この問題については、次のセクションで扱います。
ノート
/books/acme/
をリクエストしたときに404を受け取った場合は、「ACMEPublishing」という名前のパブリッシャーが実際にあることを確認してください。 この場合、ジェネリックビューにはallow_empty
パラメーターがあります。 詳細については、クラスベースビューリファレンスを参照してください。
動的フィルタリング
もう1つの一般的なニーズは、リストページで指定されたオブジェクトをURLのキーでフィルタリングすることです。 以前、URLconfに出版社の名前をハードコーディングしましたが、任意の出版社によるすべての本を表示するビューを作成したい場合はどうでしょうか。
便利なことに、ListView
には、オーバーライドできる get_queryset()メソッドがあります。 デフォルトでは、queryset
属性の値を返しますが、これを使用してロジックを追加することもできます。
これを機能させるための重要な部分は、クラスベースのビューが呼び出されると、さまざまな便利なものがself
に格納されることです。 リクエスト(self.request
)だけでなく、これには、URLconfに従ってキャプチャされた位置引数(self.args
)と名前ベースの引数(self.kwargs
)が含まれます。
ここに、キャプチャされた単一のグループを持つURLconfがあります。
# urls.py
from django.urls import path
from books.views import PublisherBookListView
urlpatterns = [
path('books/<publisher>/', PublisherBookListView.as_view()),
]
次に、PublisherBookListView
ビュー自体を記述します。
# views.py
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
template_name = 'books/books_by_publisher.html'
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
return Book.objects.filter(publisher=self.publisher)
get_queryset
を使用してクエリセットの選択にロジックを追加すると、強力であると同時に便利です。 たとえば、必要に応じて、self.request.user
を使用して、現在のユーザーまたはその他のより複雑なロジックを使用してフィルタリングできます。
同時にパブリッシャーをコンテキストに追加して、テンプレートで使用することもできます。
# ...
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().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.urls import path
from books.views import AuthorDetailView
urlpatterns = [
#...
path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
]
次に、新しいビューを記述します– get_object
はオブジェクトを取得するメソッドです–それで、それをオーバーライドして呼び出しをラップします:
from django.utils import timezone
from django.views.generic import DetailView
from books.models import Author
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
ノート
ここでのURLconfは、名前付きグループpk
を使用します。この名前は、DetailView
がクエリセットのフィルタリングに使用される主キーの値を見つけるために使用するデフォルトの名前です。
グループを別の名前で呼び出す場合は、ビューで pk_url_kwarg を設定できます。