App Engine Helper for DjangoはModelFormが使えないといった問題があり,それらを改善したプロジェクトとしてapp-engine-patchが紹介されている.しかしapp-engine-patchも既に開発が止まっていてDjango-nonrelを使えとのこと.Django-nonrelを使えばApp Engine上でもDjangoの機能の多くを利用することができる.一方でGAE DjangoやAEPにあったGoogleアカウント認証機能やログインURLテンプレートタグはDjango-nonrelでは失われているようだ.というわけ認証サポートをサクっと作ってみた.
まずはdjangoのモデルとGAE上のユーザを結びつけるモデルの定義.

project_dir/gaeutils/auth/models.py
from django.db import models
from django.contrib.auth.models import User

class GoogleProfile(models.Model):
    user = models.ForeignKey(User)
    google_user_id = models.CharField()

次に認証バックエンド.最初は認証ミドルウェアを置き換えようとしていたがdjangoの元の認証機能を最大限に活用しようと思うとバックエンドだけ変えた方が良さそうだった.ModelBackendのサブクラスとして実装したが,確認していない機能がほとんどなのでうまく動かない部分があるかもしれない.

project_dir/gaeutils/auth/backends.py
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
from google.appengine.api import users as gae_users
from gaeutils.auth.models import GoogleProfile

class GAEBackend(ModelBackend):
    def authenticate(self):
        gae_user = gae_users.get_current_user()
        if not gae_user:
            return None
        try:
            profile = GoogleProfile.objects.get(google_user_id=gae_user.user_id())
            return profile.user
        except GoogleProfile.DoesNotExist:
            user = User.objects.create_user(gae_user.nickname(), gae_user.email())
            profile = GoogleProfile(user=user, google_user_id=gae_user.user_id())
            profile.save()
            return user

最後に実際に認証処理を呼び出すビュー.

project_dir/gaeutils/auth/views.py
from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from google.appengine.api import users
import urllib

def login(request):
    dest = request.GET.get(REDIRECT_FIELD_NAME, '/')
    user = authenticate()
    if user is None:
        continued = bool(request.GET.get('continued', False))
        if continued:
            return HttpResponseRedirect('/')
        query = request.GET.items()
        query.append(('continued', 1))
        url = '%s?%s' % (reverse('login'), urllib.urlencode(query))
        return HttpResponseRedirect(users.create_login_url(url))
    auth_login(request, user)
    return HttpResponseRedirect(dest)

def logout(request):
    dest = request.GET.get(REDIRECT_FIELD_NAME, '/')
    auth_logout(request)
    return HttpResponseRedirect(users.create_logout_url(dest))

これらを有効にするためにsettings.pyとurls.pyに追記する.

settings.py
AUTH_PROFILE_MODULE = 'gaeutils.auth.models.GoogleProfile'
AUTHENTICATION_BACKENDS = ('gaeutils.auth.backends.GAEBackend', )
urls.py
urlpatterns = patterns('',
    # 省略
    url(r'^accounts/login', auth_views.login, name='login'),
    url(r'^accounts/logout', auth_views.logout, name='logout'),
)

これでlogin_requiredなども使える.

※このエントリは間違いを含んでいる可能性が多分にあります.ご了承ください.