[Django] 記事の編集ページと記事の削除機能の追加

前回、基本的な機能は実装できたので今回は、記事の編集機能と削除機能を実装します。

シリーズ一覧

  1. プロジェクトの作成とアプリケーションの作成
  2. view関数の書き方
  3. URLの指定の仕方(URLディスパッチャ)
  4. データベース(モデル)の設定
  5. 記事の投稿ページの作成
  6. 記事の一覧ページと詳細ページの追加
  7. 記事の編集ページと記事の削除機能の追加 <- 今回
  8. いいね機能の追加
  9. コメント機能の追加
  10. Bootstrapを利用したwebデザイン

編集ページの追加

すでに投稿してある記事を編集する機能を実装します。

URLの設定

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("new/", views.new, name="new"),
    path("article/all/", views.article_all, name="article_all"),
    path("article/<int:pk>/", views.view_article, name="view_article"),
    path("article/<int:pk>/edit/", views.edit, name="edit"), # 追記
]

編集用のページのURLを設定。

viewの設定

編集用のページも投稿ページ同様POSTされたときとGETした時で動作を変える必要があります。

if request.method == "POST":
  記事のデータをアップデート

GETされたときは、データベースから投稿済みのデータを引っ張り出して表示させる必要があります。
なので取り合えずデータベースから目的のデータをテンプレートに渡しておきます。

views.py

from django.shortcuts import render, redirect # redirectを追記
from django.http import Http404
from . import models

# 略

def edit(request, pk):
    template_name = "blog/edit.html"
    try:
        article = models.Article.objects.get(pk=pk)
    except models.Article.DoesNotExist:
        raise Http404
    if request.method == "POST":
        article.title = request.POST["title"]
        article.text = request.POST["text"]
        article.save()
        return redirect(view_article, pk)
    context = {"article": article}
    return render(request, template_name, context)

記事の閲覧ページ同様、article = models.Article.objects.get(pk=pk)でデータベースからデータを取り出します。
もし記事がかなった場合は例外処理で404エラーを出すようにしています。

if文の中がデータベースの更新処理です。
取り出したデータに対して更新したいフィールドに投稿されたデータを代入しています。

article.title = request.POST["title"] # 新しいタイトル
article.text = request.POST["text"] # 新しい本文
article.save() # saveメソッドで変更を更新

代入した後にsave()メソッドを実行するとデータベースに変更を反映できます。
saveし忘れるとデータの更新が行われないのでご注意を

リダイレクト

記事を修正したとき(POSTメソッドのとき)にreturn redirect(view_article, pk)という見慣れないことをやっていますね。
リダイレクトと聞けばわかる人も多いとは思いますが、これで別のページに遷移するようにしています。

redirect関数の第一引数にはリダイレクトさせたいページのview関数をとります。

# 引数を一つだけ取るview関数
def single_arg_view(request):
  return render(request, template_name)

# 引数を一つだけ取るview関数にリダイレクトする場合
def redirect_to_single_arg_view(request):
  # 第一引数にリダイレクト先のview関数
  return redirect(single_arg_view)

リダイレクトさせたいページのview関数が二つ以上の引数をとる場合、リダイレクトさせたいページに渡す用の引数をredirect関数の第二引数 以降に渡します。

# 引数を二つ取るview関数
def double_arg_view(request, pk):
  return render(request, template_name)

# 引数を二つ取るview関数にリダイレクトする場合
def redirect_to_double_arg_view(request):
  # 第一引数にリダイレクト先のview関数
  # 第二引数にはリダイレクト先のview関数に渡す第二引数
  pk = 1
  return redirect(double_arg_view, pk)

ついでに

今、新しくredirectをやったので新しく記事を投稿した際も自動的にその記事のページにリダイレクトするように変更しましょう。

views.py

# new関数を書き換えます
def new(request):
    template_name = "blog/new.html"
    if request.method == "POST":
        # articleに新しく作成したデータを代入
        article = models.Article.objects.create(title=request.POST["title"], text=request.POST["text"])
        # リダイレクトする処理を追記
        # 第二引数には新しく作成した記事の主キーを渡す
        return redirect(view_article, article.pk)
        
    return render(request, template_name)

これで、記事を投稿した際にそのページに移動するようになったので、記事が投稿できたことがわかり易くなりました。

テンプレートの編集

新しくtemplates/blog/edit.html(blogはアプリケーション名)を作成します。

基本的にはnew.htmlと同じですが、以前に投稿した内容を事前にテキストエリアに表示させておかねばなりません。

フォームに事前に値を入れておくには?

フォームに事前に値を入れておく方法のご紹介。

inputの場合

inputタグにあらかじめ値を入れておきたい場合は、value属性をつけてあげます。

<input type="text" value="あらかじめ入れておきたい値" />
textareaの場合

先ほどinputタグではvalue属性を付与して事前に値を入れておくようにしましたが、textareaの場合はもっと簡単。

<textarea>あらかじめ入れておきたい文字列</textarea>

事前に入れておきたい値をtextareaタグで囲うだけ。

それを踏まえてedit.htmlの中身を以下のようにします。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>my-blog-edit</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
    <a href="{% url 'index' %}">トップページへ</a>
    <a href="{% url 'view_article' article.pk%}">編集の取りやめ</a>

    <h3>まい ぶろぐ 記事の編集ページ</h3>
    <form action="" method="post">
        {% csrf_token %}
        <p><label for="title_input">記事のタイトル</label></p>
        <p><input type="text" name="title" id="title_input" value="{{ article.title }}"></p>
        <p><label for="text_area">記事の本文</label></p>
        <p><textarea name="text" id="text_area" cols="30" rows="10">{{ article.text }}</textarea></p>
        <p><button type="submit">更新</button></p>
    </form>
</body>

</html>

inputタグのvalue属性textareaタグの中にそれぞれ、もともとの記事のタイトル{{ article.title }}ともともとの記事の本文{{ article.text }}を展開しています。

削除機能の追加

続いて投稿した記事を削除できる機能を追加します。

URL

urls.py

urlpatterns = [
    path("", views.index, name="index"),
    path("article/<int:pk>/edit/" iews.edit, name="edit"),
    path("article/<int:pk>/delete/", views.delete, name="delete"), # 追記
]

viewの設定

削除用の関数deleteを定義します。

views.py

def delete(request, pk):
    try:
        article = models.Article.objects.get(pk=pk)
    except models.Article.DoesNotExist:
        raise Http404
    article.delete()
    return redirect(article_all)

登録してあるデータをデータを削除するには

article = models.Article.objects.get(pk=pk)

で削除対象のデータを取得した後に

article.delete()

とします。

削除が終わった後は一覧ページにリダイレクトさせます。

テンプレートの編集

今回は削除用に新しくページを作ることはしません。

代わりに記事の一覧ページに先ほど作った記事の編集ページへのリンクと削除用のリンクを設置します。
article_all.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>article all</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <a href="{% url 'index' %}">トップページへ</a>
  <h3>投稿された記事一覧</h3>
  {% for article in articles %}
  <p>
    <a href="{% url 'view_article' article.pk %}">{{ article.title }} - {{ article.posted_at }}</a>
    <a href="{% url 'edit' article.pk %}">編集</a>
    <a href="{% url 'delete' article.pk %}">削除</a>
  </p>
  {% endfor %}
</body>
</html>

まとめ

redirect(リダイレクト先のview関数)でそのページにリダイレクト出来る。

リダイレクト先のview関数が二つ以上の引数を取る場合は、redirect(リダイレクト先の関数, リダイレクト先の関数が受け取る第二引数, 第三引数, ...)にする。

データーベースからのデータの削除は

Model.objects.get(フィールド名=値).delete()
# 対象のデータを取得して -> 削除

最後に

これで十分ブログの機能が完成しました。

でもせっかくなら、皆から「いいね」とか「コメント」とかもらいたいですよね。

なので、次回はいいね機能を追加してみましょう!

一応、次々回にコメント機能を実装して、その次はbootstrapを使ってデザインを整えて最終回ということにしようと思います。

次回

[Django] いいね機能の追加