【Django3.1】generic viewを使わずにfunctionを利用してCRUDを行う方法
- 本記事で行うこと
- モチベーション
- 環境
- 事前準備 pyenv環境
- Djangoのインストール
- Django 初期設定
- CRUD用のDBを作成
- Read画面の作成
- Create画面の作成
- Update画面の作成
- Dalete画面の作成
本記事で行うこと
- DjangoでCRUD(Create/Read/Update/Delete)をgeneric viewは使わず、functionで行う。
- Pythonはpyenv環境を利用
- generic viewを使ったCRUDは以下の通り。
n-guitar.hatenablog.com
※本記事はCRUD用のDBを作成までは全く同じ手順となる。
※なお通常Djangoでアプリケーションを作成する際は、generic viewを使い、機能を拡張したい時はfunctionをオーバーライドすることが自分は多い。
余談だが、generic viewが持っているfunctionは以下を参照すると良い。
Django Class-Based-View Inspector -- Classy CBV
なお本記事のソースコードはgithub上に公開しています。
GitHub - n-guitar/django-samples at sample.function-view
モチベーション
- Djangoを教える時のサンプルとして利用したいため。
環境
$ sw_vers ProductName: macOS ProductVersion: 11.1 BuildVersion: 20C69
- Python 3.8.0
$ 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 初期設定
- プロジェクトとアプリを作成
- プロジェクトでtempletsフォルダを設定
$ django-admin startproject confug . $ django-admin startapp samplecrud $ mkdir templates
- settings.py編集 appの追加とtemplateフォルダを参照するように編集
confug/settings.py
・・・ # 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
http://127.0.0.1:8000/
にアクセスして以下が表示されればOK
以上で準備完了。
一旦control + cで止めておく。
現時点で以下のようなディレクトリ構成になっている。
CRUD用のDBを作成
今回は以下のようなDBを作成する
Column | Field |
---|---|
title | CharField(max_length=100) |
memo | TextField(max_length=255) |
samplecrud/models.py
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
DBの反映
$ 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画面の作成
早速Read画面を作成するがその前に、URLのつなぎこみを行う。
※直接configからURLを指定してもいいが、通常アプリごとに作成する多い。
URLのつなぎこみ
$ touch samplecrud/urls.py
現時点のディレクトリ構造は以下の通りになっている。
URLのつなぎこみ。
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')), # 追加 ]
上記のURLは『http://127.0.0.1:8000/samplecrud/』でアクセスした時、『samplecrud/urls.py』記載されているURLにルーティングするという意味。
samplecrud/urls.pyを編集する
from django.urls import path from .views import list_page urlpatterns = [ path('list/', list_page, name='list'), ]
Read画面の作成
いよいよ画面を作成していく。
samplecrud/views.py
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 | Django
return render(request, 'list.html', {'object_list':object_list})
はlist.htmlに先程格納したobject_listを辞書型で渡し、レンダリングしている。
レンダリング先のhtmlがないので作成する。
templates/list.htmlの作成
<h1>list Page</h1> <ul> {% for item in object_list %} <li>{{ item.title }}: {{ item.memo }}</li> {% endfor %} </ul>
Todo tableから取得した値がobject_listというオブジェクトに格納され、list.htmlに渡される
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ にアクセスする
以下のように表示されればOK。
今はテーブルに何もレコードがないため、何も表示されない。
Create画面の作成
続けてDBのレコードを追加する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), # 追記 ]
こちらは先程のlistと同じ
samplecrud/views.py
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')
createのときはどのGETとPOSTメソッドを処理する必要がある。
GETの時はCreate画面を表示し、POSTの時はformに入力された値を受け取ってdatabaseを更新させる必要がある。
Todo.objects.create(title=title, memo=memo)
は受け取ったパラメータでToDo tableにレコード追加している。
※通常databaseを更新する時はバリデーションをすべきであるが本記事ではわかりにくくなるので行わないでおく。
templates/create.html
<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>
inputタグにname="※POSTで受け取りたい変数名"を入れることでviews.pyでその変数名で受け取ることができる。
Djangoは『 {% csrf_token %}』をつけることでデフォルトでCSRFの検証を行ってくれる。(逆につけないとエラーになる)
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。
何件か登録してみるとすべて表示されることがわかる。
Update画面の作成
続けてDBのレコードを更新する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), ]
こちらは先程と若干異なり、URL Pathに/<int:pk>が記載されている。
これはDBのどのレコードを更新していいか判断できないので<int:pk>で一意にレコードを特定する。
※DjangoではデフォルトでTableを作成するとidというPrimary KeyになるColumnが作成される。
samplecrud/views.py
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})
updateではどのレコードを表示、更新するか特定する必要がある。
object = Todo.objects.get(pk=pk)
はPrimary Keyの値を条件にレコードを取得する。
Todo.objects.filter(pk=pk).update(title=title, memo=memo)
はPrimary Keyでフィルターし更新する。
templates/update.html
<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>
create.htmlと似ているが、{{ object.title }}と{{ object.memo }}でviews.pyで取得したレコードを表示している。
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/update/1
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。
しかしながら、『update/1』のURL Pathに/<int:pk>がに当たる数字を画面上表示していないため、わかりにくいので、
listページからそれぞれのレコードに対してリンクを貼ることにする。
Listページからのリンクを作成
templates/list.html
<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>
aタグを使い、リンクを作成する。
ここでもurl指定はsamplecrud/urls.pyのnameに指定した名前を利用できる。
また、どのレコードか一意に特定するため、『item.pk』でパラメータをしていることがポイントとなる。
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ を表示すると以下の用になり、クリックするとupdate画面に移動できる。
Dalete画面の作成
最後にDBのレコードを削除する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), ]
updateと同じく、どのレコードかを特定する/<int:pk>を指定する。
samplecrud/views.py
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})
deleteの場合は一度削除してよいかもう一度確認する画面を表示する。
object = Todo.objects.get(pk=pk)
で特定のレコードを取得し、
object.delete()
でレコードを削除する。
templates/delete.html
<h1>Delete Page</h1> <form action="" method="POST"> {% csrf_token %} <p> {{ object.title }}{{ object.memo }} を本当に削除しますか? </p> <input type="submit" value="削除する"> </form>
listからdeleteへのリンクもupdate同様作成しておく
<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>
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ を表示すると以下の用になり、削除をクリックするとレコードが削除される。
/dev/randomと/dev/urandomの違い
本記事で行うこと
- /dev/randomと/dev/urandomの違いについて簡単に試す
モチベーション
- /dev/randomを利用している本番処理があったが、ブロック待ちが発生してしまい障害になった。
- /dev/randomを推奨されておらず、/dev/urandomを利用することが推奨されていることを知ったため、なぜ何かを確認する。
環境
- CentOS7
調査
早速wikiの参照になるが、
/dev/random - Wikipedia
/dev/randomは真の乱数を発生させるためにデバイス入力とかのノイズを利用するけど、
エントロピープールにノイズが内場合、ブロックする仕様なんですね。なのでノイズ収集するまで待たされる。
その一方、/dev/urandomはブロック待ちがない。
というのを理解した。
真の乱数を発生させるためには、"機械的"ではだめで、外的要因が必要とのこと。
・/dev/randomは外的要因=デバイス入力 を要求し、
・/dev/urandomは機械的に乱数を計算する。
(同じことを2回言った気がするが気にしないこととする)
実際の世界では、
オンプレミスでも、クラウド利用でも基本的にVMを利用することが多く、今回の障害が発生したシステムはオンプレミスでリモート越しにアクセスする。
リモート越しにアクセスすると言うことは特にデバイス入力とかあまり発生しないイメージなので、ブロックが頻繁に起きそうじゃないか?
と考え、/dev/randomの動作を手元のCentOS7で試してみると以下の結果となった。
・mac上からterminalでssh → CentOS7(VMFusion)
mac上のssh越しだと、新しく乱数を全然生成しない。
以下は意味不明な文字列だが乱数と理解してほしい。
この状態では以下の状態で固まった状態に見える。
# cat /dev/random �^\ �i��Z >?���ݚ�K�M�E�ڛ�Mk�
・CentOS7(VMFusion)のConsoleでマウスを動かすと、新たに乱数を生成する。
上記の乱数から新たに乱数が増えていることがわかる。
# cat /dev/random �^\ �i��Z >?���ݚ�K�M�E�ڛ�Mk���ES�8�g���>=z�rPomD:��:; �8؆+
つまり我々が普段利用しているデータセンターにあるVMに対しては基本リモートで接続するため
/dev/randomを利用すると、ほぼブロックしてしまう。
誰かがデータセンタに駆けつけてコンソールアクセスしないと、ノイズ発生しないのでエントロピープールが空状態ということ。
真の乱数を使うことなんでないはずなので基本dev/randomは使わないほうが良さそう。
【Django3.1】generic viewを利用してCRUDを行う方法
- 本記事で行うこと
- モチベーション
- 環境
- 事前準備 pyenv環境
- Djangoのインストール
- Django 初期設定
- CRUD用のDBを作成
- Read画面の作成
- Create画面の作成
- Update画面の作成
- Dalete画面の作成
本記事で行うこと
GitHub - n-guitar/django-samples at feature/first/sample.generic-view
モチベーション
- Djangoを教える時のサンプルとして利用したいため。
環境
$ sw_vers ProductName: macOS ProductVersion: 11.1 BuildVersion: 20C69
- Python 3.8.0
$ 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 初期設定
- プロジェクトとアプリを作成
- プロジェクトでtempletsフォルダを設定
$ django-admin startproject confug . $ django-admin startapp samplecrud $ mkdir templates
- settings.py編集 appの追加とtemplateフォルダを参照するように編集
confug/settings.py
・・・ # 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
http://127.0.0.1:8000/
にアクセスして以下が表示されればOK
以上で準備完了。
一旦control + cで止めておく。
現時点で以下のようなディレクトリ構成になっている。
CRUD用のDBを作成
今回は以下のようなDBを作成する
Column | Field |
---|---|
title | CharField(max_length=100) |
memo | TextField(max_length=255) |
samplecrud/models.py
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
DBの反映
$ 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画面の作成
早速Read画面を作成するがその前に、URLのつなぎこみを行う。
※直接configからURLを指定してもいいが、通常アプリごとに作成する多い。
URLのつなぎこみ
$ touch samplecrud/urls.py
現時点のディレクトリ構造は以下の通りになっている。
URLのつなぎこみ。
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')), # 追加 ]
上記のURLは『http://127.0.0.1:8000/samplecrud/』でアクセスした時、『samplecrud/urls.py』記載されているURLにルーティングするという意味。
samplecrud/urls.pyを編集する
※現時点ではListPageはviewsの中に存在していないため、エラーになるがこの次に作成する。
from django.urls import path from .views import ListPage urlpatterns = [ path('list/', ListPage.as_view(), name='list'), ]
Read画面の作成
いよいよ画面を作成していくが CRUDを実現するのにdjango.viewsのgeneric viewと呼ばれるもの利用する。 マニュアルはあるが、ソースコードを見たほうが早い場合があるので必要に応じて参照する。
generic.ListViewを継承したクラスを作り、どのDBのTableを利用して、どのhtmlファイルにレンダリングするかを以下のように書くだけ。
samplecrud/views.py
from django.shortcuts import render from .models import Todo from django.views import generic class ListPage(generic.ListView): model = Todo template_name = 'list.html'
レンダリング先のhtmlがないので作成する。
templates/list.htmlの作成
<h1>list Page</h1> <ul> {% for item in object_list %} <li>{{ item.title }}: {{ item.memo }}</li> {% endfor %} </ul>
Todo tableから取得した値がobject_listというオブジェクトに格納され、list.htmlに渡される
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ にアクセスする
以下のように表示されればOK。
今はテーブルに何もレコードがないため、何も表示されない。
Create画面の作成
続けてDBのレコードを追加する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), # 追記 ]
こちらは先程のlistと同じ
samplecrud/views.py
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')
createのときはどのFieldを更新するか『fields』で指定する。
『success_url = reverse_lazy('list')』はcreateが成功したときにどの画面を表示するかするかを示す。
ちはみに『reverse_lazy('list')』のlistはsamplecrud/urls.pyのnameに指定した名前を利用できる。
templates/create.html
<h1>Create Page</h1> <form action="" method="POST"> {% csrf_token %} {{ form.as_p}} <input type="submit" value="作成する"> </form>
Djangoは『 {% csrf_token %}』をつけることでデフォルトでCSRFの検証を行ってくれる。(逆につけないとエラーになる)
『{{ form.as_p}}』は fieldsで指定した項目を
タグで展開してくれる。(余談だがCreateViewがForm classを継承しているため利用できる)
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/create/
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。
何件か登録してみるとすべて表示されることがわかる。
Update画面の作成
続けてDBのレコードを更新する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), # 追記 ]
こちらは先程と若干異なり、URL Pathに/<int:pk>が記載されている。
これはDBのどのレコードを更新していいか判断できないので<int:pk>で一意にレコードを特定する。
※DjangoではデフォルトでTableを作成するとidというPrimary KeyになるColumnが作成される。
samplecrud/views.py
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')
createのとき同じでFieldを更新するか『fields』で指定する。
templates/update.html
<h1>Update Page</h1> <form action="" method="POST"> {% csrf_token %} {{ form.as_p}} <input type="submit" value="更新する"> </form>
create.htmlと全く同じで問題ない。(valueの値だけ更新するに変更した。)
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/update/1
にアクセスすると以下によう表示され、更新するとlistが表示されて先程更新したデータが表示される。
しかしながら、『update/1』のURL Pathに/<int:pk>がに当たる数字を画面上表示していないため、わかりにくいので、
listページからそれぞれのレコードに対してリンクを貼ることにする。
Listページからのリンクを作成
templates/list.html
<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>
aタグを使い、リンクを作成する。
ここでもurl指定はsamplecrud/urls.pyのnameに指定した名前を利用できる。
また、どのレコードか一意に特定するため、『item.pk』でパラメータをしていることがポイントとなる。
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ を表示すると以下の用になり、クリックするとupdate画面に移動できる。
Dalete画面の作成
最後にDBのレコードを削除する画面を作成する。
以下のファイルをそれぞれ編集する。
samplecrud/urls.py
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'), # 追記 ]
updateと同じく、どのレコードかを特定する/<int:pk>を指定する。
samplecrud/views.py
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')
deleteの場合は一度削除してよいかもう一度確認する画面を表示する。
※余談だが、DeleteViewは今回登場させていない1レコードの詳細表示されるDetailViewを継承しているので、DetailViewを兼ねることもできる。
(Detail/Deleteでそれぞれ複数の同じページを作成させたくない時や、Deleteの確認をポップアップ表示させたい時などにDeleteViewをDetailViewを兼ねて自分は利用する)
templates/delete.html
<h1>Delete Page</h1> <form action="" method="POST"> {% csrf_token %} <p> {{ object.title }}{{ object.memo }} を本当に削除しますか? </p> <input type="submit" value="削除する"> </form>
listからdeleteへのリンクもupdate同様作成しておく
<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>
この状態でもう一度runserverし
$ python manage.py runserver
http://127.0.0.1:8000/samplecrud/list/ を表示すると以下の用になり、削除をクリックするとレコードが削除される。
【mac issue】『xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun』エラー
mac issue系
- 自分のメモようなので雑です。
本記事で行うこと
以下エラーの解消方法 * 『xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun』
再現手順
- macbook airのOSをアップデード
$ 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ツールキットをインストールする。
- 以前にインストールしたことがある場合でも、再登録するか、最新バージョンに更新する。
$ xcode-select --install xcode-select: note: install requested for command line developer tools
上記で完了。 再度git コマンドを行うと問題なく動作する。
xterm.jsの見た目を変更する
本記事で行うこと
- xterm.jsのterminal optionをつかって見た目を変更してみます。
- xterm.jsのインストールやキーボード入力をする方法は以下を参照ください。 n-guitar.hatenablog.com
参考サイト
- 公式ドキュメント ITerminalOptions
初期状態
初期状態は以下の状態です。
new Terminalに色々optionを加えてみます。
htmlファイル
<!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>
- terminalの行数を指定します。
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' //カーソルをアンダーライン });
- renderer typeをcanvasにします。
見た目はかわりませんがPerformanceが上がるようです。
xterm.jsはvscodeにも使われているのですが、canvasにしています。
Integrated Terminal Performance Improvements
let 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', } });
結構自分好みにカスタマイズできそうですね。
xterm.jsでキーボード入力を受け付ける方法
本記事で行うこと
- xterm.jsを利用して、キーボード入力を受け付けるhtmlを作成する。
- ※環境はnode.jsを使いますが、最後にCDNサイトを使ってhtmlファイルのみで行う方法も記載しておきます。
モチベーション
- vscodeのterminalなどで採用されているxterm.jsをつかってプロダクトを作ろうと思ったため。
- xterm.jsの使い方を開発メンバーと共有したかったため。
環境
- macbook pro/node.jsを利用
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H15 $ node -v v12.18.3
xterm.jsのインストール
versionは本記事を書いたときの最新版を利用します。
$ npm install --save xterm@4.9.0
Hello xterm.js
まず公式ページにあるとおり、html上にterminal windowを表示してみます。
github.com
<!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')); term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ') </script> </body> </html>
headのpathはインストールした場所を指定してください。
作成したhtmlファイルにブラウザでアクセスすると以下のようにterminalを表示できます。
- term.openでterminalを開いて、term.writeでterminalに文字を書き込めるようです。
この状態だとterminalに向かってキーボードを打っても何も反応しないのですが、
せっかくなのでこの状態で、Chromeの開発者モードで少し遊んで見たいと思います。
Chromeの開発者モードのConsoleを開き以下のように打ち込んで見ると画面が更新されました。
term.write("書き込んでみた")
Consoleにtermを打ち込むと何やら使えそうな関数が色々出てくるので、公式サイトで使い方を見てみます。
term
xtermjs.org Doc -> Class: Terminalを参照しいくつか試した結果を書いておきます。
- 書き込んで改行する。
term.writeln("改行する")
- 書き込んでpromptを表示する。 今後Enterを押した時に必ずこれを実行すれば良さそうです。
term.write('\r\n$ ')
- terminalに書き込まれたものをresetする。 clearが入力された時にこれを実行すれば良さそうな気がします。
term.reset()
キーボード入力を受け付けるhtmlを作成する。
公式が的供しているdemoサイトを参考に以下を作りました。 xterm.js/client.ts at master · xtermjs/xterm.js · GitHub
<!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>
これでキーボード入力を受け付けるようになりました。
少しだけ解説すると・・・
- term.prompt関数は呼ばれるとterm.write('\r\n$ ')を実行しバッファ2の『$ 』を書き込みます。
- keyCode === 13はEnter
- keyCode === 8はBackspace
- term._core.buffer.x > 2はtermのバッファが3以上の時1文字消します。
- printable = !ev.altKey && !ev.ctrlKey && !ev.metaKeyはこの条件がtrueの時の判定に利用
- else if (printable) {term.write(e.key);でprintableがtrueの時にterm.write(e.key)して入力をterminal上に書き込みます。
キーボード入力とコードの対応表 KeyboardEvent.keyCode - Web API | MDN
CDNサイト版
手っ取り早く試してみたい人は以下をhtmlファイルで保存すればnode.jsなしでも実行できます。
※CDNサイトがある限り。
<!doctype html> <html> <head> <link rel="stylesheet" href="https://unpkg.com/xterm@4.9.0/css/xterm.css"> <script src="https://unpkg.com/xterm@4.9.0/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>
alpine linuxをVMWareFusionにインストールする
本記事で行うこと
モチベーション
環境
macbook pro上でVMwareFusionを利用する
- macのversion
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H15
- VMware Fusionのversion
alpine linuxのisoファイルのダウンロード
以下のサイトからalpine linux(virtual版)のisoファイルをダウンロードします。
alpinelinux.org
ページ下に『古いリリースはここにあります。』とあるのでそこからisoファイルを選んでダウンロードします。
Index of /alpine/v3.12/releases/x86_64/
この時最新だった『alpine-virt-3.12.1-x86_64.iso』を選択します。
isoファイルで提供されているもので最も最小のvirtual版を使います。
たった40MB程度しかなく、よく利用しているCentOS7のminimal版でも7.9は約1GBもあるので、この時点で驚きです。
VMWareFusionにインストール
早速VMWareFusionしてみます。
あとでapline上でk3sを動かす予定でしたので、少し大きめの2CPU/2MBに変更します。
恐ろしく早く起動してきました。10秒もかかってないです。
rootでログインし、setup-apine scriptが用意されているので実行して各設定おwしていきます。
setup-apine の実行
ここで『setup- + TAB』を押すと候補が表示されます。
各項目を別々に設定することも可能です。
改めてsetup-apine を実行
キーボードの設定で『us』と『mac-us』を選択。
ホスト名はdefaultが『localhost』なのでそのままEnterを押します。
VMWareFusionはdefaultでdhcpでipを割り振ってくれるので、全てそのままEnterを押します。
自動的に設定されました。
rootのpasswordを設定します。
タイムゾーンを『Asia/Tokyo』に設定します。
Proxyは利用しないのでそのままEnterを押します。
NTPはchronyを利用したいのでそのままEnterを押します。
package repositoryは速いサイトを検索して設定してくれるfを入れてEnterを押します。
SSHはdefaultのopensshを使いたいのでそのままEnterを押します。
diskはsdaを入れてEnterを押します。
hddにインストールしてLinuxを使いたいのでsysモードにします。
yを入れて、Enterでフォーマットします。
しばらくして完了したら、rebootして設定完了です。
reboot後も10秒かからずに起動してきました。
今後はmacのterminalからsshして作業したいので、管理用のuserを作成します。
管理用のuserの作成
管理用のuserを作成します。
管理用のuserでsudoで管理用のコマンドを許可したいのですが、
標準のaplineにはsudoがdefaultでは入っていません。
apline公式でもユーザに管理アクセスさせたいときはsudoを入れて、wheelに入れてとありました。
docs.alpinelinux.org
ちなみに本内容とは脱線しますが、wheelというのは伝統的にユーザに管理権限を設定するためのグループのようです。
CentOS系でメディアインストールする時に管理者ユーザを作成すると自動的にwheelに入るので実は慣れ親しみがあります。
ホイール (コンピュータ) - Wikipedia
それではsudoをインストールして管理用のユーザをwheelに所属させます。
それではsudoをインストール
(なんと94MiBもありました・・・)
管理用のユーザをwheelに所属させます。
wheelグループにsudo権限を追加します。
macのterminalからsshで管理用のuser
この状態でmacからログインしてみる。
管理者でないとアクセスできないファイルにsudoでアクセスすることができました。
$ ssh adminuser@192.168.128.27 adminuser@192.168.128.27's password: Welcome to Alpine! The Alpine Wiki contains a large amount of how-to guides and general information about administrating Alpine systems. See <http://wiki.alpinelinux.org/>. You can setup the system with the command: setup-alpine You may change this message by editing /etc/motd. localhost:~$ $ ls -l /etc/sudoers.d ls: can't open '/etc/sudoers.d': Permission denied total 0 $ sudo ls -l /etc/sudoers.d [sudo] password for adminuser: total 4 -rw-r--r-- 1 root root 21 Nov 14 18:33 wheel