TurboGearsでメモ帳アプリを作る 1

だいぶ前にPython製のWebアプリケーションフレームワークであるTurboGearsをさわってみた事があったけど、いまいちピンとこなくて、そのままほったらかしにしていた。でも、この前なんとなくさわったみたら一気に理解が進んで楽しかったので、簡単なWebアプリを作ってみることにした。

今回作るアプリはテキストファイルを作成・編集できるメモ帳アプリ「PyNote」
TurboGearsといえばデータベースを使ったものを作るのが普通だけど、めんどくさいのでこれにした。

アプリ仕様

  • テキストファイルの作成
  • テキストファイルの編集
  • テキストファイルの削除
  • テキストファイルの表示

画面

  • メイン画面

左側に作成したファイルの一覧を表示して、そのリンクをクリックするとファイルの内容を右側に表示する。
[新規作成]をクリックすると「新規作成画面」に遷移する。
[編集]をクリックすると「編集画面」に遷移する。
[削除]をクリックすると確認のメッセージを表示して、OKならばファイルを削除して画面を更新する。
[閉じる]をクリックすると最初の画面に遷移する。(何も表示していない状態)

  • 新規作成画面

ファイル名とテキストファイルの内容を入力して、[追加]ボタンをクリックするとテキストファイルを追加する。また[キャンセル]ボタンをクリックすると前の画面に戻る。

  • 編集画面

テキストファイルの内容だけを編集できる。[更新]ボタンをクリックするとその内容でテキストファイルを保存する。また[キャンセル]ボタンをクリックすると前の画面に戻る。

プロジェクトの作成

TurboGearsのバージョンは「1.0.3.2」、Pythonは「2.4.3」を利用する。

まずはプロジェクトの作成。以下のコマンドを実行する。

PS > tg-admin quickstart PyNote

その後、モジュール名を聞いてくるので[pynote]のままでEnter。認証機能はいらないので[no]のままでEnter。これでプロジェクトの作成が完了。

作った直後のプロジェクトの構成は以下

│  dev.cfg
│  README.txt
│  sample-prod.cfg
│  setup.py
│  setup.pyc
│  start-pynote.py
│  test.cfg
│
├─pynote
│  │  controllers.py
│  │  json.py
│  │  model.py
│  │  release.py
│  │  __init__.py
│  │
│  ├─config
│  │      app.cfg
│  │      log.cfg
│  │      __init__.py
│  │
│  ├─sqlobject-history
│  ├─static
│  │  ├─css
│  │  │      style.css
│  │  │
│  │  ├─images
│  │  │      favicon.ico
│  │  │      header_inner.pn
│  │  │      info.png
│  │  │      ok.png
│  │  │      tg_under_the_ho
│  │  │      under_the_hood_
│  │  │
│  │  └─javascript
│  ├─templates
│  │      login.kid
│  │      master.kid
│  │      welcome.kid
│  │      __init__.py
│  │
│  └─tests
│          test_controllers.py
│          test_model.py
│          __init__.py
│
└─PyNote.egg-info
        dependency_links.txt
        not-zip-safe
        paster_plugins.txt
        PKG-INFO
        requires.txt
        SOURCES.txt
        sqlobject.txt
        top_level.txt

実装

まずは「メイン画面」を作っていく。ファイルの一覧を表示しようにもファイルがないので、ここでは「新規作成画面」へのリンクだけを作る事にする。

「メイン画面」のテンプレートファイルとして、「pynote/templates/welcome.kid」ファイルを「pynote/index.kid」という名前に変更して、中身を以下のように変更する。

pynote/templates/welcome.kid

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
    py:extends="'master.kid'">
<head>
	<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
	<title>PyNote</title>
</head>
<body>
<div id="pynote">
    <div id="pynote_main">
	    <div id="pynote_command">
	        <a href="new">新規作成</a>
	    </div>
    </div>
</div>
</body>
</html>

「new」というURLに対するリンクを作っただけ。

次は「pynote/controllers.py」を開く。

pynote/controllers.py
from turbogears import controllers, expose, flash
# from model import *
# import logging
# log = logging.getLogger("pynote.controllers")

class Root(controllers.RootController):
    @expose(template="pynote.templates.welcome")
    def index(self):
        import time
        # log.debug("Happy TurboGears Controller Responding For Duty")
        flash("Your application is now running")
        return dict(now=time.ctime())

デフォルトでは「index」メソッドは「pynote/templates/welcome.kid」をテンプレートとして使うように指定されているので、これを変更して、あと余計なコードを省いておく。

pynote/templates/welcome.kid
from turbogears import controllers, expose, flash

class Root(controllers.RootController):
	@expose(template="pynote.templates.index")
	def index(self):
		return dict()

ここまでで一度Webアプリを起動して、このサイトにアクセスしてみる。コマンドプロンプトから以下のコマンドを実行してWebサーバーを起動する。

PS > python start-pynote.py

デフォルトでは「http://localhost:8080」に割り当てられるので、ブラウザからこのアドレスにアクセスすると以下の画面が表示される。

次は「新規作成画面」を作る。テンプレートファイルは「pynote/templates/new.kid」

pynote/templates/new.kid

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
    py:extends="'master.kid'">
<head>
	<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
	<title>PyNote - New</title>
</head>
<body>
	<div id="pynote">
	    ${nform.display()}
	</div>
</body>
</html>

入力フォームはTurboGearsのUIコンポーネントである「Widgets」を使う。コントローラから「nform」という変数名で渡すので、ここではそれに対して「display」メソッドを実行して、HTMLを出力するだけ。

これに対応するメソッドを「pynote/controllers.py」に追加する。

入力フォームをWidgetsで作るには、widgets.Formクラスを継承したwidgets.ListFormかwidgets.TableFormを使う。これらを使うと、formタグとsubmitボタンをtableでレイアウトしたHTMLを生成してくれる。そしてインスタンス化時にfields引数で入力用のWidgetsを指定するとテーブルの各行にそのWidetsをレイアウトしてくれる。

これは非常に便利だけど、そのままではsubmitボタンの横にキャンセル用のボタンを配置することができないので、widgets.TableFormを継承して、キャンセルボタンを配置できるように変更する。

pynote/controllers.py

from turbogears import widgets

class PyNoteForm(widgets.TableForm):
    template = """
    <form xmlns:py="http://purl.org/kid/ns#"
        name="${name}"
        action="${action}"
        method="${method}"
        class="tableform"
        py:attrs="form_attrs"
    >
        <div py:for="field in hidden_fields"
            py:replace="field.display(value_for(field), **params_for(field))"
        />
        <table border="0" cellspacing="0" cellpadding="2" py:attrs="table_attrs">
            <tr py:for="i, field in enumerate(fields)"
                class="${i%2 and 'odd' or 'even'}"
            >
                <th>
                    <label class="fieldlabel" for="${field.field_id}" py:content="field.label" />
                </th>
                <td>
                    <span py:replace="field.display(value_for(field), **params_for(field))" />
                    <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" />
                    <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" />
                </td>
            </tr>
            <tr>
                <td>&#160;</td>
                <td>
                    ${submit.display(submit_text)}
                    <!-- ここ追加しただけ -->
                    <input type="button" value="キャンセル" onclick="history.back()" />
                </td>
            </tr>
        </table>
    </form>
    """

widgets.TableFormのソースから「template」属性の値をコピーしてきて、そのれ一部を変更して「PyNoteForm」クラスの「template」属性に上書きするだけ。

「新規作成画面」のURLは「new」なので、これと同じ名前のメソッドをコントローラクラスに定義する。

class Root(controllers.RootController):
    # 省略...

    @expose(template="pynote.templates.new")
    def new(self):
        nform = PyNoteForm(
            action="add",
            submit_text=u"追加",
            fields=[
                widgets.TextField("name", u"ファイル名"),
                widgets.TextArea("text", u"内容")
            ])
        return dict(nform=nform)

「expose」デコレータで先程作成したテンプレートファイルを関連付ける。
まずは「PyNoteForm」クラスをインスタンス化し、formのactionとして「add」、submitボタンのテキストを「追加」、入力用のWidgetsとして、widgets.TextFieldとwidgets.TextAreaを指定する。
そしてそのインスタンスを「nform」というキーの値にして、ディクショナリとして返す。こうすることでテンプレートに変数を渡す事ができて、テンプレート側ではディクショナリのキー名で変数にアクセスする。

この画面にブラウザからアクセスすると以下のように表示される。

ちなみにこの時点でスタイルシートを若干いじっているが、その内容は割愛する。スタイルシートをいじるには「pynote/static/css/style.css」を変更する。