2010-09-28

Python で書いたフィルタを GAE に載せる #2

前回で、おおよそのデザイン(意匠と設計)ができた。今回は、コードを書き、GAE の SDK 上で動かしてみることが目標だ。

アプリ設定

初期状態と違うのは、スタイルシート用のディレクトリを静的コンテンツとして追加している点だ。

(app.yaml より)
 1: application: bloggerglass
 2: version: 1
 3: runtime: python
 4: api_version: 1
 5: 
 6: handlers:
 7: - url: /stylesheets
 8:   static_dir: stylesheets
 9: 
10: - url: .*
11:   script: main.py

リクエストハンドラ

PageFeedExtractTitles それに MakeList が前回までのフィルタを書き直したクラスだ。そのフィルタを適用している部分が 5 〜 8 行目になる。ついでに、全記事数もフィルタの 1 つ(フィードを取得するフィルタ)から取り出している。

(main.py より)
 1: class MainHandler(webapp.RequestHandler):
 2:     def retrieve(self, page):
 3:         pf = PageFeed(page)
 4:         filters = [pf, ExtractTitles(), MakeList()]
 5:         pipe = []
 6:         for f in filters:
 7:             f.input(pipe)
 8:             pipe = f.output()
 9:         self.total_posts = pf.total_posts()
10:         return pipe
11: 

以下が、リクエストハンドラの本体だ。フィードの取得から HTML のリストとしての整形までは上記の retrieve で完了する。ここでは HTML ページを作るための、その他の情報をまとめて、テンプレートにわたしている。

(main.py より)
12:     def get(self):
13:         stylesheet = "/stylesheets/default.css"
14:         total_pages = 10
15:         request_page = int(self.request.get("page", default_value="0"))
16: 
17:         lines = self.retrieve(request_page)
18:         total_pages = self.total_posts // PAGESIZE + 1
19:         pages = []
20:         for p in range(total_pages):
21:             page = Page()
22:             page.number = p
23:             if p == request_page:
24:                 page.current = True
25:             pages.append(page)
26: 
27:         template_values = {
28:             'lang': "ja",
29:             'title': "Blogger Glass [LOG+REPO]",
30:             'stylesheet': stylesheet,
31:             'total_pages': total_pages,
32:             'current_page': request_page,
33:             'lines': lines,
34:             'pages': pages,
35:             'home_url': "http://bloggerglass.appspot.com/",
36:             }
37: 
38:         path = os.path.join(os.path.dirname(__file__), 'page.html')
39:         self.response.out.write(template.render(path, template_values))

テンプレート

リクエストハンドラが使用するテンプレートを以下に示す。

特徴は、(a) 記事一覧を作るループ(17 〜 19 行目)、(b) ページ切り替えのためのリンクを作るループ(26 〜 32 行目) の 2 箇所だ。後者では、ループの中で条件判断を行い生成する内容を切り替えている。

(page.html)
 1: <!DOCTYPE HTML>
 2: <html lang='{{ lang }}'>
 3: <head>
 4: <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
 5: <title>{{ title }}</title>
 6: <link type='text/css' rel='stylesheet' href="{{ stylesheet }}" />
 7: </head>
 8: <body>
 9: <header>
10: <h1>{{ title }}</h1>
11: <h2>Page #{{ current_page }}</h2>
12: <p>in {{ total_pages }} pages</p>
13: </header>
14: <section>
15: 
16: <div id="content">
17: {% for line in lines %}
18:   {{ line }}
19: {% endfor %}
20: </div>
21: </section>
22: 
23: <nav>
24: <div class="pager">
25:   <span>Gr</span>
26:   {% for page in pages %}
27:     {% if page.current %}
28:       <span class="current-page">a</span>
29:     {% else %}
30:       <span class="other-page"><a href="/?page={{ page.number }}">a</a></span>
31:     {% endif %}
32:   {% endfor %}
33:   <span>ss</span>
34: </div>
35: </nav>
36: 
37: <footer>
38: <div><a href="{{ home_url }}">Blogger Grass</a></div>
39: </footer>
40: </body>
41: </html>

GAE にふくまれているテンプレートシステムは Django と呼ばれるものだ。一見、{% ... %} の中には Python のコードがそのまま書けるように思えるのだが、実際には Python 風の独自の文法のようだ。ループや条件判断も書けるが、かなり制限が厳しい。その分、テンプレートを使うコードの方で工夫が必要になる。

上述のリクエストハンドラの 20 〜 25 行目は、テンプレートの 26 〜 32 行目の記述のための「工夫」だ。Django のテンプレートでは、条件判断は変数の値による真偽判定しかできない。変数の値を数値や文字列と比較するというようなことができないのだ。ここでは、現在のページと他のページで生成する内容を変えるため、ページ番号と現在のページか否かのフラグをオブジェクトにくるんで、ページ数分用意してリストに詰めるということをやっている。

Django のテンプレートは複雑な記述には向かない。今回のページ切り替え部分の記述程度でも、テンプレートとそれを使うコードが密に絡んでしまっている。これは将来の変更(デバッグやら拡張やら)の足枷になるにちがいない。

プロトタイプは動くことが大事

フィルタをもとに書き直した部分もふくめて、とりあえず動くものはできた。ただ、フィルタからの書き直し方や、ハンドラ内での情報の保持の仕組みが、どうにも気に入らない。また、Python のこと、GAE のこと、知らないことが多くて、手探り状態での作業になった。動かすので精一杯になっていた。もう少しプラットフォームに慣れないと、見通しの良い設計はできないな。

とはいえ、プロトタイプは動くことが肝心。作る過程、動かそうとする過程(デバッグとも言う)、そして動かすことで課題を見つけることがプロトタイプの役割りだ。

次回は、全体を見直しつつ、リファクタリングしてみよう。

関連リンク

関連記事

0 件のコメント:

コメントを投稿