15.Djangoクラスベースの汎用ビュー

Djangoのビュー関数の書き方には大きく分けて関数ベースとクラスベースの2通りがあります。ここまでは関数ベースでビューを記述してきましたが、クラスベースの書き方とDjangoが予め用意してくれている汎用ビューについても知っておきましょう。

目次

関数ベースとクラスベース

Djangoのビュー関数は次のような特徴を備えています。

  • 第1 引数に django.http.request.HttpRequest オブジェクトを受け取る
  • 戻り値として django.http.response.HttpResponse オブジェクトを返す

Djangoのビュー関数の書き方は大きく分けて次の2通りがあります。

1.そのまま関数として書く(関数ベース)

関数ベースのビューはどういうデータを受け取ってどういう処理をしているのか理解がしやすいというメリットがある一方で、コードの再利用がしにくいというデメリットがあります。

2.クラスで書いてビュー関数に変換する(クラスベース)

クラスベースはDjangoが汎用ビューとして予め様々な用途のクラスを用意してくれており、短いコードで書けて再利用もしやすいというメリットがある一方、コードを見ても何をしているのか理解がしづらいというデメリットがあります。

とはいえコードの見通しを良くしたいのであれば「django.views.generic.base.View」を直接継承することもできますし、せっかくDjangoを使うのであればDjangoの機能を最大限利用できるクラスベースが便利です。

例えば 10.Djangoでリダイレクトを設定してみよう で使用した RedirectView は汎用ビューの1つです。

汎用ビューを使えばモデルを参照してテンプレートで表示するだけの単純な処理を最小限のコードで実現できます。

投稿一覧ページ(ListView)

それでは、ここまで関数ベースで作ってきたbbsアプリケーションのビューをクラスベースの汎用ビューを使った記述に書き換えてみましょう。

まずは投稿一覧ページです。

クラスベースの書き方ではルーティングの書き方も異なります。

/bbs/ の下にある urls.py を次のように書き換えましょう。

from django.urls import path
from . import views

app_name = 'bbs'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'), # 一覧ページのビュー
]

as_view() はクラスベースのビューをビュー関数化するメソッドです。

つまり URLの /bbs/ の後が空欄だったら、views.py のIndexViewクラスからビュー関数化したものを返します。

次に、このIndexViewクラスを作成しましょう。

views.py に次のように記述します。

from django.views import generic # 汎用ビューをインポート
from .models import Article # models.pyのArticleクラスをインポート

# IndexViewクラスを作成
class IndexView(generic.ListView):
    model = Article

モデルから取り出したデータで一覧ページを作りたい場合は generic.ListView を使います。

中身はモデルにArticleクラスを指定するだけです。

テンプレート名を指定することもできますが、省略するとモデル名と使用した汎用ビューの名前から自動的に決めてくれます。例えばこの場合は Article_list.html がテンプレート名になります。

最後にテンプレートを作成しましょう。

/templates/bbs/Article_list.html を作成し、次のように記述します。

これまでの講座で用意した共通テンプレートをそのまま使います。

{% extends "./base.html" %}

{% block content %}
        <h1>クラスベースの汎用ビューを使ってみよう!</h1>

        {% for article in object_list %}
            <p> {{ article.content }}, {{ article.user_name }}</p>
        {% endfor %}
{% endblock %}

ビューから受け取ったデータは object_list で取り出すことができます。

あとはそれをループで1つずつ取り出して、article.content と article.user_name をそれぞれ取り出して表示しています。

では、動作を確認しましょう。

Djangoコマンドを使ってサーバーを起動し、http://127.0.0.1:8000/bbs にアクセスします。

投稿一覧ページ

投稿一覧ページが表示できました!

関数ベースの記述と比べると、投稿一覧ページがかなり少ないコードで記述できたのがわかります。

個別投稿ページ(DetailView)

個別投稿ページもクラスベースの汎用ビューを使った記述に書き換えてみましょう。

urls.py に次のように記述します。

from django.urls import path
from . import views

app_name = 'bbs'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'), # 個別投稿ページ
]

次に、このDetailViewクラスを作成します。

views.py に次のように記述します。

from django.views import generic
from .models import Article

class IndexView(generic.ListView):
    model = Article

# DetailViewクラスを作成
class DetailView(generic.DetailView):
    model = Article

テンプレート名は自動的に Article_detail.html になります。

次に、/templates/bbs/Article_detail.html を作成し、次のように記述します。

{% extends "./base.html" %}

{% block content %}
  <h1>{{ article.id }}の個別投稿ページ</h1>
  <p>{{ article.content }}, {{article.user_name }}</p>
  <div>
    <button onclick='location.href="{% url "bbs:index" %}"'>一覧</button>
  </div>
{% endblock %}

最後に投稿一覧ページから個別投稿ページにリンクを貼っておきましょう。

Article_list.html に次のように記述します。

{% extends "./base.html" %}

{% block content %}
  <h1>クラスベースの汎用ビューを使ってみよう!</h1>

  {% for article in object_list %}
    <p>
      <a href='{% url "bbs:detail" article.id %}'>{{ article.content }}</a>
      {{ article.user_name }}
    </p>
  {% endfor %}
{% endblock %}

それでは動作確認です。

Djangoコマンドを使ってサーバーを起動し、http://127.0.0.1:8000/bbs にアクセスします。

リンク付きの投稿一覧ページ

投稿一覧ページの各投稿文章がリンクになっています。

試しに「こんちゃっす」をクリックしてみましょう。

個別投稿ページ

クリックした投稿の個別ページが表示されました。

新規と編集のフォーム(edit.CreateView, edit.UpdateView)

次は新規投稿・投稿の更新画面です。

urls.py に次のように記述します。

from django.urls import path
from . import views

app_name = 'bbs'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('create/', views.CreateView.as_view(), name='create'), # 新規投稿
    path('<int:pk>/update/', views.UpdateView.as_view(), name='update'), # 投稿更新
]

CreateViewクラスとUpdateViewクラスを作成します。

views.py に次のように記述します。

from django.views import generic
from .models import Article

class IndexView(generic.ListView):
    model = Article

class DetailView(generic.DetailView):
    model = Article

# CreateViewクラスを作成
class CreateView(generic.edit.CreateView):
    model = Article
    fields = '__all__'

# UpdateViewクラスを作成
class UpdateView(generic.edit.UpdateView):
    model = Article
    fields = '__all__'

テンプレート名は新規投稿・投稿編集ともに、自動的に article_form.html になります。

投稿・編集画面なのでモデルの他にフィールドを指定しています。 __all__ としているので、フォームはすべてのカラムを入力できます。

テンプレートを作成しましょう。/templates/bbs/article_form.html を作成し、次のように記述します。

{% extends './base.html' %}
{% block content %}

<h1>投稿の{{ object|yesno:'更新,新規作成'}}</h1>

<form action='' method='post'>{% csrf_token %}
    {{ form.as_p }}<!-- すべての項目を表示 -->
    <button type='submit'>{{ object|yesno:'更新,作成'}}</button>
</form>
<div>
    <a href='JavaScript:history.back()'>戻る</a>
</div>

{% endblock %}

テンプレートを新規投稿と編集で共通としたため、新規投稿の場合と投稿を編集する場合で、見出しとボタンにそれぞれ異なる文字列を表示するよう記述しています。

汎用ビューを使用する場合、データを保存するための関数は自動的に用意されるため、別途作成する必要はありません。

ただ、このままでは新規投稿や投稿編集後のリダイレクト先URLが指定されていないため、投稿完了後にエラーページへリダイレクトしてしまいます。

そこで、モデルで get_absolute_urlメソッドを定義して、投稿完了後はその投稿の個別投稿ページにリダイレクトするようにしましょう。

models.py に次のように記述します。

from django.db import models
from django.urls import reverse # reverse関数をインポート

class Article(models.Model):
    content = models.CharField(max_length=140)
    user_name = models.CharField(max_length=50, null = True)

    def __str__(self):
        return self.content

    # その投稿の詳細へのリンク
    def get_absolute_url(self):
        return reverse('bbs:detail', kwargs={'pk': self.pk})

reverse関数は urlpatterns で name=’〇〇’ と指定したURLを呼び出す関数です。

汎用ビューではデータの保存後に get_absolute_url メソッドが呼び出されるようになっているため、このメソッドを定義し、その投稿のid(pk)を受け取って、個別投稿ページのURLを返すように記述しています。

最後に、投稿一覧ページから新規投稿ページへのリンクと、個別投稿ページから投稿編集ページへのリンクを貼っておきましょう。

まずは投稿一覧ページから新規投稿ページへのリンクです。

article_list.html に次のように記述します。

{% extends "./base.html" %}

{% block content %}
  <h1>クラスベースの汎用ビューを使ってみよう!</h1>

  {% for article in object_list %}
    <p>
      <a href='{% url "bbs:detail" article.id %}'>{{ article.content }}</a>
      {{ article.user_name }}
    </p>
  {% endfor %}

  <!-- 新規投稿ページへのリンク -->
  <div>
    <button onclick='location.href="{% url "bbs:create" %}"'>新規投稿</button>
  </div>
{% endblock %}

次に、個別投稿ページから投稿編集ページへのリンクです。

article_detail.html に次のように記述します。

{% extends "./base.html" %}

{% block content %}
  <h1>{{ article.id }}の個別投稿ページ</h1>
  <p>{{ article.content }}, {{article.user_name }}</p>
  <div>
    <button onclick='location.href="{% url "bbs:index" %}"'>一覧</button>
    <button onclick='location.href="{% url "bbs:update" article.pk %}"'>編集</button>
  </div>
{% endblock %}

それでは動作確認です。

Djangoコマンドを使ってサーバーを起動し、http://127.0.0.1:8000/bbs にアクセスします。

投稿一覧ページ

「新規投稿」をクリック。

新規投稿ページ

新規投稿ページが表示されます。

試しにデータを入力して「作成」してみましょう。

フォームにデータを入力

「作成」ボタンをクリック。

新しく作成した投稿の詳細ページ

新しく作成した投稿の詳細ページへとリダイレクトされました。

「一覧」をクリックして一覧ページに戻りましょう。

投稿一覧ページ

先ほど投稿した内容が一覧ページにも反映されています。

新規投稿機能は問題なさそうですね。

続いて投稿編集機能を確認してみましょう。

先ほど作成した「テスト」をクリックして個別投稿ページへ。

個別投稿ページ

「編集」をクリックして投稿編集ページにアクセスしましょう。

投稿編集ページ

投稿内容が表示されています。

テンプレートは新規投稿画面のものと同じですが、見出しとボタンは新規投稿の時と異なる文字列(今回は「投稿の更新」と「更新」)が表示されているのが確認できます。

実際にデータを編集して「更新」ボタンをクリックしてみましょう。

データを編集して「更新」ボタンをクリック

編集内容が更新されて、この投稿の詳細ページが再び表示されました。

一覧ページでも確認してみましょう。

投稿一覧ページ

ちゃんと反映されていますね。

投稿の削除機能(edit.DeleteView)

最後は投稿の削除機能です。

urls.py に次のように記述します。

from django.urls import path
from . import views

app_name = 'bbs'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('create/', views.CreateView.as_view(), name='create'),
    path('<int:pk>/update/', views.UpdateView.as_view(), name='update'),
    path('<int:pk>/delete/', views.DeleteView.as_view(), name='delete'), # 削除
]

DeleteViewクラスを作成します。

views.py に次のように記述します。

from django.urls import reverse_lazy # reverse_lazy関数をインポート
from django.views import generic
from .models import Article

class IndexView(generic.ListView):
    model = Article

class DetailView(generic.DetailView):
    model = Article

class CreateView(generic.edit.CreateView):
    model = Article
    fields = '__all__'
class UpdateView(generic.edit.UpdateView):
    model = Article
    fields = '__all__'

# DeleteViewクラスを作成
class DeleteView(generic.edit.DeleteView):
    model = Article
    success_url = reverse_lazy('bbs:index')

reverse_lazy関数はreverse関数と同様、urlpatterns で name=’〇〇’ と指定したURLを呼び出す関数です。汎用ビューの中ではこちらを使う必要があります。

ここでは投稿の削除が完了した場合に投稿一覧ページにリダイレクトするよう記述しています。

投稿の削除確認画面のテンプレート名は自動的に article_confirm_delete.html になります。

では、/templates/bbs/article_confirm_delete.html を作成し、次のように記述しましょう。

{% extends './base.html' %}
{% block content %}

<h1>投稿の削除</h1>
<p>'{{ object.id }}'の投稿を削除しますか?</p>
<form action='' method='post'>{% csrf_token %}
    <button type='submit'>削除</button>
</form>
<div>
    <a href='JavaScript:history.back()'>戻る</a>
</div>

{% endblock %}

最後に、個別投稿ページから削除ページへのリンクを貼っておきましょう。

article_detail.html に次のように記述します。

{% extends "./base.html" %}

{% block content %}
  <h1>{{ article.id }}の個別投稿ページ</h1>
  <p>{{ article.content }}, {{ article.user_name }}</p>
  <div>
    <button onclick='location.href="{% url "bbs:index" %}"'>一覧</button>
    <button onclick='location.href="{% url "bbs:update" article.pk %}"'>編集</button>
    <button onclick='location.href="{% url "bbs:delete" article.pk %}"'>削除</button>
  </div>
{% endblock %}

それでは動作確認です。

Djangoコマンドを使ってサーバーを起動し、http://127.0.0.1:8000/bbs にアクセスします。

投稿一覧ページ

「プロージット」をクリックしてこの投稿の詳細ページへアクセスします。

投稿の詳細ページ

「削除」をクリック。

削除確認ページ

確認ページが表示されます。

「削除」をクリック。

投稿が削除されて、投稿一覧ページにリダイレクト

投稿が削除されて、投稿一覧ページにリダイレクトしました。

このようにクラスベースの汎用ビューを使えば、関数ベースよりもはるかに少ないコードで、Webアプリケーションの標準的な機能を実装することができます。

このエントリーをはてなブックマークに追加

コメントを残す

頂いたコメントは一読した後表示させて頂いております。
反映まで数日かかる場合もございますがご了承下さい。