Django Middleware で Traceback をコンソールに出力する

Django は、コンソールにデバッグ情報を出力してくれません(クリティカルなエラーは除く)。普通に開発している分には、ブラウザに表示される Traceback が充実しているので十分なのですが、API などブラウザ以外からリクエストが飛んでくるアプリケーションを開発している場合に、かなり不便なので Middleware を使ってコンソールに Traceback を出力するようにします。

まず、以下を middleware.py としてプロジェクト直下に保存。 request オブジェクトの内容も出力したい場合は、コメントを外します。

import traceback
import sys

class TracebackMiddleware():
    def process_exception(self, request, exception):
        print '######################## Exception ##################'
        print '\n'.join(traceback.format_exception(*sys.exc_info()))
        #print '-----------------------------------------------------'
        #print repr(request)
        print '#####################################################'

settings.py に ConsoleExceptionMiddleware の設定を追加。

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.middleware.doc.XViewMiddleware',
    'myapp.middleware.ConsoleExceptionMiddleware',
)

これでプログラム中でエラーが発生した際に manage.py を実行しているコンソールに Traceback 情報が表示されるようになります。以下サンプル。

関連リンク:
Django snippets: Middleware for printing of exception to console

OAuth Consumer Request の処理フローと実装

OpenSocial の RESTful API では、OAuth を利用して権限の確認を行います。ただし、コンシューマが完全にユーザに成り代わって処理をするため、コンシューマとプロバイダ二者間の信任フローになります。この方法については、まだドラフト段階ですが次の文書に記されています。

» OAuth Consumer Request 1.0 Draft 1

処理フローを以下に説明します。コンシューマは次のパラメータをプロバイダに送信することになります。

oauth_consumer_keyコンシューマキー
oauth_signature_methodHMAC-SHA1
oauth_signatureシグニチャ
oauth_timestampUNIXタイムスタンプ
oauth_nonceランダムな文字列
oauth_version1.0 (オプション)

ここで、 oauth_signature は以下のようにして生成されます。まず、ベース文字列を生成するために次の値を用意します。

  1. GET
  2. http://provider.example.net/profile
  3. oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_version=1.0

これらをURIエスケープした後に & で連結して、ベース文字列を生成します。

GET&http%3A%2F%2Fprovider.example.net%2Fprofile&oauth_consumer_key%3Ddpf43f3p
2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1
%26oauth_timestamp%3D1191242096%26oauth_version%3D1.0

そして、このベース文字列を HMAC-SHA1 によってダイジェスト値を生成し、BASE64 でエンコードすることによってシグニチャが生成されます。この際、利用する共有キーは cosumer_secret と 空のToken Secret を & で連結したものになります。例えば、 consumer_secret が kd94hf93k423kf44 なら kd94hf93k423kf44& になります(Token Secretは空のため)。こうして、ダイジェスト値は以下のようになります。

SGtGiOrgTGF5Dd4RUMguopweOSU=

こうして生成されたパラメータをリクエスト時の Authorization ヘッダに付加します。プロバイダ側でも同様にダイジェスト値を生成し、シグニチャが合っていればプロバイダが持つリソースへのアクセスを許可します。また、タイムスタンプが5分以上前の場合にはエラーとすることで、リクエストURLが漏れた場合でもセキュリティを確保しています。

Authorization: OAuth realm="http://provider.example.net/",
                    oauth_consumer_key="dpf43f3p2l4k3l03",
                    oauth_signature_method="HMAC-SHA1",
                    oauth_signature="SGtGiOrgTGF5Dd4RUMguopweOSU%3D",
                    oauth_timestamp="1191242096",
                    oauth_nonce="kllo9940pd9333jh",
                    oauth_version="1.0"

さて、以上の処理を Django で実装してみました。コンシューマ側は、普通のPythonスクリプトですので、コンソールから実行することができます。

プロバイダ側

import oauth
from django.shortcuts import *

class MockOAuthDataStore(oauth.OAuthDataStore):
    def __init__(self):
        self.consumer = oauth.OAuthConsumer('key', 'secret')
        self.nonce = 'nonce'

    def lookup_consumer(self, key):
        if key == self.consumer.key:
            return self.consumer
        return None

    def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
        return None

def auth_test(req):
    os = oauth.OAuthServer(MockOAuthDataStore())
    os.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())

    # build from request
    base_url = req.is_secure() and 'https://' or 'http://' + req.get_host()
    try:
        os.oauth_request = oauth.OAuthRequest.from_request(
            req.method,
            base_url + req.path,
            headers={'Authorization': req.META.get('HTTP_AUTHORIZATION')},
        )

        consumer, token, params = os.verify_request(os.oauth_request)
    except oauth.OAuthError, err:
        return HttpResponse(err.message, status=401)

    return HttpResponse('OK!')

コンシューマ側

import oauth
import urllib2

CONSUMER_KEY = 'key'
CONSUMER_SECRET = 'secret'

def oauth_request(method, url, parameters=None):
    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()

    # access protected resources
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
	                consumer,
			token=None,
			http_method=method,
			http_url=url,
			parameters=parameters)
    oauth_request.sign_request(signature_method_hmac_sha1, consumer, '')

    headers = oauth_request.to_header()
    
    r = urllib2.Request(url, headers=headers)
    try:
        print urllib2.urlopen(r).read()
    except urllib2.HTTPError, e:
        print e

if __name__ == '__main__':
  oauth_request('GET', 'http://localhost:8080/auth_test/')

Leah Culverさんが書いたOAuthライブラリを利用しているのですが、空の Token だとエラーになるので、一箇所だけソースに手を加えています。

--- oauth.py.org   2008-06-14 11:52:33.000000000 +0900
+++ oauth.py       2008-06-14 12:01:44.000000000 +0900
@@ -311,7 +311,10 @@
         version = self._get_version(oauth_request)
         consumer = self._get_consumer(oauth_request)
         # get the access token
-        token = self._get_token(oauth_request, 'access')
+        try:
+            token = self._get_token(oauth_request, 'access')
+        except:
+            token = ''
         self._check_signature(oauth_request, consumer, token)
         parameters = oauth_request.get_nonoauth_parameters()
         return consumer, token, parameters

Google App Engine で1対多の結合をする

話題の Google App Engine ですが,やはり人気のようでまだアカウントが取得できていない人も多いようですね。アカウントがなくても,ローカルで開発は始められるので,これから始めようと思っている人は次の記事を参考にすると良いと思います(手前味噌ですが…)。

» 一番かんたんなGoogle App Engineの使い方 |gihyo.jp (技術評論社)

さて,本題の Google App Engine で1対多のジョインを行う方法に入っていきましょう。「BigTable」をバックエンドに利用できる Google App Engine ですが, RDBMS の考え方に慣れているとけっこう戸惑うことが多いです。 SQL に似た専用の GQL 言語は, OR や != が使えなかったりしてとても癖があります。親レコードと子レコードを JOIN する方法もわからずに最初は悩んだのですが,モデルを定義する際の ReferenceProperty に collection_name を定義しておくことで簡単にできます。正確には結合ではないかもしれませんが,親レコードから collection_name プロパティを参照することで関連する 子レコードのみを抽出することができるようになります。

以下のコードがそのサンプルになります。

# -*- coding: utf-8 -*-
import os,cgi
import wsgiref.handlers

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template

class Group(db.Model):
    name = db.StringProperty(required=True)
    
class Person(db.Model):
    name = db.StringProperty(required=True)
    age = db.IntegerProperty(required=True)
    group = db.ReferenceProperty(Group, 
        required=True, collection_name='members')

class MainPage(webapp.RequestHandler):
    def get(self):
        # Group data
        group1 = Group(name="Perfume")
        group1.put()
        group2 = Group(name="Puffy")
        group2.put()

        # Person data
        person1 = Person(name=u"大本 彩乃", age=19, group=group1)
        person1.put()
        person2 = Person(name=u"樫野 有香", age=19, group=group1)
        person2.put()
        person3 = Person(name=u"西脇 綾香", age=19, group=group1)
        person3.put()
        person4 = Person(name=u"大貫 亜美", age=34, group=group2)
        person4.put()
        person5 = Person(name=u"吉村 由美", age=33, group=group2)
        person5.put()

        groups = Group.all()
        
        path = os.path.join(os.path.dirname(__file__), 'example.html')
        self.response.out.write(template.render(path, {'groups': groups}))

def main():
    application = webapp.WSGIApplication(
        [('/', MainPage)],
        debug=True)
    wsgiref.handlers.CGIHandler().run(application)
        
if __name__ == "__main__":
    main()

アイドルがグループに所属するというモデルを考えます。大貫亜美さんと吉村由美さんは Puffy というグループに所属します。また,大本彩乃さん,樫野有香さん,西脇綾香さんは Perfume というグループに所属します。上のコードだと, Group と Person というモデルを定義して, MainPage クラスでデータを登録しています。そして, groups という変数に全てのグループを格納して,テンプレートに渡しています。

そして表示に利用するテンプレートは,これです。 Group ごとにループ処理を行い,さらにそのグループに所属するメンバーでループ処理を行っています。

  {% for group in groups %}
     <h2>{{ group.name }}</h2>
     <ul>
     {% for member in group.members %}
       <li>{{ member.name }} ({{ member.age }}歳)</li>
     {% endfor %}
     </ul>
  {% endfor %}

以上のプログラムを実行した結果は,次のようになります。

ちゃんと,グループごとに分かれて表示されていますね。

ブログに最近の訪問者を表示してみる実験

このブログの右側に最近の訪問者表示を追加してみました。バックエンドは, Google App Engine なんですが,たまにちょっとレスポンスが悪い時があるようでちょっと様子見。問題ないみたいです。時間のかかる処理をさせると,あっという間にブロックされるので,あとは統計処理的なのをどうするかだなぁ。

Wanted!

Buttons!
photo by jacobian

Django勉強会 Disc4開催

※定員追加したにもかかわらず、10時に埋まってしまいました。

来る8月25日にDjango勉強会 Disc4が開催されます。
受付開始は8月13日 AM6:00。がんばって起きないと!!

日時
2007年8月25日(土) 11:00〜17:00くらいまで
(今回はお昼を2時間程度とり懇親会とする予定です)
開催場所
サイボウズ・ラボ株式会社
費用
無料(別途お昼代)
内容(予定)
「newforms 全般について」ymasuda さん
「例えば、Djangoを避ける」perezvon さん
「Django と Senna について?」tasukuchan さん
「test 全般について?」MiCHiLU さん
「PylonsとTurboGearsとDjango?」ykzts さん
参加申込方法
専用ページにて、8月13日6:00より受付開始
募集人数 20名程度(うち発表者等6名)

サイトをDjangoに移行しました

やっと、サイトをMade with Djangoなものに置き換えました。
FastCGIにしようかWSGIにしようか迷いましたが、結局mod_pythonで動かすことにしました。いろいろ細かい部分が未完成なのと、JavaScriptでごまかしている部分もあるのですが、おいおい改良していくことにしましょう。

敬具

Django勉強会がウノウにて開催されました

1月22日、ウノウを会場にDjango勉強会が開催されました。
  • DjangoからAjaxを使うにはどうしたらいい感じか?(mopemopeさん)
  • 寺子屋「RandomNoteを作る」(uemuraさん)
  • ライブブログ構築(遠藤さん)
  • Leuchtturmデモ(酒徳さん)
寺子屋では、実際に参加者の方が、ペア・プログラミングでサンプル・アプリケーションを作成するという形で行われました。半数以上がDjango初心者だったのですが、皆さん初めてでもすぐに覚えてしまったようです(Python自体が初めてという人もいました)。

参加者は24名で、懇親会もほぼ全員の方が参加して、エンジニア同士での意見交換が活発に行われました。

Djangoは、 Pythonで書かれたWebアプリケーション・フレームワークで、見通しのよい MVC 分離、洗練された O/R マッピング API、そして汎用性の高い強力なテンプレートエンジンを備え、高い柔軟性とパフォーマンスを同時に要求されるWebアプリケーションを迅速に開発することができます。

Ruby on Railsと比較されることも多いのですが、もともとアクセスの多い新聞社のサイト向けとして開発されたこともあり、パフォーマンスに関してはDjangoが少しばかり優れているようです。

Django/Rails/Symfonyの比較記事


関連リンク:
Djangoと日本の仲間たち
Django | The Web framework for perfectionists with deadlines

Django懇親会のお店を予約

お店がこのビルの中にあるのですが、後光が射していました。

懇親会だけ参加したい人とかっていますかね?


関連リンク:
カミニート - Wikipedia

Django勉強会 Disc 2 受付終了

受付開始から11時間で定員に達したそうです。

今回は、ウノウのオフィスが会場なのですが、やっぱりもっと広い場所が必要ですね。

Django勉強会 Disc 2