ludwig125のブログ

頑張りすぎずに頑張る父

【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を選択

googleapis1 googleapis2 googleapis3

→「認証情報」→「認証情報を作成」→「サービスアカウントキー」

googleapis4 googleapis5 googleapis6

秘密鍵JSONファイルを取得できたらOK

SpreadSheetの共有設定

「memorandum」シートに上記で発行されたメールアドレスを登録

googleapis7

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の環境変数の設定方法について
  1. 上のようにheroku config:set をするか、
  2. ダッシュボードから行う方法がある

参考 - 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の最後のデータがブラウザに表示された