diadia

興味があることをやってみる。自分のメモを残しておきます。

再考:DetailViewの構造

DetailViewで詳細ページが表示できる理由

class ProductDetailView(DetailView):
    model = Product

上記の記述だけでhttprequestのGETが成立してしまうことをまず整理したい。

まずDetailViewはViewクラスを継承したものである。だからViewをカスタムしたものがDetailViewであり、GETのリクエストに対しViewのような流れでDetailViewも処理されると考えられる。そこでViewではどのようにGETのリクエストを返すか見てみる。

class ProductView(View):
    def get(self, request, *args, **kwargs):
        pk = self.kwargs["pk"]
        object = Product.objects.get(id=pk)
        context = {}
        context["object"] = object
        
        return render(request, "products/detail.html", context)

ProductDetailViewをProductViewと比べると、以下の違いがある。

  • objectを指定しないでもobjectが自動的に取得される。
  • 取得したobjectがcontextに自動で登録されている。
  • 自動的にオブジェクトをテンプレートにレンダリングされる。

細かく見ればさらに出てくるだろうが、まずこの3つが大きな違いだろう。
これらの違いはいかにして実現されているか、よく観察してみたい。

objectを指定しないでもobjectが取得される

これはDetailViewのget_object()メソッドが実行されると、自動的に特定のオブジェクトを取得することになっている。
https://github.com/django/django/blob/2.1/django/views/generic/detail.py#L20

def get_object(self, queryset=None):
    if queryset is None:
        queryset = self.get_queryset()

    pk = self.kwargs.get(self.pk_url_kwarg)
    slug = self.kwargs.get(self.slug_url_kwarg)
    if pk is not None:
        queryset = queryset.filter(pk=pk)
    ...
    try:
        # Get the single item from the filtered queryset
        obj = queryset.get()
    except queryset.model.DoesNotExist:
    ...
    return obj

DetailViewはget_objectメソッドを持つSingleObjectMixinを継承しているので、上記の機能を有する。話がそれるが、obj = queryset.get()はgetの引数がない。これについては下記の豆知識に詳細を書いておいた。

取得したobjectがcontextに自動で登録されている

これはBaseDetailViewのgetメソッドを見れば自動的に登録されていることが分かる。

BaseDetailView(SingleObjectMixin, View)のgetメソッド

def get(self, request, *args, **kwargs):
    self.object = self.get_object()      # 上記で説明したget_objectメソッドの実行
    context = self.get_context_data(object=self.object) # contextにobjectをぶち込んでいる
    return self.render_to_response(context)
    

ソース:https://github.com/django/django/blob/2.1/django/views/generic/detail.py#L105

そもそもユーザが任意のURLをリクエストすると、URLconf(=urls.py)でas_viewメソッドが実行される。as_viewメソッドはdispatchメソッドを呼び出す。そしてdispatchメソッドはhandlerという形式でViewまたはDetailViewのgetやpostメソッドを呼び起こす。この時にViewクラスはgetメソッドが存在していないので結果的にデベロッパーがgetメソッドを作らされるのである。それは先のないプラレールの線路の行き先を作るが如く...。一方DetailViewはgetメソッドが準備してあるのでデベロッパーが作る必要がない。この辺の背景を理解していると良いと思う。

自動的にオブジェクトをテンプレートにレンダリングされる

 render()関数はレンダリングしたHttpResponseオブジェクトを返す。
https://docs.djangoproject.com/ja/2.2/intro/tutorial03/#a-shortcut-render
render_to_responseメソッドはresponse_classを呼び出す。response_classはデフォルトではTemplateResponseオブジェクトを作成する。このオブジェクトはHttpResponse同じ場面で使うことができる(TemplateResponse ≒ HttpResponse)
この辺をもうちょっと資料を集めておきます。
https://docs.djangoproject.com/ja/2.2/ref/template-response/#using-templateresponse-and-simpletemplateresponse

豆知識

querysetからオブジェクトを取り出すとき、get()を使うが引数は必ずしも必要ない。通常はquerysetにオブジェクトが複数存在している時queryset.get(id=pk)みたいに記述する。しかしquerysetにオブジェクトが一個しか入っていない場合は例外として引数不要でget()を使うことができる。これは、ソースコードで使われている。
https://github.com/django/django/blob/2.1/django/views/generic/detail.py#L52

If you expect a queryset to return one row, you can use get() without any arguments to return the object for that row:

ドキュメント:https://docs.djangoproject.com/ja/2.2/ref/models/querysets/#django.db.models.query.QuerySet.get