Django
オンラインドキュメント
https://pypi.org/project/Django/
https://docs.djangoproject.com/en/
https://docs.djangoproject.com/en/4.0/ref/csrf/
インストール
pip install django |
1 |
使い方
関数型ビューを使用したWebアプリケーションを作成する、非同期関数型ビューを使用したWebアプリケーションを作成する、管理者画面を使用できるようにする、DaphneとApacheを使用してデプロイする、データベースのバックアップとリストアをする、について説明する。
また、Djangoの備える開発用httpサーバについて、他PCからのアクセスを可能とする起動方法、非同期通信を可能とする起動方法、についても説明する。
なお、Celeryを併用した非同期処理については、Celeryのページで説明する。
Djangoプロジェクトの作成
DjangoでWebアプリケーションを作成するためには、始めにWebアプリケーションを収納するプロジェクトを作成する必要がある。
ディレクトリ H:\py39 でコマンドプロンプトで django-admin startproject django_projects を実行し、プロジェクト django_projects を作成する。
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py runserver を実行して開発用httpサーバを開始する。
http://127.0.0.1:8000/ へアクセスしてページが表示されることを確認する。
他PCからアクセス可能な開発用httpサーバの起動方法
プロジェクト作成直後のままでは、開発用httpサーバはローカルPCからのみアクセスを受け付ける。以下を実施することにより、他PCからのアクセスも可能となる。
H:\py39\django_projects\django_projects\setting.py 内の記述 ALLOWED_HOSTS = [] を ALLOWED_HOSTS = ['*'] へ変更する。
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py runserver 192.168.0.8:8080 を実行して開発用httpサーバを開始する。
http://192.168.0.8:8080/ へアクセスしてページが表示されることを確認する。
関数型ビューを使用したWebアプリケーションの作成
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py startapp hello11 を実行する。
H:\py39\django_projects\django_projects\setting.py 内の記述 INSTALLED_APPS = [] に 'hello11.apps.Hello11Config', を加える。
# Application definition |
INSTALLED_APPS = [ |
'hello11.apps.Hello11Config', |
'django.contrib.admin', |
'django.contrib.auth', |
'django.contrib.contenttypes', |
'django.contrib.sessions', |
'django.contrib.messages', |
'django.contrib.staticfiles', |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
H:\py39\django_projects\hello11\views.py の内容を以下のように変更する。
from django.shortcuts import render |
# Create your views here. |
def index(request): |
text1 = '<div class="result">こんにちは、Django 関数型ビュー</div>' |
context = { 'title':'Django 関数型ビュー' , 'render_text': text1, } |
return render(request, 'hello11_index.html', context) |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
ディレクトリ H:\py39\django_projects\hello11\templates に以下の内容で hello11_layout.html を作成する。
<!DOCTYPE html> |
<html lang="ja"> |
<head> |
<meta charset="UTF-8"> |
<title>{{title}}</title> |
<style> |
body { background-color: black; color: white; margin: 30px; } |
h1 { color: gold; border-bottom: double 3px; font-weight: normal; } |
h2 { color: navajowhite; border-bottom: solid 2px; border-left: solid 20px; padding: 3px; font-weight: normal; } |
h3 { border-bottom: solid 1px white; padding: 3px; font-weight: normal; } |
h4 { border-top: solid 1px gold; padding: 3px; margin-top: 30px; font-weight: normal; } |
h4 p { padding: 0px; margin: 0px; } |
.left { text-align: left; } |
.right { text-align: right; } |
.result { margin-top: 10px; margin-bottom: 10px; margin-left: 60px; margin-right: 60px; } |
</style> |
</head> |
<body> |
{% block body %}{% endblock %} |
</body> |
</html> |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
ディレクトリ H:\py39\django_projects\hello11\templates に以下の内容で hello11_index.html を作成する。
{% extends "hello11_layout.html" %} |
{% block body %} |
<h1>{{title}}</h1> |
{{render_text|safe}} |
<footer><h4><p class="right">copyright 2022 eagle eight</p></h4></footer> |
{% endblock %} |
1 |
2 |
3 |
4 |
5 |
6 |
ディレクトリ H:\py39\django_projects\hello11 に以下の内容で urls.py を作成する。
from django.urls import path |
from . import views |
urlpatterns = [ |
path('', views.index, name='hello11_index'), |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
ディレクトリ H:\py39\django_projects\django_projects に以下の内容で urls.py を作成する。
from django.contrib import admin |
from django.urls import include, path |
urlpatterns = [ |
path('hello11/', include('hello11.urls')), |
path('admin/', admin.site.urls), |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
http://127.0.0.1:8000/hello11/ へアクセスしてページが表示されることを確認する。
非同期通信を行うためのパッケージの導入と設定
pip でパッケージ channels をインストールする。
pip install channels |
1 |
H:\py39\django_projects\django_projects\asgi.py 内の記述を以下のように変更する。
#application = get_asgi_application() |
django_asgi_app = get_asgi_application() |
from channels.routing import ProtocolTypeRouter |
application = ProtocolTypeRouter( |
{ 'http': django_asgi_app, } |
) |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
H:\py39\django_projects\django_projects\setting.py 内の記述 INSTALLED_APPS = [] に 'channels', を加える。
# Application definition |
INSTALLED_APPS = [ |
'channels', |
'hello11.apps.Hello11Config', |
'django.contrib.admin', |
'django.contrib.auth', |
'django.contrib.contenttypes', |
'django.contrib.sessions', |
'django.contrib.messages', |
'django.contrib.staticfiles', |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
H:\py39\django_projects\django_projects\settings.py の最後に以下を記述する。
# Channels |
ASGI_APPLICATION = 'django_projects.asgi.application' |
1 |
2 |
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py runserver を実行して開発用ASGI-httpサーバを開始する。
表示さるメッセージに Starting ASGI/Channels version 3.0.4 development server at ... とあることを確認する。
非同期関数型ビューを使用したWebアプリケーションの作成
バックグラウンドで100個の処理を実行(非同期実行)しつつ、Web画面に進捗状況を表示(画面更新)するアプリケーションを作成する。
画面更新コマンドの配信URLを /get_command/uuid文字列/ として各クライアントに別々の配信URLを与えることにより、複数のクライアントからの同時アクセスを可能とさせている。
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py startapp hello16 を実行する。
H:\py39\django_projects\django_projects\setting.py 内の記述 INSTALLED_APPS = [] に 'hello16.apps.Hello16Config', を加える。
# Application definition |
INSTALLED_APPS = [ |
'channels', |
'hello11.apps.Hello11Config', |
'hello16.apps.Hello16Config', |
'django.contrib.admin', |
'django.contrib.auth', |
'django.contrib.contenttypes', |
'django.contrib.sessions', |
'django.contrib.messages', |
'django.contrib.staticfiles', |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
H:\py39\django_projects\hello16\views.py の内容を以下のように変更する。
from django.shortcuts import render |
# Create your views here. |
from django.http.response import JsonResponse |
import asyncio |
import json |
import uuid |
class Command(): |
def __init__(self) : |
self.list = dict() |
def add(self, task_id) : |
self.list[task_id] = {"commands":[]} |
def remove(self, task_id) : |
del self.list[task_id] |
def set_command(self, task_id, command_dict_list) : |
self.list[task_id]["commands"] = command_dict_list |
def get_command(self, task_id) : |
return self.list[task_id] |
cmd = Command() |
async def do_task1(task_id): |
cmd.add(task_id) |
# == タスクの前処理 == ここから == |
# == タスクの前処理 == ここまで == |
img_no_list = [] |
loop_num = 100 # ここに処理件数を設定する。 |
for x in range(loop_num) : |
img_no_list.append( int((100/loop_num)*(x+1)) ) |
img_no_list[-1] = 100 |
for x in img_no_list : |
# == タスクの処理 == ここから == |
y = 1 + 2 |
# == タスクの処理 == ここまで == |
await asyncio.sleep(0.5) |
cmd.set_command(task_id, [{"COMMAND":"IMG_UPDATE","IMG_NUMBER":str(x)}]) |
# == タスクの後処理 == ここから == |
# == タスクの後処理 == ここまで == |
await asyncio.sleep(3) |
cmd.set_command(task_id, [{"COMMAND":"IMG_DELETE"},{"COMMAND":"H1_UPDATE","TEXT":"処理が完了しました"},{"COMMAND":"DIV_INSERT","TEXT":"こんにちは、Django 非同期関数型ビュー"},{"COMMAND":"END"}]) |
await asyncio.sleep(1) |
cmd.remove(task_id) |
async def index(request): |
task_id = str(uuid.uuid4()) |
task1 = asyncio.create_task(do_task1(task_id)) |
context = { 'title':'Django 非同期関数型ビュー', 'data_json':json.dumps({ 'task_id':task_id, }), } |
return render(request, 'hello16_index.html', context) |
async def get_command(request, task_id): |
return JsonResponse(cmd.get_command(task_id)) |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
ディレクトリ H:\py39\django_projects\hello16\templates に以下の内容で hello16_layout.html を作成する。
<!DOCTYPE html> |
<html lang="ja"> |
<head> |
<meta charset="UTF-8"> |
<title>{{title}}</title> |
<style> |
body { background-color: black; color: white; margin: 30px; } |
h1 { color: gold; border-bottom: double 3px; font-weight: normal; } |
h2 { color: navajowhite; border-bottom: solid 2px; border-left: solid 20px; padding: 3px; font-weight: normal; } |
h3 { border-bottom: solid 1px white; padding: 3px; font-weight: normal; } |
h4 { border-top: solid 1px gold; padding: 3px; margin-top: 30px; font-weight: normal; } |
h4 p { padding: 0px; margin: 0px; } |
.left { text-align: left; } |
.center { text-align: center; } |
.right { text-align: right; } |
.result { margin-top: 10px; margin-bottom: 10px; margin-left: 60px; margin-right: 60px; } |
</style> |
</head> |
<body> |
{% block body %}{% endblock %} |
</body> |
</html> |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
ディレクトリ H:\py39\django_projects\hello16\templates に以下の内容で hello16_index.html を作成する。
{% extends "hello16_layout.html" %} |
{% block body %} |
<h1 id="h1">しばらくお待ち下さい</h1> |
<div class="center" id="circle_progress"> |
<img id="circle_img" src="https://eagle-eight.sakura.ne.jp/_image/circle_progress_gold-olive_0.png"> |
</div> |
<script> |
let aTaskId = JSON.parse('{{data_json|safe}}').task_id; |
window.addEventListener('DOMContentLoaded', e => { |
new Promise((resolve,reject) => { setTimeout(() => { ; |
let aH1 = document.getElementById("h1"); |
let aDivCircleProgress = document.getElementById("circle_progress"); |
let aDivParent = aDivCircleProgress.parentElement; |
let aImg = document.getElementById("circle_img"); |
let previousJsonResult = { "commands":[] }; |
let thisJsonResult = { "commands":[] }; |
let aTimerId = setInterval( () => { |
let aUrl = "get_command/"+aTaskId+"/" |
fetch(aUrl) .then(response => { return response.json() } ) .then(result => { |
thisJsonResult = result; |
if(JSON.stringify(thisJsonResult)!=JSON.stringify(previousJsonResult)){ |
command_list = thisJsonResult.commands; |
for (i in command_list) { |
let aCOMMAND = command_list[i].COMMAND; |
if (aCOMMAND=="IMG_UPDATE"){ let aUpdateImgSrc = "https://eagle-eight.sakura.ne.jp/_image/circle_progress_gold-olive_"+command_list[i].IMG_NUMBER+".png"; aImg.src = aUpdateImgSrc; } |
else if(aCOMMAND=="IMG_DELETE"){ aDivParent.removeChild(aDivCircleProgress); } |
else if(aCOMMAND=="H1_UPDATE") { aH1.textContent = command_list[i].TEXT; } |
else if(aCOMMAND=="DIV_INSERT"){ let aDiv = document.createElement("div"); aDiv.setAttribute("class", "result"); aDiv.textContent = command_list[i].TEXT; aH1.after(aDiv); } |
else if(aCOMMAND=="END") { clearInterval(aTimerId); } |
} |
}; |
previousJsonResult = thisJsonResult; |
}) |
}, 100); |
resolve(); }, 100); }) |
}); |
</script> |
<footer><h4><p class="right">copyright 2022 eagle eight</p></h4></footer> |
{% endblock %} |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
ディレクトリ H:\py39\django_projects\hello16 に以下の内容で urls.py を作成する。
from django.urls import path |
from . import views |
urlpatterns = [ |
path('', views.index, name='hello16_index'), |
path('get_command/<str:task_id>/', views.get_command, name='hello16_get_command'), |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
ディレクトリ H:\py39\django_projects\django_projects に以下の内容で urls.py を作成する。
from django.contrib import admin |
from django.urls import include, path |
urlpatterns = [ |
path('hello11/', include('hello11.urls')), |
path('hello16/', include('hello16.urls')), |
path('admin/', admin.site.urls), |
] |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
http://127.0.0.1:8000/hello16/ へアクセスしてページが表示されることを確認する。
管理画面へのアクセス
管理画面へアクセスするには、管理者ユーザの作成が必要であり、準備としてデータベースの設定が必要である。
ここでは、データベースはデフォルトの sqlite3 を使用することとする。
H:\py39\django_projects\django_projects\settings.py の TIME_ZONE を変更する。
TIME_ZONE = 'Asia/Tokyo' |
1 |
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py migrate を実行してデータベースにテーブルを作成する。
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py createsuperuser を実行して管理者ユーザを作成する。
http://127.0.0.1:8000/admin/ へアクセスしてページが表示されることを確認し、作成した管理者ユーザでログインして管理画面が表示できることを確認する。
DaphneとApacheを使用したデプロイ
まず、デフィルトでエラーが発生したときデバック情報を表示するように設定されている状態を、表示しないように設定する。
H:\py39\django_projects\django_projects\settings.py 内の DEBUG を False に設定する。
次に、静的ファイルの配信について設定する。
H:\py39\django_projects\django_projects\settings.py に以下を追加し、管理画面の静的ファイルを集める場所を指定する。
import os |
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root') |
1 |
2 |
ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py collectstatic を実行し、管理画面の静的ファイルを H:\py39\django_projects\static_root へ集める。
集めたファイルを静的ファイル配信のApacheの /static/ に対応するディレクトリへコピーする。
H:\\py39\\django_projects\\django_projects\\settings.py 内の STATIC_URL の値を 静的ファイル配信のApacheの/static/のURL に設定する。例えば、STATIC_URL = 'http://192.168.0.9/static/' と設定する。
次に、Djangoの本番運用のASGI-httpサーバ Daphne のインストールと開始を行う。
pip でパッケージ Daphne をインストールする。
pip install daphne |
1 |
ディレクトリ H:\py39\django_projects でコマンドプロンプトで daphne -b 192.168.0.8 -p 8080 django_projects.asgi:application を実行してDaphneのASGI-httpサーバを開始する。
http://192.168.0.8:8080/hello11/ へアクセスしてページが表示されることを確認する。
http://192.168.0.8:8080/admin/ へアクセスしてCSS(静的ファイル)の適用されたページが表示されることを確認する。
存在しないページ、例えば http://192.168.0.8:8080/hello/ へアクセスしてデバック情報が表示されないことを確認する。
※ 著者:注 2022/02/10
Django==4.0.2、channels==3.0.4、daphne==3.0.2 の組み合わせで動作確認を行っているが、表示されるデッバグ情報から明らかに異常なディレクトリを探索しており、テンプレートファイルの探索にバグがあると思われ、H:\py39\django_projects\django_projects\settings.py 内の TEMPLATES をデフォルトのままで使用すると TemplateDoesNotExist のエラーが出ることがある。TEMPLATES 内の 'DIRS' の値を [ BASE_DIR / 'apps_templates' ], とし、ディレクトリ apps_templates にテンプレートファイルをまとめて置いておくと正しく動作できる。
※ 著者:注 2022/04/13
Django==4.0.4のリリースノートにこの問題を修正したと思われる記述があったため、Django==4.0.4、channels==3.0.4、daphne==3.0.2 の組み合わせで動作確認を行った。結果、settings.py 内の TEMPLATES をデフォルトのままで使用した場合、テンプレートファイルの探索は正常に動作することが確認できた。
データベースのバックアップとリストア
Windows10 v21H2 上で稼働させているデータベースを使用するアプリケーション wikiApp を例に説明する。
アプリケーション wikiApp のデータベースのバックアップファイルの作成は以下の手順で行う。
まず、ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py dumpdata -o wikiApp_dump_20220322.json wikiApp を実行する。
次に、ファイル wikiApp_dump_20220322.json をWindows付属の メモ帳 で開き、メニュー 名前を付けて保存 を選択し、文字コードで UTF-8 を選択してファイルを上書き保存する。
アプリケーション wikiApp のデータベースのリストアは以下の手順で行う。
まず、Djangoの管理画面へアクセスし、アプリケーション wikiApp の各テーブルについて全てのデータを削除する。
次に、ディレクトリ H:\py39\django_projects でコマンドプロンプトで python manage.py loaddata wikiApp_dump_20220322.json を実行する。
※ 著者:注 2022/03/22
Windows10 v21H2、Django==4.0.3 で動作確認を行っている。loaddataは文字コードとしてUTF-8を受け付ける。対して、dumpdataは文字コードShift-JISのJSONファイルを作成する。このため、文字コードをUTF-8へ変換を行う必要がある。
なお、Fedora35、Django==4.0.3 の環境では、dumpdataは文字コードUTF-8のJSONファイルを作成する。