7K4B blog

猫でも分かる何か

django 4-13 本棚アプリケーション1

初回操作

$ mkdir test
$ cd test
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install django==3.2

なお次から開発するときも毎回 $ source venv/bin/activate だけはコマンドする必要がある

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

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

test/bookproject5/bookproject/urls.py を書く
from django.urls import path, include を追加して include ライブラリを使えるようにする
path('', include('book.urls')), 追加
第一引数が空文字なので http://127.0.0.1:8000/ でURLマッチングして book アプリケーションの urls.py を呼び出す
なお 127.0.0.1 はローカルループバックアドレスで自分自身を指す
django はプロジェクトの urls.py でドメインURLを受け取り、各アプリの urls.py で更に深いディレクトリへの urlpatterns を定義するのが定跡らしい
参考 https://office54.net/python/django/urls-path-include

通常操作

  • django は sqlite3 という DB API が最初から用意されている(なので SQL は不要)
from django.db import models

class SampleModel(models.Model):
    title = models.CharField(max_length=100)
    number = models.IntegerField()

test/bookproject5/bookproject/book/models.py を書く
django は DB を表で管理し、モデルと呼ぶ
( DB 設計図は C++ の構造体みたいな感じで書く)

DB設計図の作成

(venv)$ python3 manage.py makemigrations

上記コマンドを打つと
Migrations for 'book':
book/migrations/0001_initial.py
- Create model SampleModel
と表示されて sqlite3 設計図が test/bookproject5/bookproject/book/migrations/ に 0001_initial.py として作成される。
(なお models.py を追記して makemigrations コマンドを実行する毎に 0002_, 0003_, ... が作成される)

(venv)$ python3 manage.py migrate

上記コマンドで sqlite3 設計図に従って実際にモデル(データベーステーブル)が作成される
( makemigrations で作成された設計図を基に sqlite3 が DB table を実際に作成するコマンド)


(venv)$ python3 manage.py createsuperuser
Username (leave blank to use 'takahiro'): tkr987
Email address: 
Password: 
Password (again): 
Superuser created successfully.

上記コマンドで superuser を作成(Emailは空白でok)

from django.contrib import admin
from .models import SampleModel

admin.site.register(SampleModel)

test/bookproject5/bookproject/book/admin.py を書く
admin.py に model を追加することで、管理画面にDB設計図が反映され管理できるようになる
サーバーを立ち上げて http://127.0.0.1:8000/admin/book/ にアクセス


CRUD (Create Read Update Delete)

  • CreateView
  • ListView / DetailView
  • UpdateView
  • DeleteView

ListView(一覧画面の作成)

from django.urls import path
from . import views

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

test/bookproject5/bookproject/book/urls.py に上記を書く
パス(url)に'book/'という文字が入っていると views.py ファイルの中の Listview を定義した ListBookView の view を呼び出す

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

test/bookproject5/bookproject/book/views.py に上記を書く
基底 ListView を class ListBookView が継承する形で作っていく

from sre_constants import CATEGORY
from unicodedata import category
from django.db import models

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

test/bookproject5/bookproject/book/models.py に上記を書く
choices は右辺で定義した tuple をプルダウンで表示するオプション
CATEGORY はコード上で識別するIDと表示を組にして定義する組み込み変数 (sre_constants)

(venv)$ python3 manage.py makemigrations
(venv)$ python3 manage.py migrate

models.py を修正したので上記コマンドで反映させる

from django.contrib import admin
from .models import Book

admin.site.register(Book)

test/bookproject5/bookproject/book/admin.py に上記を書く
サーバーを立ち上げて http://127.0.0.1:8000/admin/book/ にアクセス
本を追加できるようになる

自作HTMLでDBを表示させる

$ cd book
$ mkdir templates
$ cd templates
$ mkdir book
$ cd book
$ touch book_list.html

上記コマンドを入力して下図


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

test/bookproject5/bookproject/book/templates/book/book_list.html に上記を書く
{% for %} はDjangoがhtmlで使える繰り返し文
{{ nyaa }} はDjangoがhtmlで使えるデータ値(の表示)
サーバーを立ち上げて http://127.0.0.1:8000/book/ にアクセス(http://127.0.0.1:8000/admin/book/ ではないので注意)


DetailView(詳細表示の作成)

from django.shortcuts import render
from django.views.generic import ListView, DetailView
(略)
class DetailBookView(DetailView):
    template_name = 'book/book_detail.html'
    model = Book

test/bookproject/bookproject/book/views.py に上記を書く
from django.views.generic import ListView, DetailView を追加
class DetailBookView(DetailView): を追加

from django.urls import URLPattern
(略)

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

test/bookproject/bookproject/book/urls.py に path('book//detail/', views.DetailBookView.as_view()), を追加
pk は Django が自動的にデータに割り振るユニークID (primary_key)

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

test/bookproject5/book/templates/book/book_detail.html に上記を書く
サーバーを立ち上げて http://127.0.0.1:8000/book/1/detail/ などにアクセス

Boostrap でデザインを華やかにする

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

全てのwebページで使いまわすテンプレートの基底ファイルを作る(基底はmanage.pyと同じディレクトリに配置するのが定跡)

<!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>

Boostrap 公式からスターターテンプレートをコピペして test/bookproject/bookproject/base.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 %}

test/bookproject5/bookproject/book/templates/book/book_list.html に上記を書く
base.html で定義した後に各htmlファイルに {% block title %} などを追記、内容を書くことで基底を継承(拡張)する方法でテンプレートデザインを適用できる

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

同様に基底を拡張しつつ test/bookproject/bookproject/book/templates/book/book_detail.html に上記を書く
Boostrap 公式から components (card) をコピペしたりすると更に楽が出来る


CreateViewでブラウザ上からデータを作成できるようにする

from django.urls import URLPattern
(略)
urlpatterns = [
    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'),
]

test/bookproject5/bookproject/book/urls.py の urlpatterns に name を追加
なお name は form の遷移先を指定したりするときに使う

from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView
(略)
class CreateBookView(CreateView):
    template_name = 'book/book_create.html'
    model = Book
    fields = ('title', 'text', 'category')
    success_url = reverse_lazy('list-book')

test/bookproject5/bookproject/book/views.py に上記を書く
from django.urls import reverse_lazy を追加
from django.views.generic import ListView, DetailView, CreateView を追加
class CreateBookView(CreateView): を追加
medel でデータベーステーブルのうちBookを使うと指定
(将来的にデータベーステーブルの種類が増えると予想されるので、ここは正しく指定する必要がある)
fields でデータベースの項目どれを入力フォームにするか指定する(今回は全て指定した)
form の遷移先は success_url で指定する( reverse_lazy は views.py の name から url を逆引きする)

{% extends 'base.html' %}
{% block title %}書籍作成{% endblock %}

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

test/bookproject/bookproject/book/templates/book/book_create.html に上記を書く
form はHTMLタグで method を POST にすると送信ボタンになる
form.as_p はデータベースのフィールド情報に基づいた入力フォームをpタグを自動的に付けて作成してくれる django のテンプレート
なお form を使うときのセキュリティ対策でcsrf_token と必ず書く必要がある(定跡)
サーバーを立てて http://127.0.0.1:8000/book/create/ にアクセス


作成するボタンを押すと下図(http://127.0.0.1:8000/book/)に遷移する

DeleteViewでデータ削除

from django.urls import URLPattern
(略)
urlpatterns = [
(略)
    path('book/<int:pk>/delete/', views.DeleteBookView.as_view(), name='delete-book'),
]

test/bookproject5/bookproject/book/urls.py にデータ削除画面の url を追加

{% extends 'base.html' %}
{% block title %} 書籍削除 {% endblock %}

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

test/bookproject5/bookproject/book/templates/book/book_delete_confirm.html に上記を書く

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
    success_url = reverse_lazy('list-book')    

test/bookproject/bookproject/book/views.py に上記を書く
from django.views.generic import ListView, DetailView, CreateView, DeleteView を追加
class DeleteBookView(DeleteView): を追加
サーバーを立てて http://127.0.0.1:8000/book/1/delete/ などにアクセス

削除ボタンを押すと(http://127.0.0.1:8000/book/)に遷移する(画像略)

UpdateView ブラウザ上でデータを編集できるようにする

from django.urls import URLPattern
(略)
urlpatterns = [
(略)
    path('book/<int:pk>/update/', views.UpdateBookView.as_view(), name='update-book'),
]

test/bookproject5/bookproject/book/urls.py にデータ編集画面の url を追加

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'
    success_url = reverse_lazy('list-book')

test/bookproject/bookproject/book/views.py に上記を書く
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView を追加
class UpdateBookView(UpdateView): を追加

{% extends 'base.html' %}
{% block title %} 書籍修正 {% endblock %}

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

test/bookproject5/bookproject/book/templates/book/book_update.html に上記を書く
サーバーを立てて http://127.0.0.1:8000/book/2/update/ などにアクセス

修正するボタンを押すと(http://127.0.0.1:8000/book/)に遷移する(画像略)

リンクの設定

url から直接各画面にアクセスするのでなくwebページのボタンから各画面に遷移できるようにする

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

test/bookproject5/bookproject/book/templates/book/book_list.html に上記を書く
<a href="{% url 'detail-book' item.pk %}" class="btn btn-primary">詳細へ</a> を追加
url 'name' は reverse_lazy と同じで名前から url を逆引きしてくれる
なお、データの要素に対するwebページの url を取得するには primary key が必要なので 'name' の後ろに pk を付ける必要がある

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

test/bookproject5/bookproject/book/book_detail.html に上記を書く
<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> を追加
サーバーを立てて http://127.0.0.1:8000/book/ にアクセス


レイアウトの調整

<!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>

test/bookproject5/bookproject/templates/base.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 %}

test/bookproject5/bookproject/book/book_list.html に上記を書く

{% extends 'base.html' %}
{% block title %}{{ object.title }}{% endblock %}
{% block title %}書籍詳細{% 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 %}

test/bookproject/bookproject/book/book_detail.html に上記を書く