Pylonsでメモアプリ(1)

実際にアプリを作ってみて勉強してみます。
っていうかWebアプリを実装してみたいだけです。

仕様は一般的な感じで、

  • 作成、一覧、変更、削除
  • タグつけて管理
  • 一覧はページャつける
  • 検索
  • ユーザー認証
  • ひとまずAjaxはしない

とこんな感じを目標に、試行錯誤しながら作ってみます。

いざ作るにあたり、特に下の2つのブログでとても勉強させていただきました。
スコトプリゴニエフスク通信
aodag blog
RESTコントローラに関する記事とか、本当に勉強になりました。
1つめのd:id:perezvonさんのPylonsチュートリアルはめちゃめちゃ分かりやすいです。

モデルを作成する

DBにはMySQL、ORMにはSQLAlchemyを使いました。
pasterでモデルからテーブルを作成することができますが、
データベースはあらかじめ自分で作っておく必要があります。

モデルの内容は /model/__init__.pyに書く。
メモとタグの関係は多対多になるので、2つを関連付けるテーブルを作って管理する。
というわけで、メモ・タグ・メモとタグの関連付けと3つのテーブルを作りました。
そのうち、モデルとして操作するのはメモとタグなので、クラスとテーブルをマッピングします。

# coding=utf-8

from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper
from sqlalchemy.orm import relation

from datetime import datetime

from sacontext import PylonsSAContext

sac = PylonsSAContext()
sac.add_engine_from_config(None)

# definition table
notes = Table('note', sac.metadata,
              Column('id', Integer, primary_key=True),
              Column('title', Unicode(250), nullable=False),
              Column('content', Unicode),
              Column('posted_at', DateTime, default=datetime.now()),
              Column('modified_at', DateTime, default=datetime.now()),
              Column('user_id', Integer, ForeignKey("user.id"), nullable=False),
              Column('task_id', Integer, ForeignKey("task.id"), default=''),
              )

notetags = Table('notetag', sac.metadata,
              Column('id', Integer, primary_key=True),
              Column('name', Unicode(30), unique=True, nullable=False),
              )

note_tag_relation = Table('note_notetag', sac.metadata,
              Column('note_id', Integer, ForeignKey("note.id")),
              Column('notetag_id', Integer, ForeignKey("notetag.id")),
              )

users = Table('user', sac.metadata,
              Column('id', Integer, primary_key=True),
              Column('username', String(255), unique=True, nullable=False),
              Column('password', String(255), nullable=False),
              Column('email', String(255), unique=True, nullable=False),
              Column('created_at', DateTime, default=datetime.now()),
              Column('lastlogin', DateTime),
              )

# Domain Object
class Note(object):
  def __init__(self, title=None, content=None):
    self.title = title
    self.content = content

class NoteTag(object):
  def __init__(self, name=None):
    self.name = name

class User(object):
  def __init__(self, username=None, password=None, email=None):
    self.username = username
    self.password = password
    self.email = email


# mapping
assign_mapper(sac.session_context, NoteTag, notetags)
assign_mapper(sac.session_context, Note, notes, properties= {
  'tags': relation(NoteTag, secondary=note_tag_relation, lazy=False)
  }
)
assign_mapper(sac.session_context, User, users)

sac = PylonsSAContext でSAContextを作成して利用します。
あとはSQLAlchemyの流儀にしたがって、クラスとテーブルをマッピングするだけ。
テーブルとクラスが一体化してないのがSQLObjectとの大きな違いだなー。
日本語が入る可能性があるカラムは、Unicode()を指定します。引数を指定しない場合は、textタイプになるみたいです。

development.iniにDBの設定を書く

development.iniにSQLAlchemyを使うよという指定と、使うデータベースの指定をします。

sqlalchemy.default.uri = mysql://cheesepie@localhost/testapp?charset=utf8
sqlalchemy.default.echo = true
sqlalchemy.default.echo_pool = false
sqlalchemy.default.pool_recycle = 3600

テーブル定義でUnicode()をカラムに指定している場合は、文字通りユニコード
データとして扱われるので、development.iniでの指定だけじゃなくて、
MySQL側の文字コードも my.cnf 内でUTF-8の指定を記述しておく必要があります。
(これでハマりました・・)

テーブルを作成する

DBテーブルは定義したモデルと設定ファイルに従って、pasterを使って自動で作成できます。
  paster setup-app development.ini
このとき、テストデータも登録できるので入れてしまった方が便利です。
テストデータはwebsetup.pyに記述します。

from sqlalchemy import exceptions
from testapp.model import *
                                                                          
try:
  first_user = User('foo', 'foo', 'foo@foo.com')
  first_user.id = 2
  print "Add user..."
                                                                          
  for i in range(30):
    note = Note('Foo\'s Test'+str(i), u'テスト投稿')
    note.user_id = first_user.id
    nt = sac.session_context.current.query(NoteTag).get_by(name='foo')
    if nt is None:
      note.tags.append(NoteTag('foo'))
    else:
      note.tags.append(nt)
    nt = sac.session_context.current.query(NoteTag).get_by(name='sample')
    if nt is None:
      note.tags.append(NoteTag('sample'))
    else:
      note.tags.append(nt)
    sac.session_context.current.save(note)
    print "Add note..."
    sac.session_context.current.flush()
except exceptions.SQLError, e:
  pass

いやーいちいち調べながらなので遅い遅い。。。
でもドキュメント見まくってたらSQLAlchemyの仕様がだいぶ分かってきた気が
します、あくまで気が。
テーブルの数が増えたりwhereの条件が増えたりしても耐えられる仕様になっている
のがいいと思いました。

次回からはコントローラの作成をば!