【GoogleHomeでメモ帳アプリを作る】7. 使い勝手を良くする
概要
いままで作ったアプリの使い勝手を良くする
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
やること
やることは以下
- Herokuをスリープさせないようにする
- アプリの名前を変える
- ショートカットを作って呼び出しを簡単にする
- Herokuのアプリの名前を変更する
1. Herokuをスリープさせないようにする
現時点の課題:herokuがスリープしてしまってタイムアウトする
GoogleHomeからactions on googleのfullfilmentから実行するためには、5秒以内に返さなくてはいけないらしい
参考公式 - https://developers.google.com/actions/sdk/deploy-fulfillment
Note: Your fulfillment must respond within 5 seconds or the Assistant ends your conversation due to a timeout.
https://dialogflow.com/docs/fulfillment#limits
Timeout for service response – 5 seconds.
- herokuは無料版だと30分以上操作がないときはスリープしてしまう
- スリープを解除して応答するには時間がかかり、5秒のタイムアウトを超えてしまうことがしばしばある
- これでは使い物にならないので、herokuをスリープさせないようにする必要がある
参考 herokuの無料枠で使える時間 - https://devcenter.heroku.com/articles/free-dyno-hours#usage
Accounts are given a base of 550 hours each month in which your Free dynos can run. In addition to these base hours, accounts which verify with a credit cardwill receive an additional 450 hours to the monthly Free dyno quota.
http://awesome-linus.tk/2017/12/14/heroku-pricing/
クレジットカード認証なしだと、月に550時間、クレジットカード認証ありだと月に1000時間(550+450)使える
スケジューラを設定して定期的に叩く
公式
以下を参考に設定
- https://himakan.net/websites/heroku_free_plan_doesnt_sleep
- https://blog.mktia.com/how-to-avoid-to-sleep-the-application-on-heroku/
こんなのもあった
- https://quickleft.com/blog/6-easy-ways-to-prevent-your-heroku-node-app-from-sleeping/
- https://medium.com/@pandachain/keep-free-heroku-app-awake-during-a-specific-period-using-google-app-script-in-2017-63fe37ee9e9f
[~/git/ludwig125-heroku/googlehome] $heroku addons:create scheduler:standard (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.26-5726b6f Creating scheduler:standard on ⬢ aqueous-peak-42683... free This add-on consumes dyno hours, which could impact your monthly bill. To learn more: http://devcenter.heroku.com/addons_with_dyno_hour_usage To manage scheduled jobs run: heroku addons:open scheduler Created scheduler-horizontal-89891 Use heroku addons:docs scheduler to view documentation [~/git/ludwig125-heroku/googlehome] $
UIで以下のコマンドを10分おきに実行するようにする
if [ $(date --date "9hours" +%H) -ge 06 -o $(date --date "9hours" +%H) -le 02 ]; then curl https://aqueous-peak-42683.herokuapp.com ; fi
コマンドの説明
- curlでアプリのURLを指定して実行する
- 流石に午前2時から6時の間は使うことも無いはずなので、それ以外の時間に叩くようにしたい
- herokuの時間はGMT標準時なので、日本時間より9時間前
- そこで、「if [ $(date --date "9hours" +%H) -ge 06 -o $(date --date "9hours" +%H) -le 02 ]」とする
指定の時刻に実行できているか確認
heroku logs の結果を見る
2018-03-01T14:54:57.067107+00:00 app[scheduler.6122]: </body> 2018-03-01T14:54:57.067108+00:00 app[scheduler.6122]: </html> 2018-03-01T15:05:47.980054+00:00 app[api]: Starting process with command `if [ $(date --date "9hours" +%H) -ge 06 -o $(date --date "9hours" +%H) -le 02 ]; then curl https://aqueous-peak-42683.herokuapp.com ; fi` by user scheduler@addons.heroku.com 2018-03-01T15:05:54.034812+00:00 heroku[scheduler.5767]: Starting process with command `if [ $(date --date "9hours" +%H) -ge 06 -o $(date --date "9hours" +%H) -le 02 ]; then curl https://aqueous-peak-42683.herokuapp.com ; fi` 2018-03-01T15:05:54.827763+00:00 heroku[scheduler.5767]: State changed from starting to up 2018-03-01T15:05:56.537126+00:00 heroku[router]: at=info method=GET path="/" host=aqueous-peak-42683.herokuapp.com request_id=86071458-7414-42f7-b246-e056260137f6 fwd="174.129.137.75" dyno=web.1 connect=1ms service=7ms status=404 bytes=2344 protocol=https 2018-03-01T15:05:56.536624+00:00 app[web.1]: Not Found: / 2018-03-01T15:05:56.537059+00:00 app[web.1]: 10.151.49.221 - - [02/Mar/2018:00:05:56 +0900] "GET / HTTP/1.1" 404 2161 "-" "curl/7.47.0" 2018-03-01T15:05:56.265126+00:00 app[scheduler.5767]: Dload Upload Total Spent Left Speed 2018-03-01T15:05:56.265039+00:00 app[scheduler.5767]: % Total % Received % Xferd Average Speed Time Time Time Current 2018-03-01T15:05:56.699372+00:00 heroku[scheduler.5767]: State changed from up to complete (略)
エラーは出しているものの、叩けているのでこれでOK
アプリが起動しているかスリープしているかはアプリのアイコンを見ればわかる - https://blog.heroku.com/app_sleeping_on_heroku
2. アプリの名前を変える
actions on googleから変更する アプリとして登録するために、アプリ用のアイコンの画像が必要だったりするので結構めんどい そして、アプリ用の画像の判定が厳しすぎて辛い・・・
アプリの名前は簡単に「メモ帳」とした
これで、以下の言い方でつながる様になった
呼び出し方1
「メモ帳につないで」
↓
「はい、メモ帳のテストバージョンです。こんにちは」
↓
「メモを読んで」
↓
直近のメモが読まれる
呼び出し方2
上の方法だと冗長なので、以下のような言い方で一度に実行することもできる
「メモ帳を使ってメモを読んで」
↓
「はい、メモ帳のテストバーンジョンです。こんにちは」
↓
直近のメモが読まれる
3. ショートカットを作って呼び出しを簡単にする
GoogleHomeでショートカットを作成することで、「メモ帳を使ってメモを読んで」をもっと簡略化した言い方で済むようにする
ショートカットの言葉 - 「メモを読んで」 - 「メモ読んで」
↓
実際に実行される命令 - 「メモ帳を使ってメモを読んで」
4. Herokuのアプリの名前を変更する
- ずっとherokuが自動で発行したアプリ名「aqueous-peak-42683 」を使っていたので、これをわかりやすい名前に変える
変え方参考 - https://devcenter.heroku.com/articles/renaming-apps
$heroku apps:rename 新しいアプリ名
合わせて、dialogflowのFulfillmentで呼び出すURLと、HerokuのスケジューラでcurlするURLを変える必要があるので注意
【GoogleHomeでメモ帳アプリを作る】5. HerokuアプリでSpreadSheetの中身を取得する
概要
以前helloアプリは作ったので、ここではSpreadSheetからデータを取得するアプリを作る
この前まででSpreadSheetは用意できているので、 ここでは、Spreadsheetからデータを取得するアプリケーションを「memorandum」という名前でDjangoに追加する
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
memorandumアプリ
アプリ作成のおさらい
まずはhelloのおさらいを兼ねて、動くアプリを作成してみる
アプリの作り方は公式のチュートリアルの通り、 https://docs.djangoproject.com/ja/2.0/intro/tutorial01/#creating-the-polls-app
python manage.py startapp memorandum でいい
事前にpipenv shellを起動しておく
[~/git/ludwig125-heroku/googlehome] $pipenv shell (git)-[master] Loading .env environment variables… Spawning environment shell (/usr/bin/zsh). Use 'exit' to leave. source /home/ludwig125/.local/share/virtualenvs/googlehome-Z81pgPAi/bin/activate [~/git/ludwig125-heroku/googlehome] $source /home/ludwig125/.local/share/virtualenvs/googlehome-Z81pgPAi/bin/activate (git)-[master] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
アプリ作成
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $python manage.py startapp memorandum (git)-[master] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $ls -l (git)-[master] 合計 84 -rw-rw-r-- 1 ludwig125 ludwig125 183 2月 4 02:07 Pipfile -rw-rw-r-- 1 ludwig125 ludwig125 7710 2月 4 22:54 Pipfile.lock -rw-rw-r-- 1 ludwig125 ludwig125 43 2月 4 03:00 Procfile -rw-r--r-- 1 ludwig125 ludwig125 38912 2月 4 00:50 db.sqlite3 drwxrwxr-x 3 ludwig125 ludwig125 4096 2月 4 23:39 googlehome/ drwxrwxr-x 4 ludwig125 ludwig125 4096 2月 4 02:38 hello/ -rwxrwxr-x 1 ludwig125 ludwig125 542 2月 3 23:57 manage.py* drwxrwxr-x 3 ludwig125 ludwig125 4096 2月 7 23:55 memorandum/ -rw-rw-r-- 1 ludwig125 ludwig125 269 2月 4 23:05 requirements.txt drwxrwxr-x 3 ludwig125 ludwig125 4096 2月 4 02:11 staticfiles/ (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $ (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $tree memorandum (git)-[master] memorandum ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py 1 directory, 7 files (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
views.py
おさらいで、とりあえず文字を返すアプリを作って動作を確認する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat memorandum/views.py (git)-[master]
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the memorandum index.")
urls.py
helloのときと同様に以下のファイルを追加
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat memorandum/urls.py (git)-[master] from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
ここも同様に、プロジェクトのurlsに上のurlsを認識させる
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat googlehome/urls.py (git)-[master] from django.contrib import admin from django.urls import include, path urlpatterns = [ path('hello/', include('hello.urls')), path('memorandum/', include('memorandum.urls')), ← ここを追加 path('admin/', admin.site.urls), ] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
ローカルでアプリ立ち上げ
ここまででアプリを立ち上げる
http://127.0.0.1:8000/memorandum/ ブラウザで以下が返ってきた
→ 「Hello, world. You're at the memorandum index.」
herokuに反映
ここまでをherokuにも反映する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git add memorandum/ (git)-[master] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git commit -m 'add initial memorandum' . (git)-[master] [master 9122dd7] add initial memorandum 9 files changed, 27 insertions(+) create mode 100644 memorandum/__init__.py create mode 100644 memorandum/admin.py create mode 100644 memorandum/apps.py create mode 100644 memorandum/migrations/__init__.py create mode 100644 memorandum/models.py create mode 100644 memorandum/tests.py create mode 100644 memorandum/urls.py create mode 100644 memorandum/views.py (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $ (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git push heroku master (git)-[master] Counting objects: 15, done. Compressing objects: 100% (11/11), done. Writing objects: 100% (11/11), 1.36 KiB | 0 bytes/s, done. Total 11 (delta 5), reused 0 (delta 0) remote: Compressing source files... done.
リモートのherokuを確認
$heroku open
https://aqueous-peak-42683.herokuapp.com/memorandum/ → 「Hello, world. You're at the memorandum index.」
おさらい終わり
今作ったアプリをもとに、動作を確認しつつ修正していく
GoogleAPI秘密鍵の取得とSpreadSheetの共有設定
以下を参考に、GoogleAPIを取得し、SpreadSheetをAPIで呼べるようにする
参考 - https://qiita.com/koyopro/items/d8d56f69f863f07e9378 - https://qiita.com/AAkira/items/22719cbbd41b26dbd0d1 - http://www.yoheim.net/blog.php?q=20160205
GoogleAPIのクライアント認証用JSONを取得
cloud-resource-manager - https://console.developers.google.com/cloud-resource-manager ここで適当なプロジェクトを作成
ここでは、「getMymemo」というプロジェクトを作成する
上の参考を見ながら、作っていく
https://console.developers.google.com/cloud-resource-manager から 「APIとサービス」→ 「APIとサービスの有効化」からGoogle Drive APIを選択
→「認証情報」→「認証情報を作成」→「サービスアカウントキー」
SpreadSheetの共有設定
「memorandum」シートに上記で発行されたメールアドレスを登録
pythonでGoogleSpreadSheetにアクセスできるか確認
JSONファイルを直接使う
client認証用のjsonを以下に置く
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat google_client.json (git)-[master] { "type": "service_account", "project_id": "ludwig125-37f7c", "private_key_id": XXXXXX "private_key": XXXXXX "client_email": XXXXXX "client_id": "XXXXXX", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "XXXXXX" } (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
パッケージ追加
pythonでSpreadSheetにアクセスするために以下のパッケージを入れておく
pip install gspread pip install "oauth2client<2.0" pip install pycrypto
pythonでSpreadSheetのデータを取得する
pythonでSpreadsheetのデータが取れるか確認する
gspreadのリファレンスは以下 - http://gspread.readthedocs.io/en/latest/
サンプルスクリプト作成
このときのspreadsheetは以下のデータが存在している状態
テスト | ||
テスト に | ||
1517843323 | 2018/2/6 0:08 | テスト さん |
1517843115 | 2018/2/6 0:05 | test |
1517844297 | 2018/2/6 0:24 | test2 |
1518354065 | 2018/2/11 22:01 | test3 |
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome/sample] $cat get_memo.py
import json import gspread import oauth2client.client json_key = json.load(open('../google_client.json')) scope = ['https://spreadsheets.google.com/feeds'] credentials = oauth2client.client.SignedJwtAssertionCredentials(json_key['client_email'], json_key['private_key'].encode(), scope) gc = gspread.authorize(credentials) ss = gc.open("memorandum") sh = ss.worksheet("memo1") values = sh.get_all_values() print(values) # [['', '', 'テスト'], ['', '', 'テスト に'], ['1517843323', '2018/02/06 0:08:43', 'テスト さん'], ['1517843115', '2018/02/06 0:05:15', 'test'], ['1517844297', '2018/02/06 0:24:57', 'test2'], ['1518017810', '2018/02/08 0:36:50', 'test3']] print(len(values)) # 6 last = len(values) - 1 print(values[last]) # ['1517844303', '2018/02/06 0:25:03', 'test3']
実行結果
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome/sample] $python3 get_memo.py (git)-[master] [['', '', 'テスト'], ['', '', 'テスト に'], ['1517843323', '2018/02/06 0:08:43', 'テスト さん'], ['1517843115', '2018/02/06 0:05:15', 'test'], ['1517844297', '2018/02/06 0:24:57', 'test2'], ['1518017810', '2018/02/08 0:36:50', 'test3']] 6 ['1518017810', '2018/02/08 0:36:50', 'test3'] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome/sample] $
きちんと取れている
秘密鍵をファイルではなく環境変数から取るようにする
上のソースコードの方法をherokuでやろうとすると、gitにGoogleAPIの秘密鍵を置かないといけない
これは嫌だ
http://hakobera.hatenablog.com/entry/20111122/1321975203 の辺を見ると、herokuには環境変数を渡すことができそうなので、この方法をやってみる
jsonファイルの内容を環境変数として以下のように設定しておく
export CLIENT_EMAIL="<自分のemail>.com" export PRIVATE_KEY="-----BEGIN PRIVATE KEY-----<改行コード含む長い鍵>\n-----END PRIVATE KEY-----\n"
秘密鍵の情報をファイルから取っていたこの部分を
$ cat get_memo.py json_key = json.load(open('../google_client.json')) scope = ['https://spreadsheets.google.com/feeds'] credentials = oauth2client.client.SignedJwtAssertionCredentials(json_key['client_email'], json_key['private_key'].encode(), scope)
↓
$cat get_memo_with_env.py scope = ['https://spreadsheets.google.com/feeds'] client_email = os.environ['CLIENT_EMAIL'] private_key = os.environ['PRIVATE_KEY'].replace('\\n', '\n').encode('utf-8') credentials = oauth2client.client.SignedJwtAssertionCredentials(client_email, private_key, scope)
こんな感じに変えて、取得できることを確認する
※ private_keyは改行コードを含んでおり、これをenvironで取得すると「\」が「\\」になってしまったので、置換して「\n」にした 参考 - https://docs.python.jp/3/library/os.html - https://webkaru.net/linux/export-command/ - https://docs.python.jp/3/howto/unicode.html#converting-to-bytes
実行してスプレッドシートから取得できることを確認
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome/sample] $python3 get_memo_with_env.py (git)-[master] [['', '', 'テスト'], ['', '', 'テスト に'], ['1517843323', '2018/02/06 0:08:43', 'テスト さん'], ['1517843115', '2018/02/06 0:05:15', 'test'], ['1517844297', '2018/02/06 0:24:57', 'test2'], ['1518017810', '2018/02/08 0:36:50', 'test3']] 6 ['1518017810', '2018/02/08 0:36:50', 'test3'] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome/sample] $
ローカルのherokuで実行する
以上のことをもとに、view.pyを修正する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat memorandum/views.py
from django.http import HttpResponse import json import gspread import oauth2client.client import os def get_client(): """ return google spreadsheet client see, http://gspread.readthedocs.io/en/latest/#gspread.authorize """ scope = ['https://spreadsheets.google.com/feeds'] client_email = os.environ['CLIENT_EMAIL'] private_key = os.environ['PRIVATE_KEY'].replace('\\n', '\n').encode('utf-8') credentials = oauth2client.client.SignedJwtAssertionCredentials(client_email, private_key, scope) gc = gspread.authorize(credentials) return gc def get_value(gc): """ get google spreadsheet values return last value """ ss = gc.open("memorandum") sh = ss.worksheet("memo1") values = sh.get_all_values() last = len(values) - 1 return values[last] def index(request): gc = get_client() last_value = get_value(gc) return HttpResponse(last_value)
ローカルで実行
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $heroku local web (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.24-e5de04c [OKAY] Loaded ENV .env File as KEY=VALUE Format 23:40:49 web.1 | [2018-02-09 23:40:49 +0900] [18423] [INFO] Starting gunicorn 19.7.1 23:40:49 web.1 | [2018-02-09 23:40:49 +0900] [18423] [INFO] Listening at: http://0.0.0.0:5000 (18423) 23:40:49 web.1 | [2018-02-09 23:40:49 +0900] [18423] [INFO] Using worker: sync 23:40:49 web.1 | [2018-02-09 23:40:49 +0900] [18426] [INFO] Booting worker with pid: 18426
http://localhost:5000/memorandum/
→ 「'1518017810', '2018/02/08 0:36:50', 'test3'」
取得できた
リモートのherokuで取れるようにする
requirements.txt編集
リモートで使うために必要なパッケージをrequirements.txtに入れておく - ローカルで使ったときにインストールした以下のパッケージを追加する - 何のバージョンを入れたのかは、pip freezeで確認
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $pip freeze | egrep "gspread|oauth2client|pycrypto" (git)-[master] gspread==0.6.2 oauth2client==1.5.2 pycrypto==2.6.1 (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
requirements.txt - 既存のパッケージ以外に以下を追加する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat requirements.txt (git)-[master] (略) gspread==0.6.2 (略) oauth2client<2.0 (略) pycrypto==2.6.1 (略) (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
herokuに環境変数を追加
以下を見るとherokuのリモートに設定値を渡すことができるらしいので、 - https://devcenter.heroku.com/articles/config-vars#example - https://qiita.com/colorrabbit/items/18db3c97734f32ebdfde - http://hakobera.hatenablog.com/entry/20111122/1321975203
上の修正までをコミットしたらherokuに上げる
$ git push heroku master
ここで、heroku 環境変数を設定する
$ heroku config:set CLIENT_EMAIL="XXXX.com" $ heroku config:set PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nXXXXXX-----END PRIVATE KEY-----\n"
herokuの環境変数の設定方法について
- 上のようにheroku config:set をするか、
- ダッシュボードから行う方法がある
参考 - https://devcenter.heroku.com/articles/config-vars#setting-up-config-vars-for-a-deployed-application
設定できたかどうかは以下のコマンドで確認できる
$heroku config
リモートのherokuで確認
https://aqueous-peak-42683.herokuapp.com/memorandum/
→ 「1518017810 2018/02/08 0:36:50 test3」 Spreadsheetの最後のデータがブラウザに表示された
【GoogleHomeでメモ帳アプリを作る】4. IFTTTを使ってSpreadSheetにメモを入力
概要
前回までで色々と必要な知識を得て、準備が整ったのでこれから実際に必要な機能を作っていく
- Spreadsheetに記録する部分の作成
- Ok Google ~でSpreadSheetにメモを記録する
- これはIFTTTを使うと便利
- ここで記録したSpreadSheetの内容を次回以降のherokuアプリから呼び出す
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
IFTTTとGoogleSpreadsheet
IFTTT
アプリの作成
https://ifttt.com/discover - New Appletを選択
if thisの部分
あとから編集した画面なので、初期作成時とは背景の色が違うが、記入内容は同じ
then thisの部分
IFTTT注意
CreatedAtが効かないという問題があるらしい
- https://do-gugan.com/~furuta/archives/2017/10/google-home_spreadsheet.html
- https://qiita.com/miso_develop/items/be562d8a823ad2639d94
IFTTTを試す
GoogleSpreadSheet
IFTTTによって作成されたシートを確認
IFTTTをつくったあとは スプレッドシートが以下のように作られる
GASを追加
上のGoogleHomeに喋って作成したテキストの左の2列に日付を自動で挿入されるようにしたい
「ツール」→「スクリプト エディタ」からGAS(Google Apps Script)を作成する
参考 https://so-zou.jp/web-app/tech/programming/javascript/grammar/object/date.htm
一応プログラムで取得しやすいことを考えてUnixTimeも出すようにしておいた
function addDate() { var sheet = SpreadsheetApp.getActiveSheet(); var lastRow = sheet.getLastRow(); var date = new Date(); var unixTimestamp = Math.round( date.getTime() / 1000 ); sheet.getRange(lastRow, 1).setValue(unixTimestamp); sheet.getRange(lastRow, 2).setValue(date); }
もし1行目に日付と時刻を自動で入れるようにしたければunixTimestampの部分は不要で、以下だけでいい
sheet.getRange(lastRow, 1).setValue(date);
プロジェクト名を決める
時計の形をしたアイコンをクリックして、
プロジェクトのトリガーとして
- 実行:addDate
- イベント:スプレッドシートから 値の変更
【2019/05/13追記】 トリガーの作成方法が変わっていた。以下で上と同じことになる
ためしにデータを追加してみる
「test」「test2」を記入すると、GASによって、隣に勝手に時刻が記載された
【GoogleHomeでメモ帳アプリを作る】3. 実際にHerokuでアプリを作ってみる
概要
herokuやDjangoの公式のチュートリアルをやったけど、自分で一から作るとなると色々わからなかったので理解しながら作る
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
この資料はGoogleHome用のアプリとは直接関係ないのでほとんど飛ばしていいことしか書いていない
herokuのチュートリアル
チュートリアルと同じように自分でも作ってみる
必要なファイルや設定だけをチュートリアルから取って来ることにする
参考 各種説明 - https://devcenter.heroku.com/articles/deploying-python
作る手順
この順序にした理由は後述のProcfileとWebアプリを同じディレクトリ内に置く必要があるため
Djangoアプリの作成
まずDjangoアプリを作成する - 別にDjangoではなくてflaskとかでもいいとは思うが、チュートリアルがDjangoなので真似して作ってみる
Django参考 - https://docs.djangoproject.com/ja/2.0/intro/tutorial01/#creating-a-project
【Django】プロジェクト作成
プロジェクト名とパッケージ名は以下のコマンドだとどちらもgooglehomeになる
[~/git/ludwig125-heroku] $django-admin startproject googlehome [~/git/ludwig125-heroku] $tree (git)-[master] . ── googlehome ├── googlehome │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py 2 directories, 9 files [~/git/ludwig125-heroku] $
【Django】アプリ作成
試しに適当なアプリケーションを作ってみる
参考 - https://docs.djangoproject.com/ja/2.0/intro/tutorial01/#write-your-first-view
helloアプリの作成
作るアプリケーション: hello
- 作り方
python manage.py startapp hello
[~/git/ludwig125-heroku/googlehome] $python manage.py startapp hello [~/git/ludwig125-heroku/googlehome] $tree (git)-[master] . ├── db.sqlite3 ├── googlehome │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── settings.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── wsgi.cpython-36.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── hello │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── manage.py 4 directories, 17 files [~/git/ludwig125-heroku/googlehome] $
view.pyの作成
view.pyを編集
[~/git/ludwig125-heroku/googlehome] $cat hello/views.py (git)-[master]
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the hello index.")
urls.pyの作成
https://docs.djangoproject.com/ja/2.0/intro/tutorial01/#write-your-first-view
ビューを呼ぶために、 URL を対応付けしてやる必要があります。そのためには URLconf が必要です polls ディレクトリに URLconf を作るには urls.py というファイルを作ります。
これを参考にurls.pyを作成する
[~/git/ludwig125-heroku/googlehome] $cat hello/urls.py (git)-[master]
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
ルートのurls.pyの修正
次のステップはルートのURLconfに polls.urls モジュールの記述を反映させることです。
ここも同様に修正する
[~/git/ludwig125-heroku/googlehome] $cat googlehome/urls.py
from django.contrib import admin from django.urls import include, path urlpatterns = [ path('hello/', include('hello.urls')), ←追加 path('admin/', admin.site.urls), ]
【Django】ローカルでWebアプリ起動
ローカルでWebサーバ立ち上げ
ここまでやったら以下でWebサーバを立てる
[~/git/ludwig125-heroku/googlehome] $python manage.py runserver Performing system checks... System check identified no issues (0 silenced). You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. February 03, 2018 - 15:24:45 Django version 2.0, using settings 'googlehome.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. [03/Feb/2018 15:24:59] "GET /hello HTTP/1.1" 301 0 [03/Feb/2018 15:24:59] "GET /hello/ HTTP/1.1" 200 40
以下をブラウザから見てみる
http://127.0.0.1:8000/hello/ ↓ 「Hello, world. You're at the hello index.」が表示された
ここまででDjangoとしてするべきことはやった 次はこれをherokuで開くために色々やることがある
herokuアプリの作成
【heroku】アプリのcreate
Djangoプロジェクトを作成したときの外側のgooglehome直下でherokuアプリを作成する
git 初期化
[~/git/ludwig125-heroku/googlehome] $git init (git)-[master] Initialized empty Git repository in /home/ludwig125/git/ludwig125-heroku/googlehome/.git/ [~/git/ludwig125-heroku/googlehome] $
heroku create
[~/git/ludwig125-heroku/googlehome] $heroku create (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd Creating app... done, ⬢ aqueous-peak-42683 https://aqueous-peak-42683.herokuapp.com/ | https://git.heroku.com/aqueous-peak-42683.git [~/git/ludwig125-heroku/googlehome] $ (git)-[master]
何も指定しないと適当なアプリ名があてがわれる
【heroku】Pipfileとrequirements.txt
以下のファイルだけをとりあえず公式のチュートリアル用リポジトリから持ってきてスタートする https://github.com/heroku/python-getting-started
- requirements.txt
- Pipfile
なぜこの2ファイルかは以下の理由
https://devcenter.heroku.com/articles/getting-started-with-python#declare-app-dependencies
Heroku recognizes an app as a Python app by the existence of a Pipfile or requirements.txt file in the root directory.
herokuでは、Pipfileかrequirements.txtがherokuのルートディレクトリにあると、Pythonアプリだと認識してくれるらしい
[~/git/ludwig125-heroku/googlehome] $cat requirements.txt (git)-[master] certifi==2017.11.5 chardet==3.0.4 Django==2.0 flake8==3.5.0 idna==2.6 mccabe==0.6.1 pew==1.1.2 pipenv==9.0.1 pycodestyle==2.3.1 pyflakes==1.6.0 pytz==2017.3 requests==2.18.4 urllib3==1.22 virtualenv==15.1.0 virtualenv-clone==0.2.6 [~/git/ludwig125-heroku/googlehome] $
[~/git/ludwig125-heroku/googlehome] $cat Pipfile (git)-[master] [[source]] url = "https://pypi.python.org/simple" verify_ssl = true [packages] django = "*" gunicorn = "*" django-heroku = "*" requests = "*" [requires] python_version = "3.6" [~/git/ludwig125-heroku/googlehome] $
【heroku】pipenv実行
pipenv install を実行
Pipfileの内容でインストールする:
参考 - http://pipenv-ja.readthedocs.io/ja/translate-ja/basics.html - http://pipenv-ja.readthedocs.io/ja/translate-ja/#other-commands
[~/git/ludwig125-herokuu/googlehome] $pipenv install (git)-[master] Creating a virtualenv for this project… Using /home/ludwig125/.pyenv/versions/anaconda3-4.4.0/bin/python3.6m to create virtualenv… ⠋Running virtualenv with interpreter /home/ludwig125/.pyenv/versions/anaconda3-4.4.0/bin/python3.6m Using base prefix '/home/ludwig125/.pyenv/versions/anaconda3-4.4.0' New python executable in /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python3.6m Also creating executable in /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python3.6m: error while loading shared libraries: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory ERROR: The executable /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python3.6m is not functioning ERROR: It thinks sys.prefix is '/home/ludwig125/git/ludwig125-heroku' (should be '/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov') ERROR: virtualenv is not compatible with this system or executable Virtualenv location: Warning: Your Pipfile requires python_version 3.6, but you are using None (/bin/python). $ pipenv check will surely fail. Creating a virtualenv for this project… ⠙Using base prefix '/home/ludwig125/.pyenv/versions/3.6.3' New python executable in /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python3.6 Also creating executable in /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/python Installing setuptools, pip, wheel...done. Virtualenv location: /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov Pipfile.lock not found, creating… Locking [dev-packages] dependencies… Locking [packages] dependencies… Updated Pipfile.lock (c8a67b)! Installing dependencies from Pipfile.lock (c8a67b)… � ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 12/12 — 00:00:14 To activate this project's virtualenv, run the following: $ pipenv shell [~/git/ludwig125-herokuu/googlehome] $
soファイルが読み込めずにエラーが出ているが動いてはいるみたい
pipenv shellを実行
http://pipenv-ja.readthedocs.io/ja/translate-ja/#other-commands
shell は仮想環境が有効化されたシェルを起動します
check はセキュリティの脆弱性をチェックし、現在の環境がPEP 508の要求仕様を満たしていることを表明します。
[~/git/ludwig125-herokuu/googlehome] $pipenv check (git)-[master] Checking PEP 508 requirements… Passed! Checking installed package safety… All good! [~/git/ludwig125-herokuu/googlehome] $
[~/git/ludwig125-herokuu/googlehome] $pipenv shell (git)-[master] Spawning environment shell (/usr/bin/zsh). Use 'exit' to leave. source /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/activate [~/git/ludwig125-heroku] $source /home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/bin/activate (googlehome-Z81pgPAi) [~/git/ludwig125-heroku] $
【heroku】Procfile
Profileとは - アプリのスタート時に動くファイルを定義するもの https://devcenter.heroku.com/articles/getting-started-with-python#define-a-procfile
Procfileについて
https://devcenter.heroku.com/articles/deploying-python#the-procfile
A Procfile is a text file in the root directory of your application that defines process types and explicitly declares what command should be executed to start your app.
Your Procfile will look something like this:
web: gunicorn gettingstarted.wsgi --log-file -
チュートリアルのProcfileも同じような感じだった https://github.com/heroku/python-getting-started/blob/master/Procfile
web: gunicorn gettingstarted.wsgi
Procfileの各項目の説明
web: gunicorn gettingstarted.wsgi
web
web, is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed.
log-file
It means "log to stdout". The --log-file flag lets you set a path to a log file, and - means "stdout" (in this context).
参考
- https://stackoverflow.com/questions/26129020/what-does-log-file-mean-in-heroku-procfile
gunicorn gettingstarted.wsgi
- Gunicornはwsgiサーバのことらしい
- WSGI: Web Server Gateway Interface
- https://ja.wikipedia.org/wiki/Web_Server_Gateway_Interface
- プログラミング言語Pythonにおいて、WebサーバとWebアプリケーション(もしくはWebアプリケーションフレームワーク)を接続するための、標準化されたインタフェース定義である。
上のProcfileでは、チュートリアルのアプリケーション名が以下の通りgettingstarted なのでこう書いてあるが、自分のアプリケーションを作るのであれば直さなければならない - https://github.com/heroku/python-getting-started/tree/master/gettingstarted
自分のアプリケーション用にProcfileを作成する
今回はGoogleHome用のサーバを立てたいので、googlehomeという名前を指定する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat Procfile (git)-[master] web: gunicorn googlehome.wsgi --log-file - (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
注意点
- herokuのProcfileは、Webアプリと同じディレクトリにないと認識してくれないみたい
最初は自分は ~/git/ludwig125-heroku/ 以下にProcfileを置いて、その下にDjangoアプリを作成した
そのために、Procfileがあるのは~/git/ludwig125-heroku/googlehomeで、Djangoのアプリケーション本体があるのは一段下のディレクトリになってしまった
以下のように、Procfileとアプリケーションを同じディレクトリに置く必要がある
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $ls (git)-[master] Pipfile Procfile db.sqlite3 googlehome/ hello/ manage.py* requirements.txt staticfiles/ (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $git status (git)-[master]
最初、Procfileを外側の「googlehome」ディレクトリより上に置いてherokuを立ち上げようとしたら全くうまく行かなかった
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku] $heroku local web (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd [OKAY] Loaded ENV .env File as KEY=VALUE Format 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19199] [INFO] Starting gunicorn 19.7.1 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19199] [INFO] Listening at: http://0.0.0.0:5000 (19199) 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19199] [INFO] Using worker: sync 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19202] [INFO] Booting worker with pid: 19202 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19202] [ERROR] Exception in worker process 00:28:35 web.1 | Traceback (most recent call last): 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/arbiter.py", line 578, in spawn_worker 00:28:35 web.1 | worker.init_process() 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/workers/base.py", line 126, in init_process 00:28:35 web.1 | self.load_wsgi() 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/workers/base.py", line 135, in load_wsgi 00:28:35 web.1 | self.wsgi = self.app.wsgi() 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/app/base.py", line 67, in wsgi 00:28:35 web.1 | self.callable = self.load() 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 65, in load 00:28:35 web.1 | return self.load_wsgiapp() 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 52, in load_wsgiapp 00:28:35 web.1 | return util.import_app(self.app_uri) 00:28:35 web.1 | File "/home/ludwig125/.local/share/virtualenvs/ludwig125-heroku-G7FbBSov/lib/python3.6/site-packages/gunicorn/util.py", line 352, in import_app 00:28:35 web.1 | __import__(module) 00:28:35 web.1 | ModuleNotFoundError: No module named 'googlehome.wsgi' 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19202] [INFO] Worker exiting (pid: 19202) 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19199] [INFO] Shutting down: Master 00:28:35 web.1 | [2018-02-04 00:28:35 +0900] [19199] [INFO] Reason: Worker failed to boot. 00:28:35 web.1 Exited with exit code 3 (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku] $
→ ModuleNotFoundError: No module named 'googlehome.wsgi'
【heroku】heroku用にDjangoを少し直す
Djangoをherokuで動かすためには以下の設定が必要
https://devcenter-assets0.herokucdn.com/articles/django-memcache#configure-django-for-heroku
Django requires some special configuration in order to work on Heroku, mainly for the database to work and the static files to be served. Luckily, there is adjango-heroku package that takes care of all that. So on the bottom of the file django_queue/settings.py add the following lines:
# Configure Django App for Heroku. import django_heroku django_heroku.settings(locals())
ということで、hello/view.pyを以下のように修正する
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the hello index.")
【heroku】herokuアプリをローカルで立ち上げる
heroku local web
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $heroku local web (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd [OKAY] Loaded ENV .env File as KEY=VALUE Format 22:55:23 web.1 | [2018-02-04 22:55:23 +0900] [11851] [INFO] Starting gunicorn 19.7.1 22:55:23 web.1 | [2018-02-04 22:55:23 +0900] [11851] [INFO] Listening at: http://0.0.0.0:5000 (11851) 22:55:23 web.1 | [2018-02-04 22:55:23 +0900] [11851] [INFO] Using worker: sync 22:55:23 web.1 | [2018-02-04 22:55:23 +0900] [11854] [INFO] Booting worker with pid: 11854
ローカルのブラウザでは見られるようになった http://0.0.0.0:5000/hello/ Hello, world. You're at the hello index.
.envの追加
上のheroku local webを最初にやったときは以下のWARNが出た
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku] $heroku local web (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd [WARN] No ENV file found 23:27:09 web.1 | [2018-02-03 23:27:09 +0900] [14824] [INFO] Starting gunicorn 19.7.1 23:27:10 web.1 | [2018-02-03 23:27:09 +0900] [14824] [INFO] Listening at: http://0.0.0.0:5000 (14 824)
→ 「[WARN] No ENV file found」 そこで、公式のチュートリアルから.envをコピーした - https://github.com/heroku/python-getting-started/blob/master/.env
参考 - https://stackoverflow.com/questions/37690780/heroku-local-warn-no-env-file-found
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku] $cat .env (git)-[master] TIMES=2 (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku] $
【heroku】herokuのリモートにデプロイ
この状態でherokuにデプロイしてみよう
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $git add . (git)-[master] (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $git commit -m 'new heroku app' . (git)-[master] [master (root-commit) a80e0db] new heroku app 489 files changed, 55618 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Procfile create mode 100644 db.sqlite3 略 create mode 100644 staticfiles/staticfiles.json (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $ (git)-[master]
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $git push heroku master (git)-[master] Counting objects: 280, done. Compressing objects: 100% (274/274), done. Writing objects: 100% (280/280), 871.41 KiB | 0 bytes/s, done. Total 280 (delta 44), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected remote: ! No 'Pipfile.lock' found! We recommend you commit this into your repository. remote: -----> Installing python-3.6.4 remote: -----> Installing pip remote: -----> Installing requirements with pip remote: Collecting certifi==2017.11.5 (from -r /tmp/build_bee926373efffef7cab46947c96c8691/requirements.txt (line 1)) (中略) remote: File "<frozen importlib._bootstrap_external>", line 678, in exec_module remote: File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed remote: File "/tmp/build_bee926373efffef7cab46947c96c8691/googlehome/settings.py", line 14, in <module> remote: import django_heroku remote: ModuleNotFoundError: No module named 'django_heroku' remote: remote: ! Error while running '$ python manage.py collectstatic --noinput'. remote: See traceback above for details. remote: remote: You may need to update application code to resolve this error. remote: Or, you can disable collectstatic for this application: remote: remote: $ heroku config:set DISABLE_COLLECTSTATIC=1 remote: remote: https://devcenter.heroku.com/articles/django-assets remote: ! Push rejected, failed to compile Python app. remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to aqueous-peak-42683. remote: To https://git.heroku.com/aqueous-peak-42683.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/aqueous-peak-42683.git' (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $
remote: $ heroku config:set DISABLE_COLLECTSTATIC=1 と出ている
collectstatic は不要なので以下をする
$ heroku config:set DISABLE_COLLECTSTATIC=1
参考 - https://devcenter.heroku.com/articles/django-assets#disabling-collectstatic - http://thinkami.hatenablog.com/entry/2017/01/13/053643
(ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $heroku config:set DISABLE_COLLECTSTATIC=1 (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd Setting DISABLE_COLLECTSTATIC and restarting ⬢ aqueous-peak-42683... done, v3 DISABLE_COLLECTSTATIC: 1 (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $git push heroku master (git)-[master] Counting objects: 280, done. Compressing objects: 100% (274/274), done. Writing objects: 100% (280/280), 871.41 KiB | 0 bytes/s, done. Total 280 (delta 44), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected (中略) remote: Released v5 remote: https://aqueous-peak-42683.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/aqueous-peak-42683.git * [new branch] master -> master (ludwig125-heroku-G7FbBSov) [~/git/ludwig125-heroku/googlehome] $
pushできた!
heroku open とすると以下のURLでブラウザが開く https://aqueous-peak-42683.herokuapp.com/
「Application error」
ログの確認
困ったときはheroku logsでログを確認する
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $heroku logs (git)-[master] ▸ heroku-cli: update available from 6.14.43-73d5876 to 6.15.22-3f1c4bd 2018-02-04T13:55:51.472389+00:00 app[web.1]: self.callable = self.load() 2018-02-04T13:55:51.472390+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 65, in load (中略) 2018-02-04T13:55:51.416412+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.6/site-packages/django/conf/__init__.py", line 106, in __init__ 2018-02-04T13:55:51.416414+00:00 app[web.1]: mod = importlib.import_module(self.SETTINGS_MODULE) 2018-02-04T13:55:51.416416+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.6/importlib/__init__.py", line 126, in import_module 2018-02-04T13:55:51.416417+00:00 app[web.1]: return _bootstrap._gcd_import(name[level:], package, level) 2018-02-04T13:55:51.416419+00:00 app[web.1]: File "/app/googlehome/settings.py", line 14, in <module> 2018-02-04T13:55:51.416420+00:00 app[web.1]: import django_heroku 2018-02-04T13:55:51.416430+00:00 app[web.1]: ModuleNotFoundError: No module named 'django_heroku' 2018-02-04T13:55:51.416774+00:00 app[web.1]: [2018-02-04 13:55:51 +0000] [8] [INFO] Worker exiting (pid: 8) 2018-02-04T14:00:20.701204+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=aqueous-peak-42683.herokuapp.com request_id=58027f22-e4fc-43b9-9bbf-eb4014918628 fwd="60.76.227.16" dyno= connect= service= status=503 bytes= protocol=https 2018-02-04T14:00:21.453798+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=aqueous-peak-42683.herokuapp.com request_id=97dbadbc-8864-4be0-852a-a3a2178ac7a2 fwd="60.76.227.16" dyno= connect= service= status=503 bytes= protocol=https (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
import django_heroku ModuleNotFoundError: No module named 'django_heroku'
requirements.txtとローカルのパッケージを比較する
ローカルのパッケージ
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $pip freeze > pip_packages (git)-[master] (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat pip_packages (git)-[master] certifi==2018.1.18 chardet==3.0.4 dj-database-url==0.4.2 Django==2.0.2 django-heroku==0.2.0 gunicorn==19.7.1 idna==2.6 psycopg2==2.7.3.2 pytz==2017.3 requests==2.18.4 urllib3==1.22 whitenoise==3.3.1 (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
一方requirements.txt
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $cat requirements.txt (git)-[master] certifi==2017.11.5 chardet==3.0.4 Django==2.0 flake8==3.5.0 gunicorn==19.7.1 idna==2.6 mccabe==0.6.1 pew==1.1.2 pipenv==9.0.1 pycodestyle==2.3.1 pyflakes==1.6.0 pytz==2017.3 requests==2.18.4 urllib3==1.22 virtualenv==15.1.0 virtualenv-clone==0.2.6 (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
django-heroku==0.2.0がrequirements.txtに無かった!
このように、ローカルのパッケージと、リモートのパッケージが異なっていてうまく動かない場合があるので、 pip freezeとrequirements.txtを比較するのは大事
改めてgit push heroku master
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git diff (git)-[master] diff --git a/requirements.txt b/requirements.txt index 468f0a3..50e7c0a 100644 @@ -1,6 +1,7 @@ certifi==2017.11.5 chardet==3.0.4 Django==2.0 +django-heroku==0.2.0 flake8==3.5.0 gunicorn==19.7.1 idna==2.6 (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git commit -m 'add django-heroku==0.2.0' requirements.txt [master 7dffb4c] add django-heroku==0.2.0 1 file changed, 1 insertion(+) (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git push heroku master (git)-[master] Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 316 bytes | 0 bytes/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected remote: ! No 'Pipfile.lock' found! We recommend you commit this into your repository. remote: -----> Installing requirements with pip remote: Collecting django-heroku==0.2.0 (from -r /tmp/build_e5ffa088d26050c45e7abaa595a50b1f/requirements.txt (line 4)) remote: Downloading django_heroku-0.2.0-py2.py3-none-any.whl remote: Collecting dj-database-url (from django-heroku==0.2.0->-r /tmp/build_e5ffa088d26050c45e7abaa595a50b1f/requirements.txt (line 4)) (中略) remote: https://aqueous-peak-42683.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/aqueous-peak-42683.git 558d762..7dffb4c master -> master (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
→ 「No 'Pipfile.lock' found! We recommend you commit this into your repository.」 Pipfile.lock がないというメッセージが出ているので追加しておく
Pipfile.lock とは
http://pipenv-ja.readthedocs.io/ja/translate-ja/basics.html#pipfile-lock-security-features
Pipfile.lock は pip に新しく入った偉大なセキュリティ上の改善を利用します。 デフォルトでは、 Pipfile.lock はダウンロードされたパッケージごとのsha256ハッシュから生成されます。 これにより pip は、ネットワークが驚異に晒されていたり、信頼できないPyPIエンドポイントから依存パッケージをインストールしたりしているときに、意図したとおりのパッケージをインストールしていることを保証できます。 開発環境から本番環境へプロジェクトを昇格させる方法でデプロイに取り組むことを強く推奨します。 pipenv lock を使って開発環境の依存関係をコンパイルし、そのコンパイルされた Pipfile.lock を全ての本番環境にデプロイして再現可能なビルドができます。
追加してからgit push
(googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $git push heroku master (git)-[master] Counting objects: 4, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 3.29 KiB | 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected remote: ! The latest version of Python 3 is python-3.6.4 (you are using python-3.6.3, which is unsupported). remote: ! We recommend upgrading by specifying the latest version (python-3.6.4). remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes remote: -----> Found python-3.6.4, removing remote: -----> Installing python-3.6.3 remote: -----> Installing pip remote: -----> Installing requirements with pip remote: Collecting certifi==2017.11.5 (from -r /tmp/build_8601996728746a7313b6f8219d936dbb/requirements.txt (line 1)) (中略) remote: Collecting psycopg2 (from django-heroku==0.2.0->-r /tmp/build_8601996728746a7313b6f8219d936dbb/requirements.txt (line 4)) remote: Downloading psycopg2-2.7.3.2-cp36-cp36m-manylinux1_x86_64.whl (2.7MB) remote: Installing collected packages: certifi, chardet, pytz, Django, whitenoise, dj-database-url, psycopg2, django-heroku, mccabe, pyflakes, pycodestyle, flake8, gunicorn, idna, virtualenv, virtualenv-clone, pew, urllib3, requests, pipenv remote: Running setup.py install for virtualenv-clone: started remote: Running setup.py install for virtualenv-clone: finished with status 'done' remote: Running setup.py install for pipenv: started remote: Running setup.py install for pipenv: finished with status 'done' remote: Successfully installed Django-2.0 certifi-2017.11.5 chardet-3.0.4 dj-database-url-0.4.2 django-heroku-0.2.0 flake8-3.5.0 gunicorn-19.7.1 idna-2.6 mccabe-0.6.1 pew-1.1.2 pipenv-9.0.1 psycopg2-2.7.3.2 pycodestyle-2.3.1 pyflakes-1.6.0 pytz-2017.3 requests-2.18.4 urllib3-1.22 virtualenv-15.1.0 virtualenv-clone-0.2.6 whitenoise-3.3.1 remote: remote: -----> Discovering process types remote: Procfile declares types -> web remote: remote: -----> Compressing... remote: Done: 60.7M remote: -----> Launching... remote: Released v7 remote: https://aqueous-peak-42683.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/aqueous-peak-42683.git 7dffb4c..a11938f master -> master (googlehome-Z81pgPAi) [~/git/ludwig125-heroku/googlehome] $
これでもう一度 https://aqueous-peak-42683.herokuapp.com/hello/
→ 「Hello, world. You're at the hello index.」
表示された!
git のリモートにoriginを登録
githubでコードを見られるようにする 参考 - https://qiita.com/koshihikari/items/dcf126fa9c0de2b6fa7e
自分の設定では
[~/git/ludwig125-heroku/googlehome] $git remote -v (git)-[master] heroku https://git.heroku.com/aqueous-peak-42683.git (fetch) heroku https://git.heroku.com/aqueous-peak-42683.git (push) [~/git/ludwig125-heroku/googlehome] $c
herokuだけがリモートリポジトリとなっている
githubにこれを登録するために、 https://github.com/ からNew repositoryを押して、「googlehome」リポジトリを作成
そのリポジトリからURLをコピペして、以下のようにgit remote add origin
[~/git/ludwig125-heroku/googlehome] $git remote add origin git@github.com:ludwig125/googlehome.git [~/git/ludwig125-heroku/googlehome] $git branch (git)-[master] * master [~/git/ludwig125-heroku/googlehome] $git push origin master (git)-[master] Counting objects: 335, done. Compressing objects: 100% (326/326), done. Writing objects: 100% (335/335), 882.21 KiB | 0 bytes/s, done. Total 335 (delta 77), reused 0 (delta 0) remote: Resolving deltas: 100% (77/77), done. To git@github.com:ludwig125/googlehome.git * [new branch] master -> master [~/git/ludwig125-heroku/googlehome] $
追加後はこうなる
[~/git/ludwig125-heroku/googlehome] $git remote -v (git)-[master] heroku https://git.heroku.com/aqueous-peak-42683.git (fetch) heroku https://git.heroku.com/aqueous-peak-42683.git (push) origin git@github.com:ludwig125/googlehome.git (fetch) origin git@github.com:ludwig125/googlehome.git (push) [~/git/ludwig125-heroku/googlehome] $
【GoogleHomeでメモ帳アプリを作る】2. Djangoのチュートリアルをする
概要
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
チュートリアル
はじめての Django アプリ作成、その 1
https://docs.djangoproject.com/ja/2.0/intro/tutorial01/
Django がインストールされているかどうか、またどのバージョンがインストールされているかを調べるには、以下のコマンドをシェルプロンプト(先頭の $ は入力待ちを示す記号です)で実行します。
[~/git/django-tutorial] $python -m django --version (git)-[master] 2.0 [~/git/django-tutorial] $ (git)-[master]
プロジェクトを作成する
初めて Django を使うのなら、最初のセットアップを行う必要があります。通常は、 Django の プロジェクト (project) を構成するコードを自動生成します。プロジェクトとは、データベースの設定や Django 固有のオプション、アプリケーション固有の設定などといった、個々の Django インスタンスの設定を集めたものです。 コマンドラインから、コードを置きたい場所に cd して、以下のコマンドを 実行してください。
実行すると、現在のディレクトリに mysite ディレクトリが作成される
$ django-admin startproject mysite
プロジェクトの名前を付けるとき、組み込みの Python モジュールや Django のコンポーネントの名前を使わないようにしてください。とりわけ、 django (Django 自体と名前が衝突します) や test (組み込みの Python パッケージ名と名前が衝突します) を使わないようにしましょう。
この時点で以下のファイルができている
[~/git/django-tutorial/tutorial2] $django-admin startproject mysite (git)-[master] [~/git/django-tutorial/tutorial2] $tree (git)-[master] . └── mysite ├── manage.py └── mysite ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py 2 directories, 5 files [~/git/django-tutorial/tutorial2] $
各ファイルの意味
外側のmysite - 一番の上のmysite/ ルートディレクトリは、このプロジェクトのただの入れ物です
manage.py - Django プロジェクトに対する様々な操作を行うためのコマンドラインユーティリティです。 - 詳しくは django-admin と manage.py 内の manage.py を参照してください。
内側のmysite - 内側の mysite/ ディレクトリは、このプロジェクトの実際の Python パッケージです。この名前が Python パッケージの名前であり、 import の際に 使用する名前です - (例えば import mysite.urls) 。
settings.py - mysite/settings.py: Django プロジェクトの設定ファイルです。 - 設定の仕組みは Djangoの設定 を参照してください。
urls.py - mysite/urls.py: Django プロジェクトの URL 宣言、いうなれば Django サイトにおける「目次」に相当します。
wsgi.py - mysite/wsgi.py: プロジェクトをサーブするためのWSGI互換Webサーバーとのエントリーポイントです。
プロジェクトを作成する
Django のプロジェクトがうまく動作するか確認しましょう。外側の mysite ディレクトリに移動ができたら下記のコマンドを実行してください
$ python manage.py runserver
実行結果
[~/git/django-tutorial/tutorial2/mysite] $python manage.py runserver (git)-[master] Performing system checks... System check identified no issues (0 silenced). You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. February 02, 2018 - 14:44:21 Django version 2.0, using settings 'mysite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.Run 'python manage.py migrate' to apply them. と出ているが
適用されていないデータベースマイグレーションについての警告はここでは無視します、後ほどすぐにデータベースとともにたっぷりと取り組みます。
とチュートリアルに書いてあるので上の警告は無視してOK
注意
このサーバは開発中の利用だけを考えて作られています。絶対に運用環境では 使わないでください (筆者たちの専門は Web フレームワークであって、Web サーバではありません)。
これで http://localhost:8000/ にアクセスするとちゃんと見られた → 「The install worked successfully! Congratulations!」 の画面が出た
runserverについて
https://docs.djangoproject.com/ja/2.0/ref/django-admin/#runserver
デフォルトの IP アドレス 127.0.0.1 は、同じネットワーク上の他のマシンからはアクセスできないことに注意してください。開発用サーバーを他のマシンから見られるようにするには、ネットワーク上の自分のローカル IP アドレス (例 192.168.2.1) または 0.0.0.0 や :: (IPv6 が有効な場合) を使用してください。
サーバーのポートを変えたい場合は、以下のようにコマンドライン引数を渡してください。このコマンドによってポート 8080 で起動させれます:
$ python manage.py runserver 8080
のように、ポート番号を変えることもできる
これで http://localhost:8080/ にアクセスすると上と同じ画面が得られる
サーバの IP を指定するときには、ポート番号も一緒に指定します。 例えば、 全ての IP からのリクエストを受け付ける (サーバを他のコンピュータから見えるようにする) には、以下のようにします:
$ python manage.py runserver 0:5000
上記のWebサーバは仮想マシンのUbuntuに立てたが、ホストマシンのWindowsからも見られるはず ※自分のUbuntuでは以下の通り5000と8181のポートのみアクセスを受け付けるようにファイアウォールを設定しているので、 ここではポートを5000とした
自分の環境のubuntuのufwの設定
[~/git/django-tutorial/tutorial/mysite] $sudo ufw status (git)-[master] 状態: アクティブ To Action From -- ------ ---- 8181 ALLOW Anywhere 80 ALLOW Anywhere 5000 ALLOW Anywhere [~/git/django-tutorial/tutorial/mysite] $
Ubuntuで立てたDjangoのWebサーバにホストOSのWindowsからアクセスしてみる
さて、これでホストOSのWindowsからゲストOSで立ち上げたUbuntuのサーバが、IPアドレスとポート5000を指定して見られるはず。。 http://192.168.3.11:5000/ あれ・・・ 見られない
ALLOWED_HOSTSについて - https://docs.djangoproject.com/ja/2.0/ref/settings/#allowed-hosts
[~/git/django-tutorial/tutorial2/mysite/mysite/settings.py # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True #ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]', '192.168.3.11'] ← ここを変える
改めて実行するとWindowsからも見られた
Polls アプリケーションをつくる
チュートリアルではPollsというWebアプリケーションを作る様になっているので、それにしたがってアプリケーションを作成する
プロジェクトとアプリケーション
アプリケーションとは
アプリケーションとは、実際に何らかの処理を行う Web アプリケーションを指します。 例えばブログシステムや公開レコードのデータベース、単純な投票アプリといった具合です。
プロジェクトとは
プロジェクトとは、あるウェブサイト向けに設定とアプリケーションを集めたものです。 一つのプロジェクトには複数のアプリケーションを入れられ ます。また、一つのアプリケーションは複数のプロジェクトで使えます。
アプリケーションを作るには、 manage.py と同じディレクトリに入って、このコマンドを実行します:
[~/git/django-tutorial/mysite] $ python manage.py startapp polls
このコマンドは polls というディレクトリを作成します。中身はこのようになっています:
[~/git/django-tutorial/tutorial2/mysite] $ls (git)-[master] db.sqlite3 manage.py* mysite/ [~/git/django-tutorial/tutorial2/mysite] $python manage.py startapp polls (git)-[master] [~/git/django-tutorial/tutorial2/mysite] $tree (git)-[master] . ├── db.sqlite3 ├── manage.py ├── mysite │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── settings.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── wsgi.cpython-36.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── polls ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py 4 directories, 17 files [~/git/django-tutorial/tutorial2/mysite] $
Django で最も単純なビューを作成する
[~/git/django-tutorial/tutorial2/mysite] $cat polls/views.py (git)-[master] from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.") [~/git/django-tutorial/tutorial2/mysite] $
ビューを呼ぶために、 URL を対応付けしてやる必要があります。そのためには URLconf が必要です。 ← 重要
[~/git/django-tutorial/tutorial2/mysite] $cat polls/urls.py (git)-[master] from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ] [~/git/django-tutorial/tutorial2/mysite] $
ルートのURLconfに polls.urls モジュールの記述を反映させることが必要
mysite/mysite/urls.py に以下を記載
from django.urls import include, path ← includeを追加 from django.contrib import admin urlpatterns = [ path('polls/', include('polls.urls')), ← 追加 path('admin/', admin.site.urls), ]
注意
URLパターンをインクルードするときはいつでも include() を使うべきです。
Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
ここまでしたらWebサーバを立ち上げる
[~/git/django-tutorial/mysite] $ python manage.py runserver
ブラウザで http://localhost:8000/polls/
で 「Hello, world. You're at the polls index.」 が表示される
先程の復習だが、以下のように、0:5000としてやれば、Windowsからも見られる $python manage.py runserver 0:5000
参考 - https://qiita.com/kqxgy385/items/466b5bab67118175be65 - https://qiita.com/kaki_k/items/b76acaeab8a9d935c35c
ここまですると、DjangoでWebサーバを作るのに必要な最低限の知識が得られる
このあとのチュートリアル02以降は、Webアプリケーションのページを作成する手順が多いので、 サーバを立てるだけであればここまでで十分かも
やったけど、ここには記載しない
djangoについて参考にしたページ
https://docs.djangoproject.com/ja/2.0/intro/tutorial01/
https://qiita.com/gctfuji/items/27d2c9e4b3334447b6af
http://qh73xebitbucketorg.readthedocs.io/ja/latest/1.Programmings/python/LIB/django/tutorial/main/
GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
GoogleHomeでやりたいこと
やりたいこと(ぱっと思ったこと)
- 「シャンプー買わないと」とか「歯医者の予約しないと」とか、ぱっと頭に浮かんだことを言うだけでメモっておければ便利
- さらに、そのメモを読み上げてもらえれば嬉しい
- ドラクエ3で、主人公に他のキャラクターが言ったセリフを思い出すことができる特技があったので、ここから着想を得た
- ドラクエ3の主人公の特技
- おもいだす 会話を3つまで思い出す 会話を32個まで思い出す
- もっとおもいだす 会話を10個まで思い出す
- ふかくおもいだす 会話を32個まで思い出す
- ドラクエ3の主人公の特技
やりたいこと(より具体的に)
- GoogleHomeに話しかけることでメモの記録をしたい - 「Ok Google, メモって "シャンプー買う"」
- GoogleHomeに話しかけることで、記録したメモを読み上げてもらいたい - 「Ok Google, メモを読んで」→ 「シャンプー買う」
メモを記録
メモを読み上げる
こだわり
Raspberryがあればもっと色々できるが、GoogleHome以外は お金をかけず無料で 作ることを目指した
登場する単語
- GoogleHome
- GoolgleAssistant
- IFTTT
- GoogleSpreadSheet
- Actions on Google
- Dialogflow
- Heroku
- Django
GoolgleAssistant(Googleアシスタント)
公式 - https://assistant.google.com/intl/ja_jp/
概要 - https://ja.wikipedia.org/wiki/Google%E3%82%A2%E3%82%B7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%88
- アンドロイドスマホやGoogle Homeに搭載されているAI
IFTTT
公式 - https://ifttt.com/discover
概要 - https://ja.wikipedia.org/wiki/IFTTT
IFTTT(イフト)とは「レシピ」と呼ばれる個人作成もしくは公に共有しているプロフィールを使って数あるWebサービス(Facebook、Evernote、Weather、Dropboxなど)同士で連携することができるWebサービスである。
- 様々なアプリやGPSの位置情報とかを使って、プログラミングせずに他のアプリにデータを連携したりすることができる
- 今回作るものでは、GoogleAssistantからGoogleSpreadSheetにデータを連携するのに使う
GoogleSpreadSheet(Googleスプレッドシート)
公式 - https://www.google.com/intl/ja_jp/sheets/about/
概要 - https://ja.wikipedia.org/wiki/Google_%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88
Googleが無料提供する、ウェブブラウザ内で動くオフィスソフト。
- オンラインで使えるエクセルみたいなもの
- PCやスマホからも見られるので外出先でも閲覧・編集できるのが良い点
Actions on Google
公式 - https://developers.google.com/actions/extending-the-assistant?hl=ja
アプリは、アクションを開発できるようにすることで、Google アシスタントを拡張するものです。アクションにより、ユーザーはあなたの製品やサービスを使用して物事を行うことができます。
- Googleアシスタントから呼び出すアプリのプラットフォーム
- 今回は後述のDialogflowを使ったアプリを管理する
Dialogflow
Give users new ways to interact with your product by building engaging voice and text-based conversational interfaces powered by AI. Connect with users on the Google Assistant, Amazon Alexa, Facebook Messenger, and other popular platforms and devices.
- GoogleAssistantなどから受け取ったユーザが発声した言葉やテキストベースの入力に対して、自然言語処理して決められたメッセージを返したり、外部のWebサービスと連携したりすることができる
- 今回作るものでは、GoogleAssistantからの入力を解析してHerokuのアプリケーションに必要な値を渡し、返却値をGoogleAssistantに返す役割を担う
Heroku
公式 - https://www.heroku.com/platform
Heroku is a cloud platform based on a managed container system, with integrated data services and a powerful ecosystem, for deploying and running modern apps.
- PAASのWebサーバを作ることができる
- pythonなどの様々な言語に対応
- 今回作るものでは、Dialogflowからのリクエストを元にGoogleSpreadSheetの値を必要な件数だけ取得して返す役割を担う
- 使うにあたって最低限のgitの知識は必要(commitとかpushとか)
Django
公式 - https://docs.djangoproject.com/ja/2.0/intro/overview/
Django は変転の激しいニュースルーム環境で開発された経緯から、よくある Web 開発タスクを迅速かつ簡単化するように設計されました。ここでは Django による データベース中心の Web アプリケーション開発をざっと見てみましょう。
アプリ作成開始時のスキル
- python: 仕事でちょっと書いている
- git: 仕事で使っている
- vim: 仕事で使っている
- IFTTT: 試しに使ったことはある
- GoogleSpreadSheet: 試しに使ったことはある
- Dialogflow: よくわからない
- Heroku: 知らない(PAASとか触ったことない)
- Django: 知らない(Webフレームワークとか触ったことない)
全体の構成
方針設計
0. やりたいことの整理と設計(このページ)
アプリ作成に向けて必要なことを調べる
アプリ作成
5. HerokuアプリでSpreadSheetの中身を取得する
6. GoogleHomeに呼びかけてSpreadSheetのメモを読み上げてもらう
7. 使い勝手を良くする
【GoogleHomeでメモ帳アプリを作る】1. Herokuのチュートリアルをする
概要
PAASサーバを提供しているherokuを使ってみる
資料全体の構成はここに記載: GoogleHomeに話しかけてメモを記録したりメモを読み上げてもらう
チュートリアル
https://devcenter.heroku.com/star からpythonを選ぶ
supported-python-runtimes
https://devcenter.heroku.com/articles/python-support#supported-python-runtimes
Supportしているpythonバージョンは以下
python-3.6.3 python-2.7.14
herokuを使うための前提条件を満たす
https://devcenter.heroku.com/articles/getting-started-with-python#introduction
a free Heroku account. Python version 3.6 installed locally - see the installation guides for OS X, Windows, and Linux. Pipenv installed locally. Accomplish this by running pip install pipenv. Postgres installed locally, if running the app locally
python3.6を入れる
$ pyenv install 3.6.3 $ pyenv local 3.6.3 $ pyenv versions system 3.5.4 * 3.6.3 (set by /home/ludwig125/.python-version) $
pipenvを入れる
- pipenvとは
http://pipenv-ja.readthedocs.io/ja/translate-ja/basics.html http://pipenv-ja.readthedocs.io/ja/translate-ja/
Pipenvは、手動でパッケージのインストールおよびアンインストールを行うのと同じように Pipfile に対してパッケージの追加および削除を行うのに加え、自動でプロジェクト用の仮想環境を作成し管理します。
Pipenvの特徴 http://pipenv-ja.readthedocs.io/ja/translate-ja/#pipenv-features
何をしたいか を簡単に指定するだけで、真の 決定論的ビルド が可能です。 固定された依存関係のファイルハッシュを生成しチェックします。 pyenv が使える場合は、要求されているPythonを自動でインストールします。 Pipfile を探して、再帰的に、プロジェクトホームを自動で見付けに行きます。 Pipfile が存在していない場合、自動で生成します。 標準的な場所に仮想環境を自動で作成します。 パッケージがインストールもしくはアンインストールされたときに、自動で Pipfile に追加および削除します。 .env ファイルが存在する場合、自動で読み込みます。
$ pip install pipenv
Postgresを入れる
- Postgresとは
https://ja.wikipedia.org/wiki/PostgreSQL
https://devcenter.heroku.com/articles/heroku-postgresql#local-setup
$ export DATABASE_URL=postgres://$(whoami) $ sudo apt-get install postgresql $ which psql /usr/bin/psql $ psql psql: FATAL: role "ludwig125" does not exist $
https://qiita.com/ibara1454/items/40ce2d82926f48cf02bc を参考に以下を実施
[~] $ /etc/init.d/postgresql start chmod: `/var/run/postgresql' のパーミッションを変更しています: 許可されていない操作です * Starting PostgreSQL 9.3 database server * Error: You must run this program as the cluster owner (postgres) or root ...fail! [~] $ sudo /etc/init.d/postgresql start * Starting PostgreSQL 9.3 database server ...done. [~] $ /etc/init.d/postgresql status 9.3/main (port 5432): online [~] $ psql psql: FATAL: role "ludwig125" does not exist [~] $
$ less /etc/passwd を見ると postgres:x:119:128:PostgreSQL administrator
がある
$ sudo su - postgres postgres@ludwig125-virtual-machine:~$ postgres@ludwig125-virtual-machine:~$ psql psql (9.3.20) Type "help" for help. postgres=# postgres=# \du List of roles Role name | Attributes | Member of -----------+------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication | {} postgres=#
デフォルトでは他のロールが存在しないので、ロールはスーパユーザのpostgresだけ
postgres=# SELECT rolname FROM pg_roles; rolname ---------- postgres (1 row) postgres=#
とりあえず前提に書いてあることをしたのでこのあとからセットアップしていく
Heroku Set up
https://devcenter.heroku.com/articles/getting-started-with-python#set-up
https://devcenter.heroku.com/articles/heroku-cli#debian-ubuntu wgetでのとり方がここに書いてあった
$ wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
CLIを使ってみる
$ heroku login Enter your Heroku credentials: Email: <アカウントを作ったときのEmail> Password: <同上、パスワード> Logged in as <mail> [~] $
prepare-the-app
https://devcenter.heroku.com/articles/getting-started-with-python#prepare-the-app
アプリ作成の準備
$ git clone https://github.com/heroku/python-getting-started.git $ cd python-getting-started
Deploy the app
https://devcenter.heroku.com/articles/getting-started-with-python#deploy-the-app
herokuのアプリの作成
- 特に指定しなければ適当なアプリ名が割り振られる こんな感じ
[~/git/ludwig125/python-getting-started] $ heroku create Creating app... done, ⬢ powerful-chamber-29168 https://powerful-chamber-29168.herokuapp.com/ | https://git.heroku.com/powerful-chamber-29168.git [~/git/python-getting-started] $
herokuにこのアプリをデプロイするには以下のようにgitコマンドを実施する
[~/git/ludwig125/python-getting-started] $ git push heroku master Counting objects: 385, done. Compressing objects: 100% (177/177), done. Writing objects: 100% (385/385), 68.53 KiB | 0 bytes/s, done. Total 385 (delta 184), reused 385 (delta 184) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected remote: -----> Installing python-3.6.3 remote: -----> Installing pip remote: -----> Installing requirements with latest Pipenv… remote: Installing dependencies from Pipfile.lock (2899d6)… remote: -----> $ python manage.py collectstatic --noinput remote: 119 static files copied to '/tmp/build_ac2a9e4e970eca0b6aae1bfdd898a194/staticfiles', 145 post-processed. remote: remote: -----> Discovering process types remote: Procfile declares types -> web remote: remote: -----> Compressing... remote: Done: 60.5M remote: -----> Launching... remote: Released v4 remote: https://powerful-chamber-29168.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/powerful-chamber-29168.git * [new branch] master -> master [~/git/ludwig125/python-getting-started] $
これでアプリがデプロイされたはずなので、アプリのインスタンスが動いていることを確認
[~/git/ludwig125/python-getting-started] $ heroku ps:scale web=1 Scaling dynos... done, now running web at 1:Free [~/git/ludwig125/python-getting-started] $
この時点でhttps://powerful-chamber-29168.herokuapp.com/ にアクセスすると「Heroku | Welcome to your new app!」が出た
アプリのWebサイトを立ち上げるには以下のコマンドを実施する 以下でWebページ(https://powerful-chamber-29168.herokuapp.com/) に飛ぶ
[~/git/python-getting-started] $ heroku open [~/git/python-getting-started] $
Define a Procfile
https://devcenter.heroku.com/articles/getting-started-with-python#define-a-procfile
Profileとは
- アプリのスタート時に動くファイルを定義するもの
[~/git/ludwig125/python-getting-started] $ cat Procfile web: gunicorn gettingstarted.wsgi [~/git/ludwig125/python-getting-started] $
Scale the app
https://devcenter.heroku.com/articles/getting-started-with-python#scale-the-app
herokuのアプリは「Dyno」の単位で動いている
Dynoって? - https://devcenter.heroku.com/articles/dynos
以下でDynoのプロセスがどういう状態か確認できる
[~/git/ludwig125/python-getting-started] $ heroku ps Free dyno hours quota remaining this month: 547h 29m (99%) For more information on dyno sleeping and how to upgrade, see: https://devcenter.heroku.com/articles/dyno-sleeping === web (Free): gunicorn gettingstarted.wsgi (1) web.1: idle 2017/12/22 02:08:22 +0900 (~ 20h ago) [~/git/ludwig125/python-getting-started] $
Declare app dependencies
https://devcenter.heroku.com/articles/getting-started-with-python#declare-app-dependencies
Heroku recognizes an app as a Python app by the existence of a Pipfile or requirements.txt file in the root directory.
herokuでは、Pipfileかrequirements.txtがherokuのルートディレクトリにあると、Pythonアプリだと認識してくれるらしい
The Pipfile file lists the app dependencies together with their versions.
Pipfileはアプリの依存関係を記載する これは後述のpipenvで必要なファイルとなっている
チュートリアルのファイルを見てみるとこんな感じ
[~/git/ludwig125/python-getting-started] $ cat Pipfile [[source]] url = "https://pypi.python.org/simple" verify_ssl = true [packages] django = "*" gunicorn = "*" django-heroku = "*" [requires] python_version = "3.6" [~/git/ludwig125/python-getting-started] $
requirements.txtをつくっておく
[~/git/ludwig125/python-getting-started] $ pip3 freeze > requirements.txt [~/git/ludwig125/python-getting-started] $ cat requirements.txt certifi==2017.11.5 chardet==3.0.4 Django==2.0 flake8==3.5.0 idna==2.6 mccabe==0.6.1 pew==1.1.2 pipenv==9.0.1 pycodestyle==2.3.1 pyflakes==1.6.0 pytz==2017.3 requests==2.18.4 urllib3==1.22 virtualenv==15.1.0 virtualenv-clone==0.2.6 [~/git/ludwig125/python-getting-started] $
herokuはデプロイ時にこれらのファイルを以下のコマンドを実行することで読み込み必要な環境を作成している
pipenv install --system --skip-lock
herokuのデプロイ上の環境をローカルの開発環境で再現するために、以下pipenvの設定をする
Python 3の仮想環境を初期化するには、 $ pipenv --three を実行する
https://github.com/pypa/pipenv/issues/729 https://github.com/pypa/pipenv/issues/700 を参考に作り直す
pipenv --python 3.6.3と指定してinstallする
[~/git/ludwig125/python-getting-started] $ pipenv --rm Removing virtualenv (/home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI)… [~/git/ludwig125/python-getting-started] $ pipenv --python 3.6.3 install Creating a virtualenv for this project… Using /home/ludwig125/.pyenv/versions/3.6.3/bin/python3.6m to create virtualenv… ⠋Running virtualenv with interpreter /home/ludwig125/.pyenv/versions/3.6.3/bin/python3.6m Using base prefix '/home/ludwig125/.pyenv/versions/3.6.3' New python executable in /home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI/bin/python3.6m Also creating executable in /home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI/bin/python Installing setuptools, pip, wheel...done. Virtualenv location: /home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI Installing dependencies from Pipfile.lock (e19824)… � ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 7/7 — 00:00:05 To activate this project's virtualenv, run the following: $ pipenv shell [~/git/ludwig125/python-getting-started] $ [~/git/ludwig125/python-getting-started] $ pipenv check Checking PEP 508 requirements… Passed! Checking installed package safety… All good! [~/git/ludwig125/python-getting-started] $
仮想環境を起動
[~/git/ludwig125/python-getting-started] $ pipenv shell Loading .env environment variables… Spawning environment shell (/bin/bash). Use 'exit' to leave. source /home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI/bin/activate [~/git/ludwig125/python-getting-started] $ source /home/ludwig125/.local/share/virtualenvs/python-getting-started-1TrsocCI/bin/activate (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
Once dependencies are installed, you will be ready to run your app locally.
ここまでやれば、ローカル環境でアプリを実行する準備が整ったことになる
Run the app locally
https://devcenter.heroku.com/articles/getting-started-with-python#run-the-app-locally
The app is almost ready to start locally. Django uses local assets, so first, you’ll need to run collectstatic:
ここまででローカルで実行できる準備ができた。ここでcollectstaticを使う必要がある
python manage.py collectstatic
collectstatic とは http://django-staticfiles-doc-ja.readthedocs.io/en/latest/commands.html
collectstatic インストールしている全てのアプリケーションから静的ファイルを集めて STATICFILES_STORAGE にコピーします。
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ python manage.py collectstatic yesを入力 (長いので省略) Post-processed 'admin/css/responsive.css' as 'admin/css/responsive.25d98d3909ed.css' Post-processed 'admin/css/dashboard.css' as 'admin/css/dashboard.7ac78187c567.css' 119 static files copied to '/home/ludwig125/git/ludwig125/python-getting-started/staticfiles', 145 post-processed. (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
ローカルサーバで実行
heroku local web でできる
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku local web [OKAY] Loaded ENV .env File as KEY=VALUE Format 23:48:35 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Starting gunicorn 19.7.1 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Listening at: http://0.0.0.0:5000 (11733) 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Using worker: sync 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11736] [INFO] Booting worker with pid: 11736
ブラウザで以下を見てみる
「Getting Started with Python on Heroku」の画面が表示された (https://github.com/heroku/python-getting-started/blob/master/hello/templates/index.html のページが表示されている)
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku local web [OKAY] Loaded ENV .env File as KEY=VALUE Format 23:48:35 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Starting gunicorn 19.7.1 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Listening at: http://0.0.0.0:5000 (11733) 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11733] [INFO] Using worker: sync 23:48:36 web.1 | [2017-12-22 23:48:35 +0900] [11736] [INFO] Booting worker with pid: 11736 23:51:43 web.1 | Not Found: /favicon.ico ← ここが出力された
- 止めるときはCtrl+c
Push local changes
https://devcenter.heroku.com/articles/getting-started-with-python#push-local-changes
ローカルで変更した結果をherokuに反映させる方法を見る
$ pipenv install requests
requestsとは? http://pipenv-ja.readthedocs.io/ja/translate-ja/install.html
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ pipenv install requests Installing requests… Collecting requests Using cached requests-2.18.4-py2.py3-none-any.whl Collecting chardet<3.1.0,>=3.0.2 (from requests) Using cached chardet-3.0.4-py2.py3-none-any.whl Collecting urllib3<1.23,>=1.21.1 (from requests) Using cached urllib3-1.22-py2.py3-none-any.whl Collecting certifi>=2017.4.17 (from requests) Using cached certifi-2017.11.5-py2.py3-none-any.whl Collecting idna<2.7,>=2.5 (from requests) Using cached idna-2.6-py2.py3-none-any.whl Installing collected packages: chardet, urllib3, certifi, idna, requests Successfully installed certifi-2017.11.5 chardet-3.0.4 idna-2.6 requests-2.18.4 urllib3-1.22 Adding requests to Pipfile's [packages]… PS: You have excellent taste! ✨ � ✨ Locking [dev-packages] dependencies… Locking [packages] dependencies… Updated Pipfile.lock (c8a67b)! (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
Pipfileは以下が追加される
index 70ca30f..affd239 100644 =--- a/Pipfile =+++ b/Pipfile @@ -9,6 +9,7 @@ verify_ssl = true django = "*" gunicorn = "*" django-heroku = "*" =+requests = "*"
以下のように修正する
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ cat hello/views.py import requests ← 追加 from django.shortcuts import render from django.http import HttpResponse from .models import Greeting ## Create your views here. #def index(request): # # return HttpResponse('Hello from Python!') # return render(request, 'index.html') ← ここをコメントアウトして、以下のindex関数を追加 def index(request): r = requests.get('http://httpbin.org/status/418') print(r.text) return HttpResponse('<pre>' + r.text + '</pre>') def db(request): greeting = Greeting() greeting.save() greetings = Greeting.objects.all() return render(request, 'db.html', {'greetings': greetings}) (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
修正が終わったらheroku local
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku local [OKAY] Loaded ENV .env File as KEY=VALUE Format 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Starting gunicorn 19.7.1 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Listening at: http://0.0.0.0:5000 (12475) 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Using worker: sync 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12478] [INFO] Booting worker with pid: 12478
この状態で再び http://localhost:5000/ へアクセス 可愛い絵が現れた(以下の絵)
また、以下にも絵が出力された
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku local [OKAY] Loaded ENV .env File as KEY=VALUE Format 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Starting gunicorn 19.7.1 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Listening at: http://0.0.0.0:5000 (12475) 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12475] [INFO] Using worker: sync 00:03:32 web.1 | [2017-12-23 00:03:32 +0900] [12478] [INFO] Booting worker with pid: 12478 00:04:52 web.1 | [2017-12-23 00:04:52 +0900] [12475] [CRITICAL] WORKER TIMEOUT (pid:12478) 00:04:52 web.1 | [2017-12-22 15:04:52 +0000] [12478] [INFO] Worker exiting (pid: 12478) 00:04:52 web.1 | -=[ teapot ]=- 00:04:52 web.1 | _...._ 00:04:52 web.1 | .' _ _ `. 00:04:52 web.1 | | ."` ^ `". _, 00:04:52 web.1 | \_;`"---"`|// 00:04:52 web.1 | | ;/ 00:04:52 web.1 | \_ _/ 00:04:52 web.1 | `"""` 00:04:52 web.1 | [2017-12-23 00:04:52 +0900] [12573] [INFO] Booting worker with pid: 12573
この画像は、当然先程直したindex関数の中で指定したURLの画像 http://httpbin.org/status/418
ここまでをリモートに反映
$ git add . $ git commit -m "Demo" herokuサーバにデプロイ $ git push heroku master
全部うまく行っていることを確認する
$ heroku open
https://powerful-chamber-29168.herokuapp.com/ のブラウザが起動してさっきの画像が表示される
Start a console
https://devcenter.heroku.com/articles/getting-started-with-python#start-a-console
コンソールを起動する
さっきの画像がpythonのREPLで見られるよって話
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku run python manage.py shell Running python manage.py shell on ⬢ powerful-chamber-29168... up, run.8841 (Free) Python 3.6.3 (default, Nov 14 2017, 17:29:48) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> import requests >>> print(requests.get('http://httpbin.org/status/418').text) -=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""` >>> >>> exit() (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
bashも起動できる
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku run bash Running bash on ⬢ powerful-chamber-29168... up, run.2947 (Free) ~ $ ls app.json hello Pipfile Procfile README.md runtime.txt gettingstarted manage.py Pipfile.lock Procfile.windows requirements.txt staticfiles ~ $ echo "test" test ~ $ ~ $ exit exit (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
Define config vars
https://devcenter.heroku.com/articles/getting-started-with-python#define-config-vars
環境変数を定義 先程のhello/view.pyを以下のように書き換え
import os ← 追加 def index(request): ← このように書き換え times = int(os.environ.get('TIMES',3)) return HttpResponse('Hello! ' * times)
os.environ.get は環境変数を読み込んでなければデフォルト値(上の例だと3)を使うという意味(pythonの文法)
チュートリアルの場合、.envは以下に定義されているので、TIMES=2が適用されるはず
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ cat .env TIMES=2 (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
$ heroku local で実行
ブラウザには「Hello! Hello!」とHello!が2個実行された
この環境変数を変えるには以下のコマンドをする
$ heroku config:set TIMES=5
実行すると、変数が変わっていることが確認できる
(python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $ heroku config === powerful-chamber-29168 Config Vars DATABASE_URL: postgres://ogrzfymdqwvqaz:49b120efdf13380c9dfeba77a533744212084d29e5b3b18d5389e8015711b85a@ec2-184-73-240-228.compute-1.amazonaws.com:5432/dbejvvccb8644e TIMES: 5 (python-getting-started-1TrsocCI) [~/git/ludwig125/python-getting-started] $
これを以下の通りデプロイしてブラウザで見るとHello!は5つになる
$ git push heroku master $ heroku open
https://powerful-chamber-29168.herokuapp.com/ → Hello! Hello! Hello! Hello! Hello!
herokuについて参考にしたページ
https://qiita.com/Arashi/items/b2f2e01259238235e187
http://kasoutuuka.org/heroku-hello