【Django3.1】Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整える。
なお本記事のソースコードはgithub上に公開しています。 djangoでbootstrapの導入方法いくつかある。 今回は1のやり方で導入を行う。 staticディレクトリをプロジェクトディレクトリ直下に以下の用に作成。 staticファイルの参照先を設定。 confug/settings.py staticファイルの扱い方の詳細は以下を参照。
- 公式document
Managing static files (e.g. images, JavaScript, CSS) | Django documentation | Django Bootstrap5のサイトからbootstrapモジュールをダウンロードする。 ダウンロードしたファイルのうち以下のモジュールを以下のように配置する。
どのテンプレートにも共通で書かなければ行けない場合、Djangoにはベーステンプレートを作成することで、共通かでる。
templates/base.html bodyタグに注目すると、 以下のようなイメージ。 やって見たほうが早いのでlist.htmlを以下の用に修正する。 templates/list.html この状態でrunserverして
http://127.0.0.1:8000/samplecrud/list/
にアクセスすると、以下の用に変化したことがわかる。 list.htmlと同じ用に他のテンプレートも編集しておく。 templates/create.html templates/delete.html templates/update.html ※ここからはBootstrapの使い方になるのでDjangoとは少し遠い世界になる。 見た目といえばNavbar。 templates/base.html listとcreateページをリンクをNavbarに追加した。 レスポンシブデザインになっている。 余談だが、bootstrap4→5で.float-left、.float-rightは.float-startと.float-endに変更になっていた。 templates/list.html だいぶwebサイトっぽくなってきましたね。 templates/list.html だいぶwebサイトっぽくなってきましたね。(2回目) 以上、Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整えるでした。 まず、bootstrapのformを導入する前の templates/create.html ほぼ 今回のやり方でbootstrapを適応する場合はこのformを展開してやる必要がある。 方法としてはformクラスを個別に展開します。 上記を参照して個別展開すると以下のように表現できる。 inputタグや、textareaタグは ここまでくればbootstrapの世界に持ってこれるので以下の用にする。 この状態で
http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下の用に表示される。 これまたちょっとwebサイトっぽくなりましたね。 同じ用にupdateも更新する。
templates/update.html updateベージは以下の用に表示される。 削除ページは戻るButtonを追加 daleteベージは以下の用に表示される。 以上、Djangoで作成したアプリケーションにBootstrap5-beta1を導入し、見た目を整える。でした。 GitHub - n-guitar/django-samples at feature/django-admin/sample.generic-view 以下記事で作成したものを利用します。
n-guitar.hatenablog.com ※必要に応じて上記のアプリケーションをcloneしておく Djangoで予め用意されている、管理インターフェースで簡単にユーザ管理を行える管理サイトを利用できる。 実はdefaultで有効化されており、利用をするために特別は努力はいらない。 confug/settings.py confug/urls.py 実はdefaultでurlの設定もされている。 http://127.0.0.1:8000/admin
にアクセスして以下が表示されればOK
この次にこの管理サイトを利用する管理ユーザの作成を行う。 まずdefaultで用意されている管理用テーブルのマイグレーションを行う。 当然本番環境で利用する際はpassword管理は注意する必要があるが、開発時は適応で問題有りません。 この状態で、run serverして http://127.0.0.1:8000/admin
にアクセスして作成した管理ユーザでログインすると以下の管理サイトを利用できる用になる。
アプリケーションで作成した任意のモデル(テーブル)をこの管理サイトに登録し、簡易的なテーブルの操作が行えるようになる。 samplecrud/admin.py この状態で再び、
http://127.0.0.1:8000/admin
にアクセスすると、テーブルを操作出来る。 Todosが表示されており、レコード追加できる。 この状態で、
http://127.0.0.1:8000/samplecrud/list/
にアクセスして作成したListViewで見てみると追加したレコードが表示されていることがわかる。 余談ではあるが、DjangoでModelを作成するとdefaultで複数形になる。
正しい複数形ではなくsが不自然につく場合のほうが多いが、ModelのMeta Optionを使えば変更出来る。 以上、DjangoAdminの使い方でした。 ※なお通常Djangoでアプリケーションを作成する際は、generic viewを使い、機能を拡張したい時はfunctionをオーバーライドすることが自分は多い。 なお本記事のソースコードはgithub上に公開しています。 confug/settings.py 動作確認 http://127.0.0.1:8000/
にアクセスして以下が表示されればOK
現時点で以下のようなディレクトリ構成になっている。 今回は以下のようなDBを作成する samplecrud/models.py DBの反映 早速Read画面を作成するがその前に、URLのつなぎこみを行う。
※直接configからURLを指定してもいいが、通常アプリごとに作成する多い。 現時点のディレクトリ構造は以下の通りになっている。
URLのつなぎこみ。 上記のURLは『http://127.0.0.1:8000/samplecrud/』でアクセスした時、『samplecrud/urls.py』記載されているURLにルーティングするという意味。 samplecrud/urls.pyを編集する いよいよ画面を作成していく。 samplecrud/views.py レンダリング先のhtmlがないので作成する。 Todo tableから取得した値がobject_listというオブジェクトに格納され、list.htmlに渡される この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
にアクセスする 以下のように表示されればOK。 続けてDBのレコードを追加する画面を作成する。 samplecrud/urls.py こちらは先程のlistと同じ samplecrud/views.py createのときはどのGETとPOSTメソッドを処理する必要がある。 ※通常databaseを更新する時はバリデーションをすべきであるが本記事ではわかりにくくなるので行わないでおく。 templates/create.html inputタグにname="※POSTで受け取りたい変数名"を入れることでviews.pyでその変数名で受け取ることができる。 この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。 続けてDBのレコードを更新する画面を作成する。 samplecrud/urls.py こちらは先程と若干異なり、URL Pathに/<int:pk>が記載されている。 samplecrud/views.py updateではどのレコードを表示、更新するか特定する必要がある。 templates/update.html create.htmlと似ているが、{{ object.title }}と{{ object.memo }}でviews.pyで取得したレコードを表示している。 この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/update/1
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。 templates/list.html aタグを使い、リンクを作成する。 この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
を表示すると以下の用になり、クリックするとupdate画面に移動できる。 最後にDBのレコードを削除する画面を作成する。 samplecrud/urls.py updateと同じく、どのレコードかを特定する/<int:pk>を指定する。 samplecrud/views.py deleteの場合は一度削除してよいかもう一度確認する画面を表示する。 templates/delete.html listからdeleteへのリンクもupdate同様作成しておく この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
を表示すると以下の用になり、削除をクリックするとレコードが削除される。 早速wikiの参照になるが、 /dev/randomは真の乱数を発生させるためにデバイス入力とかのノイズを利用するけど、
エントロピープールにノイズが内場合、ブロックする仕様なんですね。なのでノイズ収集するまで待たされる。 真の乱数を発生させるためには、"機械的"ではだめで、外的要因が必要とのこと。 実際の世界では、 ・mac上からterminalでssh → CentOS7(VMFusion)
mac上のssh越しだと、新しく乱数を全然生成しない。 ・CentOS7(VMFusion)のConsoleでマウスを動かすと、新たに乱数を生成する。 つまり我々が普段利用しているデータセンターにあるVMに対しては基本リモートで接続するため GitHub - n-guitar/django-samples at feature/first/sample.generic-view confug/settings.py 動作確認 http://127.0.0.1:8000/
にアクセスして以下が表示されればOK
現時点で以下のようなディレクトリ構成になっている。 今回は以下のようなDBを作成する samplecrud/models.py DBの反映 早速Read画面を作成するがその前に、URLのつなぎこみを行う。
※直接configからURLを指定してもいいが、通常アプリごとに作成する多い。 現時点のディレクトリ構造は以下の通りになっている。
URLのつなぎこみ。 上記のURLは『http://127.0.0.1:8000/samplecrud/』でアクセスした時、『samplecrud/urls.py』記載されているURLにルーティングするという意味。 samplecrud/urls.pyを編集する いよいよ画面を作成していくが
CRUDを実現するのにdjango.viewsのgeneric viewと呼ばれるもの利用する。
マニュアルはあるが、ソースコードを見たほうが早い場合があるので必要に応じて参照する。 generic.ListViewを継承したクラスを作り、どのDBのTableを利用して、どのhtmlファイルにレンダリングするかを以下のように書くだけ。 レンダリング先のhtmlがないので作成する。 Todo tableから取得した値がobject_listというオブジェクトに格納され、list.htmlに渡される この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
にアクセスする 以下のように表示されればOK。 続けてDBのレコードを追加する画面を作成する。 samplecrud/urls.py こちらは先程のlistと同じ samplecrud/views.py createのときはどのFieldを更新するか『fields』で指定する。 templates/create.html Djangoは『 {% csrf_token %}』をつけることでデフォルトでCSRFの検証を行ってくれる。(逆につけないとエラーになる) タグで展開してくれる。(余談だがCreateViewがForm classを継承しているため利用できる) この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。 続けてDBのレコードを更新する画面を作成する。 samplecrud/urls.py こちらは先程と若干異なり、URL Pathに/<int:pk>が記載されている。 samplecrud/views.py createのとき同じでFieldを更新するか『fields』で指定する。 templates/update.html create.htmlと全く同じで問題ない。(valueの値だけ更新するに変更した。) この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/update/1
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。 templates/list.html aタグを使い、リンクを作成する。 この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
を表示すると以下の用になり、クリックするとupdate画面に移動できる。 最後にDBのレコードを削除する画面を作成する。 samplecrud/urls.py updateと同じく、どのレコードかを特定する/<int:pk>を指定する。 samplecrud/views.py deleteの場合は一度削除してよいかもう一度確認する画面を表示する。
※余談だが、DeleteViewは今回登場させていない1レコードの詳細表示されるDetailViewを継承しているので、DetailViewを兼ねることもできる。 templates/delete.html listからdeleteへのリンクもupdate同様作成しておく この状態でもう一度runserverし http://127.0.0.1:8000/samplecrud/list/
を表示すると以下の用になり、削除をクリックするとレコードが削除される。 以下エラーの解消方法
* 『xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun』
上記で完了。
再度git コマンドを行うと問題なく動作する。 初期状態は以下の状態です。 htmlファイル 少し大きくなりました。 カーソルが点滅するようになりました。
結構自分好みにカスタマイズできそうですね。本記事で行うこと
n-guitar.hatenablog.com
GitHub - n-guitar/django-samples at feature/bootstrap5/sample.generic-viewモチベーション
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
$ python -V
Python 3.8.0
Bootstrap5導入
参考:https://github.com/zostera/django-bootstrap4
※当然Bootstrap5は2021/1/11時点でまだbataなのでdjango-bootstrap5は存在しない。staticディレクトリの作成と設定
STATIC_URL = '/static/'
STATICFILES_DIRS = (
BASE_DIR / 'static',
)
Bootstrap5のダウンロードとstaticファイルの配置
Download · Bootstrap v5.0
baseテンプレートの作成
templateディレクトリ配下に以下の様にbase.htmlを作成し、先程格納したbootstrapモジュールのリンクを記述する。{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<title>sample app</title>
</head>
<header>
<h1>base.htmlのheader</h1>
</header>
<body>
{% block Content %}
<h1>base.htmlのbody</h1>
{% endblock Content %}
</body>
<footer>
<h1>base.htmlのfooter</h1>
</footer>
</html>
{% block Content %} {% endblock Content %}
と記載している。Content
というのは任意の名前で構わない。
このblockで挟まれた部分を他のテンプレートで上書きできる。
blockは任意の複数書くことも出来る。
{% extends 'base.html' %}
{% block Content %}
<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
<a href="{% url 'delete' item.pk %}">削除する</a>
</li>
{% endfor %}
</ul>
{% endblock Content %}
{% extends 'base.html' %}
でbase.htmlを継承し、上書きしたい部分を{% block Content %} {% endblock Content %}
で囲う。
↓
{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
{{ form.as_p}}
<input type="submit" value="作成する">
</form>
{% endblock Content %}
{% extends 'base.html' %}
{% block Content %}
<h1>Delete Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
{{ object.title }}{{ object.memo }}
を本当に削除しますか?
</p>
<input type="submit" value="削除する">
</form>
{% endblock Content %}
{% extends 'base.html' %}
{% block Content %}
<h1>Update Page</h1>
<form action="" method="POST"> {% csrf_token %}
{{ form.as_p}}
<input type="submit" value="更新する">
</form>
{% endblock Content %}
Bootstrapで見た目を整える。
Navbarの導入
以下のsampleに習って作成する。
Navbar · Bootstrap v5.0{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}">
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<title>sample app</title>
</head>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Todo Sample App</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link " aria-current="page" href="{% url 'list' %}">List</a>
<a class="nav-link" href="{% url 'create' %}">Create</a>
</div>
</div>
</div>
</nav>
</header>
<body>
{% block Content %}
{% endblock Content %}
</body>
<footer>
<div class="fixed-bottom">
<p class="float-end m-2">create by haku-mai</p>
</div>
</footer>
</html>
以下の用になる。
jumbotronとButtonの導入 (bootstrap5からjumbotronが削除されたので他のユーティリティを利用)
{% extends 'base.html' %}
{% block Content %}
<div class="bg-light p-3 p-sm-5 mb-4 border-top">
<div class="container">
<h1 class="display-4">List Page</h1>
<hr class="my-4">
<p>djangoでいっぱい遊びましょう</p>
<a class="btn btn-primary btn-lg" href="{% url 'create' %}" role="button">ToDoを作成する</a>
</div>
</div>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
<a href="{% url 'delete' item.pk %}">削除する</a>
</li>
{% endfor %}
</ul>
{% endblock Content %}
tableの導入
{% extends 'base.html' %}
{% block Content %}
<div class="bg-light p-3 p-sm-5 mb-4 border-top">
<div class="container">
<h1 class="display-4">List Page</h1>
<hr class="my-4">
<p>djangoでいっぱい遊びましょう</p>
<a class="btn btn-primary btn-lg" href="{% url 'create' %}" role="button">ToDoを作成する</a>
</div>
</div>
<ul>
<div class="container">
<table class="table caption-top">
<caption>Todo List</caption>
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Memo</th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for item in object_list %}
<tr>
<td>{{ item.title }}</td>
<td>{{ item.memo }}</td>
<td><a class="btn btn-secondary btn-sm" href="{% url 'update' item.pk %}">編集する</a></td>
<td><a class="btn btn-danger btn-sm" href="{% url 'delete' item.pk %}">削除する</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock Content %}
のを {% for item in object_list %} {% endfor %}
で囲うのがポイント。
form導入について
create.html
をもう一度見てみると、generic viewのCreateViewを使いDjangoの機能を最大限利用している関係で、
form文をほとんど書かなくても実装出来ている。{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
{{ form.as_p}}
<input type="submit" value="作成する">
</form>
{% endblock Content %}
{{ form.as_p}}
しか書いていない。
実際にレンダリングされる時、{{ form.as_p}}
は以下の用に展開されている。<p><label for="id_title">Title:</label> <input type="text" name="title" maxlength="100" required="" id="id_title"></p>
<p><label for="id_memo">Memo:</label> <textarea name="memo" cols="40" rows="10" maxlength="255" required="" id="id_memo"></textarea></p>
formクラスは非常に簡単であるが、見た目を返上するにはやや面倒であり、直接form用のhtmlを記載する方法もあるが、
formクラスにはエラー処理やバリデーションの機能などを兼ね備えているため、できるだけ利用してbootstrapを反映させて見ます。
formクラスが持つフィールド詳細については以下を参照
- 公式document
Working with forms | Django documentation | Django{% extends 'base.html' %}
{% block Content %}
<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
<label for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
<input type="text" name="{{ form.title.html_name }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
{% for error in form.title.errors %}
{{error}}
{% endfor %}
</p>
<p>
<label for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
<textarea name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}"></textarea>
{% for error in form.memo.errors %}
{{error}}
{% endfor %}
</p>
<input type="submit" value="作成する">
</form>
{% endblock Content %}
{{ form.title }}
と{{ form.memo }}
で置き換えることが出来るが、bootstrapで加工したいため、利用しない。
この状態で
http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下の用に表示される。
{% extends 'base.html' %}
{% block Content %}
<div class="container">
<form action="" method="POST"> {% csrf_token %}
<legend>Todoを作成</legend>
<div class="mb-3">
<label class="form-label" for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
<input class="form-control" type="text" name="{{ form.title.html_name }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
{% for error in form.title.errors %}
{{error}}
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label" for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
<textarea class="form-control" name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}"></textarea>
{% for error in form.memo.errors %}
{{error}}
{% endfor %}
</div>
<input class="btn btn-primary" type="submit" value="作成する">
</form>
</div>
{% endblock Content %}
{% extends 'base.html' %}
{% block Content %}
<div class="container">
<form action="" method="POST"> {% csrf_token %}
<legend>Todoを更新</legend>
<div class="mb-3">
<label class="form-label" for="{{ form.title.id_for_label }}">{{ form.title.label }}</label>
<input class="form-control" type="text" name="{{ form.title.html_name }}" value="{{ form.title.value }}" maxlength="100" required="" id="{{ form.title.id_for_label }}">
{% for error in form.title.errors %}
{{error}}
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label" for="{{ form.memo.id_for_label }}">{{ form.memo.label }}</label>
<textarea class="form-control" name="{{ form.memo.html_name }}" cols="40" rows="10" maxlength="255" required="" id="{{ form.memo.id_for_label }}">{{ form.memo.value }}</textarea>
{% for error in form.memo.errors %}
{{error}}
{% endfor %}
</div>
<input class="btn btn-primary" type="submit" value="更新する">
</form>
</div>
{% endblock Content %}
{% extends 'base.html' %}
{% block Content %}
<div class="container">
<h1 class="display-6">Todoを削除確認</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
{{ object.title }}{{ object.memo }}
を本当に削除しますか?
</p>
<input class="btn btn-danger" type="submit" value="削除する">
<a class="btn btn-secondary" href="{% url 'list' %}">戻る</a>
</form>
</div>
{% endblock Content %}
【Django3.1】DjangoAdminの使い方
本記事で行うこと
モチベーション
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
$ python -V
Python 3.8.0
事前準備
pyenv環境
$ python -m venv env
$ source env/bin/activate
$ export PATH=$PWD/env/bin:$PATH
Djangoのインストール
$ pip install django==3.1.3
sample アプリケーション
$ git clone -b sample.generic-view https://github.com/n-guitar/django-samples.git
Django Adminとは
また作成したdatabase tabelもdefaultで作成されるadmin.pyに登録するだけで管理サイトから操作出来るようになる。Django Adminの有効化(参考:読み飛ばして問題なし)
が参考までに、利用するために必要な条件を記載しておく。INSTALLED_APPS = [
'django.contrib.admin', # 記述されていること
'django.contrib.auth', # 記述されていること
'django.contrib.contenttypes', # 記述されていること
'django.contrib.sessions', # 記述されていること
'django.contrib.messages', # 記述されていること
<省略>
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', # 記述されていること
'django.contrib.messages.middleware.MessageMiddleware', # 記述されていること
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['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', # 記述されていること
],
},
},
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls), # 記述されていること
path('samplecrud/', include('samplecrud.urls')),
]
この状態で、run serverして$ python manage.py runserver
データベースのマイグレーションと管理ユーザの作成
データベースのマイグレーション
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, samplecrud, 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 auth.0012_alter_user_first_name_max_length... OK
Applying samplecrud.0001_initial... OK
Applying sessions.0001_initial... OK
samplecrud.0001_initial
以外はdefaultで用意されている管理用テーブル。
※samplecrud.0001_initial
は上記のサンプルアプリケーションをgit cloneした場合のみ一緒にマイグレーションされる。
これで管理用テーブルが作成されたので、以下のコマンドで管理ユーザの作成を行う。管理ユーザの作成
$ python manage.py createsuperuser
Username (leave blank to use 'XXXXX'): admin
Email address: test@test.com
Password:
Password (again):
Superuser created successfully.
$ python manage.py runserver
ここからユーザやグループの管理が行える。
任意のモデル(テーブル)を登録
登録方法は簡単で以下の用に記述するだけ。from django.contrib import admin
from .models import Todo # 追記
admin.site.register(Todo) # 追記
- 公式document
Model Meta options | Django documentation | Django
【Django3.1】generic viewを使わずにfunctionを利用してCRUDを行う方法
本記事で行うこと
n-guitar.hatenablog.com
※本記事はCRUD用のDBを作成までは全く同じ手順となる。
余談だが、generic viewが持っているfunctionは以下を参照すると良い。
Django Class-Based-View Inspector -- Classy CBV
GitHub - n-guitar/django-samples at sample.function-viewモチベーション
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
$ python -V
Python 3.8.0
事前準備 pyenv環境
$ python -m venv env
$ source env/bin/activate
$ export PATH=$PWD/env/bin:$PATH
Djangoのインストール
$ pip install django==3.1.3
Django 初期設定
$ django-admin startproject confug .
$ django-admin startapp samplecrud
$ mkdir templates
・・・
# samplecrudを追加
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'samplecrud', # 追加
]
・・・
# TEMPLATESのDIRSを変更
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['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',
],
},
},
]
$ python manage.py runserver
以上で準備完了。
一旦control + cで止めておく。
CRUD用のDBを作成
Column
Field
title
CharField(max_length=100)
memo
TextField(max_length=255)
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=100)
memo = models.TextField(max_length=255)
def __str__(self):
return self.title
$ python manage.py makemigrations samplecrud
Migrations for 'samplecrud':
samplecrud/migrations/0001_initial.py
- Create model Todo
$ python manage.py migrate samplecrud
Operations to perform:
Apply all migrations: samplecrud
Running migrations:
Applying samplecrud.0001_initial... OK
Read画面の作成
URLのつなぎこみ
$ touch samplecrud/urls.py
confug/urls.pyを編集するfrom django.contrib import admin
from django.urls import path, include # include追加
urlpatterns = [
path('admin/', admin.site.urls),
path('samplecrud/', include('samplecrud.urls')), # 追加
]
from django.urls import path
from .views import list_page
urlpatterns = [
path('list/', list_page, name='list'),
]
Read画面の作成
from django.shortcuts import render
from .models import Todo
def list_page(request):
object_list = Todo.objects.all()
return render(request, 'list.html', {'object_list':object_list})
object_list = Todo.objects.all()
はToDo Tableをすべて(all)取得し、object_listに格納している。
DjangoではSQLではなくORMを使いdatabaseを操作することが多い。
(SQLを利用することも可能だが、SQLインジェクション対策を考えなくてはいけなくなるので、特別な理由がなければORMを使うほうが良い。)
※ORMの詳細はマニュアルを参照
Making queries | Django documentation | Djangoreturn render(request, 'list.html', {'object_list':object_list})
はlist.htmlに先程格納したobject_listを辞書型で渡し、レンダリングしている。
templates/list.htmlの作成<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>{{ item.title }}: {{ item.memo }}</li>
{% endfor %}
</ul>
$ python manage.py runserver
今はテーブルに何もレコードがないため、何も表示されない。
Create画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import list_page, create_page # 追記
urlpatterns = [
path('list/', list_page, name='list'),
path('create/', create_page, name='create'), # 追記
]
from django.shortcuts import render, redirect
from .models import Todo
def list_page(request):
object_list = Todo.objects.all()
return render(request, 'list.html', {'object_list':object_list})
def create_page(request):
if request.method == 'POST':
title = request.POST['title']
memo = request.POST['memo']
Todo.objects.create(title=title, memo=memo)
return redirect('list')
return render(request, 'create.html')
GETの時はCreate画面を表示し、POSTの時はformに入力された値を受け取ってdatabaseを更新させる必要がある。
Todo.objects.create(title=title, memo=memo)
は受け取ったパラメータでToDo tableにレコード追加している。<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
<label for="id_title">Title:</label>
<input type="text" name="title" maxlength="100" required="" id="id_title">
</p>
<p>
<label for="id_memo">Memo:</label>
<textarea name="memo" cols="40" rows="10" maxlength="255" required="" id="id_memo"></textarea>
</p>
<input type="submit" value="作成する">
</form>
</form>
Djangoは『 {% csrf_token %}』をつけることでデフォルトでCSRFの検証を行ってくれる。(逆につけないとエラーになる)$ python manage.py runserver
何件か登録してみるとすべて表示されることがわかる。
Update画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import list_page, create_page, update_page
urlpatterns = [
path('list/', list_page, name='list'),
path('create/', create_page, name='create'),
path('update/<int:pk>', update_page, name='update'),
]
これはDBのどのレコードを更新していいか判断できないので<int:pk>で一意にレコードを特定する。
※DjangoではデフォルトでTableを作成するとidというPrimary KeyになるColumnが作成される。from django.shortcuts import render, redirect
from .models import Todo
def list_page(request):
object_list = Todo.objects.all()
return render(request, 'list.html', {'object_list':object_list})
def create_page(request):
if request.method == 'POST':
title = request.POST['title']
memo = request.POST['memo']
Todo.objects.create(title=title, memo=memo)
return redirect('list')
return render(request, 'create.html')
def update_page(request, pk):
if request.method == 'POST':
title = request.POST['title']
memo = request.POST['memo']
Todo.objects.filter(pk=pk).update(title=title, memo=memo)
return redirect('list')
object = Todo.objects.get(pk=pk)
return render(request, 'update.html', {'object':object})
object = Todo.objects.get(pk=pk)
はPrimary Keyの値を条件にレコードを取得する。Todo.objects.filter(pk=pk).update(title=title, memo=memo)
はPrimary Keyでフィルターし更新する。<h1>Update Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
<label for="id_title">Title:</label>
<input type="text" name="title" value="{{ object.title }}" maxlength="100" required="" id="id_title">
</p>
<p>
<label for="id_memo">Memo:</label>
<textarea name="memo" cols="40" rows="10" maxlength="255" required="" id="id_memo">{{ object.memo }}</textarea>
</p>
<input type="submit" value="更新する">
</form>
$ python manage.py runserver
しかしながら、『update/1』のURL Pathに/<int:pk>がに当たる数字を画面上表示していないため、わかりにくいので、
listページからそれぞれのレコードに対してリンクを貼ることにする。Listページからのリンクを作成
<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
</li>
{% endfor %}
</ul>
ここでもurl指定はsamplecrud/urls.pyのnameに指定した名前を利用できる。
また、どのレコードか一意に特定するため、『item.pk』でパラメータをしていることがポイントとなる。$ python manage.py runserver
Dalete画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import list_page, create_page, update_page, delete_page
urlpatterns = [
path('list/', list_page, name='list'),
path('create/', create_page, name='create'),
path('update/<int:pk>', update_page, name='update'),
path('delete/<int:pk>', delete_page, name='delete'),
]
from django.shortcuts import render, redirect
from .models import Todo
def list_page(request):
object_list = Todo.objects.all()
return render(request, 'list.html', {'object_list':object_list})
def create_page(request):
if request.method == 'POST':
title = request.POST['title']
memo = request.POST['memo']
Todo.objects.create(title=title, memo=memo)
return redirect('list')
return render(request, 'create.html')
def update_page(request, pk):
if request.method == 'POST':
title = request.POST['title']
memo = request.POST['memo']
Todo.objects.filter(pk=pk).update(title=title, memo=memo)
return redirect('list')
object = Todo.objects.get(pk=pk)
return render(request, 'update.html', {'object':object})
def delete_page(request, pk):
if request.method == 'POST':
object = Todo.objects.get(pk=pk)
object.delete()
return redirect('list')
object = Todo.objects.get(pk=pk)
return render(request, 'delete.html', {'object':object})
object = Todo.objects.get(pk=pk)
で特定のレコードを取得し、
object.delete()
でレコードを削除する。<h1>Delete Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
{{ object.title }}{{ object.memo }}
を本当に削除しますか?
</p>
<input type="submit" value="削除する">
</form>
<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
<a href="{% url 'delete' item.pk %}">削除する</a>
</li>
{% endfor %}
</ul>
$ python manage.py runserver
/dev/randomと/dev/urandomの違い
本記事で行うこと
モチベーション
環境
調査
/dev/random - Wikipedia
その一方、/dev/urandomはブロック待ちがない。
というのを理解した。
・/dev/randomは外的要因=デバイス入力 を要求し、
・/dev/urandomは機械的に乱数を計算する。
(同じことを2回言った気がするが気にしないこととする)
オンプレミスでも、クラウド利用でも基本的にVMを利用することが多く、今回の障害が発生したシステムはオンプレミスでリモート越しにアクセスする。
リモート越しにアクセスすると言うことは特にデバイス入力とかあまり発生しないイメージなので、ブロックが頻繁に起きそうじゃないか?
と考え、/dev/randomの動作を手元のCentOS7で試してみると以下の結果となった。
以下は意味不明な文字列だが乱数と理解してほしい。
この状態では以下の状態で固まった状態に見える。# cat /dev/random
�^\
�i��Z >?���ݚ�K�M�E�ڛ�Mk�
上記の乱数から新たに乱数が増えていることがわかる。# cat /dev/random
�^\
�i��Z >?���ݚ�K�M�E�ڛ�Mk���ES�8�g���>=z�rPomD:��:;
�8؆+
/dev/randomを利用すると、ほぼブロックしてしまう。
誰かがデータセンタに駆けつけてコンソールアクセスしないと、ノイズ発生しないのでエントロピープールが空状態ということ。
真の乱数を使うことなんでないはずなので基本dev/randomは使わないほうが良さそう。
【Django3.1】generic viewを利用してCRUDを行う方法
本記事で行うこと
モチベーション
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
$ python -V
Python 3.8.0
事前準備 pyenv環境
$ python -m venv env
$ source env/bin/activate
$ export PATH=$PWD/env/bin:$PATH
Djangoのインストール
$ pip install django==3.1.3
Django 初期設定
$ django-admin startproject confug .
$ django-admin startapp samplecrud
$ mkdir templates
・・・
# samplecrudを追加
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'samplecrud', # 追加
]
・・・
# TEMPLATESのDIRSを変更
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['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',
],
},
},
]
$ python manage.py runserver
以上で準備完了。
一旦control + cで止めておく。
CRUD用のDBを作成
Column
Field
title
CharField(max_length=100)
memo
TextField(max_length=255)
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=100)
memo = models.TextField(max_length=255)
def __str__(self):
return self.title
$ python manage.py makemigrations samplecrud
Migrations for 'samplecrud':
samplecrud/migrations/0001_initial.py
- Create model Todo
$ python manage.py migrate samplecrud
Operations to perform:
Apply all migrations: samplecrud
Running migrations:
Applying samplecrud.0001_initial... OK
Read画面の作成
URLのつなぎこみ
$ touch samplecrud/urls.py
config/urls.pyを編集するfrom django.contrib import admin
from django.urls import path, include # include追加
urlpatterns = [
path('admin/', admin.site.urls),
path('samplecrud/', include('samplecrud.urls')), # 追加
]
※現時点ではListPageはviewsの中に存在していないため、エラーになるがこの次に作成する。from django.urls import path
from .views import ListPage
urlpatterns = [
path('list/', ListPage.as_view(), name='list'),
]
Read画面の作成
samplecrud/views.pyfrom django.shortcuts import render
from .models import Todo
from django.views import generic
class ListPage(generic.ListView):
model = Todo
template_name = 'list.html'
templates/list.htmlの作成<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>{{ item.title }}: {{ item.memo }}</li>
{% endfor %}
</ul>
$ python manage.py runserver
今はテーブルに何もレコードがないため、何も表示されない。
Create画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import ListPage, CreatePage # 追記
samplecrud/urls.py
urlpatterns = [
path('list/', ListPage.as_view(), name='list'),
path('create/', CreatePage.as_view(), name='create'), # 追記
]
from django.shortcuts import render
from .models import Todo
from django.views import generic
from django.urls import reverse_lazy
class ListPage(generic.ListView):
model = Todo
template_name = 'list.html'
class CreatePage(generic.CreateView):
model = Todo
template_name = 'create.html'
fields = ('title','memo')
success_url = reverse_lazy('list')
『success_url = reverse_lazy('list')』はcreateが成功したときにどの画面を表示するかするかを示す。
ちはみに『reverse_lazy('list')』のlistはsamplecrud/urls.pyのnameに指定した名前を利用できる。<h1>Create Page</h1>
<form action="" method="POST"> {% csrf_token %}
{{ form.as_p}}
<input type="submit" value="作成する">
</form>
『{{ form.as_p}}』は fieldsで指定した項目を$ python manage.py runserver
何件か登録してみるとすべて表示されることがわかる。
Update画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import ListPage, CreatePage, UpdatePage # 追記
urlpatterns = [
path('list/', ListPage.as_view(), name='list'),
path('create/', CreatePage.as_view(), name='create'),
path('update/<int:pk>', UpdatePage.as_view(), name='update'), # 追記
]
これはDBのどのレコードを更新していいか判断できないので<int:pk>で一意にレコードを特定する。
※DjangoではデフォルトでTableを作成するとidというPrimary KeyになるColumnが作成される。from django.shortcuts import render
from .models import Todo
from django.views import generic
from django.urls import reverse_lazy
class ListPage(generic.ListView):
model = Todo
template_name = 'list.html'
class CreatePage(generic.CreateView):
model = Todo
template_name = 'create.html'
fields = ('title','memo')
success_url = reverse_lazy('list')
class UpdatePage(generic.UpdateView):
model = Todo
template_name = 'update.html'
fields = ('title','memo')
success_url = reverse_lazy('list')
<h1>Update Page</h1>
<form action="" method="POST"> {% csrf_token %}
{{ form.as_p}}
<input type="submit" value="更新する">
</form>
$ python manage.py runserver
しかしながら、『update/1』のURL Pathに/<int:pk>がに当たる数字を画面上表示していないため、わかりにくいので、
listページからそれぞれのレコードに対してリンクを貼ることにする。Listページからのリンクを作成
<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
</li>
{% endfor %}
</ul>
ここでもurl指定はsamplecrud/urls.pyのnameに指定した名前を利用できる。
また、どのレコードか一意に特定するため、『item.pk』でパラメータをしていることがポイントとなる。$ python manage.py runserver
Dalete画面の作成
以下のファイルをそれぞれ編集する。from django.urls import path
from .views import ListPage, CreatePage, UpdatePage, DeletePage # 追記
urlpatterns = [
path('list/', ListPage.as_view(), name='list'),
path('create/', CreatePage.as_view(), name='create'),
path('update/<int:pk>', UpdatePage.as_view(), name='update'),
path('delete/<int:pk>', DeletePage.as_view(), name='delete'), # 追記
]
from django.shortcuts import render
from .models import Todo
from django.views import generic
from django.urls import reverse_lazy
class ListPage(generic.ListView):
model = Todo
template_name = 'list.html'
class CreatePage(generic.CreateView):
model = Todo
template_name = 'create.html'
fields = ('title','memo')
success_url = reverse_lazy('list')
class UpdatePage(generic.UpdateView):
model = Todo
template_name = 'update.html'
fields = ('title','memo')
success_url = reverse_lazy('list')
class DeletePage(generic.DeleteView):
model = Todo
template_name = 'delete.html'
success_url = reverse_lazy('list')
(Detail/Deleteでそれぞれ複数の同じページを作成させたくない時や、Deleteの確認をポップアップ表示させたい時などにDeleteViewをDetailViewを兼ねて自分は利用する)<h1>Delete Page</h1>
<form action="" method="POST"> {% csrf_token %}
<p>
{{ object.title }}{{ object.memo }}
を本当に削除しますか?
</p>
<input type="submit" value="削除する">
</form>
<h1>list Page</h1>
<ul>
{% for item in object_list %}
<li>
{{ item.title }}: {{ item.memo }}
<a href="{% url 'update' item.pk %}">編集する</a>
<a href="{% url 'delete' item.pk %}">削除する</a>
</li>
{% endfor %}
</ul>
$ python manage.py runserver
【mac issue】『xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun』エラー
mac issue系
本記事で行うこと
再現手順
$ git clone XXXXX
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.1
BuildVersion: 20C69
対応方法
$ xcode-select --install
xcode-select: note: install requested for command line developer tools
xterm.jsの見た目を変更する
本記事で行うこと
参考サイト
初期状態
new Terminalに色々optionを加えてみます。
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
<script>
let term = new Terminal({
// ここに色々設定して見る。
});
term.open(document.getElementById('terminal'));
function runFakeTerminal() {
if (term._initialized) {
return;
}
term._initialized = true;
term.prompt = () => {
term.write('\r\n$ ');
};
term.writeln('Welcome to xterm.js');
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
term.writeln('Type some keys and commands to play around.');
term.writeln('');
term.prompt();
term.onKey(e => {
console.log(e)
const ev = e.domEvent
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey
if (ev.keyCode === 13) {
term.prompt();
} else if (ev.keyCode === 8) {
if (term._core.buffer.x > 2) {
term.write('\b \b');
}
} else if (printable) {
term.write(e.key);
}
});
}
runFakeTerminal()
</script>
</body>
</html>
let term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
});
let term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
cursorBlink: true, //カーソルの点滅
});
let term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
cursorBlink: true, //カーソルの点滅
cursorStyle: 'underline' //カーソルをアンダーライン
});
見た目はかわりませんがPerformanceが上がるようです。
xterm.jsはvscodeにも使われているのですが、canvasにしています。
Integrated Terminal Performance Improvementslet term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
cursorBlink: true, //カーソルの点滅
cursorStyle: 'underline', //カーソルをアンダーライン
RendererType: 'canvas', // renderer typeをcanvas
});
設定るる値はcodeでも名称でも大丈夫です。
WEB色見本 原色大辞典 - HTMLカラーコード let term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
cursorBlink: true, //カーソルの点滅
cursorStyle: 'underline', //カーソルをアンダーライン
RendererType: 'canvas', // renderer typeをcanvas
theme: {
background: 'darkslategray'
}
});
let term = new Terminal({
// ここに色々設定して見る。
rows: 30, // terminalの行数
cursorBlink: true, //カーソルの点滅
cursorStyle: 'underline', //カーソルをアンダーライン
RendererType: 'canvas', // renderer typeをcanvas
theme: {
background: 'darkslategray',
foreground: 'orange',
}
});