Djangoで405 Method not allowedをキレイに処理したい。

Django でHTTP STATUSの405を出したいと思って、 ソースとほんのり追いかけてみたよ。

django/http/__init__.py の380行目あたりに HttpResponseNotAllowed なクラスがあるんですが、 こんな感じになっていて permitted_methods 以外はセット出来ない様子。

class HttpResponseNotAllowed(HttpResponse):
    status_code = 405

    def __init__(self, permitted_methods):
        HttpResponse.__init__(self)
        self['Allow'] = ', '.join(permitted_methods)

これだと画面が真っ白になってしまうんじゃ? と思いながら試してみたら、案の定真っ白。

なら、 HttpResponse 使って status_code だけ405にしてあげたら素敵になるんじゃ? しかもHttp405とかってException作っておいて、 それを投げるように出来ればもっと素敵になるんじゃ? なんて安易な発想を実装してみた。

SumiTomohikoの日記 さんの 403 Forbiddenを表示するミドルウェア をかなり参考にさせて頂きました。

まずはExceptionを定義。 Http404を読み込んでるのは、どうせなら一緒にimportしたいってだけで、 他に意味は全くないっす。

# vim: fileencoding=utf-8 :

from django.http import Http404


class Http400(Exception):
    pass

class Http403(Exception):
    pass

class Http405(Exception):
    pass

次にミドルウェアとその他もろもろの定義。名前はそれぞれ適当にそれっぽく。

# vim: fileencoding=utf-8 :

from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.template import loader, Context, RequestContext
from exceptions import *


def _bad_request(request, template_name='400.html'):
    """
    HTTP STATUS 400を処理する。
    """
    t = loader.get_template(template_name)
    c = RequestContext(request, {})
    return HttpResponseBadRequest(t.render(c))


def _forbidden(request, template_name='403.html'):
    """
    HTTP STATUS 403を処理する。
    """
    t = loader.get_template(template_name)
    c = RequestContext(request, {})
    return HttpResponseForbidden(t.render(c))


def _not_allowed(request, permitted_methods, template_name='405.html'):
    """
    HTTP STATUS 405を処理する。
    """
    t = loader.get_template(template_name)
    # 要求されたメソッドと許可されているメソッドをテンプレートに渡す。
    c = RequestContext(request, {
        'REQUEST_METHOD'    : request.method,
        'PERMITTED_METHODS' : permitted_methods,
    })
    response = HttpResponse(t.render(c))
    # status_codeの変更。
    response.status_code = 405
    # レスポンスヘッダに許可されているメソッドをセット。
    response['Allow'] = ', '.join(permitted_methods)
    return response


class HttpExceptionMiddleware(object):
    """
    HttpなExceptionを処理する。
    """
    def process_exception(self, request, e):
        if isinstance(e, Http400):
            return _bad_request(request)
        if isinstance(e, Http403):
            return _forbidden(request)
        if isinstance(e, Http405):
            return _not_allowed(request, permitted_methods=e)
        return None

Http404はデフォルトで処理されるので、今回は見ない事に。 それぞれの処理をメソッドではなく関数にしてるのは、 どこか他でも使い回せるかなぁ?なんて浅はかな先読みでしかないです。

あとは、ココで作ったミドルウェアを settings.pyMIDDLEWARE_CLASSES に追加してやる。

MIDDLEWARE_CLASSES = (
    'applications.http.middleware.HttpExceptionMiddleware', # <-- コレを追加。
    ...
)

これで views.py とかでHttp405とかを投げると、 決められたテンプレートでレンダリングしてくれる訳ですが、 リクエストメソッドを確認する為に、 毎回同じ様な処理を書くのがメンドクサイので、 Python の勉強も含めてデコレータも作ってみた。

強烈に参考にさせて頂いたのは Agent Ultra さんの RESTful Representations for Django Views

def restrict(*accepts):
    """
    リクエストメソッドでフィルタリング。
    """
    def decorator(fn):
        def inner(request, *args, **kwargs):
            if request.method not in accepts:
                raise Http405, accepts
            return fn(request, *args, **kwargs)
        return inner
    return decorator

実際使うときはこんな感じになる。

@restrict('POST', 'PUT')
def some_view(request):
    """
    POSTかPUTしか許可されないビューになるよ。
    """

なかなかいい感じ。 期待した通りに動いてくれるし、 コード量も激減出来るし、かなり素敵 :-)

ほぼパクりっぱなしだったけど、 色々勉強出来たのでやってみて良かった! 刺激を与えてくれた某 endless 氏と、参考にさせて頂いた方と、 それを探して来てくださった 常山 さんに激しく感謝!

Posted at: 
2007/10/24 00:30:36
0 Comments
1 TrackBack
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2007/10/24/processing-more-clearly-405-on-django/trackback/

TrackBacks

[Django][Python][jQuery][その他]巡回 - 常山日記

Python温泉: [spa]Python 温泉へ持って行く課題 自分も今回の課題を決めておかないと Blog: Djangoで405 Method not allowedをキレイに処理したい。 Django tutorials, presentations and slides Fun with Beautiful Soup DjangoPaste 0.0 Edit inline with ImageField or F

Created at: 
2007/10/24 01:54:14

Comments

まだ登録されていません。

Add Comment

Add Comment