[TurboGears] ブログを作成する。その2
注意
この記事は、id:SumiTomohiko:20070115:1168855922の続きです。
ユースケース
ここで、これから作るブログの機能を整理します。具体的には、ユースケースを作ることで、必要な機能をまとめます。
このブログは、個人的に使用するものですが、それでも記事の追加や削除は、登録されているユーザのみに限定したいものです。しかし、記事を閲覧するのにわざわざログインしたくはありません。よって、アクターは、匿名ユーザとログインユーザの2種類を考えます。ここで、ログインしていないユーザを匿名ユーザ、ログインしているユーザをログインユーザとします。
ユーザを区別するとなると、ユーザを管理(追加、編集、削除)する機能も必要になりますが、このブログではこの機能は持たないことにします。ユーザの追加は最初の1回しかしないのに、管理画面を作るのは手間です。ユーザの編集も削除も基本的には行いません。ユーザの管理は、Catwalkか、tg-admin shellコマンドで行うことにします。
これをふまえると、ユースケースは以下のようになります。
アクター | アクション |
---|---|
匿名ユーザ | 記事を閲覧する。 |
匿名ユーザ | ログインする。 |
ログインユーザ | 記事を閲覧する。 |
ログインユーザ | 自分の記事を追加する。 |
ログインユーザ | 自分の記事を編集する。 |
ログインユーザ | 自分の記事を削除する。 |
ログインユーザ | ログアウトする。 |
URL
次に、このブログの機能を実行するURLを設計します。ここでは、以下のようにします。
URL | 説明 |
---|---|
/ | エラーとします。 |
/date?user= |
ユーザ |
/article?article= |
|
/add | 記事の追加画面を表示します。ユーザが匿名ユーザであれば、エラーにします。 |
/doadd | 記事を追加します。追加したら、"/date?user=<追加した記事のユーザID>&date=<追加した記事の日付>&span=1"にリダイレクトします。ユーザが匿名ユーザであれば、エラーにします。 |
/edit?article_id= |
|
/doedit?article_id= |
|
/delete?article_id= |
|
/login | ログイン画面を表示します。 |
/dologin | ログインします。ログインしたら、ログイン画面の直前に表示していた画面にリダイレクトします。 |
/logout | ログアウトします。ログアウトしたら、直前に表示していた画面にリダイレクトします。ユーザが匿名ユーザならば、エラーにします。 |
画面
本来なら、この辺りで画面を設計しなければならないのですが、これは作りながら適当に決めることとし、ここでは省略します。
モデル
id:SumiTomohiko:20070115:1168855922で、tg-admin quickstartコマンドに--idenityオプションをつけたので、tgdiary/model.pyには、既に以下のクラスが作られています。
- Visit
- VisitIdentity
- Group
- User
- Permission
このブログでは、ユーザはグループ分けしませんので、Groupクラスは不要です。また、権限もログインしているユーザのIDでしか判定しないので、Permissionクラスも不要です。よって、この2つのクラスは削除します。
Visitクラスは、セッションを区別するのに使われているようです(ログインすると、このテーブルにレコードが追加され、tg-visitという名前で、追加されたレコードの値を持つクッキーが送信されます)。VisitIdenityクラスも、どのように使われるのかわかりませんが、名前からするとVisitクラスと同様、セッションの管理に必要な気がするので、残します(VisitIdentityの使いかたは、調査中です)。
ブログの記事は、Articleクラスで表します。このクラスは、Userクラスと1体多の関係にあり、記事を書いた日時 (created) と題名 (title) 、本文 (body) を属性に持ちます。
Userクラスも、メールアドレスなどは不要なので、整理します。
結局、tgdiary/model.pyは、以下のようになりました。
from datetime import datetime from turbogears.database import PackageHub from sqlobject import * from turbogears import identity hub = PackageHub("tgdiary") __connection__ = hub # identity models. class Visit(SQLObject): class sqlmeta: table = "visit" visit_key = StringCol(length=40, alternateID=True, alternateMethodName="by_visit_key") created = DateTimeCol(default=datetime.now) expiry = DateTimeCol() def lookup_visit(cls, visit_key): try: return cls.by_visit_key(visit_key) except SQLObjectNotFound: return None lookup_visit = classmethod(lookup_visit) class VisitIdentity(SQLObject): visit_key = StringCol(length=40, alternateID=True, alternateMethodName="by_visit_key") user_id = IntCol() class User(SQLObject): """ Reasonably basic User definition. Probably would want additional attributes. """ # names like "Group", "Order" and "User" are reserved words in SQL # so we set the name to something safe for SQL class sqlmeta: table = "tg_user" user_name = UnicodeCol(length=16, alternateID=True, alternateMethodName="by_user_name", notNone=True) password = UnicodeCol(length=40, notNone=True) articles = MultipleJoin('Article') created = DateTimeCol(default=datetime.now, notNone=True) def _set_password(self, cleartext_password): "Runs cleartext_password through the hash algorithm before saving." hash = identity.encrypt_password(cleartext_password) self._SO_set_password(hash) def set_password_raw(self, password): "Saves the password as-is to the database." self._SO_set_password(password) class Article(SQLObject): title = UnicodeCol(length=255, notNone=True) body = UnicodeCol(length=8192, notNone=True) user = ForeignKey('User', notNone=True) created = DateTimeCol(default=datetime.now, notNone=True) # vim: tabstop=4 shiftwidth=4 expandtab
tg-admin sqlコマンドで、データベースを生成します。
$ tg-admin sql create [~/projects/tgdiary] Using database URI sqlite:///home/tom/projects/tgdiary/devdata.sqlite
sqlite3コマンドで、どんなスキーマになったか確認します。
$ sqlite3 devdata.sqlite [~/projects/tgdiary] SQLite version 3.3.5 Enter ".help" for instructions sqlite> .schema CREATE TABLE article ( id INTEGER PRIMARY KEY, title VARCHAR(255) NOT NULL, body VARCHAR(8192) NOT NULL, user_id INT NOT NULL CONSTRAINT user_id_exists REFERENCES tg_user(id) , created TIMESTAMP NOT NULL ); CREATE TABLE tg_user ( id INTEGER PRIMARY KEY, user_name VARCHAR(16) NOT NULL UNIQUE, password VARCHAR(40) NOT NULL, created TIMESTAMP NOT NULL ); CREATE TABLE visit ( id INTEGER PRIMARY KEY, visit_key VARCHAR(40) NOT NULL UNIQUE, created TIMESTAMP, expiry TIMESTAMP ); CREATE TABLE visit_identity ( id INTEGER PRIMARY KEY, visit_key VARCHAR(40) NOT NULL UNIQUE, user_id INT );
また、tgdiary/config/app.cfgに、GroupクラスとPermissionクラスを設定している箇所があるので、これらを削除します。
$ diff -u tgdiary/config/app.cfg.orig tgdiary/config/app.cfg --- tgdiary/config/app.cfg.orig 2007-01-17 20:32:34.489831750 +0900 +++ tgdiary/config/app.cfg 2007-01-17 20:32:42.906357750 +0900 @@ -97,8 +97,6 @@ # SQL keywords for class names (at least unless you specify a different table # name using sqlmeta). identity.soprovider.model.user="tgdiary.model.User" -identity.soprovider.model.group="tgdiary.model.Group" -identity.soprovider.model.permission="tgdiary.model.Permission" # The password encryption algorithm used when comparing passwords against what's # stored in the database. Valid values are 'md5' or 'sha1'. If you do not