Django: UpdateViewとCreateView

この記事の内容
  • DjangoのViewについて
  • views.pyについて
  • CreateViewの使い方
  • UpdateViewの使い方

この記事の対象者
  • Web開発に興味を持っている方
  • Djangoの学習者
  • Pythonエンジニア

この記事の構成
  1. CreateView
  2. UpdateView
  3. リクエストの受け取り

前回までの記事

Django: DBを扱うmodelsについて

Django: URLの設計について

Django: Viewの基本とTemplate View

はじめに

前回の記事では、DjangoのTemplateViewについて学習しました。

前提条件として、
Modelについての記事で作成したmodels.pyと
URLについての記事で作成したurls.py(createとupdateを追加)を以下に共有しておきます。

models.py

from django.db import models


class Category(models.Model):

    name = models.CharField(max_length=31, verbose_name="カテゴリ名")

    def __str__(self):
        return self.name

    class Meta:

        verbose_name_plural = "カテゴリ"


class Article(models.Model):

    title = models.CharField(max_length=62, verbose_name="タイトル")
    slug = models.SlugField(verbose_name="URLスラッグ(英語)")
    image = models.ImageField(upload_to="article_image", blank=True, verbose_name="記事のイメージ写真", help_text="登録しない場合は、デフォルトのイメージ写真を使用する")
    categories = models.ManyToManyField(Category, verbose_name="カテゴリ")
    body = models.TextField(verbose_name="本文")
    status = models.PositiveSmallIntegerField(default=1, verbose_name="公開ステータス", help_text="1:下書き, 2:公開")
    liked = models.IntegerField(default=0, verbose_name="いいね数")
    is_deleted = models.BooleanField(default=False, verbose_name="削除フラグ")
    creator = models.CharField(max_length=31, verbose_name="投稿者")
    created = models.DateTimeField(auto_now_add=True, verbose_name="投稿日時")
    modified = models.DateTimeField(auto_now=True, verbose_name="更新日")

    def __str__(self):
        return self.title

    class Meta:

        verbose_name_plural = "記事"



class Comment(models.Model):

    article = models.ForeignKey(Article, on_delete=models.CASCADE, verbose_name="記事", related_name="comments")
    commenter = models.CharField(max_length=31, verbose_name="コメント者名")
    body = models.TextField(verbose_name="コメント文")
    created = models.DateTimeField(auto_now_add=True, verbose_name="コメント投稿日時")

    def __str__(self):
        return self.article.title + ":{}のコメント".format(self.commenter)

    class Meta:

        verbose_name_plural = "コメント"

urls.py

  • index (hogehoge.com) ダッシュボード
  • create (hogehoge.com/article/create) 記事作成ページ
  • update (hogehoge.com/article/update/<int:pk>) 記事更新ページ
  • list (hogehoge.com/article/list) 記事一覧ページ
  • detail (hogehoge.com/article/<int:pk>) 記事詳細ページ
from django.urls import path
from . import views

app_name= 'article'
urlpatterns = [
    path('', views.index, name='index'),
    # 以下2つ追加
    path('article/create', views.article_create, name='article_create'),
    path('article/update/<int:pk>', views.article_update, name='article_update'),

    path('article/list', views.article_list, name='article_list'),
    path('article/<int:pk>', views.article_detail, name='article_detail'), 
]

CreateView

まずは、データの作成を行うView: CreateViewからです。

CreateViewでは、models.pyに記述してあるモデルのClass名を記述することで簡単にデータを作成することができます。
また、Viewで扱うフィールド(titleやbodyなど)を指定することもできます。

CreateView, UpdateViewで指定できる変数

  • model: models.pyのクラス
  • queryset: modelの代わりにobjects.filter()などを記述することができる。 この場合、modelの記述は不要
  • fields: models.pyのクラスに記述してあるフィールドを指定できる。 'all'で全てのフィールドを指定できる
  • success_url: 作成/更新が成功した時に遷移するURLを指定できる。
  • form_class: forms.pyなどで記述したフォームをここで呼び出せる。

views.py

from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Article

class ArticleCreateView(CreateView):

    template_name = 'article/create.html'
    model = Article
    fields = ('title', 'slug', 'image', 'status', 'body')
    success_url = reverse_lazy('article:article_list')

article_create = ArticleCreateView.as_view()

success_urlにURLを指定することでArticleデータを作成した後にそのURLに自動で遷移してくれます。

これだけで、create.html上でフォームを自動生成することができます。

create.html

<!-- 以下の記述で、データに応じたinputが生成される -->
<form method='post' action='' enctype='multipart/form-data'>
{{ form.title }}
{{ form.slug }}
{{ form.image }}
{{ form.status }}
{{ form.title }}
<button type='submit' value='submit' />
{% csrf_token %}
</form>

これだけでfieldsに指定したデータのinputが自動で生成されるのはすごく便利です。また、modelsの情報に応じて自動でデータの入力チェックも行ってくれるのが嬉しいです。

DjangoでPOSTを扱う時は、html上で{% csrf_token %}をform内に記述する必要があるので覚えておきましょう。

fields以外にも保存したい情報がある場合は、POSTの受け取り処理を書くことができます。

views.py

from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Article

class ArticleCreateView(CreateView):

    template_name = 'article/create.html'
    model = Article
    fields = ('title', 'slug', 'image', 'status', 'body')
    success_url = reverse_lazy('article:article_list')

    def form_valid(self, form):

        # 受け取ったPOST情報を仮保存
        article = form.save(commit=False)

        # データの作成者にユーザーネームを登録
        article.creator = self.request.user.username
        article.save()

        # form_validを返すことで上のsuccess_urlへ遷移
        return super().form_valid(article)

article_create = ArticleCreateView.as_view()

UpdateView

UpdateViewは、既に作成済のデータを更新してくれるViewです。
動きはほとんどCreateViewと同じですが、URLにデータの特定できる情報を入力する必要があります。
今回の場合、URLに int:pk を入力し、データをIDで判別できるようにしてあります。

UpdateViewの特徴は、このユニークなIDや情報がURLに存在することで、デフォルトで該当するデータをviewsで保持していたり、フロントに投げてくれていることです。

例えば、htmlファイルで {{ object.title }} と記述することで記事のタイトルを取得することが可能です。

あとはほとんどCreateViewのデータ更新版と思っていただいて構いません。

views.py

from django.views.generic import CreateView, UpdateView
from django.urls import reverse_lazy
from .models import Article

class ArticleUpdateView(UpdateView):

    template_name = 'article/update.html'
    model = Article
    fields = ('title', 'slug', 'image', 'status', 'body')
    success_url = reverse_lazy('article:article_list')

article_update = ArticleUpdateView.as_view()

リクエストの受け取り

少し余談になりますが、CreateViewやUpdateViewでsuccess_urlを記述する時、次の記事で紹介するDetailViewに飛ばしたいケースが多いです。

しかし、DetailViewのURLを作成するためには、
CreateViewでsave()の後にIDを取得したり、
UpdateViewでURLからIDを取得したりする必要があります。
この場合、

success_url = reverse_lazy('article:article_detail', kwargs={'pk':????})

上記の????のように、変数名にIDを指定することができません。
通常この場合、以下のようにURLからIDを取得する方法が使えます。

    ...
    success_url = None

    def get_success_url(self):
        success_url = reverse_lazy('article:article_detail', kwargs={'pk':self.kwargs['pk']})
        return success_url

しかしながら、上記のコードでも、CreateViewではURLにIDが存在しないので、実現することができません。

オススメの方法は、HttpResponseRedirectを使用する方法です。
必ずform_validを記述しなければなりませんが、URLも自由に指定できるので便利です。

from django.views.generic import CreateView
from django.contrib import messages
from django.http import HttpResponseRedirect
from .models import Article

class ArticleCreateView(CreateView):
    template_name = 'article/create.html'
    model = Article
    fields = '__all__' #全てのフィールドを使用する
    success_url = None

    def form_valid(self,form):
        article = form.save()
        article_id = article.id
        url = reverse_lazy('article:article_detail',kwargs={'pk':article_id})

        # 遷移先にメッセージを送ることもできる
        messages.success(self.request, '記事を作成しました。')

        return HttpResponseRedirect(url)

終わりに

CreateViewやUpdateViewはコードが綺麗になる上、フォームのバリデーションなどデフォルトで基本的な機能が備わっていてすごく便利です。

また、forms.pyなどで オリジナルのフォームを作成することで ここで呼び出し htmlに投げることもできるのでできることはたくさんあります。

その反面、デフォルトの機能に制限され、自由度が少し減ってしまうこともあります。

デフォルトの機能をうまく活用しながら、自分で記述するところは記述し、うまくカスタマイズして使いこなしていけると、実装の幅がグッと広がるでしょう。