- 投稿日:2020-02-13T23:58:55+09:00
Go + echo + Docker + VSCode + Remote Containers で開発環境の構築 (ホットリロードとステップ実行対応)
背景
- Go + echo で api サーバーを実装するにあたり開発環境を構築した際のメモ。
- Docker で動かしたい。
- VSCode の Remote Containers を試したい。
- ホットリロード欲しい。
- ステップ実行したい。
成果物
ホスト
- Docker 19.03.1
- docker-compose 1.24.1
- VSCode 1.42.0
- ms-vscode-remote.remote-containers 0.101.0
コンテナ内
- go 1.13.7
- realize 2.1
- delve 1.4.0
- echo 4.1.14
Dockerfile + docker-compose.yml 作成
DockerfileFROM golang:1.13.7 WORKDIR /go/src/app ENV GO111MODULE=on ENV GOPATH /go RUN apt-get update \ && apt-get install -y git \ && go get -v \ gopkg.in/urfave/cli.v2@master \ github.com/oxequa/realize \ github.com/go-delve/delve/cmd/dlv@latest \ github.com/rogpeppe/godef@latest \ golang.org/x/tools/gopls@latest \ golang.org/x/tools/cmd/goimports@latest \ github.com/ramya-rao-a/go-outline@latest \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/*
- ホットリロード用に realize 、デバッグ用に delve をインストール。
- その他には VSCode 拡張の ms-vscode.go で後にレコメンドされるツール類をイントール。
docker-compose.ymlversion: '3.7' services: app: build: . command: /bin/sh -c "while sleep 1000; do :; done" ports: - 8080:8080 volumes: - .:/go/src/app:cached
command: /bin/sh -c "while sleep 1000; do :; done"
で Remote Container が立ち上がるまで待機しておく。Remote Containers
エクステンションをインストールして起動準備
ms-vscode-remote.remote-containers
を検索してインストール。コマンドパレットが開くので
Remote-Containers: Add Development Container Configuration Files...
を選択。
.devcontainer ディレクトリに設定ファイルが作成される。
.devcontainer ├── devcontainer.json └── docker-compose.yml
.devcontainer/docker-compose.yml
は使わず、事前に作っておいた docker-compose.yml を使うので削除しておく。
.devcontainer/devcontainer.json
の変更。.devcontainer/devcontainer.json{ "name": "go", "dockerComposeFile": [ "../docker-compose.yml" ], "service": "app", "workspaceFolder": "/go/src/app", "settings": { "terminal.integrated.shell.linux": "/bin/bash", "go.gopath": "/go" }, "extensions": [ "ms-vscode.go", ], "shutdownAction": "stopCompose" }Remote Container 起動
左下のアイコンからコマンドパレットを開いて、
Remote-Containers: Reopen in Container
を選択。
VSCode のターミナルを開くとコンテナ内のワーキングディレクトリ
/go/src/app
が開く。以降はこの Remote Container 内で作業していく。
go modules の利用準備
$ go mod initMakefile 作っておく
- 開発中に色々 make コマンド足していくので、とりあえず作っておく。
.PHONY: run run: realize start --runrealize でのホットリロード設定
$ make run
を実行すると .realize.yml が作成されるのでこれを以下のように修正。.realize.ymlsettings: legacy: force: false interval: 0s schema: - name: app path: . commands: build: status: true method: go build -o bin/main run: status: true method: bin/main watcher: extensions: - go paths: - / ignored_paths: - .git - .realize - vendor動作確認用プログラム
- echo で Hello, World! を返すだけのやつ。
main.gopackage main import ( "net/http" "github.com/labstack/echo/v4" ) func main() { e := echo.New() e.GET("/", func(c echo.Context) error { return c.String(http.StatusOK, "Hello, World!") }) e.Logger.Fatal(e.Start(":8080")) }起動
$ make run realize start --run [13:38:28][APP] : Watching 1 file/s 18 folder/s [13:38:28][APP] : Build started [13:38:28][APP] : Build completed in 0.206 s [13:38:28][APP] : Running.. [13:38:28][APP] : ____ __ [13:38:28][APP] : / __/___/ / ___ [13:38:28][APP] : / _// __/ _ \/ _ \ [13:38:28][APP] : /___/\__/_//_/\___/ v4.1.14 [13:38:28][APP] : High performance, minimalist Go web framework [13:38:28][APP] : https://echo.labstack.com [13:38:28][APP] : ____________________________________O/_______ [13:38:28][APP] : O\ [13:38:28][APP] : ⇨ http server started on [::]:8080
- 別ターミナルで...
$ curl localhost:8080 Hello, World!
- ホットリロードが効いているのを確認するため以下の変更をして保存。
main.goe.GET("/", func(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") + return c.String(http.StatusOK, "Good Bye!") })
- 確認
$ curl localhost:8080 Good Bye!
ctrl + c
で終了。デバッガを起動してステップ実行
- デバッグ構成ファイルを作成。
.vscode/launch.json{ "version": "0.2.0", "configurations": [ { "name": "debug", "type": "go", "request": "launch", "mode": "debug", "program": "${workspaceFolder}", "showLog": true } ] }$ curl localhost:8080
- 投稿日:2020-02-13T20:42:23+09:00
Dockerでfailed to get keyring during saving token, operation not permittedが出たら
- keyctlがインストールされていない。
apt install keyutils
- dockerの初期設定でkeyutilsが無効になっている。
docker run --rm -it --security-opt seccomp=unconfined debian:jessie \ unshare --map-root-user --user sh -c whoami
参考
https://docs.docker.com/engine/security/seccomp/
https://github.com/Azure/azure-storage-azcopy/issues/193
- 投稿日:2020-02-13T19:38:18+09:00
【備忘】ubuntu+apache2+php7(docker) on centOS6
前提
vagrant+censos6+docker installは済
ubuntuのインストール
docker pull ubuntu:latestubuntuの起動
docker run -it -p 8080:80 --name SOMETHING_NAME ubuntu /bin/bashubuntuの初期設定
apt update # vim apt install vim # 日本語化 apt install language-pack-ja-base language-pack-ja locale-gen echo export LANG=ja_JP.UTF-8 >> ~/.profile source ~/.profile # php apt install software-properties-common #これないとadd-apt-repoが使えない add-apt-repository ppa:ondrej/php apt update apt install php7.2 php7.2-common php7.2-cli php7.2-fpm php7.2-mysql php7.2-dev php7.2-mbstring php7.2-zip # apache apt install apache2 apt install libapache2-mod-php7.2 #これないとapache+phpが使えない # redis apt install redis-server apt install php-redisvirtualHostの設定
touch /etc/apache2/sites-available/vhost_XXX.conf <VirtualHost *:80> ServerName 192.168.0.11:8080 ServerAdmin webmaster@virtual.host DocumentRoot /var/www/html_XXX ErrorLog /var/log/apache2/virtual.host.error.log CustomLog /var/log/apache2/virtual.host.access.log combined LogLevel warn </VirtualHost>あとは/var/www/html_xxx/info.phpとかでphpInfoでも出してみる
詰まりどころめも
vagrantの中のdockerの中のapacheにアクセスしたいがどうしたら
dockerのポートフォワードで解決
vimが文字化け
デフォで日本語使えるとか甘え
apache起動したもののphpが生テキストになる
apache2に拡張モジュールとしてphp7モジュールが必要だった
- 投稿日:2020-02-13T16:22:18+09:00
FluentdとDockerとKinesis Firehose その1(Dockerイメージ作成編)
はじめに
公式のFluentdのDockerイメージからKinesis Data Firehoseまでの連携までを頑張る作業ログであります。
Dockerfile
こちらが今回作ったDockerfile
FROM fluent/fluentd:v1.9-1 USER root COPY ./fluent.conf /etc/fluent/ # install plugin RUN apk add --update-cache --virtual .build-deps sudo build-base ruby-dev \ && gem install fluent-plugin-kinesis -v 3.0.0 --no-document \ && gem sources --clear-all \ && apk del .build-deps \ && rm -rf /var/cache/apk/* \ /home/fluent/.gem/ruby/*/cache/*.gem # set timezone (Alpine) RUN apk --update-cache add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata && \ rm -rf /var/cache/apk/*使った公式のFluentdのイメージはこちら
alpineを使います。
alpineを使う理由としては不必要なものがインストールされていないので非常に軽量なイメージであるという点で、ゆくゆくはECS + Fragateに載せるときにちゃんと乗るようにという意味で使っています。必要なパッケージとしては
- sudo: sudoがもともとないのと、gem installの権限エラーのため
- build-base: ビルド用のパッケージ?(こういうときにエラーが出るのだとか。)
- ruby-dev: gem install用
apk add --update-cache --virtual .build-deps ...
三つのパッケージをbuild-depsという名前で一括インストしますという意味
(delするときに一括で消せたりする)kinesis data firehoseのプラグインに関しては
AWSlabの公式プラグイン
を使っています。現在はv3なので、
gem install fluent-plugin-kinesis -v 3.0.0 --no-document
このようにバージョンを指定しております。
--no-ducumentをつけている理由はこちらを参考にしました。プラグインインストール後はパッケージが残るので消しておくとimageの軽量化に繋がります。
Timezoneに関してはここを参照にしました。
confはまだ動くのを確認してないので下記にはあげないですが、buildは成功しました$ docker build . -t fluent/fluentd:v1.9-1 Sending build context to Docker daemon 4.096kB Step 1/5 : FROM fluent/fluentd:v1.9-1 v1.9-1: Pulling from fluent/fluentd ... Step 3/5 : COPY ./fluent.conf /fluentd/etc/ ---> a6c007f06146 Step 4/5 : RUN apk add --update-cache --virtual .build-deps sudo build-base ruby-dev && gem install fluent-plugin-kinesis -v 3.0.0 --no-document && gem sources --clear-all && apk del .build-deps && rm -rf /var/cache/apk/* /home/fluent/.gem/ruby/*/cache/*.gem ---> Running in e1f69fd761c1 fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/23) Installing sudo (1.8.25_p1-r3) (2/23) Installing binutils (2.31.1-r2) (3/23) Installing libmagic (5.36-r1) (4/23) Installing file (5.36-r1) (5/23) Installing isl (0.18-r0) (6/23) Installing libgomp (8.3.0-r0) (7/23) Installing libatomic (8.3.0-r0) ... Executing busybox-1.29.3-r10.trigger OK: 189 MiB in 50 packages Successfully installed jmespath-1.4.0 Successfully installed aws-partitions-1.272.0 Successfully installed aws-eventstream-1.0.3 Successfully installed aws-sigv4-1.1.0 Successfully installed aws-sdk-core-3.90.0 Successfully installed aws-sdk-kinesis-1.20.0 Successfully installed aws-sdk-firehose-1.24.0 Building native extensions. This could take a while... Successfully installed google-protobuf-3.11.3 Successfully installed fluent-plugin-kinesis-3.0.0 9 gems installed *** Removed specs cache *** (1/23) Purging .build-deps (0) (2/23) Purging sudo (1.8.25_p1-r3) ... (12/23) Purging musl-dev (1.1.20-r5) ... ---> 4af7de5e802e Step 5/5 : RUN apk --update-cache add tzdata && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && apk del tzdata && rm -rf /var/cache/apk/* ---> Running in 1dc29757e0be fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/1) Installing tzdata (2019c-r0) Executing busybox-1.29.3-r10.trigger OK: 31 MiB in 28 packages (1/1) Purging tzdata (2019c-r0) Executing busybox-1.29.3-r10.trigger OK: 28 MiB in 27 packages Removing intermediate container 1dc29757e0be ---> f84414ed4cbc Successfully built f84414ed4cbc Successfully tagged fluent/fluentd:v1.9-1buildしたら、確認
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE fluent/fluentd v1.9-1 f84414ed4cbc 9 seconds ago 50.7MB50.7MBは結構軽いと思います
runしてみる
※runに関しては公式の「How to use this image」を参考
https://hub.docker.com/r/fluent/fluentd/$ docker run -d -p 24224:24224 -v /Users/a-honda/recochoku/learn-fluentd/fluentd-data/:/fluentd/log fluent/fluentd:v1.9-1 a1b12b666ea56c77ee2e17ace9b7eb820628da6af44a7127903a9f38f9e8d15dポートは24224:24224になっているのは
公式のfluent.confがそうなっているからである
https://github.com/fluent/fluentd-docker-image/blob/master/v1.9/alpine/fluent.confちなみにこんな感じ、また-vでマウントしているのはsymlinkで
/fluentd/log/をマウントしているから.<source> @type forward @id input1 @label @mainstream port 24224 </source> <filter **> @type stdout </filter> <label @mainstream> <match docker.**> @type file @id output_docker1 path /fluentd/log/docker.*.log symlink_path /fluentd/log/docker.log append true time_slice_format %Y%m%d time_slice_wait 1m time_format %Y%m%dT%H%M%S%z </match> <match **> @type file @id output1 path /fluentd/log/data.*.log symlink_path /fluentd/log/data.log append true time_slice_format %Y%m%d time_slice_wait 10m time_format %Y%m%dT%H%M%S%z </match> </label>psはこんな感じ
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b12b666ea5 fluent/fluentd:v1.9-1 "tini -- /bin/entryp…" 5 seconds ago Up 4 seconds 5140/tcp, 0.0.0.0:24224->24224/tcp intelligent_yonath続きます。
ちなみになんでこんなことしてるかっていうと
firelensのfluentdのカスタムイメージがないから
https://hub.docker.com/r/amazon/aws-for-fluent-bit
↑はあるのに...つづく
参考ページ
- ECS FargateでSidecarのFluentdでログをS3に送る構成をCloudFormationで構築する
- awslabs/aws-fluent-plugin-kinesis
- Alpine Linux入門 -内部構造とapkでパッケージインストール編-
- fluent/fluentd
2020年02月14日追記
Dockerfileのところ,
COPY ./fluent.conf /fluentd/etc/
こう書いてたのですが、よくみたら、confを参照するパスが間違っていて
Not Such file fluent.conf
になっていたので下記に修正します
COPY ./fluent.conf /etc/fluent/
/etc下のfluentでした〜汗
dockerfileのところも書き直しておきます。そうしたあともういっかいビルドして
docker exec -it コンテナID /bin/.bash
ではいって、fluentd
コマンドをうつと
こんな感じで読み込んでくれてます。
最初のinfoでfluent-plugin-kinesis
を読み込んでくれているのがわかります!この状態で別タブを開いて、
docker exec -it コンテナID /bin/.bash
で入って、
# echo '{"key":"value"}' | fluent-cat docker.test
と打つと、デフォルトのconfファイルのdocker.**
にマッチして'{"key":"value"}'がfluentd-data/に書き込まれているはず/fluentd/log # ls docker.b59e7f686c2e2f0d81aedbd8a0a6c887e.log docker.b59e7f686c2e2f0d81aedbd8a0a6c887e.log.meta /fluentd/log # cat docker.b59e7f686c2e2f0d81aedbd8a0a6c887e.log 2020-02-14T10:48:53+09:00 docker.test {"key":"value"}ちゃんとマウントしたホスト側にも同じデータがあればいいと思います。
- 投稿日:2020-02-13T14:13:49+09:00
docker上のOACISからホストマシンにジョブを投げる手順
oacis_dockerのイメージを使ってdocker上OACISを動かしつつ、dockerを起動しているホストを計算ホストとして利用する手順についてまとめる。
ここでは手元のマシンがmacであることを前提とする。
たとえば手元のマシンのjupyter notebookとかで開発しつつ、OACISの環境構築はdockerで行う場合に有用。以後、dockerを起動しているマシンをホストマシン、docker上で起動しているコンテナを仮想マシンと呼ぶ。
手順
- ホストマシン上でsshdを起動する。
- mac OSの場合、"System Preferences" -> "Sharing" -> "Remote Login" でsshdを起動できる。
- ホストマシン上でxsubをインストールする。
git clone https://github.com/crest-cassia/xsub.git
echo 'export PATH="$HOME/xsub/bin:$PATH"' >> ~/.bash_profile
echo 'export XSUB_TYPE="none"' >> ~/.bash_profile
- 詳細は https://github.com/crest-cassia/xsub を参照。
- docker上でoacisを起動する
docker run --name my_oacis -p 127.0.0.1:3000:3000 -dt oacis/oacis
- ホストマシン上に仮想マシンの公開鍵をコピーする。
docker exec -it -u oacis my_oacis bash -lc "ssh-copy-id $USER@host.docker.internal -p 22"
- ホストマシンにログインするためのパスワードが要求されるので入力する。
- 仮想マシン上の".ssh/config"にホストマシンにログインするための情報を記載する。
docker exec -it -u oacis my_oacis bash -lc "printf \"Host internal\n HostName host.docker.internal\n User $USER\n Port 22\n\" >> ~/.ssh/config"
- http://localhost:3000 にアクセスしてOACISのページを開き "Hosts"->"New Host" から新しいホストを登録する。
- Name: internal として登録する。他はデフォルト値でOK。
- simulatorをOACIS上で登録する。その際に
executable_on
でinternal
にチェックを入れる。
- 投稿日:2020-02-13T09:39:39+09:00
dockerの課題
docker 利用を進めてきて現在の作業上の課題または課題の候補を記録する。
32bit CPUで、古いWindowsで動作しない
docker for windowsがうなく導入または起動できていない。
直接まだ確かめていない。PC用OS(windows, macOS, linux)側のdockerの版管理
どのOSのどの版ではdockerのどの版が安定して動作するか。
dockerのcomponent管理
古いcomponentでないと動作しない場合があるかどうか。
直接まだ確かめていない。X windowsとの連携
Eclipse cheなどX Windowsを利用するソフトウェアなどの、Windows, mac OSでの動作のさせ方
docker hubに登録したソフトの管理
apt update, apt -y upgrade
をしてもよいか、しない方がよいかなどの指針。security対策
どの水準で、どういうsecurity対策をするとよいか。
- 投稿日:2020-02-13T01:37:34+09:00
Elasticsearch & Kibana をLet's EncryptでTLS対応させる
やりたいこと
「Elasticsearch 7.6 と Kibana 7.6 に Security を有効化してDockerで起動する(コピペ) 」をやったものの、最終的にやりたかったFastlyからのLoggingの宛先にするにはTLS周りが厳しかったので、Let's Encryptで証明書を手配することにした。
むしろこっちの方が簡単だったので作業の履歴メモとして残しておく。前提
- Dockerが使える
- ホスト側がRAM 4GB以上で、Elasticsearchに2GB、Kibanaに1GBを割当てられる(Amazon Lightsail あるいは MacBook Pro)
- ドメインを持っていてDNSがいじれる(仮に example.com とする)
- Let's Encrypt はDNS認証でワイルドカード証明書を発行する
- Elasticsearchを
elasticsearch.example.com
で運用するようDNSが構成済み- Kibanaを
kibana.example.com
で運用するようDNSが構成済みほぼコピペでイケる一式
1. Elasticsearchのデータを保存したり証明書一式を保存する場所を作る
- Elasticsearchのコンテナにボリュームを割当てるときにパーミッションエラーが出たら要調整
mkdir -p ~/Development/Docker/Elasticsearch/data ~/Development/Docker/Elasticsearch/cert2. Let's Encryptで証明書を手配する
- certbotのDockerイメージを使うことでAmazon Linux上で、ホストを汚さずに作業する
- 途中、認証用にDNSへTXTレコードを追加するように指示があるので、DNSにレコードを追加してしばらくしてから継続する
docker run -it --rm \ --name certbot \ -v ~/Development/Docker/Elasticsearch/cert:/etc/letsencrypt \ certbot/certbot certonly \ --manual \ -d *.example.com \ -m info@example.com \ --agree-tos \ --manual-public-ip-logging-ok \ --preferred-challenges dns-01 \ --server https://acme-v02.api.letsencrypt.org/directory3. ElasticsearchとKibanaに証明書を組み込むDockerfileを用意する
- 作業しやすい場所に移動
cd ~/Development/Docker/Elasticsearch
dockerfile-es
というファイル名でElasticsearch用Dockerfileを作るFROM docker.elastic.co/elasticsearch/elasticsearch:7.6.0 RUN \ elasticsearch-plugin install --batch analysis-icu && \ elasticsearch-plugin install --batch analysis-kuromoji RUN mkdir /usr/share/elasticsearch/config/cert ADD "./cert/archive/example.com" /usr/share/elasticsearch/config/cert RUN chown -R root:elasticsearch /usr/share/elasticsearch/config/cert
dockerfile-kibana
というファイル名でKibana用Dockerfileを作るFROM docker.elastic.co/kibana/kibana:7.6.0 ADD "./cert/archive/example" /etc/kibana/cert4. Dockerイメージをビルドする
docker build -f dockerfile-es -t elasticsearch-tls . docker build -f dockerfile-kibana -t kibana-tls .5. Elasticsearchを起動する
docker run --name Elasticsearch -d \ --restart=always \ -m 2048m \ -p 9200:9200 \ -p 9300:9300 \ -e cluster.name=ES \ -e discovery.type=single-node \ -e network.host=0.0.0.0 \ -e xpack.security.enabled=true \ -e xpack.monitoring.collection.enabled=true \ -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" \ -e "ELASTIC_PASSWORD=iY69DxipKifV7utYA4t6jgxT" \ -e xpack.security.transport.ssl.enabled=true \ -e xpack.security.transport.ssl.key=/usr/share/elasticsearch/config/cert/privkey1.pem \ -e xpack.security.transport.ssl.certificate=/usr/share/elasticsearch/config/cert/fullchain1.pem \ -e xpack.security.http.ssl.enabled=true \ -e xpack.security.http.ssl.key=/usr/share/elasticsearch/config/cert/privkey1.pem \ -e xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/cert/fullchain1.pem \ -v ~/Development/Docker/Elasticsearch/data:/usr/share/elasticsearch/data \ --ulimit nproc=4096:4096 \ --ulimit memlock=256000:256000 \ --ulimit nofile=65536:65536 \ elasticsearch-tls6. Kibana用ユーザーのパスワードを設定する
- 前提にある通り、この時点で
elasticsearch.example.com
でElasticsearchにアクセスできることが必要curl -XPUT --user elastic:iY69DxipKifV7utYA4t6jgxT 'https://elasticsearch.example.com:9200/_xpack/security/user/kibana/_password' -H "Content-Type: application/json" -d '{ "password" : "6ezji8D5jvceXUsTsvg8mAY4" }'7. Kibanaを起動する
- Elasticsearchとの通信はDocker内で
es-internal.example.com
で行うように構成する。ドメインを合わせないと証明書が機能しないため。docker run --name Kibana -d \ --restart=always \ --link Elasticsearch:es-internal.example.com \ -m 1280m \ -p 443:5601 \ -e "ELASTICSEARCH_HOSTS=https://es-internal.example.com:9200" \ -e "ELASTICSEARCH_USERNAME=kibana" \ -e "ELASTICSEARCH_PASSWORD=6ezji8D5jvceXUsTsvg8mAY4" \ -e "SERVER_SSL_ENABLED=true" \ -e "SERVER_SSL_CERTIFICATE=/etc/kibana/cert/fullchain1.pem" \ -e "SERVER_SSL_KEY=/etc/kibana/cert/privkey1.pem" \ --ulimit nproc=4096:4096 \ --ulimit memlock=256000:256000 \ --ulimit nofile=65536:65536 \ kibana-tls8. 必要な設定をする
https://kibana.example.com/
にアクセスしてelastic
ユーザーとしてログイン
- 投稿日:2020-02-13T01:37:28+09:00
【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑨身長体重を記録する@一括削除機能つき
前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
①Djangoのようこそページへたどり着くまで
②NginxでDjangoのようこそページへたどり着くまで
③カスタムユーザーを作ってadminにたどり着く
④ログインログアウトをしよう
⑤ユーザー登録(サインイン)機能を作ろう
⑥ユーザーごとのデータ登録できるようにする〜CRU編
⑦ユーザーごとのデータ登録できるようにする〜削除編
⑧画像ファイルのアップロード
⑨身長体重を記録する@一括削除機能つき <--ここです
⑩成長曲線グラフを描いてみよう
⑪本番環境へデプロイ+色々手直しGoal
身長と体重を記録できるようにする。(次の章でグラフ化するデータを登録)
やることとポイントのまとめ
この章で実装するのは、基本的に⑧で作ったもの構成と同じ。
1つ違うのは、レコードの一括削除機能をつけているところ。Template→Viewへ、削除対象レコードのIDを渡すところが前回と違う。
あとは、Djangoの基本に則って、、、
①新しいAppを作る→setting.pyとurls.pyにAppを作ったことを知らせる
②Modelを作ろう→Migrationする。admin.pyにmodelを追加する
③Templateを作ろう→urls.pyでURLの体系を指定しよう
④Viewを作ろう→Modelを扱いやすいようFormも要るね①APP作成〜プロジェクト内の指定
docker-compose run web python ./manage.py startapp physsetting.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users.apps.UsersConfig', 'shoes.apps.ShoesConfig', 'phys.apps.PhysConfig', #追加 'bootstrap4', 'bootstrap_datepicker_plus', 'django_cleanup.apps.CleanupConfig', ]mysite/urls.pyfrom django.contrib import admin from django.urls import path, include from . import settings from django.contrib.staticfiles.urls import static from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns = [ path('admin/', admin.site.urls), path('',include('users.urls')), path('',include('shoes.urls')), path('',include('phys.urls')), #追加 ] urlpatterns += staticfiles_urlpatterns() urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)②Model作成
phys/models.pyfrom django.db import models from django.conf import settings from django.utils import timezone from users.models import KidsProfile class PhysData(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) kidsProfile = models.ForeignKey(KidsProfile, on_delete=models.CASCADE) weight = models.DecimalField( max_digits=6, decimal_places=3 ) height = models.DecimalField( max_digits=5, decimal_places=2 ) date = models.DateField(default=timezone.now) def __str__(self): return self.kidsProfile.name + str(self.date)phys/admin.pyfrom django.contrib import admin from .models import PhysData admin.site.register(PhysData)③テンプレート〜URL設計
phys_data_addとphys_data_editは省略。
前回の章で作ったものと、変わらないです。phys/phys_data_list.html{% extends 'base.html' %} {% block content %} <div class="row"> <div class="col-md-12 col-lg-2"> <div class="list-group"> {% for kidsProfile in kidsProfiles %} <a href="/phys/list/{{kidsProfile.id}}" class="list-group-item list-group-item-action">{{kidsProfile.name}}</a> {% endfor %} </div> </div> <div class="col-md-12 col-lg-10 overflow-auto"> <form method="post" action="{% url 'phys:phys_data_delete' %}"> <table class="table table-hover"> <thead class="thead-dark"> <tr> <th>名前</th> <th>日付</th> <th>体重(kg)</th> <th>身長(cm)</th> <th>修正</th> <th>削除</th> </tr> </thead> {% for data_post in data_posts %} <tr> <td class="text-center">{{data_post.kidsProfile.name }}</td class="text-center"> <td class="text-center">{{data_post.date }}</td> <td class="text-center">{{data_post.weight }}</td> <td class="text-center">{{data_post.height }}</td> <td class="text-center"><a href="{% url 'phys:phys_data_edit' data_post.pk %}" class="btn btn-success btn-sm" role="button">編集</a></td> <td class="text-center"><input type="checkbox" name="delete_ids" value="{{ data_post.pk }}"></input></td> </tr> {% endfor %} <tr> <td> <a href="{% url 'phys:phys_data_add' %}" role="button" class="btn btn-primary mr-auto">追加</a> </td> <td></td> <td></td> <td></td> <td></td> <td class="text-center"> {% csrf_token %} <input type="submit" value="一括削除" class="btn btn-danger float-right"> </td> </tr> </table> </form> </div> {% endblock %}phys/urls.pyfrom django.urls import path from . import views app_name = 'phys' urlpatterns = [ path('phys/list/', views.phys_data_list, name='phys_data_list'), path('phys/list/<kidsProfileId>', views.phys_data_list, name='phys_data_list'), path('phys/data_add/', views.phys_data_add, name='phys_data_add'), path('phys/data_edit/<DataPostId>', views.phys_data_edit, name='phys_data_edit'), path('phys/data_delete/', views.phys_data_delete, name='phys_data_delete'), ]④ViewとModel
phys/views.pyfrom django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .models import PhysData from users.models import KidsProfile from .forms import PhysDataForm, PhysDataEditForm #身長体重データを昇順で返却 @login_required def phys_data_list(request, **kwargs): user_name = request.user if len(kwargs) > 0: kidsProfileId = kwargs["kidsProfileId"] else: kidsProfileId = KidsProfile.objects.filter(user=user_name).order_by('id')[0] kids_profiles = KidsProfile.objects.filter(user=user_name) data_posts = PhysData.objects.filter(user=user_name, kidsProfile=kidsProfileId).order_by('date') params = { 'data_posts': data_posts, 'kidsProfiles' : kids_profiles, } return render(request, 'phys/phys_data_list.html', params) #身長体重データを新規登録する @login_required def phys_data_add(request): user_name = request.user if request.method == 'POST': form = PhysDataForm(request.POST, user = user_name) if form.is_valid(): data = form.save(commit=False) data.user = user_name data.save() return redirect('phys:phys_data_list') else: form = PhysDataForm(user = user_name) return render(request, 'phys/phys_data_add.html', {'form': form}) #身長体重データを編集する @login_required def phys_data_edit(request, DataPostId): physData = PhysData.objects.get(pk=DataPostId) if request.method == 'POST': form = PhysDataEditForm(request.POST) if form.is_valid(): data = form.save(commit=False) data.user = request.user data.kidsProfile = physData.kidsProfile data.id = physData.id data.save() return redirect('phys:phys_data_list') else: form = PhysDataEditForm( { 'weight' : physData.weight , 'height' : physData.height , 'date' : physData.date }, user = request.user ) return render(request, 'phys/phys_data_edit.html', {'form': form}) #身長体重データを削除する @login_required def phys_data_delete(request): delete_ids = request.POST.getlist('delete_ids') PhysData.objects.filter(pk__in=delete_ids).delete() return redirect('phys:phys_data_list')phys/forms.pyfrom django import forms from .models import PhysData from users.models import KidsProfile import bootstrap_datepicker_plus as datetimepicker class PhysDataForm(forms.ModelForm): class Meta: model = PhysData fields = ('kidsProfile', 'weight', 'height', 'date',) widgets = { 'date': datetimepicker.DatePickerInput( format='%Y-%m-%d'), } def __init__(self, *args, **kwargs): user = kwargs.pop('user','') super(PhysDataForm, self).__init__(*args, **kwargs) self.fields['kidsProfile'] = forms.ModelChoiceField(queryset=KidsProfile.objects.filter(user=user)) class PhysDataEditForm(forms.ModelForm): class Meta: model = PhysData fields = ('weight', 'height', 'date',) widgets = { 'date': datetimepicker.DatePickerInput( format='%Y-%m-%d', ), } def __init__(self, *args, **kwargs): user = kwargs.pop('user','') kidsProfileId = kwargs.pop('kidsProfileId','') super(PhysDataEditForm, self).__init__(*args, **kwargs)できあがり
こんな形になります。削除レコードはチェックボックス形式で纏めて指定&削除可能。
登録画面は、普通。編集画面も、ほぼ同様。(子供の名前を選ばない程度)
- 投稿日:2020-02-13T00:52:07+09:00
dockerが占有しているディスク容量を開放するコマンド
docker system prune
自分の個人用Linux環境では現在root(
/
)に100Gのディスクをマウントしているのだけれど、最近ディスクスペースがないぞと警告がでてしまった。調べるとdockerのimageやらなんやらで25GBもぶんどられていたので、ここを削減すべく調査。
調査結果
以下のコマンドでいい感じに使っていないものを削除してくれる。
$ docker system prune -af14GBも削除できた。
とりあえず満足。$ docker system prune -af ... deleted: sha256:324e4d3a7dea2e55f7d62c69ed5c2c75e0498830d33f1bdf91ac3b53caaa3a4f deleted: sha256:4500a5ee8f0d643c3f75655827ea893133976c034aaf02e2d362c2d74b3f2553 deleted: sha256:7f097c1fef1a1ccb97a17ae6e82f72096db3337655f3e4e3b192bdd61e9ed858 deleted: sha256:3feae1c6f465840e3ea3658175c7667ba6fc29dee110de2a690e751ac0d36962 deleted: sha256:7a73e56f893cf6284a6ad2be795dfe627da1b354e95befc61e49011a84e3f846 deleted: sha256:f38296c87e1266afe76efb0b909646b2898d50450a03679d505e9ee220ad3a61 deleted: sha256:a78b5195ddd832f217b5255707d9767efd74c43a3d373e7fcdff4176998d87a4 deleted: sha256:65e53d7a0ba16f3c4c6382176d26a23af5d669e0354910e7f3cb23c1ac2b35d1 deleted: sha256:7d54cb4e40b6e9f887c39adcc92656626fd8eb5bcd3472622bcdd03a7b63bbb7 deleted: sha256:db45b38d6bbd6cab848d4958e172201e041dfbc0abbacdfe55937081e809979b deleted: sha256:05340d226ca3ff0041553d3d732257a8f3182e11612e47ac15cde17fa8a5e568 deleted: sha256:9cbb0103d6c58071dae6b3b5e5a471e954785875803067ab570008cd50e3db13 deleted: sha256:f7c732c81f969b4677ef4312ca2f70e5e3368e5e3a2244809d064a9533e0576d deleted: sha256:ebb9ae013834b54e76c8d7dfde0ca9018f6bb3495740356a8f1dc655a8552130 Total reclaimed space: 14.1GBvolumeも削除したい場合には
--volumes
フラグをつける。$ docker system prune -f --volumes ... 4290242096f39a7b48c1ed736b93395a917b682369717cbb020cacda4d3ded5a 4607556efe8c2d4450f5452f74260bd421acda8ded47f4e5b854881c08f318e5 579a2fe1ddd4dea14d8ad8320daa7c48c0fc4bc2c1c754dd7ab9f167f3ab2c4a 21bb1876436790dd3c1b1cf5696f925834f32b951d1a2aa6644d316b947722a3 50c9a60c8228b7d86a610f4a582cba2d344c9c3fc6a6f73c8ecb1e6b61f9be08 93ab608312285d3158c468ea6f8f143bb61ffc0fc813c6bc1a0179dee22565e9 a10442a600e0ef690ed8357ee82ca42c70254ace5a47ba899fb4ce81badab39c 164c8cc1a4e287ab0b5d55709e784c2cd0e73181dccefebbbb6be1f14cd8aabe 4200fd127cffb155c2c153257897755597498ce1a333f47cc3153827bc10c737 76c0c21bfdd76e2b9b8aebd77a37b0e4bbb09a5590671d7b2f327bc5fcc970f6 Total reclaimed space: 681.9MB
- 投稿日:2020-02-13T00:52:07+09:00
dockerが占有しているディスクを開放するコマンド
docker system prune
自分の個人用Linux環境では現在root(
/
)に100Gのディスクをマウントしているのだけれど、最近ディスクスペースがないぞと警告がでてしまった。調べるとdockerのimageやらなんやらで25GBもぶんどられていたので、ここを削減すべく調査。
調査結果
以下のコマンドでいい感じに使っていないものを削除してくれる。
$ docker system prune -af14GBも削除できた。
とりあえず満足。$ docker system prune -af ... deleted: sha256:324e4d3a7dea2e55f7d62c69ed5c2c75e0498830d33f1bdf91ac3b53caaa3a4f deleted: sha256:4500a5ee8f0d643c3f75655827ea893133976c034aaf02e2d362c2d74b3f2553 deleted: sha256:7f097c1fef1a1ccb97a17ae6e82f72096db3337655f3e4e3b192bdd61e9ed858 deleted: sha256:3feae1c6f465840e3ea3658175c7667ba6fc29dee110de2a690e751ac0d36962 deleted: sha256:7a73e56f893cf6284a6ad2be795dfe627da1b354e95befc61e49011a84e3f846 deleted: sha256:f38296c87e1266afe76efb0b909646b2898d50450a03679d505e9ee220ad3a61 deleted: sha256:a78b5195ddd832f217b5255707d9767efd74c43a3d373e7fcdff4176998d87a4 deleted: sha256:65e53d7a0ba16f3c4c6382176d26a23af5d669e0354910e7f3cb23c1ac2b35d1 deleted: sha256:7d54cb4e40b6e9f887c39adcc92656626fd8eb5bcd3472622bcdd03a7b63bbb7 deleted: sha256:db45b38d6bbd6cab848d4958e172201e041dfbc0abbacdfe55937081e809979b deleted: sha256:05340d226ca3ff0041553d3d732257a8f3182e11612e47ac15cde17fa8a5e568 deleted: sha256:9cbb0103d6c58071dae6b3b5e5a471e954785875803067ab570008cd50e3db13 deleted: sha256:f7c732c81f969b4677ef4312ca2f70e5e3368e5e3a2244809d064a9533e0576d deleted: sha256:ebb9ae013834b54e76c8d7dfde0ca9018f6bb3495740356a8f1dc655a8552130 Total reclaimed space: 14.1GBvolumeも削除したい場合には
--volumes
フラグをつける。$ docker system prune -f --volumes ... 4290242096f39a7b48c1ed736b93395a917b682369717cbb020cacda4d3ded5a 4607556efe8c2d4450f5452f74260bd421acda8ded47f4e5b854881c08f318e5 579a2fe1ddd4dea14d8ad8320daa7c48c0fc4bc2c1c754dd7ab9f167f3ab2c4a 21bb1876436790dd3c1b1cf5696f925834f32b951d1a2aa6644d316b947722a3 50c9a60c8228b7d86a610f4a582cba2d344c9c3fc6a6f73c8ecb1e6b61f9be08 93ab608312285d3158c468ea6f8f143bb61ffc0fc813c6bc1a0179dee22565e9 a10442a600e0ef690ed8357ee82ca42c70254ace5a47ba899fb4ce81badab39c 164c8cc1a4e287ab0b5d55709e784c2cd0e73181dccefebbbb6be1f14cd8aabe 4200fd127cffb155c2c153257897755597498ce1a333f47cc3153827bc10c737 76c0c21bfdd76e2b9b8aebd77a37b0e4bbb09a5590671d7b2f327bc5fcc970f6 Total reclaimed space: 681.9MB
- 投稿日:2020-02-13T00:24:08+09:00
【 Docker+Nginx+Django+RDS】WEBアプリができるまで⑧画像ファイルのアップロード
前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
①Djangoのようこそページへたどり着くまで
②NginxでDjangoのようこそページへたどり着くまで
③カスタムユーザーを作ってadminにたどり着く
④ログインログアウトをしよう
⑤ユーザー登録(サインイン)機能を作ろう
⑥ユーザーごとのデータ登録できるようにする〜CRU編
⑦ユーザーごとのデータ登録できるようにする〜削除編
⑧画像ファイルのアップロード <--ここです
⑨身長体重を記録する@一括削除機能つき
⑩成長曲線グラフを描いてみよう
⑪本番環境へデプロイ+色々手直しGoal
子供の靴のサイズなどを記録できる機能を作ります。
このへんから、実際のサービスっぽくなります。作るもの
子供達の靴の情報を登録・管理する機能を、アプリ分割します。
docker-compose run web python ./manage.py startapp shoes. ├── docker-compose.yml ├── nginx │ ├── conf │ │ └── mysite_nginx.conf │ └── uwsgi_params ├── src │ ├── manage.py │ ├── media │ │ └── image │ │ └── noimage.png │ ├── mysite │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── 略 │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── shoes │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── 略 │ │ ├── admin.py │ │ ├── apps.py │ │ ├── forms.py │ │ ├── migrations │ │ │ └── 略 │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── static │ │ └── 略 │ ├── templates │ │ ├── base.html │ │ ├── shoes │ │ │ ├── shoes_data_add.html │ │ │ ├── shoes_data_edit.html │ │ │ └── shoes_data_list.html │ │ └── users │ │ ├── kids_profile_add.html │ │ ├── kids_profile_edit.html │ │ ├── login.html │ │ ├── mypage.html │ │ └── signup.html │ └── users │ ├── __init__.py │ ├── __pycache__ │ │ └── 略 │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ └── 略 │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── static │ └── 略 └── web ├── Dockerfile └── requirements.txtModel
靴情報は子供ごとに持つので、userとKidsProfileのモデルを外部参照。
靴のサイズは一般的に0.5ずつ上がるものなので、range関数を使って選択肢を生成。
ただしrange関数は整数しか扱えないので、うまいことやってあげる必要あり。そして靴の画像を撮らないと、なんの靴のことを記録してるのかわからないので
画像をアップロードできるようにします。ImageField。ただしアップロードしない
人もいるかもしれないので、そんなときのためにdefaultの画像はこちらで用意。shoes/models.pyfrom django.db import models from django.conf import settings from django.utils import timezone from users.models import KidsProfile #靴データ class ShoesData(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,null=True) kidsProfile = models.ForeignKey(KidsProfile, on_delete=models.CASCADE,null=True) buy_date = models.DateField(default=timezone.now) shoes_size = models.DecimalField( max_digits=3, decimal_places=1, choices = ((x/2, str(x/2)) for x in range(16,50,1)), blank = False ) shoes_memo = models.TextField(blank=True, null=True) shoes_image = models.ImageField(upload_to="image/", blank=True, default="image/noimage.png") def __str__(self): return self.kidsProfile.name + str(self.buy_date)Form
モデルに合わせてFormも作ります。
追加するときや参照するときは子供の情報が必要なのですが、
編集するときは「どの子の情報を直すか」は分かっているので、Edito用のFormも作ります。
なんだか冗長な気がしましたが、これ以上のやり方もわからず。forms.pyfrom django import forms from .models import ShoesData from users.models import KidsProfile import bootstrap_datepicker_plus as datetimepicker class ShoesDataForm(forms.ModelForm): class Meta: model = ShoesData fields = ('kidsProfile', 'buy_date', 'shoes_size', 'shoes_memo', 'shoes_image') widgets = { 'buy_date': datetimepicker.DatePickerInput( format='%Y-%m-%d'), } def __init__(self, *args, **kwargs): user = kwargs.pop('user','') super(ShoesDataForm, self).__init__(*args, **kwargs) self.fields['kidsProfile'] = forms.ModelChoiceField(queryset=KidsProfile.objects.filter(user=user)) class ShoesDataEditForm(forms.ModelForm): class Meta: model = ShoesData fields = ('buy_date', 'shoes_size', 'shoes_memo', 'shoes_image') widgets = { 'buy_date': datetimepicker.DatePickerInput( format='%Y-%m-%d'), } def __init__(self, *args, **kwargs): user = kwargs.pop('user','') kidsProfileId = kwargs.pop('kidsProfileId','') super(ShoesDataEditForm, self).__init__(*args, **kwargs)画像を扱おう(準備)
扱うために3つほど導入します。
・pillow
ImageFieldを扱うためのは、pillowというライブラリが必要です。・django-cleanup
ImageFieldは、あくまでImageFieldオブジェクトなので、実際のjpegとかpngを管理はしてくれません。
つまり、ImageFieldを削除しても、画像ファイルは削除してくれないのです。(ゴミが残り続ける!)
そんなときのために、django-cleanupというライブラリを使います。・Lightbox
画像をぽわーんとそれっぽく表示するやつです。
必要なCSSとJSは、STATICフォルダに格納しておいてください。requirement.txtDjango==2.2.2 psycopg2==2.8.4 uwsgi==2.0.17 django-bootstrap4==1.1.1 django-bootstrap-datepicker-plus==3.0.5 pillow==7.0.0 #追加 django-cleanup==4.0.0 #追加ImageFieldで指定したupload_toは、setting.pyで指定したMEDIAフォルダに行きます。
setting.pyにも、いろいろ追加しましょう。setting.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users.apps.UsersConfig', 'shoes.apps.ShoesConfig', #追加 'bootstrap4', 'bootstrap_datepicker_plus', 'django_cleanup.apps.CleanupConfig', #追加 ] (略) MEDIA_ROOT = os.path.join(BASE_DIR, 'media') #追加 MEDIA_URL = '/media/' #追加HTML/CSS
一覧表示するTemplate。モーダルを使って削除情報出すのは、前回と同様。
ポイント
(shoes_data_list)
・子供情報はページ左部にlist-group形式で並べて、選択するとそのIDを持たせて再表示。
urls.py(とview.py)では、IDがくるときと、IDがこないときで待ち受け。
・画像ファイルは、登録がなかったらno-imageを表示させる。
・画像ファイルはbackground-imageで扱うことで、レスポンシブ対応に。
画像サイズのレスポンシブ設定はCSSにて。(shoes_data_list)
・Lightboxとdatepicker用のブロックコメントの順番は、これじゃないとダメ。
datepickerのポップアップが機能しなくなります。(結構ハマった)
・メニューもちゃんとリンクを貼るようにしてみた。
アプリを分けているので、ちゃんと名前指定(shoes:みたいに)しないとダメ。(shoes_data_add,shoes_data_edit)
ひねりなし。言うこともなし。shoes/shoes_data_list.html{% extends 'base.html' %} {% block extra_js %} <script> $(function() { $('.del_confirm').on('click', function () { $("#del_pk").text($(this).data("pk")); $('#del_url').attr('href', $(this).data("url")); $(this).attr('href', href); }); }); </script> {% endblock extra_js %} {% block content %} <div class="row"> <div class="col-md-12 col-lg-2"> <div class="list-group"> {% for kidsProfile in kidsProfiles %} <a href="/shoes/list/{{kidsProfile.id}}" class="list-group-item list-group-item-action">{{kidsProfile.name}}</a> {% endfor %} </div> </div> <div class="col-md-12 col-lg-10 overflow-auto"> <div class="card"> <div class="card-header"> {{ kidsName }} </div> <div class="card-body"> <div class="container-fluid"> {% for shoes_data_post in shoes_data_posts %} <div class="row"> {% if shoes_data_post.shoes_image %} <a href="{{ shoes_data_post.shoes_image.url }}" style="background-image: url({{ shoes_data_post.shoes_image.url }})" class="col-4 col-lg-2 border shoes_img" data-lightbox="demo"></a> {% else %} <a href="/media/image/noimage.png" style="background-image: url(/media/image/noimage.png)" class="col-4 col-lg-2 border shoes_img" data-lightbox="demo"></a> {% endif %} <div class="col-8 col-lg-10 pl-lg-5"> <div class="row"> <div class="col-md-12 col-lg-2 mb-2 rounded bg-secondary">名前</div> <div class="col-md-12 col-lg-3 mb-2">{{ shoes_data_post.kidsProfile }}</div> <div class="col-md-0 col-lg-7 "></div> <div class="col-md-12 col-lg-2 mb-2 rounded bg-secondary">購入日</div> <div class="col-md-12 col-lg-3 mb-2">{{ shoes_data_post.buy_date }}</div> <div class="col-md-0 col-lg-1 "></div> <div class="col-md-12 col-lg-2 mb-2 rounded bg-secondary">サイズ</div> <div class="col-md-12 col-lg-3 mb-2">{{ shoes_data_post.shoes_size }}cm</div> <div class="col-md-0 col-lg-1 "></div> <div class="w-100"></div> <div class="col-lg-2 rounded bg-secondary">メモ</div> <div class="col-lg-10">{{ shoes_data_post.shoes_memo }}</div> <div class="col-12 text-right mt-2"> <a href="{% url 'shoes:shoes_data_edit' shoes_data_post.id %}" role="button" class="btn btn-success btn-sm">編集</a> <button type="button" class="btn btn-danger btn-sm del_confirm" data-toggle="modal" data-target="#delete_modal" data-pk="{{ shoes_data_post.pk }}" data-url="{% url 'shoes:shoes_data_delete' shoes_data_post.id %}">削除</button> </div> </div> </div> </div> <hr> {% endfor %} </div> </div> </div> <a href="{% url 'shoes:shoes_data_add' %}" role="button" class="btn btn-primary mr-auto">追加</a> <div class="modal fade" id="delete_modal" tabindex="-1" role="dialog" aria-labelledby="label1" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-body"> 削除しても良いですか? </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">キャンセル</button> <a href="#" id="del_url" class="btn btn-danger">削除</a> </div> </div> </div> </div> </div> {% endblock %}base.html{% load static %} <html> <head> <title>kids Growth</title> <link rel="stylesheet" href="{% static 'css/kidsGrowth.css' %}"> <link rel="stylesheet" href="{% static 'css/lightbox.min.css' %}"> <script src="{% static 'js/lightbox-plus-jquery.min.js' %}"></script> {% block extra_js %}{% endblock %} {% load bootstrap4 %} {% bootstrap_css %} {% bootstrap_javascript %} </head> <body> <!-- Navigation --> <nav class="navbar navbar-expand-sm navbar-dark bg-dark mt-3 mb-3"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav4" aria-controls="navbarNav4" aria-expanded="false" aria-label="Toggle navigation"> <span class="sr-only">メニュー</span> <span class="navbar-toggler-icon"></span> </button> <a class="navbar-brand" href="">Kids Growth</a> <div class="collapse navbar-collapse" id="navbarNav4"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="">メニュー1</a> </li> <li class="nav-item"> <a class="nav-link" href="">メニュー2</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'shoes:shoes_data_list'%}">シューズ一覧</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'users:mypage'%}">マイページ</a> </li> {% if user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="{% url 'users:logout'%}">ログアウト</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="{% url 'users:login'%}">ログイン</a> </li> {% endif %} </ul> </div> </nav> <div class="content container"> {% block content %} {% endblock %} </div> </body> </html>shoes/shoes_data_add.html{% extends 'base.html' %} {% block content %} <!--日付入力フォーマット用--> {{ form.media }} <div class="col-md-12 col-lg-5"> <h2>New data</h2> <form method="POST" class="post-form" enctype="multipart/form-data">{% csrf_token %} {% bootstrap_form form %} <button type="submit" class="save btn btn-primary">Save</button> </form> </div> {% endblock %}shoes/shoes_data_edit.html{% extends 'base.html' %} {% block content %} <!--日付入力フォーマット用--> {{ form.media }} <div class="col-md-12 col-lg-5"> <h2>Data Edit</h2> <form method="POST" class="post-form" enctype="multipart/form-data">{% csrf_token %} {% bootstrap_form form %} <button type="submit" class="save btn btn-primary">Save</button> </form> </div> {% endblock %}kidsGrowth.css.shoes_img { background-size: cover; background-position: center center; }shoes/urls.pyfrom django.urls import path from . import views app_name = 'shoes' urlpatterns = [ path('shoes/list/', views.shoes_data_list, name='shoes_data_list'), path('shoes/list/<kidsProfileId>', views.shoes_data_list, name='shoes_data_list'), path('shoes/data_add/', views.shoes_data_add, name='shoes_data_add'), path('shoes/data_edit/<shoesDataId>', views.shoes_data_edit, name='shoes_data_edit'), path('shoes/data_delete/<shoesDataId>', views.shoes_data_delete, name='shoes_data_delete'), ]View
最後にView。なんか、もっと上手く書けるような気がしてならないですが。。
(shoes_data_list)
・初期表示の際に、最初に登録された子供(IDが一番若い)の情報を出すように。(shoes_data_add)
・画像ファイルはrequest.FILES.get()で受け取らないといけないらしい。
アップロードが面倒な人もいるかもしれないので、Validation回避。(shoes_data_edit)
・django-cleanupを動かすために、変更前のレコードはdelete()している。
UpdateではなくCreate&Deleteすることで、紐付く画像ファイル自体を自動で削除。sheoes/views.pyfrom django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .models import ShoesData from users.models import KidsProfile from .forms import ShoesDataForm, ShoesDataEditForm #靴リスト @login_required def shoes_data_list(request, **kwargs): user_name = request.user if len(kwargs) > 0: kidsProfileId = kwargs["kidsProfileId"] else: kidsProfileId = KidsProfile.objects.filter(user=user_name).order_by('id')[0].id kids_profiles = KidsProfile.objects.filter(user=user_name) #子供情報選択用 kids_profile = KidsProfile.objects.filter(id=kidsProfileId) shoes_data_posts = ShoesData.objects.filter(user=user_name, kidsProfile=kidsProfileId).order_by('buy_date') params = { 'shoes_data_posts' : shoes_data_posts, 'kidsProfiles' : kids_profiles, 'kidsName' : kids_profile[0].name, } return render(request, 'shoes/shoes_data_list.html', params) #靴データを新規追加 @login_required def shoes_data_add(request): user_name = request.user if request.method == 'POST': form = ShoesDataForm(request.POST, user = user_name) if form.is_valid(): data = form.save(commit=False) if request.FILES.get('shoes_image') is None: pass else: data.shoes_image = request.FILES.get('shoes_image') data.user = user_name data.save() return redirect('shoes:shoes_data_list') else: form = ShoesDataForm(user = user_name) return render(request, 'shoes/shoes_data_add.html', {'form': form}) #靴データを編集 @login_required def shoes_data_edit(request, shoesDataId): shoes_data = ShoesData.objects.get(pk=shoesDataId) if request.method == 'POST': form = ShoesDataEditForm(request.POST) if form.is_valid(): data = form.save(commit=False) data.user = request.user data.kidsProfile = shoes_data.kidsProfile data.shoes_image = request.FILES.get('shoes_image') data.save() shoes_data.delete() return redirect('shoes:shoes_data_list') else: form = ShoesDataEditForm( { 'buy_date' : shoes_data.buy_date , 'shoes_size' : shoes_data.shoes_size , 'shoes_memo' : shoes_data.shoes_memo , 'shoes_image' : shoes_data.shoes_image }, user = request.user ) return render(request, 'shoes/shoes_data_edit.html', {'form': form}) #靴データを削除 @login_required def shoes_data_delete(request, shoesDataId): ShoesData.objects.get(id=shoesDataId).delete() return redirect('shoes:shoes_data_list')動かすよ〜
- 投稿日:2020-02-13T00:08:32+09:00
Raspberry Pi上のDockerで動くイメージのCIでのビルド方法
発端
x86_64アーキテクチャ上でビルドしたDockerイメージが、Raspberry Pi上で動かない。
(Kubernetesクラスター on Raspberry Piでうまく動かなかった)エラー内容
standard_init_linux.go:211: exec user process caused "exec format error"理由
ビルドしたマシンと、Raspberry PiとでCPUアーキテクチャが異なるため。
# 普段使ってるLinux $ uname -m x86_64 # Raspberry Pi $ uname -m armv7l対策1
Raspberry Pi上でイメージをビルドする。
この時、FROM node
ではなく、FROM arm32v7/node
等、arm用のベースイメージに変更する必要がある(場合もある)。対策2
対策1には実機が必要になる。
CIでイメージを作成したい場合等には利用できない。(ほとんどのCIはx86_64アーキテクチャなので)そこで、multiarch/qemu-user-staticを利用することで、x86_64アーキテクチャ上でarm用イメージをビルドできるようになる。
例
name: main on: [push, pull_request] jobs: main: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: setup docker for arm run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: build image run: docker build -f Dockerfile.arm -t owner/name:arm . - name: push image run: | docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASS }} docker push owner/name:arm