HOME > ブログ > ブログをGoogle App Engineへ移行してみる19

ブログをGoogle App Engineへ移行してみる19

材料が揃ってきたので少し管理部分ではなく表示部分に力を入れてみることにする。現状は記事一覧のみが出ている。それに以下のものを足してみようと思う。

・最近の記事一覧
・タグ一覧
・タグ毎の記事一覧



「main.py」に表示部分のハンドラーを書いていたが、どうせどんどん肥大するので「admin.py」と同様、「blog.py」として外部ファイル化した。
/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>

デザインがアレなので見えないですが、大分ブログらしくなってきました。
まだまだ足りないところがありますが、ひとつずつひとつずつ実装していくのみです。

そろそろページ処理機能を考えていく必要があるのかな〜

| Google App Engine | Comment:7 |
コメント
hirsmet
へ〜
はじめまして。
modelの8〜57行目のような使い方もあるんですね。
思いつきませんでした。
参考にさせていただきます。
| 2011/05/11 15:53:02
hirsmet
忘れてました。
私のページもよろしくお願いします。
http://contributenews.appspot.com/
2011/05/11 15:54:19
viagra
viagra
zblflr <a href="http://cheapqviagra.com/ ">viagra</a> DYruR <a href="http://cheapqcialis.net/ ">buy cialis cheap</a> :-O <a href="http://cheapqviagra.net/ ">order sildenafil online</a> 9352 <a href="http://cheapqcialis.com/ ">cialis</a> 2063
| | 2012/02/02 06:54:13
viagra
viagra
efdczpxl <a href="http://bcheap-viagra.com/ ">viagra</a> %-[[[ <a href="http://bcheap-cialis.com/ ">cialis</a> =-] <a href="http://her-propecia.com/ ">propecia </a> 7171 <a href="http://accutane-skin.com/ ">accutane</a> 8]]]
| | 2012/02/04 04:21:39
kobe v
kobe v


Non-traditional match
<a href="http://www.kobeshoes-mall.com">Kobe basketball Shoes</a>
<a href="http://www.kobeshoes-mall.com">kobe shoes</a>
<a href="http://www.kobeshoes-mall.com">Kobe Shoes 2011</a>
<a href="http://www.kobeshoes-mall.com">kobe vi</a>
<a href="http://www.kobeshoes-mall.com">kobe 6</a>
<a href="http://www.kobeshoes-mall.com">kobe V</a>
<a href="http://www.kobeshoes-mall.com">kobe 5</a>colors, because now accept groundbreaking 4 kinds of color. Kobe Bryant V continue to limit the color of creativity and design. As a result, kobe Bryant finally broke every rule VI acceptable color. This pair of shoes rules, with the past. Bryant's VI "orange county" is a prime example of a basketball shoes bad all <a href="http://www.kobeshoes-mall.com">kobe ll</a>
<a href="http://www.kobeshoes-mall.com">kobe kids</a>
<a href="http://www.kobeshoes-mall.com">kobe olmpic</a>
<a href="http://www.kobeshoes-mall.com">kobe iv</a>
<a href="http://www.kobeshoes-mall.com">kobe 5.5</a>
<a href="http://www.kobeshoes-mall.com">kobe 6.5</a>
<a href="http://www.kobeshoes-mall.com">kobe vii</a>the rules of the basketball shoes must abide by.

| | 2012/02/13 23:08:11
cialis
cialis
sbqclc <a href="http://cheapstrongcialis.com/ ">cialis</a> %-[[[ <a href="http://cheapstrongviagra.com/ ">viagra</a> 7349 <a href="http://viagrarxviagra.com/ ">buy sildenafil cheap</a> QJEyee <a href="http://cialisrxcialis.com/ ">cialis</a> >:]]
| | 2012/02/15 15:51:42
Payday Loans
Payday Loans
whtdxlyk <a href="http://usapaydayloansrates.com/ ">Payday Loans</a> =-] <a href="http://paydayloansfastonline.co.uk/ ">payday loan online</a> >:-[ <a href="http://paydayloanscost.ca/ ">Payday Loans Cost</a> >:]]
| | 2012/02/22 01:23:07
コメント投稿












画像リロード
*半角の小英字、数字で構成されています