日記

日々のことと、Python/Django/PHP/Laravel/nodejs などソフトウェア開発のことを書き綴ります

Context Processorで views.py の仕事を肩代わり

前回はタグを作りました。タグ関数でインスタンスを作った Node クラスのrenderでは、contextが渡されていました。この context は、view や template、そして tag の中と一連の処理の中で共通の値を保持するクラスです。Java(Servlet API)の中だと ServletContext, HttpServletRequest, HttpSessionが同じような位置づけになると思います。

この context は一見すると views.py のView関数の中で定義するしか無いように見えますが、Django の Context Processorという仕組みを使って Contextに特別な値を設定することもできます。この Context Processor は、全ての View 関数の呼び出しに適用もできます。

早速、Context Processorの実装をしてみます。
今回は新しく、sample_context_processors.py というファイルを app1 ディレクトリの下に作成します。

# vim:fileencoding=utf-8
def sample(request):
    return {'remote_ip': request.META['REMOTE_ADDR']}

中身はこれだけです。。。
アクセス元のIPアドレスを remote_ip というキーでテンプレートから取得できるようにしてみました。
タグは load を使って使う準備をしましたが、Context Processor は、settings.pyに設定を追加します。
設定を追加する前に、デフォルトではどんな Context Processor が利用されているか調べてみます。Django のテストサーバを起動するときは

manage.py runserver

でしたが、今回は Django フレームワークをロードした状態の shell を起動します。

manage.py shell

これで shell にログインできます。Djangoのライブラリがロード済みであること以外は、Python の対話式シェルと同じです。では、デフォルトの Context Processor を調べます。

from django.conf import settings
settings.TEMPLATE_CONTEXT_PROCESSORS

これを順々に実行すると、次のような結果が出力されると思います。

('django.core.context_processors.auth', 'django.core.context_processors.debug',
'django.core.context_processors.i18n', 'django.core.context_processors.media')

4つ登録されていますね。この TEMPLATE_CONTEXT_PROCESSORS に自分の Context Processor も追加してみます。settings.pyを開いて、ファイルの末尾に次のコードを追加します。

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.core.context_processors.auth', 
    'django.core.context_processors.debug',
    'django.core.context_processors.i18n', 
    'django.core.context_processors.media',
    'app1.sample_context_processors.sample',
)

これで Context Processor "側" の準備は完了です。実はこれだけでは、Context Processor は呼び出されません。ちょっとだけ、views.py を変更します。

# vim:fileencoding=utf-8
from django.shortcuts import render_to_response
from django.template import RequestContext

def first(request):
    from datetime import datetime
    
    return render_to_response('first.html', {
      'abc': u'abcですと!',
      'none_data': None,
      'empty_string': '',
      'two_value': 2,
      }, 
      context_instance=RequestContext(request))

render_to_response の三つ目の引数に、context_instance=RequestContext(request))という値を追加しています。これは、templateに渡すContextオブジェクトのインスタンスを明示的に指定しています。
context_instanceを設定しないデフォルトでは、django.template.Contextが使われてしまいます。このデフォルトのContextでは、Context Processorが実行されません。その代わりに、RequestContextというクラスを使う必要があります。
では、HTMLテンプレートを修正します。

<html>
<head>
</head>
<body>
{% load sample_filters %}
{% load sample_tags %}

<p>サンプルアプリケーション はじめの一歩</p>

{% random_number a b c %}
<p>ほげほげ</p>
{% endrandom_number %}

<p>Remote Addr: {{ remote_ip }}</p>

</body>
</html>

変更したら、ブラウザでアクセスすると。。。

<html>
<head>
</head>
<body>



<p>サンプルアプリケーション はじめの一歩</p>

69463
<p>ほげほげ</p>
11605

<p>Remote Addr: 127.0.0.1</p>

</body>
</html>

できた!! ちゃんとローカルからアクセスした時のIPが取れています!!
全てのページで使うような値は、この Context Processor をうまく使うことで、面倒なコードを一か所に集められます。settings.pyに設定すると RequestContextを使う時、全てに適用されてしまいます。これを回避したいとか、ある機能だけで特定の値を使いたいというときには RequestContextのインスタンスに Context Processorの関数オブジェクトを渡してあげることもできます。(参考ページ)
これは場面ごとに使い分けをするのが良いと思います。

次は少し View に戻って、何気なく使ってしまった render_to_response がやっていることを関数を使わずにやってみたいと思います。