TurboGearsでBlog作ってみる(その1)

blogという名前でプロジェクトを作る

[C:\works]tg-admin.exe quickstart
Enter project name: blog
Enter package name [blog]:
Do you need Identity (usernames/passwords) in this project? [no] yes
Select your provider sqlobject or sqlalchemy [sqlobject]:

blogというディレクトリが作成される。
この下にもうひとつblogディレクトリがあって、その中にcontrollers.pyやmodel.pyなどのPythonファイルがある。

データモデル(もちろんオブジェクトなので適当な振る舞いも付け加えることができる。)はmodel.pyで定義する。

blogのデータは以下のような感じ

class Blog(SQLObject):
    date = DateCol()
    title = UnicodeCol()
    description = UnicodeCol()
    comments = MultipleJoin("Comment")
    author = ForeignKey("User")

class Comment(SQLObject):
    blog = ForeignKey("Blog")
    data = UnicodeCol()
    author = UnicodeCol()

Blogクラスは実際のブログエントリ用で、Commentはブログに対するコメントだ。
ひとつのBlogに対して複数のCommentがつくので、ここでは、One-to-Many関係を使う。
また、UserクラスはIdentity機構のためにすでに存在している。
UserとBlogクラス間もOne-to-Manyだ。
Userクラス側にも、逆参照用のプロパティを追加する。

    blogs = MultipleJoin("Blog")

これでとりあえずのモデルが出来上がった。
テーブルとサンプルデータを作成しておこう。
0.9a6では、デフォルトでSqliteがデータソースに設定されている。
だが、うまく動いてくれなかった。
ファイルは作成するが、開けないとエラーになる。
また、0.9a6では、以前のsqlite:///c|/filenameのような書き方と、
より自然なsqlite:///c:/filenameという書き方ができることになっているが、
同様のエラーです。
ってか%(current_dir_uri)sを展開したあとが、
後者の形式と思われるので、根本は同じエラーのようです。
なわけで、sqlobject.dburiは今までと同様の書き方でいきましょう。

sqlobject.dburi="sqlite:///c|/works/blog/devdata.sqlite"

ここで、tg-adminコマンドで、テーブルを作成できる。

[C:\works\blog]tg-admin.exe sql create
Using database URI sqlite:///c|/works/blog/devdata.sqlite

インタプリタでデータアクセスもできる。

[C:\works\blog]tg-admin.exe shell
Python 2.4.3 (#69, Mar 29 2006, 17:35:34) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import model
>>> u = model.User(user_name="aodag", display_name="aodag", email_address="aodag
@example.com", password="aodag")
2006-05-19 00:06:05,421 turbogears.identity.soprovider INFO Succesfully loaded "
blog.model.User"
2006-05-19 00:06:05,421 turbogears.identity.soprovider INFO Succesfully loaded "
blog.model.Group"
2006-05-19 00:06:05,437 turbogears.identity.soprovider INFO Succesfully loaded "
blog.model.Permission"
2006-05-19 00:06:05,437 turbogears.identity.soprovider INFO Succesfully loaded "
blog.model.VisitIdentity"
2006-05-19 00:06:05,437 turbogears.identity.soprovider INFO Identity provider no
t enabled, and no encryption algorithm specified in config.  Setting password as
 plaintext.
>>> model.hub.commit()

これでユーザーを作成できた。
アプリケーションを起動してアクセスしてみよう。

[C:\works\blog]start-blog.py

デフォルトでは、http://localhost:8080でアクセスできる。
既に用意されているトップページが表示されている。
右上にLoginリンクがあるので、そこからログインしてみよう。
Login状態で、トップページに戻ってきたはずだ。
今度は右上にLogoutリンクとUser.display_nameが表示される。

ここからは、実際のページを作成しよう。
ページはblog.templateパッケージにあるkidテンプレートが使用される。
とりあえずトップページ(welcome)と同じ内容のmypageを作ってみよう。
ただし、ログインしてないとアクセスできないようにする。
welcome.kidをコピーして、mypage.kidを作る。
controllers.pyのRootクラスにメソッドを追加する。

    @expose(template="blog.templates.mypage")
    @identity.require(identity.not_anonymous())
    def mypage(self):
        import time
        log.debug("Happy TurboGears Controller Responding For Duty")
        return dict(now=time.ctime())

ログインしていない状態でhttp://localhost:8080/mypageにアクセスすると、
ログインページに遷移する。
ログインしたら、MyPageに遷移する。

では、MyPageの中身を作っていこう。
MyPageでは以下のことができる。
日記を

  • 書く
  • 修正する
  • 削除する

ゆえに、MyPageはこれまで書いたエントリ一覧を表示しておく。
また、日記作成ページへのリンクも含めよう。

日記作成ページはnewBlogとすると、メソッドはこうなる。

    @expose(template="blog.templates.newBlog")
    @identity.require(identity.not_anonymous())
    def newBlog(self):
	return dict()

newBlog.kidは以下のとおり。
XHTMLなので、inputタグなどの/は必須だ。

<body>
<form action="save" method="post">
<table>
<tr><td><label>Date:</label></td>
<td><input name="date"/></td></tr>
<tr><td><label>Title:</label></td>
<td><input name="title"/></td></tr>
<tr><td><label>Description:</label></td>
<td><textarea name="description"></textarea></td></tr>
</table>
<input type="submit" value="Create"/>
</form>

保存用のメソッドとしてsaveを定義する。

    @expose()
    @identity.require(identity.not_anonymous())
    def save(self, date, title, description):
	hub.begin()
	try:
	    b = Blog(date=date, title=title, description=description, authorID=identity.current.user.id)
	    hub.commit()
	except:
	    hub.rollback()
	    raise
	hub.end()
	turbogears.redirect("mypage")

現在の認証情報はidentity.currentを通してアクセスできる。
更新後はmypageにリダイレクトするようにしている。

mypageの内容を変更する。

<body>
<a href="newBlog">New Blog</a>
</body>

newBlogページで間違いなく入力すると、Blog作成後mypageに遷移する。

ここで、mypageに一覧を出そう。
mypageメソッドを変更する。

    @expose(template="blog.templates.mypage")
    @identity.require(identity.not_anonymous())
    def mypage(self):
	blogs = Blog.select(authorID=identity.current.user.id)
        return dict(blogs=blogs)

selectメソッドは条件は、現在のユーザーのBlogのみを取得する条件だ。
表示するには以下のようにする。

<table>
<tr py:for="blog in blogs">
<td py:content="blog.date">YYYY/MM/DD</td>
<td py:content="blog.title">Blog Title</td>
<td py:content="blog.description">Description</td>
<td><a href="editBlog?blogId=${blog.id}">Edit</a></td>
</tr>
</table>

py:で始まる属性はkidで処理される。
forはループ、contentはタグ内を指定したデータで置き換える。
最後の列のEditは編集ページへのリンクだ。

今日はこのへんまで。

追記:
最後のsaveメソッド内でトランザクション処理が終わってなかったので、修正しました。
追記:
Commentクラスを少し変えました。mail_addressプロパティを削除しました。
追記:
分量が前後半ではとうてい収まりそうにないので、タイトルを「前半」から「その1」に変更しました。