読者です 読者をやめる 読者になる 読者になる

Web からの入力を DB まで渡す

Tornado からの SQLAlchemy です。

Tornado を使った実装から。

ui_main.py

from sqlalchemy.orm import sessionmaker
import tornado.ioloop
from tornado.web import *
from db_construct import engine
from dw_define import *
import tr_uw2dw
import tr_dw2uw

Session = sessionmaker(bind=engine)

class MainHandler(RequestHandler):
    def get(self):
        self.render("main.html")

class PersonHandler(RequestHandler):
    def get(self):
        session = Session()
        person_list = tr_dw2uw.person_list(
            session.query(Person).order_by(Person.id))
        session.close()
        self.render("person.html", person_list=person_list)

    def post(self):
        session = Session()
        session.add(tr_uw2dw.person(self.get_argument("name")))
        session.commit()
        person_list = tr_dw2uw.person_list(
            session.query(Person).order_by(Person.id))
        session.close()
        self.render("person.html", person_list=person_list)

application = Application([
    (r"/", MainHandler),
    (r"/person", PersonHandler)
])

if __name__ == "__main__":
    map_to_db()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

http://localhost:8888/person にアクセスすると、person.html が表示されます。で、その person.html。

<html>
  <head>
     <title>Person Management</title>
  </head>
  <body>
    <div>
      <a href="/">Main</a> > Person Management
    </div>
    <form action="/person" method="post">
      Name : <input type="text" name="name">
      <input type="submit" value="Add">
    </form>
    <table>
      <tr><th>ID</th><th>Name</th></tr>
      {% for i in person_list %}
        <tr><td>{{ i.id }}</td><td>{{ i.name }}</td></tr>
      {% end %}
    </table>
  </body>
</html>

person.html はテンプレートです。render メソッドで指定したパラメータ person_list を使っています。で、person_list に渡しているオブジェクトを作っているのが、tr_dw2hw.py。

import collections
from dw_define import *

def person_list(persons):
    PersonT = collections.namedtuple("PersonT", "id name")
    return [PersonT(p.id, p.name) for p in persons]

dict を使おうかとも思ったけど、参照する側(person.html)の記述量が増えるので名前付きタプルにした。変換せずに渡すという手もあるけど。ちょっと関数型チックな記述の仕方をしてみました。

今度は、person.html から Submit したときに呼ばれる post メソッド。tr_hw2dw.py で変換しています。

from dw_define import *

def person(name):
    person = Person(name)
    person.customer = Customer()
    return person

渡されたパラメータから、DBに格納するためにオブジェクトを構築しています。post メソッドの実装では、そのオブジェクトを session に add しています。

最後に残った謎の dw_define.py。

from sqlalchemy import *
from sqlalchemy.orm import mapper
from sqlalchemy.orm import relation
from db_construct import engine

class Person:
    def __init__(self, name):
        self.name = name

class Company:
    def __init__(self, name):
        self.name = name

class Customer:
    pass

class PersonCustomer:
    pass

class CompanyCustomer:
    pass

def map_to_db():
    metadata = MetaData()
    metadata.reflect(bind=engine, views=True)

    mapper(Person, metadata.tables['person'], properties={
        'customer': relation(Customer, uselist=False, backref='person')
    })
    mapper(Company, metadata.tables['company'], properties={
        'customer': relation(Customer, uselist=False, backref='company')
    })
    mapper(Customer, metadata.tables['customer'])
    mapper(PersonCustomer, metadata.tables['person_customer'],
        primary_key=[metadata.tables['person_customer'].get_children()[0]])
    mapper(CompanyCustomer, metadata.tables['company_customer'],
        primary_key=[metadata.tables['company_customer'].get_children()[0]])
    return metadata

クラス定義とDBとのマッピングです。

Tornado と SQLAlchemy の使い方の参考にはしても、設計は真似ちゃダメですよ。いい設計とは言えませんので。