ブログをGoogle App Engineへ移行してみる19
2011/02/10 21:10:10
材料が揃ってきたので少し管理部分ではなく表示部分に力を入れてみることにする。現状は記事一覧のみが出ている。それに以下のものを足してみようと思う。・最近の記事一覧
・タグ一覧
・タグ毎の記事一覧
「main.py」に表示部分のハンドラーを書いていたが、どうせどんどん肥大するので「admin.py」と同様、「blog.py」として外部ファイル化した。
/main.py
/blog.py
表示側ではどのページに行っても右カラムに
・最近の記事リスト
・タグリスト
を表示する必要があるのでどのハンドラーでも必ずデータを取得する必要がある。かといって全てのハンドラーに同じコードを書くのは冗長なので避けるとしてここでは「webapp.RequestHandler」を継承した「BaseHandler」を作り、それを元に全てのハンドラーを作ることとした。全ハンドラーの大元「BaseHandler」では右カラムで表示するデータを予めセットしているというわけ。
(まぁ正直不安に思っているところもあるけどとりあえず動いているしこのままでいこう)
また「utils.py」に書いていたクラス「View」を新たに「handlers.py」ファイルを作ってそちらに移動。理由はなんかユーティリティーファイルにあるのが嫌だったから。ほんとになんとなく。
/handlers.py
・最近の記事リンク一覧を取得するメソッド「get_recent_entries」
・タグリンク一覧を取得するメソッド「get_tags」
・タグ毎の記事一覧を取得するメソッド「get_tag_entries」
を作成。
ちなみにタグ毎の記事一覧を取得するところだが日本語をURL引数にもつと開発環境ではエラーが出る。確認してはないが本番環境では大丈夫らしい。
/model.py
タグ周りを変更したのでついでに「admin.py」も結構変更した。
/admin.py
/template/TagEntryList.html
/template/base.html
デザインがアレなので見えないですが、大分ブログらしくなってきました。
まだまだ足りないところがありますが、ひとつずつひとつずつ実装していくのみです。
そろそろページ処理機能を考えていく必要があるのかな〜
/main.py
#!-*- coding:utf-8 -*-
import logging
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import webapp
import blog
import admin
application = webapp.WSGIApplication([
('/', blog.EntryList),
('/entry/show/([\w\-]+)', blog.EntryShow),
('/comment/new/([\w\-]+)', blog.CommentNew),
('/tag/(.*)', blog.TagEntryList),
('/picture/show/([\w\-]+)', blog.PictureShow),
('/admin/', admin.EntryList),
('/admin/new', admin.EntryNew),
('/admin/edit/([\w\-]+)', admin.EntryEdit),
('/admin/delete/([\w\-]+)', admin.EntryDelete),
('/admin/picture/', admin.PictureList),
('/admin/picture/new', admin.PictureNew),
('/admin/picture/edit/([\w\-]+)', admin.PictureEdit),
('/admin/picture/delete/([\w\-]+)', admin.PictureDelete),
('/admin/tag/', admin.TagList),
('/admin/tag/new', admin.TagNew),
('/admin/tag/edit/([\w\-]+)', admin.TagEdit),
('/admin/tag/delete/([\w\-]+)', admin.TagDelete)
],debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()
/blog.py
#!-*- coding:utf-8 -*-
import logging
import urllib
from google.appengine.ext import webapp
from model import *
from handlers import View
class BaseHandler(webapp.RequestHandler):
def __init__(self):
#最近の記事リンク一覧
self.template_values['recent_entries'] = [item.to_hash() for item in get_recent_entries()]
#タグリンク一覧
self.template_values['tags'] = get_tags()
class EntryList(BaseHandler, View):
def get(self):
self.template_values['entries'] = [item.to_hash() for item in get_entries()]
self.render()
class EntryShow(BaseHandler,View):
def get(self, key):
entry = get_entry(key)
self.template_values['entry'] = entry.to_hash()
self.template_values['comments'] = [item.to_hash() for item in get_comments(entry)]
self.render()
class CommentNew(webapp.RequestHandler):
def post(self, key):
entry = get_entry(key)
if entry:
comment = Comment(parent=entry)
comment.comment = self.request.get("comment")
comment.put()
self.redirect("/entry/show/"+key)
else:
self.redirect("/")
class TagEntryList(BaseHandler, View):
def get(self, tagname):
tag = urllib.unquote_plus(tagname)
self.template_values['tag'] = tag
self.template_values['entries'] = [item.to_hash() for item in get_tag_entries(tag)]
self.render()
class PictureShow(webapp.RequestHandler):
def get(self, key):
picture = get_picture(key)
if picture and picture.photo:
self.response.headers['Content-Type'] = 'image/jpeg'
self.response.out.write(picture.photo)
return
self.error(404)

表示側ではどのページに行っても右カラムに
・最近の記事リスト
・タグリスト
を表示する必要があるのでどのハンドラーでも必ずデータを取得する必要がある。かといって全てのハンドラーに同じコードを書くのは冗長なので避けるとしてここでは「webapp.RequestHandler」を継承した「BaseHandler」を作り、それを元に全てのハンドラーを作ることとした。全ハンドラーの大元「BaseHandler」では右カラムで表示するデータを予めセットしているというわけ。
(まぁ正直不安に思っているところもあるけどとりあえず動いているしこのままでいこう)
また「utils.py」に書いていたクラス「View」を新たに「handlers.py」ファイルを作ってそちらに移動。理由はなんかユーティリティーファイルにあるのが嫌だったから。ほんとになんとなく。
/handlers.py
#!-*- coding:utf-8 -*-
import os
import logging
from google.appengine.ext.webapp import template
class View(object):
template_values = {}
def __init__(self):
self.template_values = {}
def render(self, name=None):
template_dir = 'template_admin' if (self.__module__ == 'admin') else 'template'
template_name = name + '.html' if name else self.__class__.__name__ + '.html'
path = os.path.join(os.path.dirname(__file__), template_dir, template_name)
self.response.out.write(template.render(path, self.template_values))
「model.py」にて表示側で使う・最近の記事リンク一覧を取得するメソッド「get_recent_entries」
・タグリンク一覧を取得するメソッド「get_tags」
・タグ毎の記事一覧を取得するメソッド「get_tag_entries」
を作成。
ちなみにタグ毎の記事一覧を取得するところだが日本語をURL引数にもつと開発環境ではエラーが出る。確認してはないが本番環境では大丈夫らしい。
/model.py
#!-*- coding:utf-8 -*-
import logging
from google.appengine.ext import db
import utils
def get_entry(key):
return db.get(key)
def get_entries():
return Entry.all().order('-created_at')
def get_recent_entries():
return Entry.all().order('-created_at').fetch(10)
def get_comment(key):
return db.get(key)
def get_comments(entry):
return Comment.all().ancestor(entry).order('created_at')
def get_picture(key):
try:
return db.get(key)
except:
return None
def get_pictures():
return Picture.all().order('-created_at')
def get_tag(key):
return db.get(key)
def get_tags():
return Tag.all().order('name')
def get_tag_entries(tagname):
return Entry.all().filter('tags =', tagname).order('-created_at')
def delete_items(keys):
items = db.get(keys)
if items:
db.delete(items)
return True
else:
return False
class Entry(db.Model):
title = db.StringProperty(indexed=False)
body = db.TextProperty()
tags = db.StringListProperty()
updated_at = db.DateTimeProperty(indexed=False, auto_now=True)
created_at = db.DateTimeProperty(auto_now_add=True)
def to_hash(self):
return {
'key': str(self.key()),
'title': self.title,
'body': self.body,
'tags': self.tags,
'updated_at': utils.to_jst(self.updated_at),
'created_at': utils.to_jst(self.created_at)
}
class Comment(db.Model):
comment = db.TextProperty()
updated_at = db.DateTimeProperty(indexed=False, auto_now=True)
created_at = db.DateTimeProperty(auto_now_add=True)
def to_hash(self):
return {
'key': str(self.key()),
'comment': self.comment,
'updated_at': utils.to_jst(self.updated_at),
'created_at': utils.to_jst(self.created_at)
}
class Picture(db.Model):
name = db.StringProperty(indexed=False)
photo = db.BlobProperty()
width = db.IntegerProperty(indexed=False)
height = db.IntegerProperty(indexed=False)
updated_at = db.DateTimeProperty(indexed=False, auto_now=True)
created_at = db.DateTimeProperty(auto_now_add=True)
def to_hash(self):
return {
'key': str(self.key()),
'name': self.name,
'photo': '/picture/show/'+ str(self.key()) if self.photo else '',
'width': self.width,
'height': self.height,
'updated_at': utils.to_jst(self.updated_at),
'created_at': utils.to_jst(self.created_at)
}
class Tag(db.Model):
name = db.StringProperty()
updated_at = db.DateTimeProperty(indexed=False, auto_now=True)
created_at = db.DateTimeProperty(indexed=False, auto_now_add=True)
def to_hash(self):
return {
'key': str(self.key()),
'name': self.name,
'updated_at': utils.to_jst(self.updated_at),
'created_at': utils.to_jst(self.created_at)
}
そうそう「Category」を「Tag」に変更した。記事とタグが多対多なのでどちらかというとカテゴリーは1対多なようなので。だからいっそタグとしました。さらに多対多を表現するのに「キーリスト法(このモデル法がこういう呼び方なのだと最近知った)」を採用していたが、パフォーマンスを考えてタグのkey()を持つのではなく(結局キーだけ持っていてもタグ名がわからないから取得する必要がある)、タグ名をリストで持つようにした。まあいわゆるGAEデータストアでおなじみの「非正規化」だ。これで記事を表示する際にタグ名とタグ一覧リンクへを記事エンティティのみで表現できるので「Tagクラス」にアクセスせずに済む。但しその代償にタグ名が変わった時はそのタグを持っている全ての記事に対してそれを変更する必要があるのだが、それはまだ未実装。いずれ実装するとして恐らくタスクキューを使うのがいいのかなと今のところは推論しておく。タグ周りを変更したのでついでに「admin.py」も結構変更した。
/admin.py
#!-*- coding:utf-8 -*-
import logging
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.api import images
from model import Entry, Tag
from model import *
from handlers import View
from utils import *
##### This view below is "Entry" #################
class EntryList(webapp.RequestHandler, View):
def get(self):
self.template_values['entries'] = [item.to_hash() for item in get_entries()]
self.render()
class EntryNew(webapp.RequestHandler, View):
def get(self):
self.template_values['tags'] = [item.to_hash() for item in get_tags()]
self.render()
def post(self):
entry = Entry()
entry.title = self.request.get("title")
entry.body = self.request.get("body")
entry.tags = self.request.get_all("tags")
entry.put()
self.redirect("/admin/")
class EntryEdit(webapp.RequestHandler, View):
def get(self, key):
def add_select(entry_tags_list):
tags = [item.to_hash() for item in get_tags()]
for tag in tags:
select = True if tag['name'] in entry_tags_list else False
tag.update({'select': select})
return tags
entry = get_entry(key).to_hash()
self.template_values['entry'] = entry
self.template_values['tags'] = add_select(entry['tags'])
self.render()
def post(self, key):
entry = get_entry(key)
entry.title = self.request.get("title")
entry.body = self.request.get("body")
entry.tags = self.request.get_all("tags")
entry.put()
self.redirect("/admin/")
class EntryDelete(webapp.RequestHandler):
def get(self, key):
delete_items(key)
self.redirect("/admin/")
##### This view below is "Picture" #################
class PictureList(webapp.RequestHandler, View):
def get(self):
self.template_values['pictures'] = [item.to_hash() for item in get_pictures()]
self.render()
class PictureNew(webapp.RequestHandler, View):
def get(self):
self.render()
def post(self):
picture = Picture()
name = self.request.get("name")
picture.name = name if name else 'unknown'
photo, width, height = utils.convert_to_jpeg(self.request.get("photo"))
picture.photo = photo
picture.width = width
picture.height = height
picture.put()
self.redirect("/admin/picture/")
class PictureEdit(webapp.RequestHandler, View):
def get(self, key):
self.template_values['picture'] = get_picture(key).to_hash()
self.render()
def post(self, key):
picture = get_picture(key)
name = self.request.get("name")
picture.name = name if name else 'unknown'
if self.request.get("photo"):
photo, width, height = utils.convert_to_jpeg(self.request.get("photo"))
picture.photo = photo
picture.width = width
picture.height = height
picture.put()
self.redirect("/admin/picture/")
class PictureDelete(webapp.RequestHandler):
def get(self, key):
delete_items(key)
self.redirect("/admin/picture/")
##### This view below is "Tag" #################
class TagList(webapp.RequestHandler, View):
def get(self):
self.template_values['tags'] = [item.to_hash() for item in get_tags()]
self.render()
class TagNew(webapp.RequestHandler, View):
def get(self):
self.render()
def post(self):
tag = Tag()
tag.name = self.request.get("name")
tag.put()
self.redirect("/admin/tag/")
class TagEdit(webapp.RequestHandler, View):
def get(self, key):
self.template_values['tag'] = get_tag(key).to_hash()
self.render()
def post(self, key):
tag = get_tag(key)
tag.name = self.request.get("name")
tag.put()
self.redirect("/admin/tag/")
class TagDelete(webapp.RequestHandler):
def get(self, key):
delete_items(key)
self.redirect("/admin/tag/")
/template/TagEntryList.html
{% extends "base.html" %}
{% block script %}{% endblock %}
{% block script_onload %}{% endblock %}
{% block script_function %}{% endblock %}
{% block h1 %}タグ「{{ tag|escape }}」一覧{% endblock %}
{% block contents %}
{% for entry in entries %}
<p>
{{ entry.created_at|date:"Y年m月d日" }}
<a href="/entry/show/{{ entry.key }}">{{ entry.title|escape }}</a>
</p>
{% endfor %}
{% endblock %}これはタグ毎の記事一覧のテンプレート。/template/base.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>ブログをGoogle App Engineへ移行してみる</title>
<link rel="stylesheet" type="text/css" href="/static/css/all.css" />
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
{% block script %}{% endblock %}
{% block script_onload %}{% endblock %}
{% block script_function %}{% endblock %}
</head>
<body>
<div id="header">
<p><a href="/">ブログをGoogle App Engineへ移行してみる</a></p>
<!-- /#header --></div>
<div id="left">
<h1>{% block h1 %}{% endblock %}</h1>
{% block contents %}{% endblock %}
<!-- /#left --></div>
<div id="right">
■最近の記事
{% for recent_entry in recent_entries %}
<p>
{{ recent_entry.created_at|date:"m/d" }}
<a href="/entry/show/{{ recent_entry.key }}">{{ recent_entry.title|escape }}</a>
</p>
{% endfor %}
■タグ
{% for tag in tags %}
<p><a href="/tag/{{ tag.name|escape }}">{{ tag.name|escape }}</a></p>
{% endfor %}
<!-- /#right --></div>
<div id="footer">
<p>copyright(C)2011 jinlingren.com. All Rights Reserved.</p>
<!-- /#footer --></div>
</body>
</html>
デザインがアレなので見えないですが、大分ブログらしくなってきました。
まだまだ足りないところがありますが、ひとつずつひとつずつ実装していくのみです。
そろそろページ処理機能を考えていく必要があるのかな〜