[Django]http_method_namesの指定は通常不要な理由
はじめに
Djangoのクラスベースビューでは http_metho_names
という属性が定義されています。これはサポートしているHTTPメソッドを定義するためのものです。
ですが通常は設定不要です。この理由について記載します。
以下は Django 3.2.7で確認しています。
http_method_names
による挙動の違い
まずシンプルなクラスベースビューの1つ、TemplateViewを使って挙動を確認してみます。TemplateViewは次のように、 get()
インスタンスメソッドが定義されています。
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
この TemplateView
を使ったViewの使用例は次のようになります。
from django.views.generic import TemplateView
class VanillaTemplateView(TemplateView):
template_name = "example.html"
このViewに対してアクセスすると、次のようになります。
- GET: 成功
- POST: 失敗(
405 Method Not Allowed
)
正確には GETメソッドだけでなく、HEAD, OPTIONSメソッドも成功します。ただしHEADメソッドはヘッダだけ返すのではなく、GETメソッドと同じ挙動になります。
$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS /vanilla/ HTTP/1.1
Host: 127.0.0.1
HTTP/1.1 200 OK
Date: Mon, 20 Sep 2021 12:06:02 GMT
Server: WSGIServer/0.2 CPython/3.9.7
Content-Type: text/html; charset=utf-8
Allow: GET, HEAD, OPTIONS
(以下略)
post() メソッドを実装したときの挙動の変化
このViewに post()
インスタンスメソッドの定義を加えてみます。
from django.views.generic import TemplateView
class PostSupportView(TemplateView):
template_name = "example.html"
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
すると、POSTメソッドも成功します。OPTIONSメソッドを実行すると、先ほどと比べ、POSTメソッドが加わっていることが分かります。
$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS /post_support/ HTTP/1.1
Host: 127.0.0.1
HTTP/1.1 200 OK
Date: Mon, 20 Sep 2021 12:08:22 GMT
Server: WSGIServer/0.2 CPython/3.9.7
Content-Type: text/html; charset=utf-8
Allow: GET, POST, HEAD, OPTIONS
http_method_names
を定義してみる
このViewに対して http_metho_names
を定義するとどうなるでしょうか。
from django.views.generic import TemplateView
class PostWithHttpMethodNamesView(TemplateView):
http_method_names = ["get"]
template_name = "example.html"
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
すると、GETメソッドのみが有効になり、POSTメソッドが無効になります。HEAD, OPTIONSメソッドも無効になります。
$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS /post_with_http_method_names/ HTTP/1.1
Host: 127.0.0.1
HTTP/1.1 405 Method Not Allowed
http_method_names = ["get", "options"]
とすると、OPTIONSメソッドも使えます。ただし、HEADメソッドは使えません。
$ telnet localhost 8000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
OPTIONS /post_with_http_method_names/ HTTP/1.1
Host: 127.0.0.1
HTTP/1.1 200 OK
Date: Mon, 20 Sep 2021 12:13:41 GMT
Server: WSGIServer/0.2 CPython/3.9.7
Content-Type: text/html; charset=utf-8
Allow: GET, OPTIONS
これらの挙動から、次のことが推測されます。
- HTTPメソッドに対応したインスタンスメソッドを定義することで、そのHTTPメソッドが有効になる。
- ただしHEAD, OPTIONSメソッドについてはデフォルトで定義されている。
- インスタンスメソッドが未定義の場合、405 Method Not Allowed になる。
- http_method_namesを定義すると、1.〜3.の挙動を上書きする。
この挙動をソースコードで確認してみます。
http_method_names
の定義
Djangoでの Django本体(テスト・ドキュメント除く)で http_method_names
が出てくるのは django/views/generic/base.py 1ファイルだけです。このファイルに4回出てきます。
http_method_names
と関係する箇所のみ抜き出したコードは次になります。
class View:
# ①
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
# ②
if key in cls.http_method_names:
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# ここからは無関係なので省略
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
# ③
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response.headers['Allow'] = ', '.join(self._allowed_methods())
response.headers['Content-Length'] = '0'
return response
def _allowed_methods(self):
# ④
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
まず①で http_method_names
のデフォルトが定義されています。これは HTTP リクエストメソッド のうち、 CONNECT
を除く9個が定義されています。
②では、 as_view()
の引数のキーに対し、 http_method_names
に含まれていないことを検証しています。クラスベースビューでは TemplateView.as_view(template_name="example.html")
のように as_view()
に引数を渡すことができるのですが、 TemplateView.as_view(post=post_method)
のような書き方を禁止しています。
③はリクエストが到達した際に最初に呼ばれるメソッドである、 dispatch()
の内容です。リクエストメソッドを小文字にしたものが http_method_names
に含まれていれば、対応するクラスメソッドを取得しています。すなわち、次のような挙動になります。
-
http_method_names
に定義されていないHTTPメソッドは、インスタンスメソッドの有無に関わらず、405 Method Not Allowed
になる。 -
http_method_names
に定義されているHTTPメソッドは、インスタンスメソッドの存在をチェックし、それを呼び出す。
①で http_method_names
のデフォルトとしてほとんどのHTTPメソッドが定義されているため、実質的にはHTTPメソッドに対応するインスタンスメソッドが定義されていれば有効になります。Viewクラスには options()
メソッドが定義されているので、OPTIONSメソッドがデフォルトで有効になっていることが分かります。
④はその options()
メソッドで呼び出されるインスタンスメソッドで、 http_method_names
の各メソッドに対し、インスタンスメソッドが定義されているものを返すようになっています。
HEADメソッドがデフォルトで有効な理由
実際の挙動から、次のように推測しました。このうち1.と3.と4.については先ほど説明した通りです。残っているのは2.のうち、HEADメソッドが定義されている理由です。
- HTTPメソッドに対応したインスタンスメソッドを定義することで、そのHTTPメソッドが有効になる。
- ただしHEAD, OPTIONSメソッドについてはデフォルトで定義されている。
- メソッドが未定義の場合、405 Method Not Allowed になる。
- http_method_namesを定義すると、1.〜3.の挙動を上書きする。
HEADメソッドは、Viewクラスにある setup()
メソッドで定義されています。
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
これを読み解くと、 get() が定義されていて、 head() が未定義のときは head() の定義は get() と同じになる
となります。これで2.のHEADがデフォルトで定義されている理由も説明できました。
http_method_names
の定義が必要な場面
明示的に Djangoのクラスベースビューでは、Viewクラス以外に http_method_names
を定義、あるいは使用している箇所はありません。 get()
メソッドを定義すれば自動的に GETメソッドが有効になり、 post()
メソッドを定義すれば自動的に POSTメソッドが有効になります。そのため、 http_method_names
の定義が必要な場面はほとんどありません。
例外は、クラスベースビューの機能を使いつつ、デフォルトのHTTPメソッドを無効にしたい場合です。例えば、UpdateViewを使ってモデルの変更を行いたいが、編集画面は表示させたくない場合です。このときは http_method_names
を使って get()
を無効化するのがいいでしょう。
サンプルコード
こちらに掲載しています。
Discussion