【Django】クラスベースビュー(class-based view):CreateViewを使用したタスク登録機能の実装

概要

クラスベースビュー(class-based view)でタスク登録機能を作成したのでまとめた。
タスク登録機能は、Djangoがもともと用意しているCreateViewクラスを継承して作成する。

CreateViewをどのように使用するのか、また処理イメージについて紹介する。

 

 

前提

以下の記事で作成した一覧画面にタスク登録ボタンを追加する。

あわせて読みたい

概要 クラスベースビュー(class-based view)で一覧検索機能を作成したのでまとめた。 一覧検索機能は、Djangoがもともと用意しているListViewクラスを継承してを作成する。   前提 Djang[…]

【Django】クラスベースビューListViewを使用した一覧取得機能

 

 

画面遷移

今回実装するタスク登録機能の画面イメージは以下となる。

タスク登録画面遷移

【Django】クラスベースビュー(class-based view):CreateViewを使用したタスク登録機能の実装_タスク登録ボタン表示
▲一覧画面に【タスク登録】ボタン表示

 

【Django】クラスベースビュー(class-based view):CreateViewを使用したタスク登録機能の実装_タスク登録画面表示
▲【タスク登録】ボタンを押下すると、タスク登録画面を表示する

 

エラーメッセージ

【Django】クラスベースビュー(class-based view):CreateViewを使用したタスク登録機能の実装_入力エラー
▲モデル定義により自動生成されたバリデーションエラー

 

 

タスク登録機能の実装

サーバー側

ルーティング

todoapps/urls.py


from django.urls import path
from . import views


urlpatterns = [
    path("tasks/", views.TodoTopView.as_view(), name="top"),
    path("detail/<int:pk>/", views.TodoDetailView.as_view(), name="detail"),
    path("task/create/", views.TodoCreateView.as_view(), name="create_task"),
]

上記により、サーバーを起動して「http://localhost:8000/task/create/」にアクセスすると、viewsファイルに定義しているタスク登録ビュー(TodoCreateViewクラス)の処理を呼ぶ

 

タスク登録ビュー

todoapps/views.py


from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView
from . import models
from django.contrib.auth.mixins import LoginRequiredMixin


class TodoTopView(LoginRequiredMixin, ListView):
    """TODOリスト一覧表示"""

    # DB参照モデル
    model = models.Todo
    # テンプレート
    template_name = "todo/top.html"
    # コンテキスト名の変更
    context_object_name = "tasks"

    def get_queryset(self):
        """ 検索条件をオーバーライドする """
        # ステータス0(未完了)かつ期日の昇順で取得
        return models.Todo.objects.filter(status=0).order_by("due_date")


class TodoDetailView(LoginRequiredMixin, DetailView):
    """TODO詳細画面表示"""

    # DB参照モデル
    model = models.Todo
    # テンプレート
    template_name = "todo/detail.html"
    # コンテキスト名の変更
    context_object_name = "todo"
    

class TodoCreateView(LoginRequiredMixin, CreateView):
    """タスク登録画面"""

    # DB登録モデル
    model = models.Todo
    # テンプレート
    template_name = "todo/create.html"
    # 画面表示フィールド
    fields = ["task", "memo", "due_date", "category"]
    # 登録成功後リダイレクトURL
    success_url = reverse_lazy("top")

    def get_context_data(self, **kwargs):
        """コンテキストをオーバーライドする"""
        # 既存のコンテキスト取得
        context = super().get_context_data(**kwargs)
        # カテゴリ一覧の追加
        context["categories"] = models.TodoCategory.objects.all()
        return context

    def form_valid(self, form):
        """フォームバリデーション後の処理"""
        # ステータスをデフォルトで0(未完了)に設定
        form.instance.status = consts.TASK_STATUS_IMCOMPLETE
        # モデル情報にuserを追加
        form.instance.user = self.request.user
        # DB登録処理呼び出し
        return super().form_valid(form)

 

class TodoCreateView(LoginRequiredMixin, CreateView):

登録画面の表示処理を作成するには、クラスベースビューのCreateViewを継承して作成する。
これにより、親クラスの登録処理をカスタマイズできる。

 

# 画面表示フィールド
fields = ["task", "memo", "due_date", "category"]

タスク登録画面では、上記4項目を画面に表示する。
fields属性を定義することで、モデル情報の入力フィールドをもつ画面入力フォームが自動生成される。
また、入力チェックもあわせて自動生成される。

自動生成されるバリデーションはmodels.pyに定義しているカラム情報に準拠している。

 

def get_context_data(self, **kwargs):
    """コンテキストをオーバーライドする"""
    # 既存のコンテキスト取得
    context = super().get_context_data(**kwargs)
    # カテゴリ一覧の追加
    context["categories"] = models.TodoCategory.objects.all()
    return context

タスク登録画面のカテゴリセレクトボックスに表示するカテゴリ一覧は、カテゴリ一覧テーブルから取得して表示する必要がある。
親クラスのget_context_dataをオーバーライドして、コンテキスト(画面表示用辞書データ)に新たにカテゴリ一覧情報を追加することで、カテゴリデータを画面側に渡すことができる。

 

def form_valid(self, form):

上記をオーバーライドすることで、自動生成されたバリデーションが終わった後のDB登録前処理を実装できる

 

# ステータスをデフォルトで0(未完了)に設定
form.instance.status = consts.TASK_STATUS_IMCOMPLETE
# DB登録前のモデル情報にuserを追加

form.instance.user = self.request.user

Todo(タスク)テーブルを登録するためには、画面入力情報だけだと足りない
タスクのステータスと、どの利用者に紐づいているのかというUser情報が必要になる。

form.instanceはバリデーションが終わった後のDB登録対象のフォームデータになる。
そこに、ステータスとリクエストに保持しているログインユーザーパラメータを設定している。

※Djangoのリクエストオブジェクトはuser、session、method、pathなど様々な情報を保持しているようだが、詳細は以下の公式ドキュメントを参照

Django Project

The web framework for perfectionists with deadlines.…

 

# 親クラスのDB登録処理
return super().form_valid(form)

親クラスのform_validを呼び出すことで、入力チェック後のフォーム情報をもとにデータベース保存を実行できる。

 

画面側

タスク登録ボタン

タスク登録を表示するリクエストを送るため、ボタンを追加する。

templates/todo/top.html


<a href="{% url 'create_task' %}" class="btn btn-primary mb-3">タスク登録</a>

urls.pyに定義したタスク登録ビューにリクエストしている。

タスク登録画面

templates/todo/create.html


{% extends "base.html" %}
{% block title %}タスク登録{% endblock %}

{% block main %}
<div class="container">
  <div class="row justify-content-center mt-3">
    <div class="col-md-8">
      <a href="{% url 'top' %}" class="btn btn-primary mb-3">戻る</a>
      <div class="card">
        <div class="card-header text-center">
          <h4>タスク登録</h4>
        </div>
        <div class="card-body">
          <form method="post">
            {% csrf_token %}
            <div class="form-group">
              <label for="task">タスク<span class="text-primary ml-3">※必須</span></label>
              <input type="text" class="form-control" id="task" name="task">
            </div>
            {% for error in form.task.errors %}
            <div id="title-error" class="text-danger mb-3 error-msg">
                {{ error }}
            </div>
            {% endfor %}
            <div class="form-group">
              <label for="memo">メモ</label>
              <textarea class="form-control" id="memo" name="memo" rows="3"></textarea>
            </div>
            {% for error in form.memo.errors %}
            <div id="title-error" class="text-danger mb-3 error-msg">
                {{ error }}
            </div>
            {% endfor %}
            <div class="form-group">
              <label for="due_date">期日<span class="text-primary ml-3">※必須</span></label>
              <input type="date" class="form-control" id="due_date" name="due_date">
            </div>
            {% for error in form.due_date.errors %}
            <div id="title-error" class="text-danger mb-3 error-msg">
                {{ error }}
            </div>
            {% endfor %}
            <div class="form-group">
              <label for="category">カテゴリ<span class="text-primary ml-3">※必須</span></label>
              <select class="form-control" id="category" name="category">
                <option value="">選択してください</option>
                {% for category in categories %}
                <option value="{{ category.id }}">{{ category.category_name }}</option>
                {% endfor %}
              </select>
            </div>
            {% for error in form.category.errors %}
            <div id="title-error" class="text-danger mb-3 error-msg">
                {{ error }}
            </div>
            {% endfor %}
            <button type="submit" class="btn btn-primary mt-3">登録</button>
          </form>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock %}

 

{% for error in form.task.errors %}
<div id="title-error" class="text-danger mb-3 error-msg">
{{ error }}
</div>
{% endfor %}

form.フィールド名.errorsにエラーリストが格納されるため、ループしてエラー内容を表示している。

 

{% for category in categories %}
<option value="{{ category.id }}">{{ category.category_name }}</option>
{% endfor %}

コンテキストのカテゴリ一覧をループして、セレクトボックスを動的に作成している。
以下のように値がバインドされる。
<option value="1">趣味</option>

以上がタスク登録機能となる。

 

 

その他

フォームの分離

views.pyにフォーム情報(fields)を設定したが、forms.pyに分離することで他の画面でも同じフォーム情報を使用できるようになる。
また、フォーム情報をviews.pyから切り離すことで、入力チェックのカスタマイズもしやすくなる。
あとは改修する際にも、フォーム情報がforms.pyに集約されていれば楽になる。(保守性の向上など)

タスク情報入力フォーム

todoapps/forms.py


from django import forms
from . import models


class TodoForm(forms.ModelForm):
    """タスク登録フォーム"""

    class Meta:
        # 入力チェック生成対象のモデル
        model = models.Todo
        # 画面入力フィールド
        fields = ["task", "memo", "due_date", "category"]

 

class TodoForm(forms.ModelForm):

フォームクラスを作成する場合、基本的にはforms.ModelFormを継承して作成する模様。
上記を継承することで、モデル(DBのテーブル)に紐づくフィールドや入力チェック機能を自動生成してくれる。

GETリクエストで画面表示に使用する際には、入力フィールドを自動で提供して、
POSTリクエストでDB更新を行う際には、入力フィールドに対応した入力チェック機能を自動で提供してくれるイメージ。

 

class Meta:
    # 入力チェック生成対象のモデル
    model = models.Todo
    # 画面入力フィールド
    fields = ["task", "memo", "due_date", "category"]

フォームクラスにモデルやフィールドを定義する場合、Metaクラス内に定義する。
これはそういうルールなんだと慣れるしかない。

 

タスク登録ビュー

todoapps/views.py


class TodoCreateView(LoginRequiredMixin, CreateView):
    """タスク登録画面"""

    # DB登録モデル
    model = models.Todo
    # テンプレート
    template_name = "todo/create.html"
    # タスク登録フォーム
    form_class = forms.TodoForm
    # 画面表示フィールド ⇒削除
    # fields = ["task", "memo", "due_date", "category"]
    (略)

作成したフォームクラスを設定する。
定義していたフィールドは削除すること。

以上でフォームクラスを使用した登録ビューができる。

スポンサーリンク