Open1

【Django】関数ベースビューとクラスベースビューでそれぞれTodoリストをつくり、2つの間の違いを見てみた(後編)

中井圭輔中井圭輔

はじめに

長いので前編・後編に分割した。

  • 前編:Django初期設定・関数ベースビュー
  • 後編:クラスベースビュー

前編はこちら

クラスベースビューの前に

ここまで関数ベースビューでTodoリストをつくった。ここからは関数ベースビューをクラスベースビューに置き換えていく。

その前にCRUD操作を行う関数ベースビューのpath()をコメントアウトしておく(nameがかぶるので)。クラスベースビューには別のnameをつけることもできるが、その場合テンプレート内のURLを書きかえないといけないので面倒くさい。

django/todo/urls.py
urlpatterns = [
    path("hello/", views.hello),
    path("hello_str/<str:str>/", views.hello_str),
    path("hello_slug/<slug:slug>/", views.hello_slug),
    path("template/", views.template),
    path("template_str/<str:str>/", views.template_str),
    # path("list/", views.todo_list_view, name="list"),
    # path("detail/<int:pk>/", views.todo_detail_view, name="detail"),
    # path("create/", views.todo_create_view, name="create"),
    # path("update/<int:pk>/", views.todo_update_view, name="update"),
    # path("delete/<int:pk>/", views.todo_delete_view, name="delete"),
]

クラスベース|一覧

ビュー

比較用に関数ベースビューを再掲する。

参考 関数ベースビュー
def todo_list_view(request):
    object_list = Todo.objects.all()
    context = {"object_list": object_list}
    return render(request, "todo/todo_list.html", context)

views.pyに以下のクラスベースビューを追加する。これで関数ベースビューのときと同じものを表示できる。modelの指定だけで一覧を表示でき、関数ベースビューと比べて格段に短い。データをすべて取得し、コンテキストに格納し、テンプレートに渡すという処理が全てショートカットされている。

  • modelは表示対象のモデル。ListViewにてmodel = Todoと指定することは、queryset = Todo.objects.all()と同じ。
ListView 最低限の設定
from django.views.generic import ListView

from todo.models import Todo

⋮

class TodoListView(ListView):
    model = Todo

以上が最低限の設定であるが、変数を追加することでカスタマイズできる。一部を紹介する。

  • queryset
    単にmodelを指定した場合は、modelのテーブルの全レコードを取得する。querysetを使うと、特定の条件を満たすレコードのみを指定できる。
  • template_name
    お好みのテンプレート名を指定する。指定しない場合はアプリ名/モデル名_list.htmlファイルを表示する(今回だとtodo/todo_list.html)。
  • context_object_name
    テンプレートに渡すオブジェクト名を指定する(デフォルトはobject_list)。
  • ordering
    指定したフィールドでレコードを並び替える。フィールドの前にハイフンをつけると降順、何もつけないと昇順になる。
  • paginage_by
    1ページに表示されるレコード数。
class TodoListView(ListView):
    queryset = Todo.objects.filter(text__contains="掃除")  # textに「掃除」を含むレコード
    template_name = "todo/hoge.html"                       # テンプレート名は"todo/hoge.html"
    context_object_name = "todo"                           # テンプレートに渡すオブジェクト名は"todo"
    ordering = "-updated_at"                               # 更新日時の降順
    paginate_by = 10                                       # 1ページに10件表示

URL

urls.pyに以下を追加する。クラスベースビューでは、クラス名に.as_view()を付けなければならない(以下同じ)。

django/todo/urls.py
urlpatterns = [
    ⋮
    path("", views.TodoListView.as_view(), name="list"),
]

クラスベース|詳細

ビュー

参考 関数ベースビュー
def todo_detail_view(request, pk):
    object = Todo.objects.get(pk=pk)
    context = {"object": object}
    return render(request, "todo/todo_detail.html", context)

views.pyに以下を追加する。一覧画面と同様にmodelを指定するだけで表示できる。関数ベースビューでは表示するデータをTodo.objects.get(pk=pk)で取得するが、その処理も必要ない。おそらくurls.py<int:pk>から自動で選択しているのだと思う(たぶん)。

DetailView 最低限の設定
from django.views.generic import DetailView

from todo.models import Todo

⋮

class TodoDetailView(DetailView):
    model = Todo

一覧画面と同様に、モデル以外にもいろいろ設定できる(一覧画面で説明したものは省略)。

  • template_name
    指定しない場合は、アプリ名/モデル名_detail.htmlファイルを表示する。
  • pk_url_kwarg
    プライマリーキーの名前をpkから変更する。例えばpk_url_kwarg = "todo_id"にすると、urls.pyのURLパターンは"detail/<int:todo_id>/"となる。
class TodoDetailView(DetailView):
    queryset = Todo.objects.filter(text__contains="あああ")
    template_name = "todo/hoge.html"
    context_object_name = "todo"
    pk_url_kwarg = "todo_id"   プライマリーキーの名前は"todo_id"

URL

urls.pyに以下を追加する。関数ベースビューと同様に、URLパターンに<int:pk>を含める。

django/todo/urls.py
urlpatterns = [
    ⋮
    path("detail/<int:pk>/", views.TodoDetailView.as_view(), name="detail"),
]

クラスベース|新規作成

ビュー

参考 関数ベースビュー
def todo_create_view(request):
    if request.method == "POST":
        text = request.POST["text"]
        Todo.objects.create(text=text)
        return redirect("todo:list")
    if request.method == "GET":
        return render(request, "todo/todo_form.html")

views.pyに以下のクラスベースビューを追加する。

  • model
    • 表示対象のモデル
  • fields
    • どのフィールドを扱うか指定
  • success_url
    • フォームが正常に保存されたときに遷移するURL。今回はtodo:listを指定しているので一覧画面にリダイレクトする。

また

  • 関数ベースビューではmethodPOSTGETで分岐していたが、クラスベースビューでは切り替える必要がない。
  • テンプレート内に{{ form }}と書くと、自動で入力フォームが生成される(今回は関数ベースビューのときのテンプレートを使いまわしたいので<input type="submit" value="新規作成" />のままにしている)。
django/todo/views.py
from django.urls import reverse_lazy
from django.views.generic import CreateView

from todo.models import Todo

⋮

class TodoCreateView(CreateView):
    model = Todo
    fields = ("text",)
    success_url = reverse_lazy("todo:list")

他にもいろいろ指定できる。

  • template_nameを指定しない場合は、新規作成画面としてアプリ名/モデル名_form.htmlファイルを表示する。
class TodoDetailView(DetailView):
    model = Todo
    fields = ("text",)
    success_url = reverse_lazy("todo:list")
    template_name = "todo/hoge.html"
    context_object_name = "todo"

URL

urls.pyに以下を追加する。

django/todo/urls.py
urlpatterns = [
    ⋮
    path("create/", views.TodoCreateView.as_view(), name="create"),
]

クラスベース|更新

ビュー

参考 関数ベースビュー
def todo_update_view(request, pk):
    object = Todo.objects.get(pk=pk)
    if request.method == "POST":
        object.text = request.POST["text"]
        object.save()
        return redirect("todo:list")
    else:
        context = {"object": object}
        return render(request, "todo/todo_update.html", context)

views.pyに以下を追加する。新規作成画面とほぼ同じである。

  • methodPOSTGETか分岐する必要がない。
  • どのデータを更新するかTodo.objects.get(pk=pk)で指定する必要もない。
  • template_nameを指定しない場合は、更新画面としてアプリ名/モデル名_form.htmlファイルを表示する(CreateViewと同じ名前)。新規作成画面と同じくtodo/todo_form.htmlにしたら、新規作成画面と同じ画面になってしまうので、更新画面はtodo/todo_update.htmlにした。
  • テンプレート内に{{ form }}と書くと、自動で入力フォームが生成される。
django/todo/views.py
from django.urls import reverse_lazy
from django.views.generic import UpdateView

from todo.models import Todo

⋮

class TodoUpdateView(UpdateView):
    model = Todo
    fields = ("text",)
    template_name = "todo/todo_update.html"
    success_url = reverse_lazy("todo:list")

URL

urls.pyに以下を追加する。

django/todo/urls.py
urlpatterns = [
    ⋮
    path("update/<int:pk>/", views.TodoUpdateView.as_view(), name="update"),
]

クラスベース|削除

ビュー

参考 関数ベースビュー
def todo_delete_view(request, pk):
    object = Todo.objects.get(pk=pk)
    if request.method == "POST":
        object.delete()
        return redirect("todo:list")
    else:
        context = {"object": object}
        return render(request, "todo/todo_confirm_delete.html", context)

views.pyに以下を追加する。

  • methodPOSTGETか分岐する必要がない。
  • どのデータを削除するかTodo.objects.get(pk=pk)で指定する必要もない。
  • template_nameがない状態でGETした場合は、削除画面としてアプリ名/モデル名_confirm_delete.htmlファイルを指定する。
django/todo/views.py
from django.urls import reverse_lazy
from django.views.generic import DeleteView

from todo.models import Todo

⋮

class TodoDeleteView(DeleteView):
    model = Todo
    success_url = reverse_lazy("todo:list")

URL

urls.pyに以下を追加する。

django/todo/urls.py
urlpatterns = [
    ⋮
    path("delete/<int:pk>/", views.TodoDeleteView.as_view(), name="delete"),
]

GitHub

TODO:コードをGitHubにアップロードする

感想

この手の記事はネットにあふれている。こんな初歩的なことを、こんなに長々と記事にする必要があるのかと正直何回も思った。とはいえ説明を読んだからといって理解したことにはならない。自分でコードを書いて2割ほど、自分で説明してようやく5割ほど理解できるのではないかと思う。今後も記事を書くことにより理解を深めたい。

また今回は関数ベースビューとクラスベースビューの違いに焦点を当てるため、最低限の機能だけ実装した。次は機能を増やして、より多くの技術に触れたい。

参考