Pyramid アプリケーションにおける view callable は、 典型的には request という名前の 1 つのパラメータを受け取るシンプル な Python 関数です。ビュー callable は response オブジェクトを返す ことが想定されます。
ルートマッチの結果として呼び出される全てのビューに渡される request オブ ジェクトは、 route 文の パターン によって URL に placed into された 要素が格納されている matchdict という名前の属性を持っています。 例えば、 __init__.py の中で行っている pyramid.config.Configurator.add_route() の呼び出しに {one}/{two} というパターンがあり、 http://example.com/foo/bar という URL が呼び出された場合、このパターンにマッチして、 'foo' という 値が 'one' というキーに、 'bar' という値が 'two' というキーに 割り当てられた matchdict という辞書が request に付け加えられてビューに 渡されます。
このチュートリアルステージのソースコードを以下の場所で閲覧することができます。 http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/views/.
私たちのアプリケーションのビューのコードはオリジナルの “tutorial” アプリケーションの依存関係にはないパッケージに依存しています。オリジナルの “tutorial” アプリケーションは pcreate によって生成され、私たちの カスタムアプリケーションに必要なものを知りません。
tutorial パッケージの setup.py ファイルでこの依存関係を setup 関数内の requires パラメータに割り当てることによって docutils パッケージへの依存を追加する必要があります。
tutorial/setup.py ファイルを開き、以下のように編集してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = [
'pyramid',
'SQLAlchemy',
'transaction',
'pyramid_tm',
'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
'docutils',
]
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
"Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
author='',
author_email='',
url='',
keywords='web wsgi bfg pylons pyramid',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
install_requires=requires,
entry_points="""\
[paste.app_factory]
main = tutorial:main
[console_scripts]
initialize_tutorial_db = tutorial.scripts.initializedb:main
""",
)
|
(ハイライトされた行は変更が必要な箇所です)
新しいソフトウェア依存関係が追加されたので、新たに追加された依存パッケージ を登録および取得するために tutorial パッケージのルート内で python setup.py develop を再実行する必要があります。
現在のワーキングディレクトリがプロジェクトのルート (seetup.py のある ディレクトリ) であることを確認して、次のコマンドを実行してください:
UNIX の場合:
$ cd tutorial
$ ../bin/python setup.py develop
Windows の場合:
c:\pyramidtut> cd tutorial
c:\pyramidtut\tutorial> ..\Scripts\python setup.py develop
このコマンドの実行に成功すると、コンソールに次のような出力が行われるでしょう:
Finished processing dependencies for tutorial==0.0
大幅な変更をするときが来ました。 tutorial/tutorial/views.py ファイルを開き、以下のように編集してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import re
from docutils.core import publish_parts
from pyramid.httpexceptions import (
HTTPFound,
HTTPNotFound,
)
from pyramid.view import view_config
from .models import (
DBSession,
Page,
)
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@view_config(route_name='view_wiki')
def view_wiki(request):
return HTTPFound(location = request.route_url('view_page',
pagename='FrontPage'))
@view_config(route_name='view_page', renderer='templates/view.pt')
def view_page(request):
pagename = request.matchdict['pagename']
page = DBSession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
exists = DBSession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
return '<a href="%s">%s</a>' % (view_url, word)
else:
add_url = request.route_url('add_page', pagename=word)
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.route_url('edit_page', pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url)
@view_config(route_name='add_page', renderer='templates/edit.pt')
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url)
@view_config(route_name='edit_page', renderer='templates/edit.pt')
def edit_page(request):
pagename = request.matchdict['pagename']
page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
page=page,
save_url = request.route_url('edit_page', pagename=pagename),
)
|
(ハイライトされた行は変更が必要な箇所です)
alchemy scaffold を使ってプロジェクトを生成した時に加えられた my_view ビュー関数とそのデコレータを取り除きました。それは単なる 例で、このアプリケーションには適切ではありません。
そして、 views.py モジュールに4つの view callable 関数を 追加しました:
各々について簡潔に記述し、結果の views.py ファイルを後で示します。
Note
views.py というファイル名に特別な意味はありません。プロジェクト はそのコードベース全体で任意の名前のファイル中に多くのビューを持つこ とができます。ビューを実装するファイルは多くの場合 view というファ イル名を持っています (もしくは views という名前の アプリケーショ ンパッケージの中の Python サブパッケージに存在しています) が、これは 単なる慣例です。
view_wiki() は wiki のルート URL に対してリクエストが行われたときに 呼び出される default view です。それは常に “FrontPage” へのパス を表す URL にリダイレクトします。
1 2 3 4 | @view_config(route_name='view_wiki')
def view_wiki(request):
return HTTPFound(location = request.route_url('view_page',
pagename='FrontPage'))
|
view_wiki() は pyramid.httpexceptions.HTTPFound クラスの インスタンスを返します (それは pyramid.response.Response のように pyramid.interfaces.IResponse インターフェースを実装した インスタンスです) 。
それは FrontPage ページの URL (例えば http://localhost:6543/FrontPage) を構築するために pyramid.request.Request.route_url() APIを使用します。そして、 それを HTTPFound レスポンスの “location” として使用し、 HTTP リダイレクト を生成します。
view_page() は wiki の単一のページを表示するために使用されます。 それは (Page モデルオブジェクトの data 属性として保存された) ReStructuredText で書かれたページの内容を HTML としてレンダリング します。その後、コンパイル済み正規表現を使用して、レンダリング済み HTML 中の各 WikiWord 参照を HTML アンカーに置換します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @view_config(route_name='view_page', renderer='templates/view.pt')
def view_page(request):
pagename = request.matchdict['pagename']
page = DBSession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
exists = DBSession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
return '<a href="%s">%s</a>' % (view_url, word)
else:
add_url = request.route_url('add_page', pagename=word)
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.route_url('edit_page', pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url)
|
check() 関数は wikiwords.sub の最初の引数として使用されます。 それはコンテンツ内で見つかった各 WikiWord のマッチに対して値を提供するために 呼び出す必要があると指示しています。もし、マッチした WikiWord 名を持つ ページが wiki にすでに含まれている場合、 check() は置換する値として view リンクを生成してそれを返します。もし、マッチした WikiWord 名を持つ ページがまだ wiki に含まれていない場合、関数は置換する値として “add” リンクを生成してそれを返します。
この結果、 content 変数は現在のページオブジェクトの内容に基づいて WikiWord への様々な view または add リンクを含む完全な HTML 形式に なっています。
その後、 edit URL を生成し (テンプレートの中よりもここで行うほうが簡単だ からです)、いくつかの引数を含む辞書を返します。このビューが (response オブジェクトではなく) 辞書を返すという事実は、 テンプレートをレンダリングするためにビュー設定で関連付けられた renderer を使用する必要があることを Pyramid に知らせます。 この場合、レンダリングされるテンプレートは view_page() に適用された @view_config デコレータが示すように templates/view.pt テンプレート になります。
add_page() は、まだシステム内のページとして表されていない WikiWord をユーザがクリックしたときに呼び出されます。 view_page ビュー内の check 関数がこのビューへの URL を生成します。 add_page はまた、ページオブジェクトを追加するときに生成される フォームのハンドラとして機能します。 add_page() ビューに渡される リクエストの matchdict 属性は、 URL の構築とモデルオブジェクトの 検索に必要な値を含んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 | @view_config(route_name='add_page', renderer='templates/edit.pt')
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(pagename, body)
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
page = Page('', '')
return dict(page=page, save_url=save_url)
|
matchdict は追加したいページの名前に一致する 'pagename' キーを 持つことになります。もし、 add ビューが例えば http://localhost:6543/add_page/SomeName 経由で呼び出された場合、 matchdict の中の 'pagename' の値は 'SomeName' になります。
ビューの実行がフォーム送信の結果で ある 場合 (つまり評価式 'form.submitted' in request.params が True の場合)、 フォームデータからページの本体を取り出し、このページの本体と matchdict['pagename'] から取り出した名前から Page オブジェクトを 生成し、 DBSession.add を使ってそれをデータベースに保存します。 その後、新しく作成したページの view_page ビューにリダイレクトします。
ビューの実行がフォーム送信の結果では ない 場合 (つまり評価式 'form.submitted' in request.params が False の場合) 、ビュー callable はテンプレートをレンダリングします。そのために、テンプレート のレンダリング時にフォームのポスト URL として使用される “save url” を 生成します。ここでは手を抜いて、同じテンプレート (templates/edit.pt) を追加ビューだけでなく、ページ編集ビューに使用することにします。 page として公開される なんらかの ページオブジェクトを持っていると いう編集フォームの要求を満たすために、ダミー Page オブジェクトを生成します。 そして、 Pyramid はレスポンスとしてこのビューに関連付けられている テンプレートをレンダリングします。
edit_page() は、ユーザーが view フォームの “Edit this Page” ボタン をクリックしたときに呼び出されます。 edit_page は編集フォームを レンダリングしますが、レンダリングしたフォームのハンドラとしても機能します。 edit_page ビューに渡されるリクエストの matchdict 属性は、ユーザー が編集するページの名前に一致する 'pagename' キーを持つことになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @view_config(route_name='edit_page', renderer='templates/edit.pt')
def edit_page(request):
pagename = request.matchdict['pagename']
page = DBSession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
DBSession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
page=page,
save_url = request.route_url('edit_page', pagename=pagename),
)
|
ビューの実行がフォーム送信の結果で ある 場合 (つまり評価式 'form.submitted' in request.params が True の場合)、 リクエストパラメータの body 要素を取得し、 page オブジェクトの data 属性としてセットします。次に wiki ページの view_page ビューにリダイレクトします。
ビューの実行がフォーム送信の結果では ない 場合 (つまり評価式 'form.submitted' in request.params が False の場合)、 page オブジェクトと、生成されたフォームのアクションとして使用するための save_url を渡して単に編集フォームをレンダリングします。
追加した view_page, add_page, edit_page ビューは template を参照しています。各テンプレートは Chameleon ZPT テンプレートです。これらのテンプレートは tutorial パッケージの templates ディレクトリの中にあります。 Chameleon テンプレートとして 認識されるためには .pt 拡張子を持たなければなりません。
tutorial/tutorial/templates/view.pt を作成して次の内容を追加してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<title>${page.name} - Pyramid tutorial wiki (based on
TurboGears 20-Minute Wiki)</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon"
href="${request.static_url('tutorial:static/favicon.ico')}" />
<link rel="stylesheet"
href="${request.static_url('tutorial:static/pylons.css')}"
type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet"
href="${request.static_url('tutorial:static/ie6.css')}"
type="text/css" media="screen" charset="utf-8" />
<![endif]-->
</head>
<body>
<div id="wrap">
<div id="top-small">
<div class="top-small align-center">
<div>
<img width="220" height="50" alt="pyramid"
src="${request.static_url('tutorial:static/pyramid-small.png')}" />
</div>
</div>
</div>
<div id="middle">
<div class="middle align-right">
<div id="left" class="app-welcome align-left">
Viewing <b><span tal:replace="page.name">Page Name
Goes Here</span></b><br/>
You can return to the
<a href="${request.application_url}">FrontPage</a>.<br/>
</div>
<div id="right" class="app-welcome align-right"></div>
</div>
</div>
<div id="bottom">
<div class="bottom">
<div tal:replace="structure content">
Page text goes here.
</div>
<p>
<a tal:attributes="href edit_url" href="">
Edit this page
</a>
</p>
</div>
</div>
</div>
<div id="footer">
<div class="footer"
>© Copyright 2008-2011, Agendaless Consulting.</div>
</div>
</body>
</html>
|
このテンプレートは、単一の wiki ページを表示するために view_page() によって使用されます。以下の内容が含まれています:
tutorial/tutorial/templates/edit.pt を作成して次の内容を追加してください:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<title>${page.name} - Pyramid tutorial wiki (based on
TurboGears 20-Minute Wiki)</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<meta name="keywords" content="python web application" />
<meta name="description" content="pyramid web application" />
<link rel="shortcut icon"
href="${request.static_url('tutorial:static/favicon.ico')}" />
<link rel="stylesheet"
href="${request.static_url('tutorial:static/pylons.css')}"
type="text/css" media="screen" charset="utf-8" />
<!--[if lte IE 6]>
<link rel="stylesheet"
href="${request.static_url('tutorial:static/ie6.css')}"
type="text/css" media="screen" charset="utf-8" />
<![endif]-->
</head>
<body>
<div id="wrap">
<div id="top-small">
<div class="top-small align-center">
<div>
<img width="220" height="50" alt="pyramid"
src="${request.static_url('tutorial:static/pyramid-small.png')}" />
</div>
</div>
</div>
<div id="middle">
<div class="middle align-right">
<div id="left" class="app-welcome align-left">
Editing <b><span tal:replace="page.name">Page Name Goes
Here</span></b><br/>
You can return to the
<a href="${request.application_url}">FrontPage</a>.<br/>
</div>
<div id="right" class="app-welcome align-right"></div>
</div>
</div>
<div id="bottom">
<div class="bottom">
<form action="${save_url}" method="post">
<textarea name="body" tal:content="page.data" rows="10"
cols="60"/><br/>
<input type="submit" name="form.submitted" value="Save"/>
</form>
</div>
</div>
</div>
<div id="footer">
<div class="footer"
>© Copyright 2008-2011, Agendaless Consulting.</div>
</div>
</body>
</html>
|
このテンプレートは wiki ページの追加と編集のために add_page() と edit_page() によって使用されます。これは以下のようなフォームを含む ページを表示します。
このフォームは、ビューによって提供される “save_url” 引数に POST 送信されます (45行目)。ビューは body と form.submitted の値を使います。
Note
これらのテンプレートでは、いずれのビューでもその辞書の中で返していない request オブジェクトを利用しています。 request はテンプレート内で “デフォルトで” 利用可能ないくつかの名前のうちの1つです。レンダラーとして Chameleon テンプレートを使用しているときにテンプレート内でデフォルトで 利用可能な他の名前についての情報は *.pt または *.txt: Chameleon テンプレートレンダラー を参照してください。
これらのテンプレートは pylons.css という名前の静的アセットを 参照しています。このファイルはプロジェクトを作成した時点で提供されているので、 パッケージの static ディレクトリ内にこのファイルを作成する必要は ありません。このファイルはこのガイドの本体内で置き換えるには少し長すぎますが、 オンライン. で 利用可能です。
この CSS ファイルは __init__.py ファイルの中で行なった add_static_view の宣言の呼び出しのために、例えば http://localhost:6543/static/pylons.css を介してアクセスされます。 任意の数と種類の静的アセットはこのディレクトリ (またはサブディレクトリ) に配置することができ、 URL によって参照するか、便利なメソッド static_url を使用して参照します。例えばテンプレート内で request.static_url('{{package}}:static/foo.css') のように使用します。
__init__.py ファイルにはアプリケーションにルートを追加するための pyramid.config.Configurator.add_route() の呼び出しが含まれています。 最初に、テンプレート (訳注: scaffold) によって作成された 'home' という 名前を使用している既存のルートを取り除きます。これは単なる例で、この アプリケーションには関係ありません。
その後、 add_route を4回呼び出す必要があります。なお、これらの宣言 の 順番 は非常に重要です。 ルート 宣言はそれらが __init__.py ファイルの中で見つかった順番でマッチされます。
編集の結果として __init__.py ファイルはこのようになるはずです:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from .models import (
DBSession,
Base,
)
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
Base.metadata.bind = engine
config = Configurator(settings=settings)
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('view_page', '/{pagename}')
config.add_route('add_page', '/add_page/{pagename}')
config.add_route('edit_page', '/{pagename}/edit_page')
config.scan()
return config.make_wsgi_app()
|
(ハイライトされた行は変更が必要な箇所です)
ようやくブラウザでアプリケーションを実行することができます (アプリケーションの起動 参照) 。ブラウザを起動して次の 各 URL を開き、結果が予想通りであることをチェックしてください: