Django レビュー機能の実装

初めに

前回に続いて本棚アプリケーションの作成を行いました。今回は投稿された本に対してレビューをすることができる機能を実装しました。
これに対して行った具体的な手順をまとめます。
nissin-geppox.hatenablog.com

概要

  • レビュー投稿画面の作成
  • 本の情報の表示

レビュー投稿画面の作成

まず初めに、レビュー投稿機能のルーティングをurls.pyで以下のように設定しました。

from django.urls import path
from . import views
urlpatterns = [
  path('', views.index_view, name='index'),
  path('book/', views.ListBookView.as_view(), name='list-book'),
  path('book/<int:pk>/detail/', views.DetailBookView.as_view(), name='detail-book'),
  path('book/create/', views.CreateBookView.as_view(), name='create-book'),
  path('book/<int:pk>/delete/', views.DeleteBookView.as_view(), name='delete-book'),
  path('book/<int:pk>/update/', views.UpdateBookView.as_view(), name='update-book'),
  path('book/<int:book_id>/review/', views.CreateReview.as_view(), name='review'),
]

次に、以下のようにレビュー投稿用のモデルをmodels.pyに実装しました。

from django.db import models
from .consts import MAX_RATE

RATE_CHOICES = [(x, str(x)) for x in range(0, MAX_RATE + 1)]

CATEGORY = (('business', 'ビジネス'), ('life', '生活'), ('other', 'その他'))

class Book(models.Model):
  title = models.CharField(max_length=100)
  text = models.TextField()
  category = models.CharField(
    max_length=100,
    choices = CATEGORY
  )

  def __str__(self):
    return self.title

class Review(models.Model):
  book = models.ForeignKey(Book, on_delete=models.CASCADE)
  title = models.CharField(max_length=100)
  text = models.TextField()
  rate = models.IntegerField(choices=RATE_CHOICES)
  user = models.ForeignKey('auth.User', on_delete=models.CASCADE)

  def __str__(self):
    return self.title

続けて、このモデルで使用する定数を格納するファイルを以下のコマンドより生成しました。

$ touch book/consts.py

ここで作成したconsts.pyにモデルで使用する定数を以下のように追加しました。

MAX_RATE = 5

次に、以下のコマンドより作成したモデルをもとにテーブルを作成しました。
また、同時にレビュー画面を表示するhtmlファイルを生成しました。

$ python3 manage.py makemigrations
$ python3 manage.py migrate

$ touch book/templates/book/review_form.html

続いて、生成したreview_form.htmlに以下のようなコードを追加しました。

{% extends 'base.html' %}

{% block title %}レビュー投稿{% endblock %}
{% block h1 %}レビュー投稿{% endblock %}
{% block content %}
<form method="post" class="p-4 m-4 bg-light border border-success rounded form-group">
  {% csrf_token %}
  <label>
    対象書籍
  </label>
  <input class="form-control" value="{{ book.title }}" readonly>
  <label>
    タイトル
  </label>
  <input class="form-control" name="title">
  <label>
    本文
  </label>
  <textarea class="form-control" name="text" rows="3"></textarea>
  <label>
    星の数
  </label>
  <select class="form-control" name="rate">
    <option value="0">0 (最低)</option>
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3" selected>3 (普通)</option>
    <option value="4">4</option>
    <option value="5">5 (最高)</option>
  </select>
  <input type="hidden" name='book' value="{{ book.id }}">
  <button type="submit" class="btn btn-success mt-4">投稿する</button>
</form>
{% endblock %}

最後に、サーバーを起動しレビュー投稿画面を表示しました。

上の画像より、正常に投稿画面が表示されていることを確認することができました。

本の情報の表示

続いて、レビュー投稿画面に異なるモデルのデータである書籍の情報を表示できるようにしました。

まず、views.pyを以下のように変更しました。

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.urls import reverse, reverse_lazy
from django.views.generic import (
  ListView,
  DetailView,
  CreateView,
  DeleteView,
  UpdateView,
)
from .models import Book
from .models import Book, Review


class ListBookView(ListView):
@@ -38,3 +38,22 @@ class UpdateBookView(UpdateView):
def index_view(request):
  object_list = Book.objects.order_by('category')
  return render(request, 'book/index.html',{'object_list': object_list})

class CreateReview(CreateView):
  model = Review
  fields = ('book', 'title', 'text', 'rate')
  template_name = 'book/review_form.html'

  def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['book'] = Book.objects.get(pk=self.kwargs['book_id'])
    print(context)
    return context

  def form_valid(self, form):
    form.instance.user = self.request.user

    return super().form_valid(form)

  def get_success_url(self):
    return reverse('detail-book', kwargs={'pk': self.object.

また、管理画面からReviewモデルのデータを追加できるようにadmin.pyを以下のように変更しました。

from django.contrib import admin
from .models import Book, Review

admin.site.register(Book)
admin.site.register(Review)

続いて、使用できるようになった本のデータを表示できるようreview_form.htmlを以下のように変更しました。

-- 一部省略 --
<form method="post" class="p-4 m-4 bg-light border border-success rounded form-group">
  {% csrf_token %}
  <label>
    対象書籍
  </label>
  <input class="form-control" value="{{ book.title }}" readonly>
  <label>
    タイトル
  </label>
  <input class="form-control" name="title">
  <label>
    本文
  </label>
  <textarea class="form-control" name="text" rows="3"></textarea>
  <label>
    星の数
  </label>
  <select class="form-control" name="rate">
    <option value="0">0 (最低)</option>
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3" selected>3 (普通)</option>
    <option value="4">4</option>
    <option value="5">5 (最高)</option>
  </select>
  <input type="hidden" name='book' value="{{ book.id }}">
  <button type="submit" class="btn btn-success mt-4">投稿する</button>
</form>
{% endblock %}

以下の画像は変更が加えられた後のレビュー投稿画面の様子です。

上の画像より、対象書籍の欄に他のモデルの情報が表示されていることを確認することができました。

最後に、詳細画面からレビュー投稿画面へ遷移できるようにbook_detail.htmlを以下のように変更しました。

-- 一部省略 --
<div class="p-4 m-4 bg-light border border-success rounded">
  <h2 class="text-success">{{ object.title }}</h2>
  <p>{{ object.text }}</p>
  <a href="{% url 'review' object.pk %}" class="btn btn-primary">レビューする</a>
  <a href="{% url 'list-book' %}" class="btn btn-primary">一覧へ</a>
  <a href="{% url 'update-book' object.pk %}" class="btn btn-primary">編集する</a>
  <a href="{% url 'delete-book' object.pk %}" class="btn btn-primary">削除する</a>
  <h6 class="card-title">{{ object.category }}</h6>
</div>
{% endblock content %}