Google App Engineで「CRUD」1ファイル+テンプレート版
2010/04/23 13:23:55
以外に情報が少ないPythonでのCRUDのやり方。処理ハンドラを1ファイルに全部詰め込み、出力はテンプレートを使用しています。
最初に断っておく。これが正しいやり方なのかは全くもってわからない。
レベル的には「とりあえず動いているから最低限あっている」というもの。
「key」をもち回していいのだろうか?とかもっと例外処理した方がいいのだろうか?とか気になるところ、つっこみどころは満載のことうけあいである。
■画面
画面としては
・一覧(/list)
・詳細(/view/キー)
・追加(/add)
・編集(/edit/キー)
の4画面で、削除は処理のみで画面はない。
■データストア
「Person」というクラス名で
・name(名前)
・age(年齢)
・birthday(誕生日)
・sex(性別)
・tel(電話番号)
・email(メールアドレス)
・blog(ブログアドレス)
・isAdmin(管理者有無)
・comment(コメント)
・modified(更新日時)
・created(追加日時)
というごく一般的な項目を用意した。
またこれらサンプルとしては少なくない項目を用意した意図は、フォームでテキストフォームだけではなく、チェックボックス、ドロップダウンリスト、ラジオボタン、テキストエリアなどWebアプリでよく使われるフォームを一通り実装してみたかったらである。
app.yaml
main.py
template/list.html
template/view.html
template/add.html
template/edit.html




やはりフレームワークに頼らずシンプルに「webapp」のみでベタ書きすると、たったひとつのモデルのCRUDでもこのボリュームになってしまう。
見ての通り、サニタイズも自前でやらなければならないし、なにより重複している箇所もあって(もちろんクラス化、関数化したら解決するだろうが今回のようなものでしたところで再利用できないものになるのは明白)非効率この上ないような感覚。
作ってみた感想としては「ああこのやり方ではダメだな…」と感じる。
もちろんこれでも使えることは使えるだろうが、たったひとつのCRUDのためにGAEにのせたいわけではなく中規模〜大規模のものを作れるようになりたいからなので。
やはり「Djangoフォーム」か「GAEO(Google App Engine Oil)」を素直に使う方が賢明なのか?
レベル的には「とりあえず動いているから最低限あっている」というもの。
「key」をもち回していいのだろうか?とかもっと例外処理した方がいいのだろうか?とか気になるところ、つっこみどころは満載のことうけあいである。
■画面
画面としては
・一覧(/list)
・詳細(/view/キー)
・追加(/add)
・編集(/edit/キー)
の4画面で、削除は処理のみで画面はない。
■データストア
「Person」というクラス名で
・name(名前)
・age(年齢)
・birthday(誕生日)
・sex(性別)
・tel(電話番号)
・email(メールアドレス)
・blog(ブログアドレス)
・isAdmin(管理者有無)
・comment(コメント)
・modified(更新日時)
・created(追加日時)
というごく一般的な項目を用意した。
またこれらサンプルとしては少なくない項目を用意した意図は、フォームでテキストフォームだけではなく、チェックボックス、ドロップダウンリスト、ラジオボタン、テキストエリアなどWebアプリでよく使われるフォームを一通り実装してみたかったらである。
app.yaml
application: jinling-ren version: 1 runtime: python api_version: 1 handlers: - url: .* script: main.py全てのHTTPアクセスは「main.py」へ。
main.py
#!/usr/bin/env python
#!-*- coding:utf-8 -*-
# CRUDサンプル
# http://www.jinlingren.com/
from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import util, template
import os
import cgi
import datetime
import logging
class Person(db.Model):
name = db.StringProperty(required=True)
age = db.IntegerProperty(required=True)
birthday = db.DateProperty()
sex = db.StringProperty()
tel = db.StringProperty()
email = db.StringProperty()
address = db.StringProperty()
blog = db.StringProperty()
isAdmin = db.BooleanProperty(default=False)
comment = db.TextProperty()
modified = db.DateTimeProperty(auto_now=True)
created = db.DateTimeProperty(auto_now_add=True)
class Root(webapp.RequestHandler):
def get(self):
self.redirect('/list')
class List(webapp.RequestHandler):
def get(self):
persons = Person.all().order('-created')
html = template.render(
os.path.join(os.path.dirname(__file__), 'template', 'list.html'),
{
'persons': persons
}
)
self.response.out.write(html)
class View(webapp.RequestHandler):
def get(self, key):
person = Person.get(key)
person.comment = person.comment.replace("\n","<br>\n")
html = template.render(
os.path.join(os.path.dirname(__file__), 'template', 'view.html'),
{
'person': person
}
)
self.response.out.write(html)
class Add(webapp.RequestHandler):
def get(self):
forms = {}
forms['ages'] = range(18, 61)
thisyear = int(datetime.datetime.now().strftime('%Y'))
forms['b_years'] = range(1945, thisyear+1)
forms['b_months'] = range(1, 13)
forms['b_days'] = range(1, 32)
html = template.render(
os.path.join(os.path.dirname(__file__), 'template', 'add.html'),
{
'forms': forms
}
)
self.response.out.write(html)
def post(self):
try:
name = cgi.escape(self.request.get('name'))
age = long(self.request.get('age'))
person = Person(name=name, age=age)
birthday_year = long(self.request.get('birthday_year'))
birthday_month = long(self.request.get('birthday_month'))
birthday_day = long(self.request.get('birthday_day'))
if birthday_year and birthday_month and birthday_day:
person.birthday = datetime.date(birthday_year, birthday_month, birthday_day)
person.sex = cgi.escape(self.request.get('sex'))
person.tel = cgi.escape(self.request.get('tel'))
person.email = cgi.escape(self.request.get('email'))
person.address = cgi.escape(self.request.get('address'))
person.blog = cgi.escape(self.request.get('blog'))
if self.request.get('isAdmin') == 'True':
person.isAdmin = True
else:
person.isAdmin = False
comment = cgi.escape(self.request.get('comment'))
person.comment = db.Text(comment)
person.put()
self.redirect('/list')
except:
self.redirect('/add')
class Edit(webapp.RequestHandler):
def get(self, key):
forms = {}
forms['ages'] = range(18, 61)
thisyear = int(datetime.datetime.now().strftime('%Y'))
forms['b_years'] = range(1945, thisyear+1)
forms['b_months'] = range(1, 13)
forms['b_days'] = range(1, 32)
forms['sex_man'] = u"男"
forms['sex_woman'] = u"女"
#logging.info(forms)
person = Person.get(key)
if person.birthday:
person.birthday_year = int(person.birthday.strftime('%Y'))
person.birthday_month = int(person.birthday.strftime('%m'))
person.birthday_day = int(person.birthday.strftime('%d'))
html = template.render(
os.path.join(os.path.dirname(__file__), 'template', 'edit.html'),
{
'forms': forms,
'person': person
}
)
self.response.out.write(html)
def post(self, key):
try:
person = Person.get(key)
person.name = cgi.escape(self.request.get('name'))
person.age = long(self.request.get('age'))
birthday_year = long(self.request.get('birthday_year'))
birthday_month = long(self.request.get('birthday_month'))
birthday_day = long(self.request.get('birthday_day'))
if birthday_year and birthday_month and birthday_day:
person.birthday = datetime.date(birthday_year, birthday_month, birthday_day)
person.sex = cgi.escape(self.request.get('sex'))
person.tel = cgi.escape(self.request.get('tel'))
person.email = cgi.escape(self.request.get('email'))
person.address = cgi.escape(self.request.get('address'))
person.blog = cgi.escape(self.request.get('blog'))
if self.request.get('isAdmin') == 'True':
person.isAdmin = True
else:
person.isAdmin = False
comment = cgi.escape(self.request.get('comment'))
person.comment = db.Text(comment)
person.put()
self.redirect('/list')
except:
self.redirect('/edit/'+key)
class Delete(webapp.RequestHandler):
def get(self, key):
try:
person = Person.get(key)
if person:
person.delete()
self.redirect('/list')
except:
self.redirect('/list')
def main():
application = webapp.WSGIApplication(
[
('/', Root),
('/list', List),
('/view/(\w+)', View),
('/add', Add),
('/edit/(\w+)', Edit),
('/delete/(\w+)', Delete)
],
debug=True)
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
template/list.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CRUDサンプル</title>
</head>
<body>
<h1>List(一覧)</h1>
> <a href="/add">追加</a><br><br>
<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
<tr bgcolor="#EBEBEB">
<td> </td>
<td align="center">名前</td>
<td align="center">性別</td>
<td align="center">年齢</td>
<td align="center">メールアドレス</td>
<td> </td>
<td> </td>
</tr>
{% for person in persons %}
<tr bgcolor="#FFFFFF">
<td><a href="/view/{{ person.key }}">詳細</a></td>
<td>{{ person.name }}</td>
<td align="center">{{ person.sex }}</td>
<td align="center">{{ person.age }}歳</td>
<td>{{ person.email }}</td>
<td><a href="/edit/{{ person.key }}">編集</a></td>
<td><a href="/delete/{{ person.key }}" onClick="if(confirm('本当に削除しますか?')){return true;}else{return false;}">削除</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
template/view.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CRUDサンプル</title>
</head>
<body>
<h1>View(参照)</h1>
> <a href="/list">一覧</a><br><br>
<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
<tr>
<td width="100" bgcolor="#EBEBEB">名前</td>
<td width="250" bgcolor="#FFFFFF">{{ person.name }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">年齢</td>
<td bgcolor="#FFFFFF">{{ person.age }} 歳</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">誕生日</td>
<td bgcolor="#FFFFFF">{{ person.birthday|date:"Y年m月d日" }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">性別</td>
<td bgcolor="#FFFFFF">{{ person.sex }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">電話番号</td>
<td bgcolor="#FFFFFF">{{ person.tel }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">メールアドレス</td>
<td bgcolor="#FFFFFF">{{ person.email }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">住所</td>
<td bgcolor="#FFFFFF">{{ person.address }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">ブログ</td>
<td bgcolor="#FFFFFF">{{ person.blog }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">管理者権限</td>
<td bgcolor="#FFFFFF">{{ person.isAdmin }}</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">コメント</td>
<td bgcolor="#FFFFFF">{{ person.comment }}</td>
</tr>
</table>
</body>
</html>
template/add.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CRUDサンプル</title>
</head>
<body>
<h1>Add(追加)</h1>
> <a href="/list">一覧</a><br><br>
<form action="/add" method="post">
<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
<tr>
<td width="100" bgcolor="#EBEBEB">名前 <sup><font color="#FF0000">*</font></sup></td>
<td width="250" bgcolor="#FFFFFF"><input type="text" name="name" value=""></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">年齢 <sup><font color="#FF0000">*</font></sup></td>
<td bgcolor="#FFFFFF"><select name="age">
{% for age in forms.ages %}
<option value="{{ age }}">{{ age }}</option>
{% endfor %}
</select> 歳</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">誕生日</td>
<td bgcolor="#FFFFFF"><select name="birthday_year">
<option value="}">-</option>
{% for b_year in forms.b_years %}
<option value="{{ b_year }}">{{ b_year }}</option>
{% endfor %}
</select> 年 <select name="birthday_month">
<option value="}">-</option>
{% for b_month in forms.b_months %}
<option value="{{ b_month }}">{{ b_month }}</option>
{% endfor %}
</select> 月 <select name="birthday_day">
<option value="}">-</option>
{% for b_day in forms.b_days %}
<option value="{{ b_day }}">{{ b_day }}</option>
{% endfor %}
</select> 日</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">性別</td>
<td bgcolor="#FFFFFF">
<input type="radio" name="sex" value="男">男
<input type="radio" name="sex" value="女">女</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">電話番号</td>
<td bgcolor="#FFFFFF"><input type="text" name="tel" value=""></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">メールアドレス</td>
<td bgcolor="#FFFFFF"><input type="text" name="email" value=""></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">住所</td>
<td bgcolor="#FFFFFF"><input type="text" name="address" value=""></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">ブログ</td>
<td bgcolor="#FFFFFF"><input type="text" name="blog" value=""></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">管理者権限</td>
<td bgcolor="#FFFFFF"><input type="checkbox" name="isAdmin" value="True"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">コメント</td>
<td bgcolor="#FFFFFF"><textarea name="comment"></textarea></td>
</tr>
</table><br>
<input type="submit" value="追加する">
</form>
<br>
<font color="#FF0000">*</font>は必須項目
</body>
</html>
template/edit.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CRUDサンプル</title>
</head>
<body>
<h1>Edit(編集)</h1>
> <a href="/list">一覧</a><br><br>
<form action="/edit/{{ person.key }}" method="post">
<table cellspacing="1" cellpadding="8" border="0" bgcolor="#999999">
<tr>
<td width="100" bgcolor="#EBEBEB">名前 <sup><font color="#FF0000">*</font></sup></td>
<td width="250" bgcolor="#FFFFFF"><input type="text" name="name" value="{{ person.name }}"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">年齢 <sup><font color="#FF0000">*</font></sup></td>
<td bgcolor="#FFFFFF"><select name="age">
{% for age in forms.ages %}
<option value="{{ age }}"{% ifequal person.age age %} selected{% endifequal %}>{{ age }}</option>
{% endfor %}
</select> 歳</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">誕生日</td>
<td bgcolor="#FFFFFF"><select name="birthday_year">
<option value="}">-</option>
{% for b_year in forms.b_years %}
<option value="{{ b_year }}"{% ifequal person.birthday_year b_year %} selected{% endifequal %}>{{ b_year }}</option>
{% endfor %}
</select> 年 <select name="birthday_month">
<option value="}">-</option>
{% for b_month in forms.b_months %}
<option value="{{ b_month }}"{% ifequal person.birthday_month b_month %} selected{% endifequal %}>{{ b_month }}</option>
{% endfor %}
</select> 月 <select name="birthday_day">
<option value="}">-</option>
{% for b_day in forms.b_days %}
<option value="{{ b_day }}"{% ifequal person.birthday_day b_day %} selected{% endifequal %}>{{ b_day }}</option>
{% endfor %}
</select> 日</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">性別</td>
<td bgcolor="#FFFFFF">
<input type="radio" name="sex" value="男"{% ifequal person.sex forms.sex_man %} checked{% endifequal %}>男
<input type="radio" name="sex" value="女"{% ifequal person.sex forms.sex_woman %} checked{% endifequal %}>女</td>
</tr>
<tr>
<td bgcolor="#EBEBEB">電話番号</td>
<td bgcolor="#FFFFFF"><input type="text" name="tel" value="{{ person.tel }}"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">メールアドレス</td>
<td bgcolor="#FFFFFF"><input type="text" name="email" value="{{ person.email }}"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">住所</td>
<td bgcolor="#FFFFFF"><input type="text" name="address" value="{{ person.address }}"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">ブログ</td>
<td bgcolor="#FFFFFF"><input type="text" name="blog" value="{{ person.blog }}"></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">管理者権限</td>
<td bgcolor="#FFFFFF"><input type="checkbox" name="isAdmin" value="True"{% if person.isAdmin %} checked{% endif %}></td>
</tr>
<tr>
<td bgcolor="#EBEBEB">コメント</td>
<td bgcolor="#FFFFFF"><textarea name="comment">{{ person.comment }}</textarea></td>
</tr>
</table><br>
<input type="submit" value="変更する">
</form>
<br>
<font color="#FF0000">*</font>は必須項目
</body>
</html>




やはりフレームワークに頼らずシンプルに「webapp」のみでベタ書きすると、たったひとつのモデルのCRUDでもこのボリュームになってしまう。
見ての通り、サニタイズも自前でやらなければならないし、なにより重複している箇所もあって(もちろんクラス化、関数化したら解決するだろうが今回のようなものでしたところで再利用できないものになるのは明白)非効率この上ないような感覚。
作ってみた感想としては「ああこのやり方ではダメだな…」と感じる。
もちろんこれでも使えることは使えるだろうが、たったひとつのCRUDのためにGAEにのせたいわけではなく中規模〜大規模のものを作れるようになりたいからなので。
やはり「Djangoフォーム」か「GAEO(Google App Engine Oil)」を素直に使う方が賢明なのか?