第4章 本棚アプリケーションの作成(CRUDの理解)

目的

 この章では、本の情報を整理するためのアプリケーションを作成することを目的とする。イメージとしては、まず、ブラウザの画面の先頭に書籍一覧という文字を表示する。そして、その下に書籍名とカテゴリ名、詳細へのリンクをセットとした本の情報を表示する。さらに、詳細へのリンクにアクセスすると、本の情報の編集と削除を行えるようにする。

仮想環境の構築・Djangoのインストール

 まずは仮想環境を構築していく。まず、以下のコマンドを実行して任意の場所に「project3」というディレクトリを作成する。

$ mkdir project3

 次に以下のコマンドを実行して作成した「project3」ディレクトリに移動し、仮想環境を作成する。

$ cd project3
$ python3 -m venv venv

 以下のコマンドを実行して仮想環境を立ち上げ、Djangoのインストールを行う。(ここでは本書に準拠してdjangoのバージョンを3.2とする)

$ source venv/bin/activate
(venv)$ pip install django==3.2 

プロジェクトとアプリのベースの作成

 以下のコマンドを実行して、プロジェクトの作成を行う。今回は、プロジェクト名をbookproject、アプリ名をbookとする。

(venv)$ django-admin startproject bookproject
(venv)$ cd bookproject
(venv)$ python3 manage.py startapp book

 次に、「bookproject/bookproject/urls.py」を以下のように書き換える。

from django.contrib import admin
from django.urls import path, include #コード追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('book.urls')), #コード追加
]

 また、「bookproject/bookproject/setting.py」も次のように書き換える。

INSTALLED_APPS = [

~~~ 省略 ~~~

    'django.contrib.staticfiles',
    'book.apps.BookConfig', #コード追加
]

~~~ 省略 ~~~

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], #コード追加
        'APP_DIRS': True,

~~~ 省略 ~~~

 最後に、以下のコマンドを実行してurls.pyを作成し、内容を記述する。

(venv)$ touch urls.py
urlpatterns = [] #コード追加

model(データベースとは)

別記事リンク

CRUDとは

別記事リンク

一覧画面(ListView)

 ここでは、本の一覧を表示するListViewの作成を進めていく。

urls.pyファイルの作成

 まず「bookproject/book/urls.py」を以下のように書き換える。

from django.urls import path #コード追加

from . import views #コード追加

urlpatterns = [
    path('book/', views.ListBookView.as_view()), #コード追加
]

views.pyファルの作成

 次に、「bookproject/book/views.py」を以下のように編集する。

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

class ListBookView(ListView):  #コード追加
    template_name = 'book/book_list.heml' #コード追加
    model = Book #コード追加

models.pyファイルの作成

 「bookproject/book/models.py」を以下のように編集する。これにより、タイトル、内容、カテゴリーの3つの情報を扱えるようにしている。

from django.db import models

CATEGORY = (('business', 'ビジネス'),(('life', '生活')),('othre', 'その他'))  #コード追加
class Book(models.Model):  #コード追加
    title = models.CharField(max_length=100) #コード追加
    text = models.TextField() #コード追加
    category = models.CharField( #コード追加
               max_length=100, #コード追加
               choices = CATEGORY #コード追加
    ) #コード追加

admin.pyファイルの編集

 SampleModelの削除とモデルの作成に伴い、「bookproject/book/admin.py」を以下のように編集する。

from django.contrib import admin
from .models import Book #コード追加

admin.site.register(Book) #コード追加

 ここで、管理画面にアクセスしてログインすると、Booksという名前でテーブルが表示される。

models.pyファイルの編集

 現状ではデータを追加してもタイトルがBook objectとなってしまうので、「bookobject/book/models.py」を以下のように編集して、タイトルを反映させる。

from django.db import models

CATEGORY = (('business', 'ビジネス'),(('life', '生活')),('othre', 'その他'))
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 #コード追加

 ここで、管理画面にログインしタイトル確認すると、正常に反映されていることが確認できる。

htmlファイル(テンプレートの作成)

 以下のコマンドを実行して、bookアプリ直下にtemplatesフォルダを作成し、その中にbook_list.htmlを作成する。

(venv)$ cd book
(venv)$ mkdir templates
(venv)$ cd templates
(venv)$ mkdir book
(venv)$ cd book
(venv)$ touch book_list.html
htmlファイル(1)

 「bookproject/book/templates/book/book_list.html」を以下のように編集する。

{% for item in object_list %}
  {{ item.title }}
  {{ item.text }}
  {{ item.category }}
{% endfor %}

 すると、「http://127.0.0.1:8000/book/」では以下のような画面が出力される。

htmlファイル(2)

 先程のデータの表示ではタイトルと中身の区別がつかないので、「bookproject/book/templates/book/book_list.html」を以下のように改良する。

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

 すると、「http://127.0.0.1:8000/book/」では以下のような画面が出力される。

詳細画面(DetailViewの作成)

 ここでは、データベースに入っている個別のデータを表示する際に使われる「DetailsView」を作成する。

urls.pyの作成

 まず、「bookproject/book/templates/book/urls.py」を以下のように編集する。

from django.urls import path
from . import views

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

views.pyの作成

 次に、「bookproject/book/templates/book/views.py」を以下のように編集する。

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

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

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

book_detail.htmlの作成

 次に、以下のコマンドを実行してbook_detail.htmlを作成し、以下のように編集する。

(venv)$ touch book/templates/book/book_details.html


{{ object.category }}
{{ object.title }}
{{ object.text }}

Bootstrapで見た目を整える

 最後に、「bookproject/book/templates/book/book_list.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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.rtl.min.css"
    integrity="sha384-+4j30LffJ4tgIMrq9CwHvn0NjEvmuDCOfk6Rpg2xg7zgOxWWtLtozDEEVvBPgHqE" 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>

 ここで、「http://127.0.0.1:8000/book/」にアクセスすると以下のような画面が出力される。

テンプレートの作成

 Djangoでは、同様の内容のhtmlファイルを何度も作成することがないようにするための機能が標準で搭載されている。具体的には、元となるテンプレートを作成し、それを継承するというものである。これより、そのテンプレートの作成を行っていく。

テンプレートの継承のイメージ

別記事リンク p149~p150

base.htmlファイルの作成

 以下のコマンドを実行して、テンプレートとなる「base.htmlファイル」を作成する。なお、「base.html」のようなテンプレートとなるファイルはプロジェクト直下のディレクトリ(manage.pyが入っているディレクトリ)に作成することが一般的である。

(venv)$ mkdir templates
(venv)$ touch templates/base.html

 そして、作成した「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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.rtl.min.css"
    integrity="sha384-+4j30LffJ4tgIMrq9CwHvn0NjEvmuDCOfk6Rpg2xg7zgOxWWtLtozDEEVvBPgHqE" crossorigin="anonymous">

  <title>{% block title %}{% endblock title %}本棚アプリ</title>
  </head>
  <body>
    {% block content %}{% endblock content %}
  </body>
</html>

book_list.htmlファイルの編集

 次に、「bookproject/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="#" class="btn btn-primary">Go somewhere</a>
      <h6 class="card-title">{{ item.category }}</h6>
    </div>
  </div>
  {% endfor %}
{% endblock content%} #コード追加

 {% block content %}と {% endblock content%}の間に、個別のhtmlファイルのコードを書いていく形となる。

book_detail.htmlの編集

 「bookproject/book/templates/book/book_detail.html」を以下のように編集する。

# 全てコード追加
{% extends 'base.html' %}

{% block title %}書籍詳細{% 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="#" class="btn btn-primary">ボタン</a>
      <h6 class="card-title">{{ object.category }}</h6>
    </div>
  </div>
{% endblock content%}

 これにより、base.htmlファイルを編集するだけで、継承したすべてのファイルを一気に変更することが出来るようになった。

データをブラウザ上で作れるようにする

 ここでは、データベースにブラウザ上からデータを追加できるという仕組みを作っていく。

urls.pyの実装

 まず、「bookproject/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.DetailBookView.as_view(), name='details-book'),
    path('book/create/', views.CreateBookView.as_view(), name='create-book'), #コード追加
]

views.pyの実装

 次に、「bookproject/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 

~~~省略~~~

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

book_create.htmlファイルの作成

 以下のコマンドを実行して、「book_create.htmlファイル」を作成する。

(venv)$ touch book/templates/book/book_create.html

 そして、「book/templates/book/book_create.html」を以下のように編集する。

#全てコード追加
{% extends 'base.html' %}

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

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

 このコードの簡単な解説を行う。

  • input type・・・は、テキストやボタンなど、html上でfromを構成される際に必要とされるパーツを示している。(今回は送信ボタンのみ作成なので、submitとし、valueで「作成する」という文字を表示させている)

  • {{ form.as_p }}は、Djangoのテンプレートの1つで、viewの中で指定したmodelで定義された項目をpタグで囲って表示させるという意味を持っている。

  • fromの中のmethod用いてデータを送るときのmethodはPOSTが使われる

データをブラウザ上で削除できるようにようにする

 ここでは、データベースにあるデータをブラウザ上から削除できるという仕組みを作っていく。

urls.pyの実装

 まず、「bookproject/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.DetailBookView.as_view(), name='details-book'),
    path('book/create/', views.CreateBookView.as_view(), name='create-book'), 
    path('book/<int:pk>/delete/', views.DeleteBookView.as_view(), name='delete-book'),  #コード追加
]

views.pyの実装

 次に、「bookproject/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 

~~~省略~~~

class DeleteBookView(DeleteView): #コード追加
    template_name = 'book/book_confirm_delete.html' #コード追加
    model = Book #コード追加
    seccess_urls = reverse_lazy{'list-book'} #コード追加

book_confirm_delete.htmlファイルの作成

 以下のコマンドを実行して、「book_confirm_delete.htmlファイル」を作成する。

(venv)$ touch book/templates/book/book_confirm_delete.html

 そして、「book/templates/book/book_confirm_delete.html」を以下のように編集する。

{% extends 'base.html' %}

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

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

データをブラウザ上で更新できるようにようにする

 ここでは、ブラウザ上で入力されたデータが、データベースに反映されるという仕組みを作っていく。

urls.pyの実装

 まず、「bookproject/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.DetailBookView.as_view(), name='details-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'),  #コード追加
]

views.pyの実装

 次に、「bookproject/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 

~~~省略~~~

class UpdateBookView(UpdateView): #コード追加
    model = Book #コード追加
    fields = {'title', 'text', 'category'} #コード追加
    template_name = 'book/book_update.html' #コード追加
    seccess_urls = reverse_lazy{'list-book'} #コード追加

book_update.htmlファイルの作成

 以下のコマンドを実行して、「book_update.htmlファイル」を作成する。

(venv)$ touch book/templates/book/book_update.html

 そして、「book/templates/book/book_confirm_delete.html」を以下のように編集する。

#全てコード追加
{% extends 'base.html' %}

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

{% block content %}
  <from method='post'>{% csrf_token %}
    {{from.as_p}}
    <input type='submit' value="修正する">
  </from>
{% endblock content%}

ブラウザ上でページを遷移させる

 ここでは、ブラウザ上に配置されているボタンをクリックすることで、画面が遷移するように実装を行う。

book_list.htmlファイルの実装

 「bookproject/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%}

 このコードの追加部分の簡単な解説を書く。

  • url 'detail-book'は、urlpatternで指定したnameを示している。つまり、urls.pyファイルの中で、name='detail-book'と定義したコードに対応したurlが呼び出される。
  • item.pkは、<int:pk>と同じイメージで、それぞれのデータのprimary keyに基づいたid番号を取得することが出来る。これにより、特定の番号のデータを表示させることが可能になる。

book_detail.htmlファイルの実装

 「bookproject/book/templates/book/book_detail.html」を以下のように編集する。

{% extends 'base.html' %}

{% block title %}書籍詳細{% 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%}

成果物

レイアウト等の修正

 見た目を綺麗にするための実装を行っていく。

レイアウトの調整とヘッダーの挿入

 「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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.rtl.min.css"
    integrity="sha384-+4j30LffJ4tgIMrq9CwHvn0NjEvmuDCOfk6Rpg2xg7zgOxWWtLtozDEEVvBPgHqE" crossorigin="anonymous">

  <title>{% block title %}{% endblock title %}本棚アプリ</title>
  </head>
  <body>
    <nav class="navbar navbar-dark bg-success sticky-top"> #コード追加
      <a class="nav-link mx-3" href="{% url 'list-book' %}">書籍一覧</a> #コード追加
      <a class="nav-link mx-3" href="{% url 'create-book' %}">書籍登録</a> #コード追加
    </nav> #コード追加
    <div class="p-4"> #コード追加
      <h1>{% block h1 %}{% endblock%}</h1> #コード追加
      {% block content %}{% endblock content %}
    </div>
  </body>
</html>

書籍一覧ページのレイアウトの変更

 "https://getbootstrap.com/docs/5.1/components/navbar"を参考に、"bookproject/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 botder 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%}

書籍詳細ページのレイアウトの変更

 "https://getbootstrap.com/docs/5.1/components/navbar"を参考に、"bookproject/book/templates/book/book_detail.html"を以下のように編集する。

{% extends 'base.html' %}
{% block title %}{{ object.title }}{% endblock %} #コード追加
{% block h1 %}書籍詳細{% endblock %} #コード追加

{% block content %}
  <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 '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%}