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

概要

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

UpdateViewをどのように使用するのかを紹介する。

 

 

前提

以下の記事の続きとなる。

あわせて読みたい

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

【Django】クラスベースビュー(class-based view):CreateViewを使用したタスク登録機能の実装_アイキャッチ画像

 

 

画面遷移

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

タスク編集画面遷移

【Django】クラスベースビュー(class-based view):UpdateViewを使用したタスク編集機能の実装:タスク詳細画面表示
▲詳細画面に【編集する】ボタン表示

 

【Django】クラスベースビュー(class-based view):UpdateViewを使用したタスク編集機能の実装:タスク編集画面表示
▲編集画面には初期値が設定される

 

【Django】クラスベースビュー(class-based view):UpdateViewを使用したタスク編集機能の実装:タスク編集画面の内容を更新
▲値を編集

 

【Django】クラスベースビュー(class-based view):UpdateViewを使用したタスク編集機能の実装:タスク編集画面の内容を更新完了後の詳細画面
▲更新が完了すると詳細画面に戻る

 

入力エラーの場合

【Django】クラスベースビュー(class-based view):UpdateViewを使用したタスク編集機能の実装:タスク編集画面のエラーメッセージ
▲モデル定義により自動生成されたバリデーションエラー

 

 

タスク編集機能の実装

サーバー側

ルーティング

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"),
    path("task/edit/<int:pk>", views.TodoEditView.as_view(), name="edit_task"),
]

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

 

タスク編集ビュー

todoapps/views.py


from django.views.generic import ListView, DetailView, CreateView, UpdateView
(略)


class TodoEditView(UpdateView):
    """タスク編集画面"""

    # DB更新モデル
    model = models.Todo
    # テンプレート
    template_name = "todo/edit.html"
    # タスク編集フォーム
    form_class = forms.TodoEditForm

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

    def get_success_url(self):
        return reverse_lazy("detail", kwargs={"pk": self.object.pk})

 

class TodoEditView(LoginRequiredMixin, UpdateView)

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

 

# タスク編集フォーム
form_class = forms.TodoEditForm

画面入力フィールドに関するフォームクラスは、forms.pyに定義した編集画面用フォームクラスを参照する。

 

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

タスク登録画面同様に、カテゴリのセレクトボックスを編集画面に表示する必要がある。
そのため、画面に渡すコンテキスト(辞書データ)にDBから取得したカテゴリ一覧を追加する。

 

def get_success_url(self):
    """更新完了後のリダイレクトURLを返却する"""
    return reverse_lazy("detail", kwargs={"pk": self.object.pk})

更新完了後にはタスク詳細画面に遷移するが、詳細画面はパラメータとしてタスクIDを送る必要がある。
self.objectの中身はDB更新した後のタスクオブジェクトを保持している

reverse_lazyのkwargsを指定するパターンは、URLパターンに含まれるキーワードを指定することができる。
上記のように辞書データでプライマリキーを指定しているのは、urls.pyにてpkを指定しているためとなる。
※todo/urls.pyの「detail」を参照

補足(success_url属性とget_success_urlメソッドの違いについて)

success_url属性にURLを設定すると、固定URLへリダイレクトする。
一方で、動的なURLへリダイレクトさせたいとき(例えばクエリパラメータを設定したいとき)は、get_success_urlメソッドにURLを設定する必要がある。

 

タスク編集フォーム

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 TodoEditForm(TodoForm):
    """タスク編集フォーム"""

    class Meta(TodoForm.Meta):
        # 編集画面ではステータスの状態を画面に表示するため、statusを追加
        fields = ["task", "memo", "due_date", "category", "status"]

 

class TodoEditForm(TodoForm):
    """タスク編集フォーム"""

    class Meta(TodoForm.Meta):

タスク登録フォームであるTodoFormを継承して、タスク編集フォームとしている。
フォームを継承する場合、上記のように各クラスで継承元を記述する必要があるらしい。

 

class Meta(TodoForm.Meta):
    # 編集画面ではステータスの状態を画面に表示するため、statusを追加
    fields = ["task", "memo", "due_date", "category", "status"]

タスク登録画面では、登録するとステータスは0(未完了)固定とするため、画面に表示していない。
しかし、タスク編集画面ではステータスを更新できるようにしているため、画面表示する必要がある。

そのため、タスク登録画面用のフォームを継承してステータスだけ追加している。

 

画面側

編集ボタン

templates/todo/detail.html


<a href="{% url 'edit_task' todo.id %}" class="btn btn-primary mb-3 me-3">編集する</a>

urls.pyに定義したタスク編集ビューにリクエストしている。
※パラメータとしてtodo.idとしているが、todo.pkとしても問題ない

 

タスク編集画面

templates/todo/edit.html


{% extends "base.html" %}
{% load todo_template_filter %}
{% block title %}タスク編集{% endblock %}

{% block main %}
<div class="container">
  <div class="row justify-content-center mt-3">
    <div class="col-md-8">
      <a href="{% url 'detail' form.instance.pk %}" 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" value="{{ form.task.value }}">
            </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">{{ form.memo.value|default_if_none:''}}</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" value="{{ form.due_date.value|to_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 }}" {% if category.id|to_string == form.category.value|to_string %}selected{% endif %}>{{ 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 %}         
            <div class="form-group">
              <label for="status">ステータス<span class="text-primary ml-3">※必須</span></label>
              <select class="form-control" id="status" name="status">
                <option value="">選択してください</option>
                <option value="0" {% if form.status.value|to_string == '0' %}selected{% endif %}>未完了</option>
                <option value="1" {% if form.status.value|to_string == '1' %}selected{% endif %}>完了</option>
              </select>
            </div>
            {% for error in form.status.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 %}

基本的にタスク登録画面と構成は同じ。

 

<input type="text" class="form-control" id="task" name="task" value="{{ form.task.value }}">

タスク編集画面を表示する際に、初期値を設定するため上記のようにvalueを設定している。
※タスク登録画面でも上記のように記述すると、入力エラー時に画面入力情報を保持してくれる

 

<a href="{% url 'detail' form.instance.pk %}" class="btn btn-primary mb-3">戻る</a>

【戻る】ボタンを押下すると前の画面(詳細画面)に遷移させる。
詳細画面ビューのリクエストパラメータにはform.instanceとしているが、これは編集画面表示時のDBから取得したタスク情報になる。

スポンサーリンク