2009/09/07

Google App Engine Oil で twitter もどき STEP 1

前回、Webアプリケーションをさくっと手軽に構築できてしまう Google App Engine (GAE) を試してみましたが、今回は、もう少しまともなアプリケーションを作ってみます。

作成する前に、つい先日 GAEO が 0.3 にバージョンアップしているようなので、早速最新のリリースを試してみましょう。

バージョンアップ作業は、環境変数のパスを新しいバージョンのものに変更しただけです。

Twitter

Twitter は、140文字以下の短いメッセージをつぶやきあうシンプルなサービスです。現在とても注目されているサービスですね。
シンプルでわかりやすいですので、今回は Twitter もどきのアプリケーションを作成してみます。

mockker

それでは、Twitter もどきの「mockker」というプロジェクトを作成します。
とりえあず今回は、データの登録ができればOKとします。また、アカウントは Google App Engine の ユーザーサービスを使用し、Google アカウントと連携させます。

gaeo.py mockker
cd mockker

まずは、つぶやきの scaffold を作成します。

gaeogen.py scaffold status create show "text:StringProperty()" "user:UserProperty()" "created_at:DateTimeProperty(auto_now_add=True)"

ルーティング

デフォルトのルートへのルーティングを、welcome/index から status/index へ変更します。

gaeo/dispatch/router.py
class Router:

  """ Handles the url routing... """


  class __impl:

    def __init__(self):
      self.__routing_root = {'controller': 'status',
                   'action': 'index'}

model

scaffold により、 application/model/status.py が作成されます。

application/model/status.p
from google.appengine.ext import db
from gaeo.model import BaseModel, SearchableBaseModel

class Status(BaseModel):
  created_at = db.DateTimeProperty(auto_now_add=True)
  text = db.StringProperty()
  user = db.UserProperty()

controller

scaffold により作成された status コントローラを次のように修正します。

application/controller/status.py
import cgi
import logging

from google.appengine.ext import db
from google.appengine.api import users

from gaeo.controller import BaseController

from model.status import Status

class StatusController(BaseController):
    def create(self):
        r = Status(
            # Uncomment all required properties here.
            # text = self.params.get('text', None),
            # created_at = self.params.get('created_at', None),
            user = users.get_current_user(),
        )
        for prop in Status.properties():
            if prop in self.params:
                setattr(r, prop, self.params.get(prop))
        r.put()
        self.flash['notice'] = u"独り言を更新したよ"
        self.redirect('/')

    def index(self):
        notice = self.flash.get('notice', '')
        self.msg = notice
        if self.current_user:
            query = Status.all().filter('user = ', self.current_user)
            query.order("-created_at")
            self.result = query.fetch(limit=10)

    def show(self):
        r = Status.get(self.params.get('id'))
        if r:
            for prop in Status.properties():
                setattr(self, prop, getattr(r, prop))
        else:
            self.redirect('/')

ユーザー認証のため、base コントローラの before_action を次のように修正します。
これにより、全てのアクションの前にユーザー認証が実行されます。

gaeo/controller/__init__.py
from google.appengine.api import users

  def before_action(self):
    self.current_user = users.get_current_user()
    if not self.current_user:
      self.signed_in = False
      self.login_url = users.create_login_url(self.request.uri)
      #self.redirect(users.create_login_url(self.request.uri))
    else:
      self.signed_in = True
      self.logout_url = users.create_logout_url(self.request.uri)
      self.nickname = self.current_user.nickname()

view

view を次のように修正します。

application/templates/base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>{% block title %}{% endblock %}</title>
  <link rel="stylesheet" href="/css/common.css" type="text/css" media="screen" />
  {% if signed_in %}
  <link rel="stylesheet" href="/css/login.css" type="text/css" media="screen" />
  {% endif %}
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
  <script type="text/javascript" src="/js/application.js"></script>

</head>
<body>
<div id="header">
  <div id="header_inner">
  <h3><a href="/" title="Mockker">Mockker</a></h3>
  {% if signed_in %}
  <ul id="nav">
    <li><a href="{{ logout_url }}" title="Logout">Logout</a></li>
  </ul>
  {% else %}
  <ul id="nav">
    <li><a href="{{ login_url }}" title="Login">Login</a></li>
  </ul>
  {% endif %}
  </div>
</div>
<div id="content">
  <div id="content_inner">
  {% if msg %}
  <div class="flash">
    <div class="notice">
    <p>{{ msg }}</p>
    </div>
  </div>
  {% endif %}
  {% if signed_in %}
  <div id="update">
    <form method="post" action="/status/create">
    <span id="countdown">140</span>
    <label for="text">何か言いたいことは?</label>
    <textarea id="text" name="text" rows="10" cols="10"></textarea>
    <input type="submit" name="update" value="Update" />
    </form>
  </div>
  {% endif %}

  {% block content %}{% endblock %}
  </div>
</div>

<div id="footer">
  <div id="footer_inner">
  <div class="column">
    <ul>
    <li><a href="/" title="Home">Home</a></li>
    </ul>
  </div>
  </div>
</div>
</body>
</html>
application/templates/status/index.html
{% extends "../base.html" %}

{% block title %}StatusController#index{% endblock %}

{% block content %}

<div id="statuses">
{% for r in result %}

  <div id="status_{{ r.key }}" class="status">
  <div class="info">

    <p class="who_when">
    {{ r.user.nickname }}<br />
    {{ r.created_at }}
    </p>

    <p class="actions">
    </p>
  </div>

  <div class="text">
    {{ r.text }}
  </div>
  </div>
{% endfor %}
</div>


{% endblock %}

あとは、Javascript や CSS などで、見栄えを整えます。

実行

dev_appserver.py .

登録したデータをクリアしたい場合は、「--clear_datastore」オプションをつけて実行します。

dev_appserver.py --clear_datastore .

以上で、自分だけがつぶやくことができる、独り言アプリケーションの完成ですw
次回は、他人のつぶやきを表示できるようにしてみたいと思います。