web2pyビュー覚書

controllers/default.pyを辿る

routes.pyで指定されていない場合、URLパスルートをリクエストすると、当該アプリケーションのcontrollers/default.pyのindex()が呼び出される。

def index():

"""

example action using the internationalization operator T and flash

rendered by views/default/index.html or views/generic.html

"""

response.flash = "Welcome to web2py!"

return dict(message=T('Hello World'))

の二行。

respones.flashは変数で、「Storageインスタンス」(*1)になっており、ビュー側で次のようなHTMLコードが最終生成される。

<div class="flash" style="display: block;">Welcome to web2py2!</div>

このresponse.flashの題材にして、web2py2のview動作をたどっていくなかで、わかったことをメモしていく。

response.flash

documentによれば(*2)

response.flash: ビューに組み込まれるオプションパラメータです。通常は何か起 こったことをユーザに通知するために使用されます。

views/layout.html

さて、このresponse.flashはdefault.pyのviewではviews/layout.htmlの中で呼び出されている。

<div class="flash">{{=response.flash or ''}}</div>

web2pyのview内の等号付き二重波括弧にある「等号x」{{=x}}は、

response.write(x)

を表しており(*3)、「出力ページにボディにテキストを書くメソッド」(*4)ということになっている。

ちなみにテンプレートは二重波括弧{{}}でPythonの構文を囲むことによって、プログラムをかけるので(ただしPython構文に必須のインデントを記述できないために(*5)、終了はpassを明示的に書くことになる)、while構文などは等号付き二重波括弧{{=x}}を使って次のように出力される(*6)。

{{k = 3 }}

<ul>

{{while k > 0:}}<li>{{=k}}{{k = k-1 }}</li>{{pass}}

</ul>

layout.htmlってなに?

まず、このlayout.htmlというビューレイアウトの基底ファイル(らしいもの)がどこで定義されているのかといえば、

各ビューの*.htmlの冒頭で指定している

{{extend 'layout.html'}}

最初よくわからなくて、web2pyコアのgluon/や、各アプリケーションのディレクトリでgrep layout.htmlして、定義を探したのであった・・。

layout.htmlと各ビューファイルの関係

デフォルトだと

controller class
|
v
default/index
        A
        |
    default class の def名

なので、

initアプリがデフォルトであれば、

applications/init/views/default/index.html

にアクセスされる。

でindex.htmlの冒頭で上記layout.htmlを拡張するよ、と宣言してやる。

layout.htmlは

applications/init/views/layout.html

にある。

ソース例

layout_ext.html
<html>
<title>TEST</title>
<body>
{{include}} <=これがミソ
</body>
</html>

index.html
{{extend 'layout_ext.html'}}
<p>hello world</p>
出力されたhtml
<html>
<title>TEST</title>
<body>
<html>
<p>hello world</p>
</body>
</html>

要旨

index.htmlはlayout.htmlのextendなのに、layout.html側でincludeしているというところがなかなか理解できなかったのだ。

入れ子になっていて、

  • index.htmlとは layout.htmlに対する各レイアウトブロックの定義ファイル、あるいは事前処理ファイルである。
  • layout.htmlとは、index.htmlで定義されたもののレンダリングや各種変数出力を含む最終htmlファイルである。

ビューを拡張する

ようやくdocument 3rdの「5.1-ビュー内の関数」「5.2-ビュー内のブロック」(*7)を実感的に理解できた。以下書いてみる。

関数拡張

上で

index.htmlの冒頭で上記layout.htmlを拡張するよ、と宣言してやる。

と書いたが、ビューファイルのなかで{{}}を使ったPython命令を書いて拡張元(layout.html)に反映させたい場合、拡張先(index.html)ファイルの中では{{'extend layout.html'}}の前に書いてやる必要がある。

index.htmlで
{{def doraemon():}}
BOKU DOREMON
{{return}}
{{'extend layout.html'}}
layout.htmlで
{{doraemon()}}

とすればBOKU DORAEMON文字列がhtmlとしてレンダリングされる。

なおこの場合は、response.write(x)を表わす=xではなく単なる関数標記なので等号=は書いてはいけない。

ところで、デフォルトで作成されるindex.htmlファイルの冒頭が

{{left_sidebar_enabled,right_sidebar_enabled=False,True}}
{{extend 'layout.html'}}

となっているのも、変数left_sidebar_enabledとright_sidebar_enabledを定義していて、拡張元のlayout.htmlの中でif文で表示ON/OFFを行なっているからである。

block文

これにたいしてテンプレート言語としての{{block}}...{{end}} 文はPythonではないのでextend宣言後に書ける。yiiで言うところのpartials renderである(cf Yii その1)。

index.htmlで
{{extend 'layout.html'}}
**snip**
{{block doraemon}}
BOKU DORAEMON
{{end}}
layout.htmlで
{{block doraemon}}
WATASHI DORAMI
{{end}}

とすればBOKU DORAEMON となる。doraemonが定義されていなければWATASHI DORAMIとなる。

def index():二行目:return dict..

ようやくdef index():の二行目に到達した(´・ω・`)

return dict(message=T('Hello World'))

といっても、これは「辞書」オブジェクトとして書かれておりmessageがビューへの変数として渡されていることはわかる。

return の意味

さてこのreturnの挙動チェック。

  • コメントアウトすると・・・Noneという文字列表示(html化されていない)
  • returnのみ・・・同上
  • return dict()・・・・ページ表示される
  • return dict(something='anything')・・・ページ表示される。かつ、ビューで使われていない変数がsomething:anything形式で表示される

ふむ。


国際化対応 T

ここでのもうひとつのポイントはTである。Tは国際化対応のAPI(*8)。

Tはapplications/*/languagues/配下の*.pyと対応関係にある。日本語対応ファイルはないので

cp fr-fr.py jp.py

して'Hello World'のみを書き換えてやった。

・・・・がうまくいかない・・・languagesフォルダに入ってる言語を指定してもダメ。。。

やったこと。

models/0.pyを作り(*9)、

T.set_current_languages('zh-tw')(*10)
T.force('zh-tw')
T.lazy = False

0.pyが読まれているのは確実。なぜだ・・・。とりあえず先に進もう。

URLのリライト

ビューに関するページでURLリライトについてメモるのはおかしいかもしれないが、順番に確認していると次はURLリライトということになる。

URLリクエストと controller view

web2pyの場合、URL requestがあるとまずあらかじめmodel処理されたのちにコントローラーにリクエストがわたるようになっている(これがMVC Fwの一般的なかたちかどうかは素人なのでわからん)。

model理解は後に行うことにして、URLリクエストとcontroller/viewの関係をまとめておく。ドキュメントで言えばchap.4.2 ディスパッチとchap.4.15のURLリライトあたりを読む。

ルーティング(リライト)は、web2pyルート/routes.example.pyをroutes.pyとして定義する。

routes.pyでデフォルトアプリ設定

とりあえず

default_application = 'init' # ordinarily set in base routes.py

としてinitアプリをデフォルトにする。

デフォルトアプリはそのままではURLルートのみを「正常」表示する。

これで

http://web2py.radio-age.com/

でinitアプリが読み込まれる。

ところが

http://web2py.radio-age.com/index.html

とすれば

index.html access invalid


実際の「ルート」は、デフォルトでは以下の様だからである。

index.html access valid


単一ホスト(アプリ)のみで極めて単純に動作するURLリライト

このケースは、あまりにも余りでFWを使う意味が見出せないというご批判もあるだろうが、自分的にはこれが出来れば十分なので、以下単純なリライトを試してみた。

http://web2py.radio-age.com

これに、staticファイル以外すべて/init/default/indexにマッピングする。

routes.pyで

routes_in = (
  (r'/init/static/(?P<file>[\w./-]+)', r'/init/static/\g<file>'),
  ('$anything','/init/default/index'),
)

あるいは二行目にも正規表現を使って

routes_in = (
  (r'/init/static/(?P<file>[\w./-]+)', r'/init/static/\g<file>'),
  ('/(?P<any>.*)','/init/default/index') ,
)

とすればいい。

実際のリクエストを確認してみる。

web2pyは、request.envにリクエストuriオブジェクトをrequest.env.web2py_original_uri名で格納している。

init/default.pyのdef index():を次のようにする。

def index():

response.flash = "Welcome to web2py!!!!!!!!!"

return dict(message=request.env.web2py_original_uri)

request.env.web2py_original_uri check


( `д´)b オッケー!

ここらへんまではわかりやすい。

さてそれではroutes_outはどのような機能をもっていて、どのようなことに使うのかが今ひとつぴんと来ない。でもまあ、いまのところ、わからないまま、そっとしておくことにする。

request.envについて

ドキュメントchap 4.6 requestに記載の表があって、利用できるrequestインスタンスを見ることができるが、そこに掲載されているものが全部ではない。詳しくはprint request.envで一覧を取得することになる。

上記のrequest.env.web2py_original_uriもこの一覧からわかった。

no title


なおこれら変数はgluon/rewrite.pyで定義されているようだ。

grep -ni web2py_original_uri rewrite.py
509:    e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
804:        self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (self.query and ('?' + self.query) or '')

補足:Python 正規表現

なおPythonの正規表現構文についてはhttp://www.kt.rim.or.jp/~kbk/regex/pythonre.html

上の?P<any>は、グループ名を表し直後からの文字列をグループ化する。アクセスには\g<any>とすればいい。

名前付きグループについてはhttp://toucha.blog.shinobi.jp/Entry/6/が勉強になる。







*1: responseはrequestオブジェクトとならぶgulon.storage.Storageインスタンスということになっている。 document 3rd.,chap. 4.6-4.7

*2: ibid.,p.142(chap. 4.7)

*3: idid.,p.174

*4: ibid.,p.144

*5: ビューの場合、インデントはhtmlのインデントとして認識される。ibid.,pp.174-5

*6: ibid.,p.176

*7: ibid.,pp.204-206

*8: ibid.,p.137(chap. 4.5) and pp.152-153(chap. 4.12)

*9: モデルはcontroller以前に呼び出され、かつmodels/*pyをすべて読み込むみたいだがこの挙動は今の段階(20120229)ではよくわかりません。

*10: ibid.,pp.153-154。なお、日本語訳現行版はset_current_languageとTypoがあるので注意。

this file --> last modified:2012-04-18 15:40:24