Turbogears(SQLAlchemy)でテーブルを動的に読み込む

http://www.josw.net/blog/?p=1053

というわけで__new__の続きです。

Turbogearsでは、model.pyでテーブルとクラスを定義してそれらをマッピングしますね。

で、controller.pyでsession使ってmodel.Userみたいな感じでデータを読み出します。

ここまでは普通に紹介されてることですね。

では本題に移ります。

まず、同じ構造のテーブルが、時間が経つごとに名前を変えて増えていく、というシチュエーションを思い浮かべてください。

違うのはテーブル名だけなので、model.pyには以下のような定義があればよさそうな感じがします。


name = 'foo'
tablename = 'user_' + name
table_user = Table(tablename, metadata,
Column('id', Integer, primary_key=True),
Column('hitokoto', Unicode(400)),
)

class TableUser(object):
def __init__(self, hitokoto):
self.hitokoto = hitokoto

mapper(TableUser, table_user)

これでとりあえずtg-admin sql createが通り、実際に動作させることができます。

しかしこれだけだと、user_fooというテーブル以外を扱おうとすると、それなりに労力がかかりそうです。

というわけでこいつを以下のように書き換えて、もう少し扱いやすくします。


username = 'foo'
class MetaTableUser(object):
def __new__(cls, name):
tablename = 'user_' + name
table_user = Table(tablename, metadata,
Column('id', Integer, primary_key=True),
Column('hitokoto', Unicode(400)),
)
return table_user

table_user = MetaTableUser(username)

class TableUser(object):
def __init__(self, hitokoto):
self.hitokoto = hitokoto

mapper(TableUser, table_user)
metadata.remove(table_user)

はい。ここで出てきました我らが__new__さん。関数返してます。

何のためにこういうことしてるかというと、普通の関数に初期値を与えて扱えるようにするためなのと、controller.pyに同じ内容を書かなくて済むようにです。メタ関数(適当言ってます)。

それから、最後の行はおまじないです。さらっと言ってますが、この1行のために1日ほどかかってます。

ではでは下準備も済んだので、controller.pyの記述に入って行きましょう。

とりあえずこんなかんじです。


@expose(template="alchemymytest.templates.userpage")
def user(self, username='anonymous'):
flash(("This is", username, "\'s page."))

meta_table_user = model.MetaTableUser(username)

othermapper = mapper(model.MetaUser, meta_table_user, non_primary=True)
hitokotos = [hitokoto.hitokoto for hitokoto in session.query(othermapper)]
metadata.remove(meta_table_user)
return dict(username=username, hitokotos=hitokotos)

簡単に説明します。まず、http://localhost:8080/user/ というページを定義しました。

そして、そのページにはuser_hogehogeに当たる部分を引数として与えます。http://localhost:8080/user/hoge/ みたいな感じです。

次に、5行目は__new__さんがusernameのテーブル名を持ったTableオブジェクト(関数)を返してきてます。

そして7行目でマッピングするわけですが、そのままマッピングしちゃうとmodel.pyでマップしたものとかぶっちゃうので、別物としてマッピングします。

そして8行目でuser_usernameなテーブルを読み出していくというわけです。

そして9行目ですが、同じテーブルを定義しようとすると怒られるので、今定義したテーブルを削除してます。


では http://localhost:8080/user/ にアクセスしてみましょう。あ、テンプレートの準備がまだでしたね。


<!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>user page - ${username}</title>
</head>
<body>
<h1>${username}'s hitokoto</h1>
<ul>
<li py:for="hitokoto in hitokotos">
<${hitokoto}</a>
</li>
</ul>
</body>
</html>

これをuserpage.kidとして保存します。それから、ついでにuser_hogeとかでテーブルを何個か作って(user_anonymousは必須)、その中に適当なデータを格納しておいてください。

ここまでうまく行ってれば、user_anonymousに格納したデータがずらっとでてくるはずです。せっかくなので他のもみてみましょう。

さて、こんなところです。

眠さの限界なので考えずに書いてます。ごめんなさい。

わからないことあったらコメントで聞いてくださいね!


2008/4/2追記
本文に入れよう入れようと思って忘れてました。
別に、__new__を使わなくても出来ます。
こっちのほうが好きだったし、こういうやり方もあるよ的な感じです。