インストールとプロジェクトの初期化、アプリの初期化†
インストール†
[local] munakata:~/source/python/vlman$ pip3 install django
[local] munakata:~/source/python/vlman$ python3 -m django --version
2.2.2
PYTHONPATH の指定†
- vscode 上の lint が Django のパッケージが見つけられないというワーニングを出すことを抑止するために明示的に指定
- PYTHONPATH には pip show django を実行して location で指定されたパスを設定する
- PYTHONPATH 自体は vscode の設定ファイル (プロジェクトディレクトリ)/.vscode/settings.json 内に記載するのが良さそう
プロジェクト と アプリ の初期化†
[local] munakata:~/source/python/vlman$ django-admin startproject vlmanui
[local] munakata:~/source/python/vlman/vlmanui$ django-admin startapp file_viewer
管理用ユーザーの登録†
[local] munakata:~/source/python/vlman/vlmanui$ python3 manage.py createsuperuser
Username (leave blank to use 'munakata'):
Email address: magu775@gmail.com
Password:
Password (again):
Superuser created successfully.
timezone とロケールの設定 (settings.py)†
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
インストール完了時のディレクトリー構造†
django ビルトイン管理ツール(= manage.py)†
カスタムコマンドの追加†
- BaseCommand クラスもしくはその サブクラス の一つを継承した Command クラスを定義する必要がある
- python プログラム名がコマンド名になる(但し実行時に .py は不要)
- プログラムを配置する場所は決まっている ( アプリ/management/command/(カスタムコマンド).py )
- プログラム引数は def add_arguments(self, parser): という関数で評価する
- プログラム実体は def handle(self, *args, **kwargs): という関数の中に書く
python argument parser (argpaser)†
- Argparse チュートリアル (python 公式チュートリアル)
- argparseの引数と設定例について調べてみた
- argparseでコマンドライン引数を使ってみよう!
- ArgumentParser()の引数
ArgumentParser([description][, epilog][, prog][, usage][, add_help][, argument_default][, parents][, prefix_chars][, conflict_handler][, formatter_class])
| 引数 | デフォルト値 | 概要 |
| prog | sys.argv[0] | プログラム名 (デフォルト: sys.argv[0]) |
| usage | パーサーに追加された引数から生成される | プログラムの利用方法を記述する文字列 |
| description | None | 引数のヘルプの前に表示されるテキスト |
| epilog | None | 引数のヘルプの後で表示されるテキスト |
| parents | ??? | ArgumentParser オブジェクトのリストで、このオブジェクトの引数が追加される |
| formatter_class | ??? | ヘルプ出力をカスタマイズするためのクラス |
| prefix_chars | ‘-‘ | オプションの引数の prefix になる文字集合 (デフォルト: ‘-‘) |
| fromfile_prefix_chars | None | 追加の引数を読み込むファイルの prefix になる文字集合 |
| argument_default | None | 引数のグローバルなデフォルト値 |
| conflict_handler | ??? | 衝突するオプションを解決する方法 (通常は不要) |
| add_help | True | -h/–help オプションをパーサーに追加する |
- add_argument()の引数
add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
| 引数 | 概要 |
| name or flags | 名前か、あるいはオプション文字列のリスト (例: foo や -f, –foo) |
| action | コマンドラインにこの引数があった時のアクション |
| nargs | 消費すべきコマンドライン引数の数 |
| const | 一部の action と nargs の組み合わせで利用される定数 |
| default | コマンドラインに引数がなかった場合に生成される値 |
| type | コマンドライン引数が変換されるべき型 |
| choices | 引数として許される値のコンテナー |
| required | コマンドラインオプションが省略可能かどうか (オプション引数のみ) |
| help | 引数が何なのかを示す簡潔な説明 |
| metavar | 使用法メッセージの中で使われる引数の名前 |
| dest | parse_args() が返すオブジェクトに追加される属性名 |
- nargsに設定できる値
| 設定値 | 概要 |
| N | N 個の引数がコマンドラインから集められ、リストに格納されます |
| ? | コマンドライン引数が存在しない場合、 default の値が生成されます |
| * | 全てのコマンドライン引数がリストに集められます |
| + | 全てのコマンドライン引数がリストに集められます |
URL ディスパッチャー(パスコンバーターによるリインテリジェントな URL ルーティング機構)†
path('abc/', include('(プロジェクト名).urls', name = 'reverse serch name')
rpath(r'^abc*$', include('application name.urls'), name='reverse name')
- 正規表現を使ってパス変換する場合には rpath を使うと python の正規表現(re.)の構文が使える
- name で逆引き( URL をハードコードしないで済むように名前で参照させるために必要)
- (プロジェクトルート)/(プロジェクト名)/urls.py <----- 自動で作成される
- (プロジェクトルート)/(アプリケーションルート)/urls.py <----- 手動で作成する必要あり
- 各アプリケーション毎のルーティング(=view との対応関係)
Model (データー構造の定義)†
Django における DB の扱い方(SQL 文は一切使わなくても良い)†
- DjangoではORM(オブジェクト関係マッピング)を使ってデータベースを操作
- DjangoのORMの実装は ActiveRecord パターンを採用
- モデルを設計すればマイグレーション機能でDB定義を自動作成する。
- SQLの記述は不要。モデルマネージャ経由でクエリを実行するとレコードをモデルのインスタンスとして扱える。
- クエリは人間に読みやすく命名されたマネージャメソッドの連鎖として表現する
Django 外で作成した DB を利用するには制約がある場合も†
- DJANGO からデーターベースを操作する場合には、専用に用意された記述を用いる。ほぼ SQL 文を書くことは無い
model と DB の同期処理 (=migration)†
- モデル変更時には以下のコマンドでデータベースに更新を反映する
- python3 manage.py makemigrations (アプリケーション名)
- python3 manage.py migrate
[local] munakata:~/source/python/vlman/vlmanui$ python3 manage.py makemigrations
Migrations for 'fview':
fview/migrations/0001_initial.py
- Create model Filedir
[local] munakata:~/source/python/vlman/vlmanui$ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, fview, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying fview.0001_initial... OK
Applying sessions.0001_initial... OK
- migration のリセット
- アプリケーションディレクトリーにある migrations というフォルダーを削除
- キャッシュデータのクリア
(vman) munakata@muna-E450:~/code/python/vman/vmanhmi$ python manage.py shell
Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.core.cache import cache
>>> cache.clear()
- manage.py showmigrations で該当アプリのマイグレーション履歴が消えている事を確認
pylint の Class '(クラス名)' has no 'objects' member エラー対策†
Django キャッシュクリア (データーベース更新時など)†
- from django.core.cache import cache
- cache.clear()
munakata@muna-E450:~/code/python/vman/vmanhmi$ python manage.py shell
Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.core.cache import cache
>>> cache.clear()
View(ビジネスロジック)†
- Djangoのビューは以下の条件を満たす必要がある
- request オブジェクトを(第一引数として)受け取る
- callable であること
- response オブジェクトを返す
- view には各アプリケーション内でデーターベース内のデータの操作(CRUD=新規作成、リード、更新、削除)を行うためのロジック(=関数)を規定する
- python の関数として定義する方法
- python の Class として定義し、(クラス名).as_view() でクラスをビュー関数化する方法 <--- こちらが推奨
Template(Look & Feel の規定)†
bootstrap4 の導入†
munakata@muna-E450:~/code/python/vman/vmanhmi$ pip install django-bootstrap4
Collecting django-bootstrap4
Downloading https://files.pythonhosted.org/packages/02/5a/485d61f6dafa4e4d001a7880b04f40f04fe485a54b2756b0536ed2052342/django-bootstrap4- 0.0.8.tar.gz
Building wheels for collected packages: django-bootstrap4
Running setup.py bdist_wheel for django-bootstrap4 ... done
Stored in directory: /home/munakata/.cache/pip/wheels/f6/58/7f/fcdbcc8c631cc8f775cb8b9349ded99aff7a3f5b28e0f40ef3
Successfully built django-bootstrap4
Installing collected packages: django-bootstrap4
Successfully installed django-bootstrap4-0.0.8
- bootstrap4 ボタン [#tc3adc6f]
choice に対応した専用フォーム(ラジオボタン、ドロップダウンリストなど)†
url タグ(別画面へのリンク)= {% url '{namespace}:{name}' %}†
url タグでページ遷移する時に引数を渡す方法†
<a タグを使って別ページへのリンクを指定するときには view で規定したメソッド名を使う†
画面遷移を伴わない イベントによるデータ操作†
{% csrf_token %} = クロスサイトリクエストフォージェリ(CSRF) 対策†
- POST 処理がエラーとして誤検出されないようにする目的で追加する必要がある
画像の取扱い†
- プロジェクトルートディレクトリに media ディレクトリーを作成
- settings.py に以下の行を追加
# media file for "pillow"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
- ルート URL ルーティングファイル (urls.py) に以下の行を追加
# To support media directory path finding
from . import settings
from django.contrib.staticfiles.urls import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- model に画像を扱うための設定を追加(upload_toを’documents’にしておくことで、/media/documentsに画像が保存される)
class Document(models.Model):
description = models.CharField(max_length=255, blank=True)
photo = models.ImageField(upload_to='documents/', default='defo')
uploaded_at = models.DateTimeField(auto_now_add=True)
ImageField 型を宣言することが重要な意味を持つ
- データが画像データであるかの validity check が行われる
- 更に実際の画像データ(バイナリー)はデーターベースには登録せず、実際のファイルの場所へのリンク情報を登録する
画面分割 = boostrap4 のグリッドシステム†
- container 領域内を水平方向に 12分割 された領域に画面を分解可能 (例. 8 + 4 = 12)

<div class="container">
<div class="row">
<div class="col-lg-8">
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-6"></div>
<div class="col-xs-6"></div>
<div class="col-xs-6"></div>
</div>
</div>
<div class="col-lg-4"></div>
</div>
</div>
インストール†
(vman) munakata@muna-E450:~/code/python/vman$ pip3 install channels channels_redis asgi_redis
- routing.py をプロジェクト直下(アプリの下ではなく)に作成
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({
# (http->django views is added by default)
})
- setting.py の installed apps に channels を追加
INSTALLED_APPS = [
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'fview',
'bootstrap4',
'fontawesome_5',
]
- channels 2.0 では (参考にした記事で使っている) delay server は無くなり、以下の機能を利用するようになったようだ リンク
Delay server
If you used the delay server before to put things on hold for a few seconds, you can now instead use an AsyncConsumer and asyncio.sleep:
class PingConsumer(AsyncConsumer):
async def websocket_receive(self, message):
await asyncio.sleep(1)
await self.send({
"type": "websocket.send",
"text": "pong",
})
- runserver 時にASGI が有効になっている事を確認する
System check identified no issues (0 silenced).
July 20, 2019 - 15:43:38
Django version 2.2.2, using settings 'vmanhmi.settings'
Starting ASGI/Channels version 2.2.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
- が、LISTREN ポートのプロセスが死なない。 kill -s QUIT で強制停止できた
munakata@muna-E450:~/code/python/vman/vmanhmi$ kill -s QUIT 4055
munakata@muna-E450:~/code/python/vman/vmanhmi$ lsof -i:8000
- WebSocketはフレーム探知で送受信しますが、相手が決まっているために送信先の情報などはもちません(一対一の通信)
- WebSocketが他のHTTPベースのプロトコル異なるのは「ステートフルな通信である」という点
- 通信はサーバーが受信を受けている状態で、必ずクライアントから接続 (クライアント起点の通信)
python プログラムから Websocket を利用する方法†
- Websocket は通常は Java Script の中で送受信するが、今回は素の python プログラム(実際には Django Managment.py 配下のプログラム)として動作させるワーカープログラムの中から websocket で Django Channel の ASGI Consumer と通信させたい。
- python で websocket を使うためのライブラリーは2種類 (websocket-client, websockets) ある
- And here’s an echo server:
#!/usr/bin/env python
import asyncio
import websockets
async def echo(websocket, path):
async for message in websocket:
await websocket.send(message)
asyncio.get_event_loop().run_until_complete(
websockets.serve(echo, 'localhost', 8765))
asyncio.get_event_loop().run_forever()
Channels (ワーカーと consumer 間の通信のは channel を使う、websocket は使えない)†
- backscan_worker.py (← manage.py のカスタムコマンド)を worker として動かし consumer と通信させる。
- そのままでは backscan_worker は Radis バックエンドと繋がっていない のでエラーになる
munakata@muna-E450:~/code/python/vman/vmanhmi$ python3 manage.py runworker prog-sync
[16/Aug/2019 16:05:22] INFO [django.channels.worker:40] Running worker for channels ['prog-sync']
(snip)
File "/home/munakata/code/python/vman/lib/python3.6/site-packages/channels/routing.py", line 61, in __call__
"No application configured for scope type %r" % scope["type"]
ValueError: No application configured for scope type 'channel'
- そんな事はない。1つの websocket 接続で複数の通信をハンドルすることができる
- How to use multiple websocket connections using Django Channels?
from django.conf.urls import url
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
application = ProtocolTypeRouter({
# WebSocket chat handler
"websocket": AuthMiddlewareStack(
URLRouter([
url(r"^first_application/stream/$", app_2_consumers.AsyncApp1Consumer),
url(r"^second_application/stream/$", app_2_consumers.AsyncApp2Consumer),
])
),
})
channels group_send を使った同期(イベント一斉送信)†
- backscan_worker (Django custom command, standalone python program) と channels を使ってメッセージ送受信をする場合には、worker プログラム側も Redis に接続する必要がある。
- manage.py runworker backscan_worker prog-sync (=グループ名)で常駐化ができるが、この場合 channel からのメッセージを受け取って処理をする形になるので、コマンドラインからの単独実行と両立できない(まぁプログラムを分割すれば良い話ではあるが)
- 結局 ブラウザー(=JavaScript) ⇔ Consumer 間とは別に ワーカー ⇔ Consumer 間に別の WebSocket による接続を行う
- WebSocket にはアドレス情報を付加できない(組み込まれている)ので、ワーカー ⇒ ブラウザー に直接メッセージを送ることは出来ない
- Consumer の中で別々のハンドラーを用意し、その間を group_send メッセージでイベントをブロードキャストする方法を採用した
- group_send は非同期処理になるので async_to_sync で命令を wrapping する必要がある
- それぞれのハンドラーに対して、明示的にグループへの登録、削除を行う必要がある
- グループからの離脱
def disconnect(self, close_code):
print("Web socket [browser] disconnect code = [" + str(close_code) + "]")
async_to_sync(self.channel_layer.group_discard)("fview_sync", self.channel_name)
self.close()
- group_send 時には グループ名、type、メッセージ本体を指定する必要がある
- type は アプリ名.タイプ名 の形を指定する
- アプリ名_タイプ名 の名前の ハンドラーを別に用意する (. を _ に置き換える)
参考 URL†
通常はcheck_allやcheck_output(およびrun)などを使えばよいのですが、子プロセスの終了をまたずに何か処理をしたいとかそういうことをしたい場合にはPopenを使います。
# -- test.py --
from time import sleep
print('child start!')
sleep(1)
print('child end!')
# -- main.py --
import subprocess
print('parent start!')
ps = subprocess.Popen(['python','test.py'])
print('parent end!')
$ py main.py
parent start!
parent end!
child start!
child end!
- Celery は非同期処理の登録の方法として使えるが(理解が正しければ)処理結果が出た時点で結果を返すことはできる
- が処理の途中経過を通知する(=同期する)手段は提供されないと理解。 なので、この記事では別プロセスへの登録の部分だけが参考になると想像

- Task = 非同期処理をひとかたまりにまとめたもの。ブラウザからのリクエストによって必要となった実行処理する関数やその引数などが記録される
- Queue = タスクを格納する入れ物です。構造が先入れ先出しになっているものが Queue、後入れ先出しが FIFO
- Producer = タスクを作成してブローカーに渡す役割を持ったもの。『Celery Client』を利用
- Broker = タスクをキューに登録したり、登録されたタスクをワーカーに渡したりするもの。『Redis』を利用
- Woker = ブローカーによってキューから取り出されたタスクを実際に処理。『Celery Woker』を利用
wscat (websocket テストツール)†
python プログラム内で別の python プログラムを起動する方法†
- 実現方法
- os.fork
import os
if os.fork()==0:
long_process()
else:
return HttpResponse()
- subprocess
import subprocess
p = subprocess.Popen([sys.executable, '/path/to/script.py'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
(where the script is likely to be a manage.py command)
- threads
import threading
t = threading.Thread(target=long_process,
args=args,
kwargs=kwargs)
t.setDaemon(True)
t.start()
return HttpResponse()
- Due to the Global Interpreter Lock, in CPython only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better of use of the computational resources of multi-core machines, you are advised to use multiprocessing. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.
- multiprocessing
from multiprocessing import Process
p = Process(target=_bulk_action,args=(action,objs))
p.start()
return HttpResponse()
- python interpreter には thread scheduler が無い。 故に multi-thread にしても並列化されない。
- これを GIL (Global Interpreter Lock) 問題とよぶ
- python で並列化を実現するにはプロセス単位で実行を分ける(= thread scheduler を二重化する)のが良い
vscode を使ったデバッグ†
- vscode 全般の説明は VsCode にまとめている
- vscode を使ってdjango のデバッグができる。
- vscode のデバッガー設定で python django を選択する
- デバッガーの RUN コマンド(緑色の矢印ボタン)でサーバーを起動する (別のターミナルで起動しない)
- ブラウザーでアプリを起動してブレークポイントにヒットすると vscode 画面に切り替わり、変数の内容などが確認できる
参考 URL†