2008年9月6日土曜日

Google App Engine でダイエット表の作成 (4) - 完成 o(^^)o

追記(2008.9.6) : ダイエット表を作成するには → http://4diet.appspot.com/


Google App Engine でダイエット表の作成 (3)」の続き。前回は、実際にダイエット表を作成するクラスである DietTable を作成した。今回は、これをテンプレートから呼出し、ダイエット表を生成する部分、そして最初の設定画面、画面遷移を扱うコントローラ  ( webapp.RequestHandler のサブクラス ) を作成して完成。実際には、この部分から作りはじめているのだけれど ^^;

 

ダイエット表を生成するテンプレート

まずは、DietTable クラスをテンプレートで呼出している部分。シンプルに、

{{dietTable.toHtml}}

としているだけ。そして、このテンプレートにおいて、 DietTable クラスで設定した CSS のクラス指定に対して、CSS を設定している。 CSS における背景の色をプリンタで印刷する場合、デフォルトでは印刷されないのでブラウザの設定が必要。 (cf. Firefox, IE で背景色も印刷する )

 

diet_table.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>ダイエット表</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <!-- Diet.py の Diet.DietTable#_addCssToHtmlTable() で設定したものに対応 -->
    <style type="text/css">
    <!--
    table {
        border-collapse: collapse;
        width: 175mm;
        height: 240mm;
        border-color: #000;
    }
    table, td {
        border: solid 1px;
        text-align: center;
    }
    td {
        height: 4mm;
        font-size: x-small;
    }
    
    .sunday{ background-color: #FFEEFD; }
    .saturday{ background-color: #EEF2FF; }

    .x_axis{ border-top: solid 3px; width:2em;}
    .y_axis{ border-right: solid 3px; }

    .x_axis_05 { border-top: dotted 2px; width:2em;}
    .x_axis_1 { border-top: solid 2px; width:2em;}
    .y_axis_10 { border-right: solid 2px; }
    .y_axis_05 { border-right: dotted 2px; }
    
    .target , .weightNow { background-color: #ffc; }

    h1 {
        color: #000;
        text-align: center;
    }
    
    .forPrint {
        text-align: center;
    }

    @media print {
        .forPrint {
            display: none;
        }
    }
    -->
    </style>
  </head>
  <body>

    <p class="forPrint">表の中の色を印刷するには
        <a href="http://jutememo.blogspot.com/2008/08/firefox_23.html#ie">
        こちら</a>の説明を参考にしてください。
        (Firefox の場合は
        <a href="http://jutememo.blogspot.com/2008/08/firefox_23.html#firefox">
        こちら</a>。)
    </p>

    <h1>{{dietTable.year}} / {{dietTable.month}}</h1>
    
    {{dietTable.toHtml}}
    
  </body>
</html>

 

最初に表示される設定画面

上記のダイエット表を出力するページや設定画面は、当然ながら最初にデザインを無視して作っている。ここでやっと設定画面をデザインすることにした。デザインするのに使ったのは Inkscape 。絵心はないので適当にシンプルなものを作った。 ^^;  以前に少しだけ使ったときのことを参考に。

とりあえず、何かスリムな感じがするようなイメージを… (@_@;)

080906-002

絵が描けたら、今度はフォームを送信する部分を、表示内容に応じて長さが変化するように設定しなければならない。入力に不備があった場合、エラーメッセージを表示するためだ。作成ボタンを含んでいる影のある四角の部分を切取り、 Gimp で縁を拡大して、CSS で指定して伸縮できるように画像を抜き出した。 (cf. CSS と JavaScript で伸縮する領域を作成 )

HTML のファイルは、フォームの部分だけ抽出して別ファイルとした。

 

ソースコード

 

setting_page.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>表の設定ページ | 計るだけダイエット</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
   
    <script type="text/javascript">
    <!--
    /* このページが読み込まれたときに呼出される関数 */
    function setup(){
        resizeContent();
    }
    /* ウィンドウをリサイズしたときに呼出される関数
     * id が topLeft の要素の幅を広げる
     */
    function resizeContent(){
        var topRightDiv = document.getElementById("topRightCorner");
        contentDiv  = document.getElementById("topLeft");
        contentDiv.style.width = topRightDiv.offsetWidth  + 11 + "px";
    }
    /* 入力された「現在の体重」に応じて「目標体重」を設定する */
    function setTargetValue(){
        var weight = parseFloat(document.forms["createTable"].weight.value)
        if (!isNaN(weight)){
            document.forms["createTable"].target.value = weight - getTargetValue();
        }
    }
    /* 設定できる目標体重の上限の 3/4 の値を返す */
    function getTargetValue(){
        return parseFloat(
            document.getElementById(
                "intervalLimit").firstChild.nodeValue) * 3 / 4
    }
    //-->
    </script>

    <style type="text/css">
    <!--
    body{
        background: #ffffff url("/img/title.png") top center no-repeat;
    }
    h1{ display: none; }
    .error{
        color: red;
        margin-bottom: 10px;
    }
    #content {
        margin: 300px auto 0px;
        text-align: center;
    }
    #input {
        margin: 0px auto;
        width: 450px;
    }
    #input table{
        text-align: left;
        margin: 0px auto;
    }
    .small { font-size: small }

    #createBtn{
        background: url('/img/createBtn.png');
        width: 338px;
        height: 114px;
        border: none;
        cursor: pointer;
    }
    #createBtn:hover{
        background: url('/img/createBtnActivated.png');
        border: none;
    }

    /*---------------------------------------------------------------
     * 影
     */
    /* 右側の縦の影のライン */
    #rightLine{
        position: relative;
        background: transparent url("/img/rightLine.png") right repeat-y;
    }
    /* 右上の角の影 */
    #topRightCorner{
        position: relative;
        background: transparent url("/img/topRightCorner.png") top right no-repeat;
    }
    /* 右下の角の影 */
    #bottomRightCorner{
        position: relative;
        top: 6px;
        background: transparent url("/img/bottomRightCorner.png") bottom right no-repeat;
    }
    /* 下の横の影のライン */
    #bottomLine{
        position: relative;
        left: -11px;
        background: transparent url("/img/bottomLine.png") bottom repeat-x;
    }
    /* 左下の角の影 */
    #bottomLeftCorner{
        position: relative;
        left: -11px;
        background: transparent url("/img/bottomLeftCorner.png") bottom left no-repeat;
    }
    /* 背景となる色を指定する領域。
     * リサイズにより、JavaScript でこの領域の幅を変更する。
     */
    #topLeft{
        position: relative;
        background-color: #fff;
        top: -6px;
    }
    /* 文字を表示する領域 */
    #content2{
        position: relative;
        padding: 10px 0px;
    }
    
    /* --------------------------- */
    #bottom_ad{
        text-align: center;
        margin: 30px auto 0px;
    }
    
    -->
    </style>

  </head>
  <body onresize="resizeContent()" onload="setup()">
    
    <h1>計るだけダイエットのための表を作成</h1>
    
    <div id="content">
        <p>以下の項目を半角数字で入力してください。</p>
        <!-- ダイエットの情報を設定したときのエラー表示 -->

        <div id="input">
        <div id="rightLine">
            <div id="topRightCorner">
                <div id="bottomRightCorner">
                    <div id="bottomLine">
                        <div id="bottomLeftCorner">
                            <div id="topLeft">
                                <div id="content2">

                                    {%include "setting_form.htm" %}
                                
                                </div><!-- end content -->
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        </div><!-- end input -->

    </div><!-- end content -->
    
  </body>
</html>

現在の体重を入力すると、設定できる上限の 3/4 の値が「目標体重」に自動的に入力されるように JavaScript で設定した。 (cf. JavaScript でキー入力に応じて HTML の要素の値とスタイルを変化させる )

 

setting_form.html

<div class="error">
{% if errors %}
    {% for error in errors %}
        {{error}}<br />
    {% endfor %}
{% endif %}
</div><!-- end error -->

<form name="createTable" action="/createTable" method="post">
    <table>
        <tr>
            <td>年 / 月</td>
            <td>
                <input type="text" name="year"
                    value="{{diet.year}}" maxlength="4" />
                 /
                <input type="text" name="month"
                    value="{{diet.month}}" maxlength="2" />
            </td>
        </tr>
        <tr>
            <td>現在の体重</td>
            <td><input type="text" name="weight"
                value="{{diet.weight}}" onkeyup="setTargetValue()" /></td>
        </tr>
        <tr>
            <td>目標体重</td>
            <td>
                <input type="text" name="target"
                    value="{{diet.target}}" />

                {% if targetLimit %}
                    <span class="error">
                        {{targetLimit}} より小さい値は設定できません。
                    </span>
                {% endif %}
            </td>
        </tr>
    </table>

    <p><input id="createBtn" type="submit" value=""></p>

</form>

<p class="small">
    ※「目標体重」は「現在の体重」より
    -<span id="intervalLimit">{{ dt.intervalLimit }}</span> kg
    以内に設定してください。
</p>

 

設定ファイル

背景となる画像を Google App Engine で参照できるように設定が必要となる。 (cf. Google App Engine で画像を扱うには )

 

app.yaml

application: 4diet
version: 1
runtime: python
api_version: 1
  
handlers:
- url: /img
  static_dir: img
- url: /.*
  script: dietsetting.py

 

コントローラ

最後になってしまったけれど、コントローラの部分。webapp.RequestHandler のサブクラス。

 

dietsetting.py

import os
import wsgiref.handlers
from google.appengine.ext.webapp import template
from google.appengine.ext import webapp
import Diet
import logging

class SettingPage(webapp.RequestHandler):
    """ 設定画面を表示する """
    def get(self):
       moveTo(self.response, 'setting_page.html', {
                                    "dt"    : Diet.DietTable(),
                                    "diet"  : Diet.Diet().setDefaultSetting()
                                    })

class TablePage(webapp.RequestHandler):
    """ ダイエット表を作成する """
    def post(self):
        diet = Diet.Diet(self.request.get("weight"),
                            self.request.get("target"),
                            self.request.get("year"),
                            self.request.get("month"))
        dt = Diet.DietTable()
        dt.setDiet(diet)
        try:
            # 入力された値を検証する
            diet.validate()
            # ダイエット表を作成する
            dt.createHtml()
            moveTo(self.response, 'diet_table.html', {'dietTable' : dt})
        except Diet.DietValErr, e:
            template_values = {
                'errors'        : e.errorMessages,      # エラーメッセージ
                'targetLimit'   : e.targetLowerLimit,   # 目標体重の下限
                'diet'          : diet,                 # ダイエット情報
                'dt'            : dt }                  # ダイエット表
            moveTo(self.response, 'setting_page.html', template_values)

def moveTo(response, tmpl, templateValues={}):
    u""" 画面遷移 """
    path = os.path.join(os.path.dirname(__file__), tmpl)
    response.out.write(template.render(path, templateValues))

def main():
    # ログの設定
    logging.getLogger().setLevel(logging.DEBUG)
    # URL と対応するクラス
    application = webapp.WSGIApplication(
                    [('/', SettingPage),            # 設定
                     ('/createTable', TablePage)],  # ダイエット表
                    debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()

うーん、エラー処理が何か今一 … (+_+)

 

アップロード

アプリケーションを登録後、作成したファイルをアップロードした。diet フォルダの中に作成していたので、コマンドラインより、

appcfg.py update diet/

 

完成!

http://4diet.appspot.com/