はじめに
今回は、djangoのテンプレートシステムを用いてWebアプリの作り込みを実施します。
django入門⑦で整理したListViewとDetailViewを用いて、登録したTodoの一覧表示と詳細を表示します。Todoの追加(CreateView)、削除(DeleteView)、編集(UpdateView)は次回以降に実装します。Todoの追加と編集はdjangoの管理ページで行います。
本ブログをトレースすることで、下記のWebアプリを構築することができます。
参考にした書籍
Todo一覧を表示する際の動作フロー
Todoの一覧を表示するときの動作フローは下記となります。Todoの詳細を表示する際も同様の動作になります。
テンプレートシステムを用いることで、list.htmlとdetail.htmlで共通して使用しているヘッダーや削除ボタンの記述をbase.htmlにまとめることができます。
- ユーザーがブラウザを通じて特定のURLにアクセスします。
- Webサーバーが受け取ったHTTPリクエストをdjangoのルーティングシステム(urls.py)に渡します。
- プロジェクトのurls.pyがリクエストを適切なアプリケーション(この場合はtodolist)のurls.pyにルーティングします。
- アプリケーション(この場合はtodolist)のurls.pyはリクエストを関連するビュー(views.py)に渡します。
- ビューは、必要に応じてデータベースからデータを取得するためのクエリをデータベースに送信し、情報を取得します。
- データベースから取得した情報、base.html、list.htmlをビュー(views.py)によって組み合わせられます。
- ビュー(views.py)は最終的なHTTPレスポンスを生成し、Webサーバーに返します。
- WebサーバーはこのHTTPレスポンスをユーザーのブラウザに送信し、ユーザーはTodo一覧ページを表示できます。
開発環境
開発環境はVisual Studio Codeを使用します。djangoの実行環境はWindows上で動作させたUbuntuで実行します。WindowsにUbuntu環境を構築する手順は下記を参照してください。
プロジェクトのディレクトリの作成からdjangoアプリの作成までを、下記のコマンドで行います。
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install python3.12
$ sudo apt install python3.12-venv
$ mkdir todo
$ cd todo
$ python3.12 -m venv venv
$ source venv/bin/activate
(venv)$ pip install --upgrade pip
(venv)$ pip install django==4.2.9
(venv)$ django-admin startproject todo_project .
(venv)$ python manage.py startapp todolist
(venv)$ python manage.py startapp tododetail
(venv)$ python manage.py startapp common
(venv)$ mkdir templates
djangoのアプリは下記の3つ作成します。
- todolist:登録したTodoの一覧を表示するためのアプリ
- tododetail:登録した各Todoの詳細を表示するためのアプリ
- common:複数の他のアプリケーション間で共有される機能やモデルを保持するアプリ
開発環境を構築するコマンドを実行すると、下記のファイルとディレクトリが作成されます。
(venv)$ tree -L 2
.
├── common
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
├── templates
├── todo_project
│ ├── __init__.py
│ ├── __pycache__
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── tododetail
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── todolist
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
todo_project配下のファイル編集
todo_projectのsettings.pyを編集
- プロジェクト(todo_todoprojectディレクトリ内)のsettings.pyに以下を追加してプロジェクトに「common,todolist,tododetail」アプリを追加したことを設定します。
- settings.pyを編集し、33行目あたりにある「INSTALLED_APPS」の箇所に「’common’,’todolist’,’tododetail’」を追記します。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'common',
'todolist',
'tododetail',
]
- テンプレートhtmlを配置している場所について、BASE_DIRで設定します。
- settings.pyを編集し、57行目あたりにある「TEMPLATES」の箇所の「’DIRS’」に「[BASE_DIR / ‘templates’]」を記載します。
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
todo_projectのurls.pyを編集
- HTTPリクエストオブジェクトのURLパターンに応じたviewの設定を行います。
- 記載するURLパターンは下記です。
- 「http://127.0.0.1:8000/admin」であった場合、djangoの管理者ページを表示する
- 「http://127.0.0.1:8000/list/」であった場合、todolistのurls.pyにリクエストを投げる
- 「http://127.0.0.1:8000/」であった場合、「http://127.0.0.1:8000/list/top/」にリダイレクトする
- 「http://127.0.0.1:8000/detail/」であった場合、tododetailのurls.pyにリクエストを投げる
上記の内容をurls.pyに記載します。
from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import RedirectView
urlpatterns = [
path('admin/', admin.site.urls),
path('list/', include('todolist.urls')),
path('', RedirectView.as_view(url='/list/top/', permanent=True)),
path('detail/', include('tododetail.urls')),
]
common配下のファイル編集
commonのmodels.pyを編集
- commonアプリでは、複数の他のアプリケーション間で共有される機能やモデルを保持します。
- 今回はモデルのみを保持します。
<models.pyで設定する内容>
項目 | 変数名 | フィールド | オプション | 補足 |
タイトル | title | CharField | 入力可能文字数:100 | ー |
作業内容 | description | TextField | 無し | ー |
優先度 | priority | CharField | 入力可能文字数:10 優先度:高、通常、低 デフォルト優先度:通常 |
優先度はPriorityクラスで設定 |
期限 | duedate | DateField | 無し | ー |
from django.db import models
class Priority(models.TextChoices):
HIGH = 'danger', '高'
NORMAL = 'warning', '通常'
LOW = 'primary', '低'
class TodoModel(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
priority = models.CharField(
max_length=10,
choices=Priority.choices,
default=Priority.NORMAL
)
duedate = models.DateField()
def __str__(self):
return self.title
commonのadmin.pyを編集
- 管理者ページで管理するモデルを登録します。
from django.contrib import admin
from .models import TodoModel
admin.site.register(TodoModel)
todolist配下のファイル編集
todolistのurls.pyを編集
- 新規に作成します。
- HTTPリクエストオブジェクトのURLパターンに応じたviewの設定を行います。
- 記載するURLパターンは下記です。
- 「http://127.0.0.1:8000/list/top/」であった場合、todolistの一覧を表示する
上記の内容をurls.pyに記載します。
from django.urls import path, include
from .views import TodoListTop
urlpatterns = [
path('top/', TodoListTop.as_view(), name='list')
]
todolistのviews.pyを編集
- クラスベースビューを使用します。
- commonアプリのTodoModelを一覧表示するためのビューを定義します。
- TodoListTopクラスはListViewを継承しています。
- TodoModelのオブジェクトリストをlist.htmlテンプレートに渡して表示するためのビューを作成しています。
- クラス変数template_nameには、このビューで使用されるテンプレートの名前が指定しています。
- modelには表示したいオブジェクトのリストを提供するモデルが指定されています。
from django.shortcuts import render
from django.views.generic import ListView
from common.models import TodoModel
class TodoListTop(ListView):
template_name = 'list.html'
model = TodoModel
tododetail配下のファイル編集
tododetailのurls.pyの編集
- 新規に作成します。
- HTTPリクエストオブジェクトのURLパターンに応じたviewの設定を行います。
- 記載するURLパターンは下記です。
「http://127.0.0.1:8000/detail/(プライマリキー)/」であった場合、プライマリキーに該当するtodoの詳細を表示する。
上記の内容をurls.pyに記載します。
from django.urls import path, include
from .views import TodoDetail
urlpatterns = [
path('<int:pk>/', TodoDetail.as_view(), name='detail')
]
- 「<int:pk>/」URLからキャプチャされた整数値をpkという名前のキーワード引数としてビューに渡すことを意味します。
- 通常、pk(プライマリキー)はデータベースのレコードを一意に識別するために使われます。
- pk(プライマリキー)には、/1/や/2/の数字が入ります。
- それぞれの数字はレコードのIDとして扱われます。
tododetailのviews.pyの編集
- クラスベースビューを使用します。
- commonアプリのTodoModelを一覧表示するためのビューを定義します。
- TodoDetailクラスはDetailViewを継承しています。
- TodoModelのオブジェクトリストをdetail.htmlテンプレートに渡して表示するためのビューを作成しています。
- クラス変数template_nameには、このビューで使用されるテンプレートの名前が指定しています。
- modelには表示したいオブジェクトのリストを提供するモデルが指定されています。
from django.shortcuts import render
from django.views.generic import DetailView
from common.models import TodoModel
class TodoDetail(DetailView):
template_name = 'detail.html'
model = TodoModel
templates配下のファイル編集
base.htmlの編集
- 新規に作成します。
- list.htmlとdetail.htmlのテンプレートとなるhtmlファイルです。
- CSSフレームワークとしてbootstrap5.3.0を使用します。
- base.htmlに記載しているhtml基本構造タグ
<head>,<body>,<script> - base.htmlに記載しているdjangoテンプレートタグ
{% block header %}、 {% block content %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Todo 管理</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
<div class="bg-body-tertiary p-3 p-sm-3 mb-2">
<div class="container">
<h2 class="display-4">{% block header %}{% endblock header %}</h2>
</div>
</div>
{% block content %}
{% endblock content %}
<!-- 共通の削除モーダル -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">削除の確認</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p id="deleteItemName">このアイテムを本当に削除しますか?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">キャンセル</button>
<button type="button" class="btn btn-danger" id="deleteConfirmButton">削除</button>
</div>
</div>
</div>
</div>
<!-- 共通のJavaScript -->
<script>
function setupDeleteModal(itemId, itemName) {
document.getElementById('deleteItemName').textContent = '「' + itemName + '」を本当に削除しますか?';
var deleteButton = document.getElementById('deleteConfirmButton');
deleteButton.onclick = function() {
document.getElementById('deleteForm' + itemId).submit();
};
}
</script>
</body>
</html>
list.htmlの編集
- 新規に作成します。
- テンプレート構造の解説
タグ | 解説 |
{% extends ‘base.html’ %} | base.htmlを継承する。 |
{% block header %}…{% endblock header %} | base.html 内の header ブロックを上書きする。ページのタイトルとして “Todo 一覧” を設定している。 |
{% block content %}…{% endblock content %} | テンプレートのメインコンテンツ部分です。base.html の content ブロックを上書きする。 |
- 動的コンテンツ表示の解説
タグ | 解説 |
{% for item in object_list %}…{% endfor %} | object_listはビューから渡されるmodelのオブジェクト。 forループで、object_listの各オブジェクトに対して反復処理を行う。ここでは、Todoの各項目を表示する。 |
<div class=”alert alert-{{ item.priority }}”…> | 各Todoの優先度に応じて背景色を変える。 |
{{ item.title }} | 各Todoのタイトル。 |
{{ item.duedate|date:”o/n/j” }} | 各Todoの期日。|date:”o/n/j” は日付を特定の形式で表示するためのフィルタ。 |
- ボタン・フォームの解説
タグ | 解説 |
<a class=”btn btn-warning mb-2″…> | “作成”ラベルのボタンを表示する。※現時点でのリンク先は”#” |
<a class=”btn btn-primary”…> | “詳細”ラベルのボタンを表示する。 |
href=”{% url ‘detail’ item.pk %}” | ‘detail’はtododetailのurls.pyのnameで設定した値。 item.pk は対象となる詳細のプライマリキー。 |
<a class=”btn btn-success”…> | “編集”ラベルのボタンを表示する。※現時点でのリンク先は”#” |
<button class=”btn btn-secondary”…> | “削除”ラベルのボタンを表示する。JavaScript関数 setupDeleteModal を呼び出し、削除を確認するモーダルウィンドウを表示する。 |
<form id=”deleteForm{{ item.pk }}”…> | 削除操作を行うための隠しフォーム。実際の削除は、このフォームをサブミットすることで行わる。 |
{% extends 'base.html' %}
{% block header %}Todo 一覧{% endblock header %}
{% block content %}
<div class="container">
<a class="btn btn-warning mb-2" href="#" role="button">作成</a>
{% for item in object_list %}
<div class="alert alert-{{ item.priority }}" role="alert">
<p>{{ item.title }} 【期日:{{ item.duedate|date:"o/n/j" }}】</p>
<a class="btn btn-primary" href="{% url 'detail' item.pk %}" role="button">詳細</a>
<a class="btn btn-success" href="#" role="button">編集</a>
<button class="btn btn-secondary" onclick="setupDeleteModal({{ item.pk }}, '{{ item.title }}')" data-bs-toggle="modal" data-bs-target="#deleteModal">削除</button>
<form id="deleteForm{{ item.pk }}" action="#" method="POST" style="display: none;">
{% csrf_token %}
</form>
</div>
{% endfor %}
</div>
detail.htmlの編集
- 新規に作成します。
- テンプレート構造の解説
タグ | 解説 |
{% extends ‘base.html’ %} | base.htmlを継承する。 |
{% block header %}…{% endblock header %} | base.html 内の header ブロックを上書きする。ページのタイトルとして “Todo 詳細” を設定している。 |
{% block content %}…{% endblock content %} | テンプレートのメインコンテンツ部分です。base.html の content ブロックを上書きする。 |
- 動的コンテンツ表示の解説
タグ | 解説 |
{{ object.title }} | objectはビューから渡されるmodelのオブジェクト。 Todoのタイトル。 |
{{ object.description|linebreaksbr }} | Todoの説明。「|linebreaksbr」は改行を <br> タグに変換するフィルタ。 |
{% if %}…{% endif %} | 条件に基づいて異なる内容を表示する。Todoの優先度に応じて「高」「通常」「低」と表示する。 |
- ボタン・フォームの解説
タグ | 解説 |
<a class=”btn btn-info”…> | “Todo一覧”ラベルのボタンを表示する。 |
<a class=”btn btn-success”…> | “編集”ラベルのボタンを表示する。※現時点でのリンク先は”#” |
<button class=”btn btn-secondary”…> | “削除”ラベルのボタンを表示する。JavaScript関数 setupDeleteModal を呼び出し、削除を確認するモーダルウィンドウを表示する。 |
<form id=”deleteForm{{ item.pk }}”…> | 削除操作を行うための隠しフォーム。実際の削除は、このフォームをサブミットすることで行わる。 |
{% extends 'base.html' %}
{% block header %}Todo 詳細{% endblock header %}
{% block content %}
<div class="container">
<table class="table table-striped-columns">
<tr>
<th>タイトル</th>
<td>{{ object.title }}</td>
</tr>
<tr>
<th>作業内容</th>
<td>{{ object.description|linebreaksbr }}</td>
</tr>
<tr>
<th>優先度</th>
<td>
{% if object.priority == 'danger' %}
高
{% elif object.priority == 'warning' %}
通常
{% elif object.priority == 'primary' %}
低
{% else %}
{{ object.priority }}
{% endif %}
</td>
</tr>
<tr>
<th>期限</th>
<td>{{ object.duedate|date:"o/n/j" }}</td>
</tr>
</table>
<a class="btn btn-success" href="#" role="button">編集</a>
<button class="btn btn-secondary" onclick="setupDeleteModal({{ item.pk }}, '{{ item.title }}')" data-bs-toggle="modal" data-bs-target="#deleteModal">削除</button>
<form id="deleteForm{{ item.pk }}" action="#" method="POST" style="display: none;">
{% csrf_token %}
</form>
<a class="btn btn-info" href="{% url 'list' %}" role="button">Todo一覧</a>
</div>
{% endblock content %}
Todo管理のWebアプリの起動
下記のコマンドを実行してデータベースを構築します。
(venv)$ python manage.py makemigrations
(venv)$ python manage.py migrate
下記のコマンドを実行して管理ページにログインするアカウントを作成します。下記ではUsenameで「admin」を指定して作成しています。パスワードが短いと警告が出ます。
(venv)$ python manage.py createsuperuser
Username: admin
Email address:
Password:
Password (again):
The password is too similar to the username.
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
下記のコマンドを実行してWebアプリを起動します。
(venv)$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
February 03, 2024 - 07:55:32
Django version 4.2.6, using settings 'todo_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Todo管理のWebアプリの利用方法
Todoの登録
Todo管理には何も登録されていない状態です。
最初に管理ページに接続してTodoを登録してください。
管理ページのアドレスは下記となります。
「http://127.0.0.1:8000/admin/」
登録手順は、本ページの「はじめに」の動画を参照してください。
Todo管理の参照
Webブラウザで下記URLに接続してください。
「http://127.0.0.1:8000/」
「作成」「削除」「編集」の機能は含まれていません。
次回以降に実装します。
コメント