原口 豊(はらぐち・ゆたか)
大手証券会社システム部に在籍後、1998年ベイテックシステムズ(現サテライトオフィス)を設立し、社長就任。2008年に、いち早くクラウドコンピューティングの可能性に注目し、GoogleApps(TM)導入サポートを開始。導入実績は、ガリバー、アデランス、三井倉庫などの大手企業から、中堅・中小企業まで、1,200社以上。「組織&グループカレンダー for Google Apps」など、多数のテンプレートを無償提供するなど、Google Appsの普及に尽力。Google Enterprise Day 2011ではパートナーアワードを3年連続で受賞した。

フォームの内容を反映するスプレッドシート作成

今回はGoogle App Engineを使った応用編として、フォームへ入力した内容を任意のスプレッドシートへ書き込むと同時に、ユーザーへメール送信するというテクニックを紹介したい。これは前々回に取り上げた「メール送信」、前回に取り上げた「スプレッドシートへの書き込み」を応用したものだ。

これまでの記事をご覧いただけばわかると思うが、まずはWebアプリケーションの登録を済ませておく。次に、フォームの内容を書き込むためのスプレッドシートを用意する。こちらは特に細かな設定が必要なわけではなく、フォームで入力させたい項目を事前に書き込んでおくだけでOK。

今回はテストケースとして、入力フォームで使いがちな「投稿日/姓/名/セイ/メイ/メールアドレス/郵便番号/都道府県/市区町村/電話番号/性別/生年月日/その他」という13項目を設定してみた。

スプレッドシートの1行目に「投稿日/姓/名/セイ/メイ/メールアドレス/郵便番号/都道府県/市区町村/電話番号/性別/生年月日/その他」という13項目を入力しておく

フォーム用のスクリプトやHTLMファイルを用意

用意するファイルは、前回使った「init.py」「app.yaml」「index.py」、Python用のライブラリ「gdata-python-client」から抽出した「atom」および「gdata」という2つのフォルダに加え、フォーム投稿のメインとなる「entry.py」、フォーム入力完了時の画面表示用に「thanks.py」。さらに「templates」フォルダ内に2種類のHTMLファイル「index.html」と「thanks.html」、「css」フォルダ内にスタイルシート設定用の「style.css」、「images」フォルダ内にフォームで使用する画像を入れておく。

今回のサンプルには、サテライトオフィスが行っている「Google Apps プレミアエディション お友達紹介キャンペーン」のフォームを使用した。それでは以下、各ファイルの詳細について見ていこう。なお、HTMLおよびCSSファイルに関する説明は省いてあるが、興味がある方はサンプルファイルをダウンロードしてみていただきたい。

「__init__.py」

メール送信やスプレッドシートへ書き込みと同様に、中身は空のままで問題ない。

「app.yaml」

こちらは1行目「application:」のアプリケーションIDに加えて、読み込むファイルを下記のように記述する。

application: アプリケーションIDを入力
version: 1
runtime: python
api_version: 1
default_expiration: "4d 5h"


handlers:

- url: /favicon.ico
static_files: images/favicon.ico
upload: images/favicon.ico
- url: /images
static_dir: images
secure: optional
- url: /css/(.*)
static_files: css/\1
upload: css/(.*)
secure: optional

- url: /entry
script: entry.py

- url: /thanks
script: thanks.py

- url: /.*
script: index.py

「index.py」

今回はページの表示だけを行うため、「index.py」自体の内容は少ない。

# coding: utf-8
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

from google.appengine.ext.webapp import template

############################################################
## フォーム投稿サンプル 入力画面
############################################################
class Page(webapp.RequestHandler):
    def get(self):
        # ページの表示のみを行う
        self.response.out.write(template.render('templates/index.html', {}))

def main():
    run_wsgi_app(webapp.WSGIApplication([(r'.*', Page)]))
if __name__ == "__main__":
    main()

「entry.py」

こちらが今回のメインとなるスクリプトだ。これまでの連載で紹介したメール送信およびスプレッドシートへの書き込みという2種類の要素が盛り込まれている。個々の環境に応じて、スプレッドシートキー、スプレッドシートを編集可能なユーザーアカウントのメールアドレスとパスワード、送信元メールアドレス、件名と本文を変更しよう。送信先メールアドレスは、ユーザーがフォーム上から入力したものが引用されるようになっている。必要ならばCCとBCCを設定することも可能だ。

# coding: utf-8
import datetime

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

import gdata.spreadsheet.service
import gdata.service
import gdata.alt.appengine
import atom.service
import gdata.spreadsheet

from google.appengine.api import mail

############################################################
## フォーム投稿サンプル 内部処理(メール送信、スプレッドシートへの書き込み)
############################################################
class Page(webapp.RequestHandler):
    def post(self):
        try:
            # 入力値を取得
            lastname = unicode(self.request.get('lastname'))
            firstname = unicode(self.request.get('firstname'))
            lastname_yomi = unicode(self.request.get('lastname_yomi'))
            firstname_yomi = unicode(self.request.get('firstname_yomi'))
            mailaddress = unicode(self.request.get('mailaddress'))
            post_number = unicode(self.request.get('post_number'))
            province = unicode(self.request.get('province'))
            city = unicode(self.request.get('city'))
            phone = unicode(self.request.get('phone'))
            gender = unicode(self.request.get('gender'))
            birthday = unicode(self.request.get('birthday'))
            other = unicode(self.request.get('other'))

#####################################################################
# ここから編集が必要な項目
#####################################################################

            # スプレッドシート書き込み 設定

            # スプレッドシートキーを設定 
            spreadsheet_key = 'ここにスプレッドシートキーを入力'

            # スプレッドシートを編集可能なユーザーアカウントを設定
            user_mail = 'ここにメールアドレスを入力'
            # ユーザーアカウントのパスワードを設定
            password = 'ここにパスワードを入力'

            # 書き込み先のシート名を設定
            # ''の前に u を付け、ユニコードに変換
            # 変換を行わない場合、エラーが発生する
            spreadsheet_name = u'シート1'


            # メール送信 設定

            kw = {}
            # 送信先メールアドレス 
            # Toを設定(ユーザーが入力したメールアドレスへ送信)
            kw['to'] = mailaddress
            # Ccを設定(省略可能)
            kw['cc'] = 'admin@domain.com'
            # Bccを設定(省略可能)
            kw['bcc'] = 'admin@domain.com'

            # 送信元メールアドレス(Google App Engineの「owner」もしくは「developer」のメールアドレスが必要)
            kw['sender'] = 'admin@domain.com'
            # 件名
            kw['subject'] = '件名'

            # 本文はTEXT形式、HTML形式のどちらかを入力
            # 本文 TEXT形式

            # 入力された名前を、メール内に埋め込んでいます。
            kw['body'] = lastname + u' ' + firstname + u'様へ\nサンプルメール:\nこれは本文です'
            # 本文 HTML形式 (コメントアウト中)
            #kw['html'] = 'サンプルメール:\nこれは<strong>本文</strong>です'

            # メールの送信はワークシートの書き込み成功後に実行される

#####################################################################
# ここまで編集が必要な項目
#####################################################################


            # 投稿時間を取得
            now_date = unicode(datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S"))

            # 書き込み内容のリストを設定(投稿時間 + ユーザーが入力した情報を設定)
            values = [now_date ,lastname ,firstname ,lastname_yomi ,firstname_yomi ,mailaddress ,post_number ,province ,city ,phone ,gender ,birthday ,other]

            # SpreadsheetsServiceを取得
            service = gdata.spreadsheet.service.SpreadsheetsService()
            gdata.alt.appengine.run_on_appengine(service, store_tokens=True,single_user_mode=True)

            # 編集に使用するユーザーアカウントの情報を設定
            service.email = user_mail
            service.password = password
            # 設定したユーザーアカウントでログインを実行
            service.ProgrammaticLogin()

            # スプレッドシートIDを取得
            spreadsheet_id = self.getSpreadsheetIdByName(service, spreadsheet_key, spreadsheet_name)

            # ワークシートIDが取得出来なかった場合、メッセージを表示して処理を終了
            if spreadsheet_id == '':
                self.response.out.write('error<br/>')
                self.response.out.write('Spreadsheet not found.')
                return

            # ワークシートIDが取得できた場合、書き込み処理の実行
            self.addSpreadsheetRecord(service, spreadsheet_key, spreadsheet_id, values)

            # メール送信の実行
            message = mail.EmailMessage(**kw)
            message.send()

            # 全ての処理が成功したら、完了ページへ移動
            self.redirect('/thanks')
            return
        except BaseException, e:
            self.response.out.write('error<br/>')
            self.response.out.write(e)
            return


    def getSpreadsheetIdByName(self, service, spreadsheet_key, Spreadsheet_name):
        u'''スプレッドシートIDを取得'''

        # keyを使ってスプレッドシートを取得
        feed = service.GetWorksheetsFeed(spreadsheet_key)
        spreadsheet_entry = None
        spreadsheet_id = ''

        if not feed:
            # 取得出来なかった場合、空欄を返却
            return ''

        # スプレッドシートが取得できた場合、書き込み先のシートを取得
        for entry in feed.entry:
            # 取得したいシート名と一致した場合、そのシートを取得
            if(entry.title.text == Spreadsheet_name):
                spreadsheet_entry = entry
                break
        # 取得したシートからスプレッドシートIDを取得
        if spreadsheet_entry:
            id_parts = spreadsheet_entry.id.text.split('/')
            spreadsheet_id = id_parts[-1]
        else:
            # 取得出来なかった場合、空欄を返却
            return ''
        # 取得したスプレッドシートIDを返却
        return spreadsheet_id


    def addSpreadsheetRecord(self, service, spreadsheet_key, spreadsheet_id, values):
        u'''スプレッドシートへ書き込み処理を実行'''
        try:
            data={}
            # スプレッドシートのタイトル一覧(一列目の項目)を取得
            titles = self.getSpreadsheetTitleList(service, spreadsheet_key, spreadsheet_id)

            # 書き込み内容と、タイトル一覧を対応付ける
            for i,value in enumerate(values):
                if i < len(titles):
                    key = titles[i]
                    data[key] = value
                else:
                    break
            # スプレッドシートキー、スプレッドシートIDを使ってスプレッドシートへの書き込みを実行
            return service.InsertRow(data, spreadsheet_key, spreadsheet_id)

        except Exception, e:
            raise e



    def getSpreadsheetTitleList(self, service, spreadsheet_key, spreadsheet_id):
        u'''スプレッドシートのタイトル一覧を取得'''
        first_row_contents = []
        #スプレッドシートの一行目(タイトル)を取得
        query = gdata.spreadsheet.service.CellQuery()
        query.max_row = '1'
        query.min_row = '1'
        feed = service.GetCellsFeed(spreadsheet_key, spreadsheet_id, query=query)

        # 取得できた場合、タイトルのリストを作成
        for entry in feed.entry:
            first_row_contents.append(entry.content.text)

        # タイトル一覧を格納したリストを返却
        return first_row_contents

def main():
    run_wsgi_app(webapp.WSGIApplication([(r'.*', Page)]))
if __name__ == "__main__":
    main()

「thanks.py」

フォーム入力が完了した際に表示する画面を設定する。

# coding: utf-8
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

from google.appengine.ext.webapp import template

############################################################
## フォーム投稿サンプル 完了画面
############################################################
class Page(webapp.RequestHandler):
    def get(self):
        # ページの表示のみを行う
        self.response.out.write(template.render('templates/thanks.html', {}))

def main():
    run_wsgi_app(webapp.WSGIApplication([(r'.*', Page)]))
if __name__ == "__main__":
    main()

「atom」フォルダ内の「__init__.py」

スプレッドシートへ書き込みと同様に、72行目・73行目を変更する。

変更前
MEMBER_STRING_ENCODING = 'utf-8'
MEMBER_STRING_ENCODING = unicode

変更後
MEMBER_STRING_ENCODING = 'utf-8'
MEMBER_STRING_ENCODING = unicode

なお、「templates」フォルダ内の「index.html」、「templates」フォルダ内の「thanks.html」、「css」フォルダ内の「style.css」のサンプルファイルはダウンロード可能だ。これらのファイルを必要に応じて、書き換えていただきたい。

こちらからダウンロード

「images」フォルダ内の画像

前回・前々回とも「images」フォルダにはアイコン画像「favicon.ico」しか含まれていなかったが、今回はヘッダーやボタンなどの画像も入れておく。

アイコンやヘッダー、ボタンなどの画像が入った「images」フォルダ

ここまで用意ができれば、あとは今までと同じ方法でWebアプリケーションの追加およびフォルダ指定を実行。デプロイ後にブラウザでアクセスして、フォーム入力画面が表示されるかを確認しよう。

実際にフォームへの入力を済ませると、「thanks.html」の画面へと移行。同時にスプレッドシートへの反映と、ユーザーに対するメール送信が行われる。

デプロイ後、ブラウザでアクセスした際に表示されるフォーム画面

フォームに必要事項を入力して「応募する」をクリックしてみる

ブラウザ上には「thanks.html」の画面が表示される面

フォーム入力の完了と同時に、スプレッドシート上には各項目が自動入力される

フォーム入力を行ったユーザー向けに送られたメール。サンプルのため簡素だが、件名や本文のカスタマイズ、さらにはHTML形式を選択すれば見栄えも大きく変わるだろう

ここまでの連載でGoogle App Engineを使ったメール送信、スプレッドシートへの書き込み、そして2つを組み合わせたフォームまでを順番に紹介してきた。個別の機能ではわかりづらかった部分も、具体的な形になると身近に感じられ、なおかつ幅広い活用性を持つことを理解できるだろう。

このようにGoogle App Engineを使うと、GoogleドキュメントやGmailなどサービス単位では困難だった、もしくはできなかったことも実現可能なのである。