前回はCreateVIewを使って日記の詳細表示の方法を勉強したな
今回はUpdateViewを使って、日記の更新画面を作っていくぞ。
完成形の確認
下記が完成した更新画面だ
仕様はこんな感じだ。
- http://127.0.0.1:8000/<日記ID>/ にアクセスすると日記の詳細画面が表示される
- 詳細画面の編集リンクをクリックすると、http://127.0.0.1:8000/<日記ID>/update/ に遷移し
更新画面が表示される - 投稿にupdated_byを追加する
- 更新画面は
- ログイン必須
- タイトル、本文、カテゴリーの入力フォームが表示される
- タイトル、本文は入力必須
- 投稿の
updated_by
がログイン中の投稿者に紐つく - 作成完了したら、日記の詳細画面に遷移する
UpdateViewを使うとどれだけ分かりやすく、簡単に書けるかやってみるぞ!
ベースアプリの準備
リポジトリのクローンとベース機能のチェックアウト
まずは詳細画面を表示する機能をあらかじめチェックアウトしておく
$ git clone https://github.com/yheihei/base_diary.git
$ cd base_diary
$ git fetch && git checkout feature/#14
マイグレーションと初期データ投入
次にマイグレーションとloaddataによる初期データの投入だ
$ python manage.py migrate
$ python manage.py loaddata initial.json
この状態でhttp://127.0.0.1:8000/
1/を開くと。。。
よし、日記詳細画面が表示されたね
ログインする場合はhttp://127.0.0.1:8000/admin/
に入って行う。
ログインできるユーザー名とパスワードは下記だ。
UserName: yhei
Password: password
完成版ソースだけ見る場合
今回も完成形を書いたブランチも用意しておいた。
答えだけ先に見たいやつは下記コマンドでブランチをチェックアウトしてくれ
# 完成版ブランチを見たい場合はこちら
$ git fetch && git checkout feature/#16
UpdateViewで機能を作ってみる
基本のフォームを表示する
まずUpdateViewの処理をdiary/views.pyに記述する。
from .models import Post
from django.views.generic.edit import UpdateView
class PostUpdateView(UpdateView):
model = Post
fields = ['user', 'title', 'body', 'categories']
UpdateViewを継承したモデルを作成、保存するモデル(Post)をmodel
変数に入力する。
また、表示したいフォームをfields
変数に入力する。下記のようにだ。
fields = ['user', 'title', 'body', 'categories']
ここで指定するフォーム名は、Postモデルのカラム名だ。Postモデルを見てみよう。
class Post(models.Model):
...
user = models.ForeignKey(User, on_delete=CASCADE)
title = models.CharField(max_length=2048)
body = models.TextField()
...
categories = models.ManyToManyField(
...
)
投稿者のカラムのuser、日記のタイトルと本文を表すtitleとbody、日記のカテゴリを多対多で紐つけるcategoriesを指定すれば、日記の更新ができるな。
次はルーティングの設定だ。
http://127.0.0.1:8000/<日記ID>/update/のアクセスを、UpdateView
にルーティングさせる。
diary/urls.py
に下記を入力する。
from django.urls import path
from . import views
from diary.views import PostDetailView, PostUpdateView
app_name = "diary"
urlpatterns = [
path("", views.index, name="index"),
path('<int:pk>/', PostDetailView.as_view(), name='post-detail'),
# UpdateViewにルーティングさせる
path('<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
]
最後に、UpdateViewで表示させるテンプレートを作成する。
UpdateViewのテンプレート名は他のクラスベースビューと同じく自動でデフォルトが決まる。
(指定も可能)
デフォルトは/templates/アプリ名/<modelに指定したクラス名の小文字>_form.html
だ。
ということで、diary/templates/diary/post_form.html
に下記を記述する。
{% extends 'diary/base.html' %}
{% block content %}
<article>
<h2>記事の更新</h2>
<div class="mt-3">
<form method="POST">
{% csrf_token %}
{% for field in form.visible_fields %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }}
{{ field.errors }}
{{ field.help_text }}
</div>
{% endfor %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<input class="btn btn-primary" type="submit" value="投稿" />
</form>
</div>
</article>
{% endblock %}
これでUpdateViewの基本形は完成した。
http://127.0.0.1:8000/1/update/にアクセスしてみると。。。
おお、更新フォームっぽいのができた!
ログイン必須にする
ただ、誰でも日記を更新できてしまうと問題がある。
ログインしているユーザのみこのページを開けるようにするぞ。
ログイン必須にするには、UpdateViewを継承しているクラスに、LoginRequiredMixin
クラスを継承してやると良い。
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
class PostUpdateView(LoginRequiredMixin, UpdateView):
# ログインURLも指定する
login_url = reverse_lazy('admin:login')
...
login_url変数に、ログインURLを指定するのも忘れずに。
一旦ログアウトして、http://127.0.0.1:8000/1/update/にアクセスしてみると。。。
ログインしてないと見られなくなった!
ログインユーザーを投稿更新者(updated_by)として保存する
更新処理完成まであともう少しだ。
今は投稿者を選択するようなUIになっているが、
普通投稿者は変えるべきではないよな?
そこで、
- 投稿者を変更できないようにし、
- Postに新たにupdated_by(投稿更新者)を追加
- そこにログインユーザーを保存する
と言う仕様に変えてみる。
まずは、updated_byを追加する。
diary/models.pyに下記を追加する。
class Post(models.Model):
...
updated_by = models.ForeignKey(
User,
default=None,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='updated_posts',
)
もし投稿者が削除された場合、updated_by
はNULL
になって欲しい。そのため、on_delete=SET_NULL
にしておこう。
次にマイグレーションファイルを作り、マイグレートしていく。
$ python manage.py makemigrations
$ python manage.py migrate
次に、
- Formから投稿者を消し
- Formの保存時に
updated_by
にログインユーザーを紐つけて登録する処理
をやってみる。
こんな感じだ。
class PostUpdateView(LoginRequiredMixin, UpdateView):
...
# fields = ['user', 'title', 'body', 'categories']
fields = ['title', 'body', 'categories']
def form_valid(self, form):
object: Post = form.save(commit=False)
object.updated_by = self.request.user
object.save()
return super().form_valid(form)
form_valid関数はレコードが保存される直前で呼ばれる関数だ。
form_valid
関数をオーバーライドし、保存処理のついでにobject.
updated_byにログインユーザー(self.request.user
)をセットしてやる。
これでupdated_byがきちんと保存される。
保存が完了したら日記詳細画面に遷移させる
また、更新完了後は、日記の詳細画面に遷移して欲しい。
そのためにget_success_url
関数をオーバーライドして、遷移先を指定してやる。
class PostUpdateView(LoginRequiredMixin, UpdateView):
...
def get_success_url(self):
'''
更新完了したらどこに遷移させるか
'''
return reverse('diary:post-detail', kwargs={'pk': self.object.pk})
self.objectには保存したPostモデルが入ってくる。self.object.pkを指定すれば、日記のIDが取得できる。
つまり、どの日記に遷移させるかが指定できたと言うわけ。
ではやってみるぞ。
http://127.0.0.1:8000/1/update/ に遷移して。。。
更新ができた〜〜〜
Formのデザインやバリデーションを変更する方法
Formが多少野暮ったいのでカスタマイズしたいと思うかもしれない。
また変わったバリデーションを行いたい場合もある。
そのやり方はCreateViewの講義でまとめておいた。
気になるヤツは↑を参照してくれ。
まとめ
UpdateViewの使い方をまとめるぞ
UpdateView
はModelを指定することで、そのModelに紐ついたレコードを更新画面が作成可能- save時に特殊な操作をする場合は
form_valid
関数が使える get_success_url
関数で更新完了後のページを指定する
CreateViewと結構似てたな
そう、新規作成も更新も基本は同じだな
これでCRUDのCRUができるようになった
残りはD(Delete)だな
簡単なのでサクッと次回のレッスンでやってみるぞ
\ 講座で学んだことを即アウトプットしよう /
次の講座
Djangoの基礎を学びたい方は
Djangoの基礎を固めたい方はこちら(セール時に買うのがおすすめ)
『Djangoパーフェクトマスター』〜インスタ映えを支えるPython超高速開発Webフレームワークを徹底解説!
動画講座で手を動かしながら、ほとんどのことが学べます。
ウサギもここから始めました。
コメント