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

前回までで「編集画面」を表示するところまで作ったので、次はその画面で[更新]ボタンをクリックした時の処理を実装する。

「pynote/controllers.py」を開いて、「update」メソッドを定義する。このメソッドはファイル名とその内容を引数に取る。

pynote/controllers.py
def writeText(file_name, text):
    f = open(contents_dir + file_name, "w")
    f.write(text)
    f.close()

class Root(controllers.RootController):
    # 省略...
    
    @expose()
    def update(self, name, text):
        writeText(name, text)

        flash(u"「%s」 を編集しました。" % name)
        return redirect("index", redirect_params=dict(file=name))

これでファイルの内容を編集できるようになった。

これで完成!あ〜しんど
だいたいこのアプリを作るのにかかった時間が2時間ぐらい。JavaC#で同じものを作る場合の生産性を考えてみると、

TurboGears > C#ASP.NET) >>>> 超えられない壁 >>>> JavaJSP/Servlet/Struts

かな。(多分に好き嫌いが入っています)

このぐらいの規模のアプリを作るのなら圧倒的にTurboGearsが上だと思う。覚えることが少ないので*1導入のための学習コストが低くすむ点も見逃せない。問題があるとすればPythonという日本ではマイナーな言語とTurboGearsという将来が約束されているわけではないフレームワークを使用することによるリスク*2をどう考えるかだけ。

まぁ実際のところ、もう少し規模が大きくなってきて要件が増えてくると、とたんにしっちゃかめっちゃかになる可能性も無きにしも非ずなわけで、そうなると結局は一番手になじんでいるASP.NETを使うという選択肢になるんだけどね。
しかし、さわっていて楽しいのは断然 TurboGears

ということで以下全ソース

pynote/controllers.py

from turbogears import controllers, expose, flash, widgets, redirect, validators

import os
import turbogears

contents_dir = "contents/"

# TableFormのキャンセルボタンをつけただけ
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>
	"""

def readText(file_name):
	if file_name == "": return ""

	f = open(contents_dir + file_name, "r")
	text = f.read()
	f.close()

	return text

def writeText(file_name, text):
	f = open(contents_dir + file_name, "w")
	f.write(text)
	f.close()

nform = PyNoteForm(
    action="add",
    submit_text=u"追加",
    fields=[
        widgets.TextField("name", u"名称"),
        widgets.TextArea("text", u"内容")
    ])

class Root(controllers.RootController):
	@expose(template="pynote.templates.index")
	def index(self, file=""):
		return dict(
			files=os.listdir(contents_dir),
			edit_text=readText(file), edit_file=file)

	@expose(template="pynote.templates.new")
	def new(self):
		return dict(nform=nform)

	@expose()
	@turbogears.error_handler(new)
	@turbogears.validate(form=nform,
		validators=dict(name=validators.NotEmpty(), text=validators.NotEmpty()))
	def add(self, name, text):
		f = open(contents_dir + name, "w")
		f.write(text)
		f.close()

		flash(u"「%s」 を追加しました。" % name)
		redirect("index")

	@expose(template="pynote.templates.edit")
	def edit(self, file):
		eform = PyNoteForm(
		    action="update",
		    submit_text="更新",
		    fields=[
		        widgets.Label(label=u"名称", default=file),
		        widgets.TextArea("text", u"内容", default=readText(file))
		    ],
		    hidden_fields=[
		        widgets.HiddenField("name", default=file)
		    ]
		)
		return dict(eform=eform)

	@expose()
	def update(self, name, text):
		writeText(name, text)

		flash(u"「%s」 を編集しました。" % name)
		return redirect("index", redirect_params=dict(file=name))
		
	@expose()
	def delete(self, file):
		os.remove(contents_dir + file)

		flash(u"「%s」 を削除しました。" % file)
		return redirect("index")

pynote/templates/index.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_sidebar">
        <ul>
            <li py:for="f in files">
                <a href="index?file=${f}" py:content="f">item</a>
            </li>
        </ul>
    </div>
    
    <div id="pynote_main">
        <div id="pynote_command">
            <a href="new">新規作成</a>
            <div py:if="edit_file != ''" style="display: inline;">
                <a href="edit?file=${edit_file}">編集</a>
                <a href="delete?file=${edit_file}"
                    onclick="return confirm('本当に削除しますか?')">削除</a>
                <a href="index">閉じる</a>
            </div>
        </div>
        <div id="pynote_content">
            <pre py:content="edit_text">
            </pre>
        </div>
    </div>
</div>
</body>
</html>

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>

pynote/templates/edit.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 - Edit</title>
</head>
<body>
    <div id="pynote">
        ${eform.display()}
    </div>
</body>
</html>

pynote/static/css/style.css

/*
 * Quick mash-up of CSS for the TG quick start page.
 */

html, body {
  color: black;
  background-color: #ddd;
  font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, verdana, sans-serif;
  margin: 0;
  padding: 0;
}

#header {
  height: 80px;
  width: 777px;
  background: blue URL('../images/header_inner.png') no-repeat;
  border-left: 1px solid #aaa;
  border-right: 1px solid #aaa;
  margin: 0 auto 0 auto;
}

a.link, a, a.active {
  color: #369;
}

#main_content {
  color: black;
  font-size: 127%;
  background-color: white;
  width: 757px;
  margin: 0px auto 0px auto;
  border-left: 1px solid #aaa;
  border-right: 1px solid #aaa;
  padding: 10px;
}

#footer {
  border: 1px solid #aaa;
  border-top: 0px none;
  color: #999;
  background-color: white;
  padding: 10px;
  font-size: 80%;
  text-align: center;
  width: 757px;
  margin: 0 auto 1em auto;
}

#status_block {
  margin: 0px auto 15px auto;
  padding: 15px 0px 15px 55px;
  background: #cec URL('../images/ok.png') left center no-repeat;
  border: 1px solid #9c9;
  width: 450px;
  font-size: 120%;
  font-weight: bolder;
}

.notice {
  margin: 0.5em auto 0.5em auto;
  padding: 15px 10px 15px 55px;
  width: 450px;
  background: #eef URL('../images/info.png') left center no-repeat;
  border: 1px solid #cce;
}

.fielderror {
    color: red;
    font-weight: bold;
}


#pynote {
  height: 270px;
  padding-top: 0px;
}
#pynote_sidebar {
  float: left;
  width: 150px;
}
#pynote_sidebar ul {
  margin: 0px;
  padding-top: 20px;
}
#pynote_sidebar li {
  padding: 4px;
}
#pynote_main {
  margin-left: 160px;
}
#pynote_command {
}
#pynote_command a {
  font-size: small;
  margin-right: 5px;
}
#pynote_content {
  height: 200px;
  margin: 10px 10px 0px 0px;
  border: solid 1px silver;
}
#pynote_content pre {
  height: 90%;
  margin: 0px;
  padding: 6px;
  overflow: scroll;
}
#pynote .tableform th {
  width: 100px;
}

*1:といっても、HTML、CSSJavaScriptなどのWeb標準技術とPythonの基礎知識ぐらいは必要なわけだが

*2:TurboGearsで作ったアプリを保守する人材をどうやって確保するか?とか