レビュー機能の実装

はじめに

前回↓に引き続き、本棚アプリケーションの作成を行います。今回は新たに本のレビューをすることのできる機能を追加していきます。
urhayataro.hatenablog.com

レビューは、レビューする本に対して評価、詳細、星の数を投稿できるものを目指します。


urlpatternとviewの作成

レビュー機能の実装にあたってまずurls.pyの実装から行います。やることはCreateReviewViewを呼び出すためのurlpatternsを設定します。book/urls.pyに以下の記述を追加します。

path('book/<int:book_id>/review/', views.CreateReviewView.as_view(), name='review'),

次にモデルの作成をします。book/models.pyに以下の記述を追加します。

from .consts import MAX_RATE

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

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

ここで設定した'tittle'が本のレビューの評価、'text'が詳細、'rate'が星の数を表しています。

モデルについて見ていきます。まずクラスの1行目のForeignKeyは別のデータベーステーブルのデータを使うときに利用します。今回はどの本に対してのレビューかを明確にするために、同じbook/models.pyにあるBookモデルのデータを参照します。ReviewモデルがBookモデルのデータを使うのでForeignKeyを利用します。これで管理画面などでレビューを作成するときにBookモデルのデータを選択することができるようになります。

ForeignKeyの第一引数はBookモデルのBookとしていますが、第二引数について見ていきます。これは参照先のデータが削除されたときの処理方法を指定します。つまり、本のデータを削除されたときの処理方法のことです。models.CASCADEは本を削除したときにその本に関するレビューも削除されます。ForeignKeyを利用した場合は、on_deleteの設定が必須となっています。

rateを指定するときに利用したIntegerFieldは整数型の数字を扱うときに利用します。また、引数のRATE_CHOICESは予めfor文で定義している変数です。この変数を別で書いているのは将来的に数字を変更する際に便利なためです。今回は星5つまでとしますが、10こまでに変更するときに別で定義することでエラーのリスクを下げることができます。


ではRATE_CHOICESの数字を設定します。数字の設定にはMAX_RATEを指定する必要があります。新たにMAX_RATEを指定するファイルを作成します。

touch book/consts.py

作成したファイルに以下の記述をします。今回は星が5つまでとするのでMAX_RATEは5としています。

MAX_RATE = 5

これでモデルの作成ができました。次にviews.pyの実装をします。
book/views.pyに以下の記述を追加します。

from .models import Book, Review

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

モデルはReviewモデル、フィールドは本の名前、評価、詳細、星の数を設定しています。

ここでReviewモデルを作成します。モデルの作成はターミナルで以下を実行します。

python3 manage.py makemigrations
python3 manage.py migrate

次にhtmlファイルを作成します。

touch book/templates/book/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-success rounded form-group">
        {% csrf_token %}
        <label>
            対象書籍
        </label>
        <input class="form-control" value="{{ object.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="0">1</option>
            <option value="0">2</option>
            <option value="0">3 (普通)</option>
            <option value="0">4</option>
            <option value="0">5 (最高)</option>
        </select>
        <button type="submit" class="btn btn-success mt-4">投稿する</button>
    </form>
{% endblock %}

ここまでできればサーバーを立ち上げて127.0.1:8000/book/1/reviewにアクセスします。すると以下の画面になっています。

今はまだReviewモデルの実装はしたものの、Bookモデルの実装はしていないので対象書籍の欄が空欄になっています。ここからBookモデルの実装をして対象書籍の欄に表示させるようにしていきます。


Bookモデルからデータを取得し書籍情報を表示させる

ここからレビューをする書籍情報を表示できるようにしていきます。書籍情報を取得するにはCreateViewに備わっているget_content_dataというメソッドを利用します。
book/views.pyに以下の記述を追加します。

class CreateReviewView(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  #追加

新たに追加した**kwargsについて説明します。これはキーワード引数というもので、urlに入力された数字がキーワード引数としてviewに渡されます。今回の場合はbook/urls.pyで追加した、path('book//review/', views.CreateReviewView.as_view(), name='review'),のがキーワード引数に当たります。

次にcontext['book']に付いて見ていきます。これは辞書型のデータにオブジェクトを追加する事をしています。今回はBookオブジェクトのpk=self.kwargs['book_id']で指定したものに対応したデータが取得されます。これをターミナルで表示させ、返り値として設定しています。


ではサーバーを立ち上げて127.0.1:8000/book/1/reviewにアクセスします。ターミナルを見てみると以下の表示がされています。

'book':という表示はBookモデルにあるidが1の本のことを指しています。

これでBookモデルの実装ができました。合わせてbook/templates/book/review_form.htmlを修正します。

{% 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>

objectだったものをbookにすることでget_context_dataで追加したcontext[book]に対応し、bookオブジェクトのtittleフィールドの情報を取得できるようになります。ここには上で説明したとおりbook_idで指定したBookモデルのデータが入っています。

再度サーバーを立ち上げて127.0.1:8000/book/1/reviewにアクセスします。対象書籍の欄が書かれています。



レビュー内容のデータを追加し反映させる

これまでで本に対してレビューを入力することはできました。ここからは入力した内容を投稿すると反映させるようにしていきます。

まず管理画面からReviewモデルのデータを追加できるようにします。book/admin.pyに以下の記述をします。

from django.contrib import admin
from .models import Book, Review
# Register your models here.

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

次にBookオブジェクトの情報をフォームに渡すことをしなければなりません。book/templates/book/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-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="0">1</option>
            <option value="0">2</option>
            <option value="0">3 (普通)</option>
            <option value="0">4</option>
            <option value="0">5 (最高)</option>
        </select>
        <input type="hidden" name='book' value="{{ book.id }}">
        <button type="submit" class="btn btn-success mt-4">投稿する</button>  #追加
    </form>
{% endblock %}

htmlからフォームでデータを送るときにはnameを使います。また、どの本のデータを保存するのかがわかるようにidの指定も行う必要があります。htmlファイルでidの情報を表示する必要は無いのでhiddenとしています。


ここで一度CreateReviewViewのfieldsの項目を確認します。

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

このように4つのフィールドが設定されています。一方Reviewモデルを確認すると5つのフィールドが設定されています。

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)

これではuserの情報が抜け落ちているのでViewからuserの情報をおくる必要があります。ここでuserの情報はブラウザ上に表示させてはいけません。この解決には以下のようにします。book/views.pyに以下の記述を追加します。

class CreateReviewView(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)  #追加

まずform_vaidはフォームが送信され、入力に間違いが無い場合データが保存される前に呼び出されます。

追加した2行目の左側はformクラスのinstanceというデータにuserという属性でデータを追加する事を表しています。
そして右側は取得したuserのデータをどのように追加するのかを表しています。ここではuserがログインしているときrequestオブジェクトにあるuserの情報を表しています。

この記述でユーザーの情報をformのデータに送ることができます。


ここまでで、レビューの内容をデータに追加することができます。ただ、投稿するを押せばレビューの投稿ができるはずですが、押したあとの画面の遷移先を設定していません。

投稿するを押して、データ送信後の遷移先のurlを設定します。book/views.pyで以下の記述を追加します。

from django.urls import reverse, reverse_lazy  #追加

class CreateReviewView(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.book.id})  #追加

get_success_urlメソッドを新たに作成し画面の遷移先を指定します。今まではreverce_lazyを利用していましたが、メソッドの中に書く場合はreverceを使います。
返り値の第一引数でdetail-bookを指定することで、書籍詳細ページに遷移するようにしました。また、第二引数でキーワード引数に書籍のidの番号を渡しています。DetailBookViewでは、どののデータを表示するのかを指定する必要があるのでこの記述としています。


これでレビューのデータを追加し、投稿するボタンで送信すると書籍詳細ページに画面が遷移するようになりました。

サーバーを立ち上げて127.0.0.1:8000/book/1/review/にアクセスし投稿してみましょう。

投稿すると、書籍詳細ページに画面が遷移したはずです。


追加したレビューの確認

レビューが投稿できれば、きちんと送信され、データが追加されているか確認しましょう。

サーバーを立ち上げて127.0.0.1:8000/admin/book/review/にアクセスしましょう。
ログインすると以下の画面が表示されます。

また、この良かったというところを押すと以下のようにレビューの編集ができます。


これでレビューした内容が反映されていることもわかりました。ブラウザ上でレビューを表示するページは後ほど作成します。

最後に、書籍詳細から、レビューを書くためのページに遷移するリンクを付けます。book/templates/book/book_detail.htmlに以下の記述を追加します。

{% extends 'base.html' %}

{% block title %}{{ object.title }}{% endblock %}
{% block h1 %}書籍詳細{% endblock %}

{% block content %}
<div class="card">
    <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 'list-book' %}" class="btn btn-primary">一覧へ</a>
            <a href="{% url 'review' object.pk %}" 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>
</div>
{% endblock content %}

これでサーバーを立ち上げて適当な本の詳細ページを見てみると以下のようにレビューするためのボタンが追加されています。

レビューするをおすと、レビューを書く画面に遷移しました。

これでレビュー機能の実装ができました。


おわりに

今回は本のレビューを投稿する機能の追加をしました。評価、詳細、星の数を設定し、投稿することができるようになりました。
また、本のレビューをデータに追加し、確認することができました。
次回からは本の名前だけで色々していたものを本の写真を付けて見栄え良くしていきます。

会員登録機能の追加

はじめに

今回も引き続き本棚アプリケーションの作成を行います。前回までのものに追加でアプリケーションを会員限定で使えるようにする会員登録機能を追加します。過去記事↓
urhayataro.hatenablog.com


これまではログイン、ログアウトの処理がバラバラに書いていましたが、会員登録機能を実装する上で、今回からアカウントの情報などは一つにまとめて書いていきます。


アプリケーションの作成とログアウトの修正

まずはじめにaccountsという名前の新規のアプリケーションを作成します。ターミナル上で以下を入力します。

python3 manage.py startapp accounts

作成できれば、settingファイルのINSTALLED_APPSに新規のアプリケーションを認識させる必要があります。bookproject/setting.pyに以下を記述します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts.apps.AccountsConfig',
    'book.apps.BookConfig',
]

それからbookproject/urls.pyも修正します。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),
    path('', include('book.urls')),
]


つぎにログアウトに関してバラバラに書いていたものを一つにするために修正します。
book/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/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('logout/', views.logout_view, name='logout'),  #削除
]

また、book/views.pyのlogoutをインポートしたもの、def logout_viewを削除します。

これで会員登録機能を実装したときにログイン関係をひとまとめにする準備ができました。
ここからはログイン関係をひとまとめにしていきます。

まずはurls.pyとviews.pyに記述していきます。urls.pyの作成から行います。

touch accounts/urls.py

作成したaccounts/urls.pyに以下を記述します。

from django.urls import path
from django.contrib.auth.views import LoginView, LogoutView

from .views import SignupView

app_name = 'accounts'

urlpatterns = [
    path('login/', LoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path('signup/', SignupView.as_view(), name='signup'),
]

まず、2行目でDjangoのauthアプリケーションのviews.pyで定義されているLoginViewとLogoutViewを呼び出しています。これでログイン、ログアウトした時の状態の遷移ができるようになります。
ではログイン、ログアウトした時の画面の遷移先を指定しておきます。どちらも本棚アプリケーションのトップページに遷移するように設定します。bookproject/settings.pyに以下を記述します。

LOGIN_REDIRECT_URL = 'index'
LOGOUT_REDIRECT_URL = 'index'

次に6行目のapp_nameについての記述について見ていきます。
なぜapp_nameで名付けをしたかというと、例えばurlを指定するときに'login'としたとして、複数のアプリケーションで'login'という名前で定義されていたとしましょう。するとどのアプリケーションの'login'のことを指定しているのかがわからなくなります。そのためapp_nameで定義しておくことでどのアプリケーションか判別することができます。

ではtemplates/base.htmlに'login'と記述されているところがあるので修正します。

<div class="navbar-nav d-flex flex-row">
          {% if request.user.is_authenticated %}
            <a class="nav-link mx-3 href={% url 'accounts:logout' %}">ログアウト</a>  #修正
          {% else %}
            <a class="nav-link mx-3 href={% url 'accounts:login' %}">ログイン</a>  #修正
          {% endif %}
        </div>

会員登録機能の実装

まずはaccounts/views.pyに以下の記述をします。

from django.shortcuts import render
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.views.generic import CreateView

from .forms import SignupForm

# Create your views here.
class SignupView(CreateView):
    model = User
    form_class = SignupForm
    template_name = 'accounts/signup.html'
    success_url = reverse_lazy('index')

会員登録機能にはDjangoにあるformを利用します。formはmodelと似たようなものでデータを扱うときに利用します。それぞれ以下のような場合に利用します。

  • formはデータを保存せずに、ただデータのやり取りをする際
  • modelはデータベースのデータを扱う際

今回はformを利用するのでform.pyファイルを作成します。

touch accounts/forms.py

作成したaccounts/forms.pyに以下の記述をします。

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User

class SignupForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('username',)

まず1行目で新しいUserを作成できるformをインポートします。2行目で新しいユーザーが登録された際にUserモデルにデータを追加するためのインポートをします。
次にクラスについて見ます。UserCreationFormというクラスでパスワードの設定とユーザーの登録をすることができます。

UserCreationFormのソースコードにはpassword1とpassword2があり、それぞれ登録と確認用となっています。アカウントを作成するにはユーザーID、パスワード、確認用パスワードが必要になります。

これらを踏まえてhtml記述をしていくことで会員登録機能が完成します。


htmlファイルはaccountsの中にtemplatesを作り、その中に作成していきます。ターミナルで以下を入力します。

mkdir accounts/templates
mkdir accounts/templates/accounts

それから会員登録をするためのフォームを動作させるhtmlの記述をします。作成したディレクトリにsignup.htmlを作成します。

touch mkdir accounts/templates/accounts/signup.html

作成したファイルに以下の記述をします。

{% extends 'base.html' %}

{% block title %}アカウント作成{% endblock %}
{% block h1 %}アカウント作成{% endblock %}
{% block content %}
    <form method="post" class="p-4 m-4 bglight border border-success rounded form-group">
        {% csrf_token %}
        <input type="text" name='username' class="form-control mt-4"
        placeholder="ユーザーID">
        <input type="password" name='password1' class="form-control mt-4"
        placeholder="パスワード">
        <input type="password" name='password2' class="form-control mt-4"
        placeholder="パスワード確認用">
            <small class="mb-2 d-block text-start">
                パスワードは8文字以上で設定してください
            </small>
            {% if form.errors %}
            <span class="mb-2 small text-danger d-block text-start">
                利用できないユーザーIDやパスワードの可能性があります。入力内容を再度ご確認ください
            </span>
            {% endif %}
            <button type="submit" class="btn btn-success m-2">
                アカウント作成
            </button>
    </form>
{% endblock %}

Bootstrapを利用してhtmlの作成をしました。
会員登録にはpassword1とpassword2を使用するように記述し、パスワードの設定の際のエラーも表示できるようにしました。


会員登録機能の確認

では、会員登録ができるかの確認をします。サーバーを立ち上げて127.0.0.1:8000/acounts/signup/にアクセスします。
以下のような画面になっており、適当に入力してアカウント作成を押します。

登録ができれば管理画面で確認します。127.0.0.1:8000/admin/にアクセスします。

ユーザーが登録されています。ここでSTAFF STATUSにチェックが入っているものは管理者権限を持っていて、バツのものは持っていないことを表しています。新たに登録したものはバツのものでしょう。

最後に会員登録の文字情報が表示されるようにtemplates/base.htmlに以下を追加します。

<div class="navbar-nav d-flex flex-row">
          {% if request.user.is_authenticated %}
            <a class="nav-link mx-3 href={% url 'accounts:logout' %}">ログアウト</a>
          {% else %}
            <a class="nav-link mx-3 href={% url 'accounts:login' %}">ログイン</a>
            <a class="nav-link mx-3 href={% url 'accounts:signup' %}">会員登録</a> # 追加
          {% endif %}
        </div>

文字情報を追加しただけなので押しても何も起こりませんが、画面の遷移先は今後実装します。
これで会員登録機能の実装ができました。



おわりに
今回は会員登録機能の追加をしました。ユーザーIDとパスワードで登録することができましたが、この本棚アプリケーションが完成すれば、改良としてgoogleアカウントで登録やログインができるようにします。

トップページの作成とログイン機能の追加

はじめに

今回はトップページの作成とログイン機能、ログアウト機能を実装します。


トップページの作成

トップページを作るにあたってrenderについて確認します。
Djangoではrequestオブジェクトを受け取ってresponseオブジェクトを返します。このときのresponseオブジェクトの作成にrender関数を使います。

では、book/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.ListBookView.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'),
]

次にbook/views.pyに以下の記述をします。

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

# Create your views here.
def index_view(request):
    return render(request, 'book/index.html',{'somedata':100})

class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

class DeleteBookView(DeleteView):
    template_name = 'book/book_confirm_delete.html'
    model = Book
    success_url = reverse_lazy('list-book')

ここで追加した

def index_view(request):
    return render(request, 'book/index.html',{'somedata':100})

について、返り値の第一引数のrequestは必ず入れます。次の第二引数はファイルを指定します。第三引数はデータを指定します。今回は100を表示させます。左のsomedataは辞書のキーの部分で、右の100はバリューです。


ここからはindex.htmlを用いてブラウザ上データを表示させます。まず、index.htmlを新規作成します。

touch book/templates/book/index.html

作成した、book/templates/book/index.htmlに以下を記述します。

{{ somedata }}
||>

この、somedataは先程述べたようにviews.pyのrender関数で設定したキーを表しており、ブラウザ上での出力は100となります。



ここまではfunction-based viewの流れについて作成していましたが、ここからはviewとModelを連携させます。
book/views.pyのindex_view関数を以下のように変更します。
>|python|
def index_view(request):
    object_list = Book.objects.all()
    return render(request, 'book/index.html',{'object_list': object_list})

新たにobject_listを設定しました。これは、.allとなっているように、Bookテーブルにある全てのobjectsを表しています。
また、キーとバリューのところもobject_listという名前で呼び出せるようにしました。

更に、Book.objects.all()のall()の部分をorder_by('catefgory')やorder_by('title')とするとカテゴリーやタイトル順で一覧の並び替えができます。


views.pyの記述が終われば、book/templates/book/index.htmlを以下のように変更します。

{% extends 'base.html' %}

{% block title %}書籍一覧{% endblock %}
{% block h1 %}書籍一覧{% endblock %}

{% block content %}
    {% for item in object_list %}
    <div class="p-4 m-4 bg-light border border-success rounded">
        <h2 class="text-success">{{ item.title }}</h2>
        <h6>カテゴリー: {{ item.category }}</h6>
        <div class="mt-3">
            <a href="{% url 'detail-book' item.pk %}">詳細へ</a>
        </div>
    </div>
    {% endfor %}
{% endblock content %}

これでサーバーを立ち上げ、127.0.0.1:8000/にアクセスします。前回127.0.0.1:8000/book/にあったページがトップページとして表示されます。


ログイン、ログアウト機能の追加

ログイン機能

ログイン機能の実装には、フォームに入力されたユーザー名とパスワードがデータベースにあるユーザーの情報と一致しているかを照合し、一致しているユーザーのみログインさせる必要があります。
ログイン機能はDjangoでサポートされている認証のViewを呼び出すことで使えます。呼び出しにはauthアプリケーションのurls.pyを呼び出します。

呼び出すためにbookproject/urls.pyに以下を記述します。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),
    path('', include('book.urls')),
]

これによりaccounts/というurlをrequestすることでauthアプリケーションのurls.pyを呼び出すことができます。


ここまででログイン機能のやり方はできましたが、ログイン画面のviewは作っていません。これは127.0.0.1:8000/accounts/login/にアクセスすると呼び出されるviewになります。

ログイン画面のviewを以下のように作成します。

mkdir temprates/registration
touch templates/registration/login.html

作成したtemplates/registration/login.htmlに以下の記述をします。

{% extends 'base.html' %}

{% block content %}
    <h1>ログイン</h1>
    <form method="post" class="p-4 m-4 bg-light border border-success rounded form-group">
        {% csrf_token %}
        {% for error in form.errors.values %}
            {{ error }}
        {% endfor %}
        <label>
            ユーザ ID
        </label>
        <input class="form-control" name="username">
        <label>
            パスワード
        </label>
        <input type="password" class="form-control" name="password">
        <button type="password" class="btn btn-success mt-4">ログインする</button>
    </form>
{% endblock %}

これでサーバーを立ち上げ127.0.0.1:8000/accounts/login/にアクセスします。すると以下のような画面が表示されます。

ただ、ログインしようとするとエラーがでます。

ログインするとaccounts/profile/というurlにアクセスするような設定になっているのに、urlの設定をしていないのでエラーがでました。
解決のためにbookproject/setting.pyに以下の記述を追加します。

LOGIN_REDIRECT_URL = 'list-book'

これでログインすると書籍一覧のページが表示されます。


ログアウト機能

まずはbook/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.ListBookView.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('logout/', views.logout_view, name='logout'), # 追加
]

また、book/views.pyにも以下の記述をします。

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

# Create your views here.
def index_view(request):
    object_list = Book.objects.order_by('category')
    return render(request, 'book/index.html',{'object_list': object_list})

def logout_view(request):
    logout(request)
    return redirect('index')

class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

class DeleteBookView(DeleteView):
    template_name = 'book/book_confirm_delete.html'
    model = Book
    success_url = reverse_lazy('list-book')

class UpdateBookView(UpdateView):
    template_name = 'book/book_update.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

これでサーバーを立ち上げ、127.0.0.1:8000/logout/にアクセスします。アクセスするとトップページが表示され、ログアウトできているかの確認には管理画面にアクセスします。ユーザー名とパスワードを求められたらログアウトできています。


つぎにトップページにログイン、ログアウトするためのリンクを付けます。リンクはbase.htmlで作成したnavbarに追加します。

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" 
    rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
    crossorigin="anonymous">

    <title>{% block title %}{% endblock title %}| 本棚アプリケーション</title>
    </head>
    <body>
      <nav class="navbar navbar-dark bg-success sticky-top">
        <div class="navbar-nav d-flex flex-row">
          <a class="nav-link mx-3" href="{% url 'list-book' %}">書籍一覧</a>
          <a class="nav-link mx-3" href="{% url 'create-book' %}">書籍登録</a>
        </div>

        <div class="navbar-nav d-flex flex-row">
          {% if request.user.is_authenticated %}
            <a class="nav-link mx-3 href={% url 'logout' %}">ログアウト</a>
          {% else %}
            <a class="nav-link mx-3 href={% url 'login' %}">ログイン</a>
          {% endif %}
        </div>
      </nav>
      <div class='p-4'>
        <h1>{% block h1 %}{% endblock %}</h1>
        {% block content %}{% endblock content %}
      </div>
    </body>
</html>

新たに追加した、{% if request.user.is_authenticated %}はユーザーがログイン状態かを判定しています。



おわりに
今回は本棚アプリケーションのトップページとログイン、ログアウト機能を追加しました。
はじめの方に登録したユーザー名とパスワードを使ってログイン、ログアウトをするのはアプリケーションを作る上で大切です。

本棚アプリケーション ページの遷移とレイアウト

はじめに

前回まではブラウザ上でリンクが作成されておらず、127.0.0.1:8000/...などurlを打ち込んで画面の遷移をしていました。
今回はブラウザ上のボタンをクリックすると別の画面に遷移するようにリンクの作成を行います。
さらに各ページでレイアウトを整えます。


リンクの設定

htmlの記述をしていきますが、aタグ()の中にDjangoのタグ({% %})を書いて実装します。Djangoタグには遷移先のviewを書きます。

まずは、book/templates/book/book_list.htmlに以下の記述をします。

{% extends 'base.html' %}

{% block title %}書籍一覧{% endblock %}

{% block content %}
    {% for item in object_list %}
    <div class="card">
        <h5 class="card-header">{{ item.title }}</h5>
        <div class="card-body">
            <p class="card-text">{{item.text}}</p>
            <a href="{% url 'detail-book' item.pk %}" class="btn btn-primary">詳細へ</a>
            <h6 class="card-title">{{item.category}}</h6>
        </div>
    </div>
    {% endfor %}
{% endblock content %}

aタグ()の中にDjangoのタグ({% %})を書いて実装しますと述べたが、上のコードではurl 'detail-book'としており、これはurlpatternsで指定したnameを表しています。つまり、urls.pyでname='detail-book'と定義したコードに対応したurlが呼び出されます。


次に、遷移先である、book/templates/book/book_detail.htmlに以下の記述をします。

{% extends 'base.html' %}

{% block h1 %}書籍詳細{% endblock %}

{% block content %}
<div class="card">
    <h5 class="card-header">{{ object.title }}</h5>
    <div class="card-body">
            <p class="card-text">{{ object.text }}</p>
            <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>
</div>
{% endblock content %}

できればサーバーを起動して127.0.0.1:8000/book/1/detailにアクセスして、ボタンが追加されているかを確認します。


レイアウトの調整

では、書籍一覧のページなどレイアウトを調整していきます。やることはbase.htmlにブラウザの見た目が綺麗になるよう記述します。
bookproject/templates/base.htmlに以下を記述していきます。

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" 
    rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
    crossorigin="anonymous">

    <title>{% block title %}{% endblock title %}| 本棚アプリケーション</title>
    </head>
    <body>
      <nav class="navbar navbar-dark bg-success sticky-top">
        <div class="navbar-nav d-flex flex-row">
          <a class="nav-link mx-3" href="{% url 'list-book' %}">書籍一覧</a>
          <a class="nav-link mx-3" href="{% url 'create-book' %}">書籍登録</a>
        </div>
      </nav>
      <div class='p-4'>
        <h1>{% block h1 %}{% endblock %}</h1>
        {% block content %}{% endblock content %}
      </div>
    </body>
</html>

bodyの中に記述した、navbar navbar-darkは文字をグレーに、bg-successはbockgroundをsuccessは緑色にするように指定しています。また、mx-3に関しては左右の余白を示しています。


次に、今回は書籍一覧のページのレイアウトを整えるので、book/templates/book/book_list.htmlに以下の記述をします。

{% extends 'base.html' %}

{% block title %}書籍一覧{% endblock %}
{% block h1 %}書籍一覧{% endblock %}

{% block content %}
    {% for item in object_list %}
    <div class="p-4 m-4 bg-light border border-success rounded">
        <h2 class="text-success">{{ item.title }}</h2>
        <h6>カテゴリー:{{ item.category }}</h6>
        <div class="mt-3">
            <a href="{% url 'detail-book' item.pk %}">詳細へ</a>
        </div>
    </div>
    {% endfor %}
{% endblock content %}

これで書籍一覧ページのレイアウトの調整ができました。サーバーを立ち上げて127.0.0.1:8000/book/にアクセスします。
すると以下のようなレイアウトになっています。これでレイアウトの調整を終わります。


おわりに

今回はブラウザ上での画面遷移とレイアウトの調整を行いました。次回からはトップページやログイン機能を追加してよりきちんとしたアプリケーションの作成を行います。

CRUDを実装する(CRUDのCUD)

はじめに

以前↓にCRUDのRは作成しました。今回はその他を作成していきます。
urhayataro.hatenablog.com

まず復習から、CRUDとは

  • C-作成
  • R-読み取り
  • U-更新
  • D-削除

でした。また、対応するViewは

  • C - CreateView
  • R - ListView, DetailView
  • U - UpdateView
  • D - DeleteView

です。この中のC,U,Dを作成していきます。
今回も触るファイルは主に、urls.py、views.py、そして各htmlファイルです。


CreateViewの作成

CreateViewで実装するのは、ブラウザ上でデータの作成をするためのものです。ブラウザ上で入力したデータをデータベースに追加するためのプログラムを組んでいきます。

まずはbook/urls.pyから以下の記述をします。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view(), name='list-book'),
    path('book/<int:pk>/detail/', views.ListBookView.as_view(), name='detail-book'),
    path('book/create/', views.CreateBookView.as_view(), name='create-book'),
]

そして、追加したurlpetternsに対応させるようにbook/views.pyも記述をしていきます。

from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView
from .models import Book

# Create your views here.
class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}

ここで追加した最後の行のfieldsはブラウザで表示する項目を表しています。CreateViewを使うときには、modelのどの項目を表示させるかをviewの中で指定する必要があります。

これができればcreateのページを作るためのhtmlファイルを作成します。

touch book/templates/book/book_create.html

作成したbook_create.htmlに以下を記述します。

{% extends 'base.html' %}

{% block title %}書籍作成{% endblock %}

{% block content %}
    <form method='POST'>
        {{form.as_p}}
        <input type='submit' value='作成する'>
    </form>
{% endblock content %}

これで一度サーバーを立ち上げ127.0.0.1:8000/book/create/にアクセスします。すると以下のような入力画面が表示されます。

適当に投稿してみると、CSRFが発行されていないというエラーが表示されます。CSRFはセキュリティ上の問題で、この対策としてDjangoではformにcsrf_tokenというタグを付けることとします。このcsrf_tokenはワンタイムパスワードのようなもので、持っていない状態では処理を行うことができません。

csrf_tokenのタグをbook/bookproject/book_create.htmlに以下を追加します。

{% extends 'base.html' %}

{% block title %}書籍作成{% endblock %}

{% block content %}
    <form method='POST'>{% csrf_token %} # 追加
        {{form.as_p}}
        <input type='submit' value='作成する'>
    </form>
{% endblock content %}

これでcsrf_tokenの取得ができましたが、まだ「作成する」を押したときのurlの遷移先を設定していません。設定するにはform項目の作成が完了したときに指定のurlに遷移させるようにします。そのためにviewの中でsuccess_urlという変数を定義します。
book/views.pyに以下を追加します。

from django.shortcuts import render
from django.urls import reverse_lazy # 追加
from django.views.generic import ListView, DetailView, CreateView
from .models import Book

# Create your views here.
class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book') # 追加

最後に追加したlist-bookについてurls.pyで指定したviews.pyの名前の設定をしていきます。また、list_book以外のurlpatternもnameの追加をしていきます。
book/urls.pyに以下を追加します。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view(), name='list-book'), # 追加
    path('book/<int:pk>/detail', views.ListBookView.as_view(), name='detail-book'), # 追加
    path('book/create/', views.CreateBookView.as_view(), name='create-book'), # 追加
]

これでCreateViewの実装ができました。サーバーを立ち上げてデータの追加をすると、list-bookの画面に遷移し、データが追加されているはずです。



DeleteViewの作成

DeleteViewで実装するのは、データの削除をするためのものです。URLを使って削除するデータのidを指定し、リクエストを送るとデータベースが操作され削除されるという仕組みとなっています。

記述はほとんどCreateViewと同じで、urls.py、views.py、htmlに記述をしていきます。
まずはbook/urls.pyに以下の記述をします。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view(), name='list-book'),
    path('book/<int:pk>/detail/', views.ListBookView.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'),
]

それからbook/views.pyにも記述をしていきます。

from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, DeleteView,
from .models import Book

# Create your views here.
class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

class DeleteBookView(DeleteView):
    template_name = 'book/book_confirm_delete.html'
    model = Book
    success_url = reverse_lazy('list-book')

さらにhtml記述をします。

touch book/templates/book/book_confirm_delete.html

作成したbook_confirm_delete.htmlに以下を記述します。

{% extends 'base.html' %}

{% block title %}書籍削除{% endblock %}

{% block content %}
    <form method='post'>
        {% csrf_token %}
        <button type='submit'>{{ object.title }}を削除する</button>
    </form>
{% endblock %}

これでDeleteViewは実装できました。


UpdateViewの作成

CRUDの最後にUpdateの作成をしていきます。作成の目的としては、ブラウザ上でデータの編集をできるようにします。データの削除(D)から修正後を追加(C)ではなく、その場で編集ができるようにします。

作成するにあたって、これも基本的にCreateViewやDeleteViewと同じように、urls.py、views.py、htmlへの記述で作成していきます。

まずはbook/urls.pyに以下の記述をします。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view(), name='list-book'),
    path('book/<int:pk>/detail/', views.ListBookView.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'),
]

そしてbook/views.pyにも以下の記述をします。

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

# Create your views here.
class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

class DeleteBookView(DeleteView):
    template_name = 'book/book_confirm_delete.html'
    model = Book
    success_url = reverse_lazy('list-book')

class UpdateBookView(UpdateView):
    template_name = 'book/book_update.html'
    model = Book
    fields = {'title', 'text', 'category'}
    success_url = reverse_lazy('list-book')

さらにhtmlファイルを追加し、記述をしていきます。

touch book/templates/book/book_update.html

作成したbook_update.htmlに以下の記述をします。

{% extends 'base.html' %}

{% block title %}書籍修正{% endblock %}

{% block content %}
    <form method='post'>{% csrf_token %}
        {{ form.as_p }}
        <button type='submit'>修正する</button>
    </form>
{% endblock %}

これでUpdateViewの実装もできました。
ここまででCRUDの実装がすべてできました。


おわりに

今回はCRUDのC、U、Dの作成をしました。どれもviews.py、urls.py、htmlの記述で実装できることがわかりました。
今のところurlを直接打ってアクセスしているので、次回はブラウザ上でボタンのクリックで画面を遷移させていきます。

Bootstrapでwebサイトの見た目を整える

はじめに

前回の続き↓で進めていく。
urhayataro.hatenablog.com

今回やることは、bootstrapを利用してwebサイトの見た目を整えていきます。


Bootstrapで見た目を整える

前回、ListViewとDetailViewで作成したwebサイトの見た目を整えるために、Bootstrapを利用する。
webでBootstrapと検索して出てくるページから、はじめるをクリックし、Starter templateをコピーする。

このコピーしたコードをhtmlに貼り付けてbootstrapが使えるようになります。
では、前回作成したbook/book_list.htmlにbootstrapを利用したプログラムを書いていきます。

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" 
    rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
    crossorigin="anonymous">

    <title>本棚アプリケーション</title>
    </head>
    <body>
    {% for item in object_list %}
    <ul>
        <li>{{ item.title }}</li>
        <li>{{ item.text }}</li>
        <li>{{ item.category }}</li>
    </ul>
    <% endfor %>
    </body>
</html>

また、レイアウトの調整は以下よりCardレイアウトを利用します。

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" 
    rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
    crossorigin="anonymous">

    <title>本棚アプリケーション</title>
    </head>
    <body>
    {% for item in object_list %}
    <div class="card">
        <h5 class="card-header">{{ item.title }}</h5>
        <div class="card-body">
            <p class="card-text">{{ item.text }}</p>
            <a href="#" class="btn btn-primary">Go somewhere</a>
            <h6 class="card-title">{{ item.category }}</h6>
      </div>
    </div>
    <% endfor %>
  </body>
</html>

これでサーバーを立ち上げ127.0.0.1:8000/book/にアクセスします。
以下のように少し整ったサイトが表示


base.htmlにbootstrapを移す

先程は、book_list.htmlにbootstrapを適用しましたが、通常のwebアプリケーションには複数のhtmlファイルがあり、他のhtmlにもbootstrapを利用します。そのため、bootstrapを利用するhtmlはbase.htmlに書いてきます。

mkdir templates
touch templates/base.html

作成したbookproject/templates/base.htmlに以下を記述します。

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" 
    rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 
    crossorigin="anonymous">

    <title>{% block title %}{% endblock title %}| 本棚アプリケーション</title>
    </head>
    <body>
        {% block content %}{% endblock content %}
    </body>
</html>


base.htmlにbootstrapを書くことでいくつものhtmlファイルでbootstrapが使えるようになります。


おわりに

今回はhtmlファイルにbootstrapを利用してwebサイトの見た目を整えました。また、base.htmlに記述することで複数のhtmlファイルにbootstrapを適用させる事ができました。

CRUDを実装する(CRUDのR)

はじめに

前回の続き↓で本棚アプリケーションを作成していきます。
urhayataro.hatenablog.com

CRUDの実装を行い、webページのレイアウトを整えていきます。
今回メインで行うのは、CRUDのRであるwebサイトからの読み込みの実装です。

CRUDとは

CRUDとはwebサイトでユーザーが行う動作のことであり、以下の頭文字をとったものです。

  • C - Create
  • R - Read
  • U - Update
  • D - Delete

Cは作成する、投稿する、Rは読み込む、Uは更新する、Dは削除するという意味となっています。
Djangoではviews.pyでクラスの継承を用いてCRUDの実装をします。対応しているViewは、以下のようになります。

  • C - CreateView
  • R - ListView, DetailView
  • U - UpdateView
  • D - DeleteView

以上のviewを継承することでこれからCRUDを実装していきます。


本の一覧画面の作成

まず、本の一覧を表示するListViewという画面を作成していきます。
ListViewはCRUDでいうR(読み込み)の所です。
はじめにbook/urls.pyに以下の記述をします。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view()),
]

pathで書いてあるように、urlにbook/が含まれていると、views.pyでListBookViewと定義されたviewを呼び出すようにしています。
では次にviews.pyでListBookViewを定義しています。

from django.shortcuts import render
from django.views.generic import ListView
from .models import Book

class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

それから、book/models.pyも書き込んでいきます。本棚アプリケーションで必要となる、本のタイトル、内容、カテゴリーといった情報を扱えるようにします。

from django.db import models

# Create your models here.
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
    )

CATEGORYについては適当に何でも構いません。クラスBookはmodelsモジュールのModelクラスを継承するようにします。

そして、結果を管理画面で見ていくため、モデルを反映させます。book/admin.pyに以下の記述をしていきます。

from django.contrib import admin
from .models import Book
# Register your models here.

admin.site.register(Book)

ここまでできれば前回学んだ以下のコマンドを実行してみます。

python3 manage.py makemigrations
python3 manage.py migrate

できれば以下のような画面が表示されます。

それからサーバーを立てて、管理画面の表示をしていきます。

python3 manage.py runserver

立ち上げられたら127.0.0.1:8000/admin/にアクセスします。すると、管理画面にBooksが追加されています。

これで、Booksの横の+Addを押して、適当な本のデータを追加してみます。すると管理画面は以下のようになります。

ただBook object(1)のようになっており、このままでは何の本かわかりません。
ここには本のタイトルを表示させたいので、book/models.pyに以下の記述を追加します。

from django.db import models

# Create your models here.
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 # 追加

__str__とすることで、オブジェクトの文字列を返すようにしています。また、selfを引数にとり、self.titleを返すことでオブジェクトのタイトルを返すようにしています。これでブラウザを再読み込みして、タイトルが書かれていれば成功です。


では今度はhtmlを使い、データの一覧(タイトル、内容、カテゴリー)を表示させます。
まず、templatesというディレクトリでhtmlファイルを作成していきます。このディレクトリはbookproject/book/に作成し、その中でbook_list.htmlファイルを作成します。

cd ~/bookproject/book
mkdir templates
cd templates
mkdir book
touch book_list.html

ファイルの作成ができれば、book/templates/book_list.htmに以下の記述をします。

{% for item in object_list %}
<ul>
    <li>{{ item.title }}</li>
    <li>{{ item.text }}</li>
    <li>{{ item.category }}</li>
</ul>
{% endfor %}

1行目と最終行目はfor文のようなもので、中にあるitemのタイトル、内容、カテゴリーを全て表示させるような記述となっています。
また、タグを用いて見た目を少し整えるようにしています。

これでサーバーを立ち上げ、今度は127.0.0.1:8000/book/にアクセスします。
先程適当に本棚アプリケーションに追加した、タイトル、内容、カテゴリーが一覧で表示されていれば成功です。


本の詳細ページの作成

次に、ListViewと同じくCRUDのR(読み込み)であるDetailViewを作成していきます。
はじめに、book/urls.pyに以下を追加します。

from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.ListBookView.as_view()),
    path('book/<int:pk>/detail', views.ListBookView.as_view()), # 追加
]

ここで追加したところのについてはBookモデルのどのデータを取るのかというのを明確にするために記述しています。
これは、int primarykeyの略でデータベースがデータを特定するときに使うものです。空でも重複しているわけでもないデータを整理するためのキーであり、前回の記事のモデルの作成で説明した図のidの部分を指します。は、idの番号のデータを明示するために使用しています。


それからListViewと同じように、book/views.pyにも以下を追加します。

from django.shortcuts import render
from django.views.generic import ListView, DetailView # 追加
from .models import Book

# Create your views here.
class ListBookView(ListView):
    template_name = 'book/book_list.html'
    model = Book

class DetailBookView(DetailView): # 追加
    template_name = 'book/book_detail.html' # 追加
    model = Book # 追加


次にbook_list.htmlと同じ場所にbook_detail.htmlの作成をします。

touch book/templates/book/book_detail.html

作成したファイルにhtml記述をしていきますが、今回は本ごとの詳細を読み込みたいので、book_listとは違い、for文が不要です。
ではbook_detail.htmlに以下の記述をします。

{{ object.catefory }}
{{ object.title }}
{{ object.text }}

できれば、127.0.0.1:8000/adminにアクセスします。そして、適当な本を選択します。
本を選択してidを確認します。idの確認はurlを見ます。127.0.0.1:8000/admin/book/book/id/change/となっているところのidを見ます。具体的には1や2などの数字が入っているはずです。

このidを確認して127.0.0.1:8000/book/id/detailにアクセスします。するとそのidの本のカテゴリー、タイトル、内容が一行に表示されます。
これで本の詳細ページも読み込むことができました。


おわりに

今回は、CRUDとはなにかについて触れ、Rである、ListViewとDetailViewの記述をすることで本の一覧や詳細ページの作成をしました。
今回作成したのはシンプルすぎるので、次回はbootstrapを利用して見た目を整えたり、CRUDのR以外の部分の実装もしていきます。

続き
urhayataro.hatenablog.com