結局 request に突っ込むのがいいみたい。

id:mopemope さんがいろいろ考えてくださったみたいなので、改めて Django の設計思想についていろいろ調べてみました。

結論から先に言うと、HttpRequest の次の属性にユーザとプロバイダを突っ込むことにしました。ちょっと行儀が悪いかなと思って HttpRequest をいじるのはあえて避けていたんですが、セッション・ミドルウェアでは session って属性を勝手に定義しているわけで、それならうちも、ということで。

  • auth_user
  • auth_provider

あ、ちなみにプロバイダというのは何かというと、ユーザ名に対応するパスワードのハッシュを提供するクラスです。なんでこんなまどろこしいことするの?っていう人もいるでしょうけど、これは、いいんです。

    class Provider:
        manager = None
        realm = None

        def __init__(self, manager, realm):
            self.manager = manager
            self.realm = realm

        def __getitem__(self, key):
            if key[1] <> self.realm:
                raise KeyError('No such key: ' + repr(key))
            try:
                return self.manager.get(username=key[0]).pw_hash
            except Exception, e:
                raise KeyError(e)

        def __setitem__(self, key, val):
            if key[1] <> self.realm:
                raise KeyError('No such key: ' + repr(key))
            try:
                user = self.manager.get(username=key[0])
            except Exception, e:
                raise KeyError(e)
            user.pw_hash = val
            user.save()
        def set_password(self, key, pw):
            self[key] = make_credential_hash(key[0], key[1], pw)

加えていくつかくだらないバグもあったので、それを直した物を以下に置きました。
http://voltex.jp/downloads/Mozo.Django.DigestAuth-0.1.tar.gz

django.contrib.auth ではデコレータを使ってますね。

django.contrib.auth.decorator より抜粋:

from django.contrib.auth import LOGIN_URL, REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
from urllib import quote

def user_passes_test(test_func, login_url=LOGIN_URL):
    """
    Decorator for views that checks that the user passes the given test,
    redirecting to the log-in page if necessary. The test should be a callable
    that takes the user object and returns True if the user passes.
    """
    def _dec(view_func):
        def _checklogin(request, *args, **kwargs):
            if test_func(request.user):
                return view_func(request, *args, **kwargs)
            return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
        _checklogin.__doc__ = view_func.__doc__
        _checklogin.__dict__ = view_func.__dict__

        return _checklogin
    return _dec

login_required = user_passes_test(lambda u: u.is_authenticated())
login_required.__doc__ = (
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    )

で、これを使うには次のようにするというわけですね。
django.contrib.auth.views.py:70:

def password_change(request, template_name='registration/password_change_form.html'):
    ....
password_change = login_required(password_change)

なんだろう、Javaフレームワークでいうと AOP つかってインターセプトしているような感じでしょうか。

「django.utils.decorators の decorator_from_middleware をつかえばキミのミドルウェアをデコレータにできちゃう!」みたいなので、これを使ってみるのもよさそう。