|
フォームの POST データをデータベースにストアする例。最近流行の SQLite を使っ
てみました。もちろん DB_DIVER と DB_ARGS を変更すれば、他のデータベー
スでも動きます。
データベースのクエリ実行している render() メソッドで、直接 Response オ
ブジェクトを返さずに Deferred オブジェクトを返してるところがポイント。
from twisted.enterprise import adbapi, util as dbutil
from twisted.web2 import http, http_headers, responsecode, resource, server
from twisted.web2.channel import HTTPFactory
from twisted.web2.error import ERROR_MESSAGES
DB_DRIVER = "pysqlite2.dbapi2"
DB_ARGS = {"database": "microblog"}
ENCODING = "utf-8"
CTYPE_HTML ={
'content-type': http_headers.MimeType('text', 'html',
{'charset': '%s' % ENCODING})}
def uEncode(data):
if isinstance(data, unicode):
return data.encode(ENCODING)
else:
return data
class FoundResponse(http.StatusResponse):
def __init__(self, location):
super(FoundResponse, self).__init__(
responsecode.FOUND,
ERROR_MESSAGES[responsecode.FOUND] % {"location": location})
self.headers.setHeader("location", location)
class NewPage(resource.Resource):
def render(self, request):
html = """
<html>
<head><title>New Post<title></head>
<body>
<h1>New Post</h1>
<form action='save' method='post'>
Title: <input type='text' name='title' /><br />
Body: <br />
<textarea cols='70' name='body'></textarea><br />
<input type='submit' value='Save' />
</form>
</body>
</html>
"""
return http.Response(responsecode.OK, CTYPE_HTML, html)
class SavePage(resource.PostableResource):
def __init__(self, dbConnection):
super(SavePage, self).__init__()
self.db = dbConnection
def render(self, request):
title = request.args['title'][0]
body = request.args['body'][0]
query = "insert into posts (title, body) values (%s, %s)" % (
dbutil.quote(title, "text"),
dbutil.quote(body, "text"))
d = self.db.runQuery(query)
d.addCallback(self._saved)
d.addErrback(self._saveFailed)
return d
def _saved(self, result):
return FoundResponse("/")
def _saveFailed(self, failure):
return http.StatusResponse(
responsecode.INTERNAL_SERVER_ERROR,
"Error saving record: %s" % failure.getErrorMessage())
class HomePage(resource.Resource):
addSlash = True
child_new = NewPage()
def __init__(self, dbConnection):
super(HomePage, self).__init__()
self.db = dbConnection
self.child_save = SavePage(dbConnection)
def render(self, request):
query = "select title, body from posts order by post_id desc"
d = self.db.runQuery(query)
d.addCallback(self._gotPosts)
d.addErrback(self._dbError)
return d
def _gotPosts(self, results):
html = """
<html>
<head><title>MicroBlog</title></head>
<body>
<h1>MicroBlog</h1>
<i>Like a blog, but less useful</i>
<p><a href='/new'>New Post</a></p>
"""
for title, body in results:
html += "<h2>%s</h2>" % uEncode(title)
html += uEncode(body)
html += """
</body>
</html>
"""
return http.Response(responsecode.OK, CTYPE_HTML, html)
def _dbError(self, failure):
return http.StatusResponse(
responsecode.INTERNAL_SERVER_ERROR,
"Error fetching posts: %s" % failure.getErrorMessage())
if __name__ == "__main__":
from twisted.internet import reactor
dbConnection = adbapi.ConnectionPool(DB_DRIVER, **DB_ARGS)
site = server.Site(HomePage(dbConnection))
reactor.listenTCP(8000, HTTPFactory(site))
reactor.run()
データベーステーブルはこんなの。
create table posts (post_id int primary key,
title str not null,
body);
SQLite 3 って入力テキストは UTF-8 決め打ちみたいね。select 結果が Unicode
で返ってくるので、エンコード処理 uEncode() を付け加えています。
FoundResponse というのはリダイレクト用の Response サブクラスで 302 Found レスポンスを返します。リダイレクト用のクラスはほかに twisted.web2.http.RedirectResponse というのがあるけど、こっちは 301 Moved Permanently を返すやつです。条件によってリダイレクト先が変わる場面では 302 を使います。
|
|
twisted.web の Request オブジェクトにはレスポンスを直接書き込める
write() メソッドというのがあったのだけど、Web2 では ProducerStream を
使ってストリームの書き込みをおこないます。
以下は ProducerStream を使って ColorRoot の render() を書き直した例。
def render(self, request):
from twisted.web2 import stream
s = stream.ProducerStream()
s.write("""
<html>
<head>
<title>Colors</title>
<link type='text/css' href='/styles.css' rel='Stylesheet' />
</head>
<body>
<h1>Colors</h1>
To see a clor, enter a url like
<a href='/color/ff0000'>/color/ff0000</a>.<br />
Colors viewed so far:
<ul>""")
for color in self.requestedColors:
s.write("<li><a href='%s' style='color: #%s'>%s</a></li>" % (
color, color, color))
s.write("""
</ul>
</body>
</html>
""")
s.finish()
return Response(
responsecode.OK,
{'content-type': http_headers.MimeType('text', 'html')}, s)
ストリームへの書き込みが終了時に finish() メソッドを呼び出すのを忘れな
いように。
この方式を使うと、データの書き込み終了を待たずにレスポンスの出力が開始
されます。上の例なんかはその意味ほとんどないけど、やたら長い出力をだら
だら流し続けるときなんかに向いてます。
|