ビューを定義する¶
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/.
setup.py
ファイルに依存関係を宣言する¶
私たちのアプリケーションのビューのコードはオリジナルの “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
""",
)
|
(ハイライトされた行は変更が必要な箇所です)
setup.py develop
を実行する¶
新しいソフトウェア依存関係が追加されたので、新たに追加された依存パッケージ
を登録および取得するために 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
views.py
ファイルを変更する¶
大幅な変更をするときが来ました。
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 関数を
追加しました:
view_wiki()
- wiki 自体を表示します。それはルート URL の上で答えます。view_page()
- 個々のページを表示します。add_page()
- ページの追加を可能にします。edit_page()
- ページの編集を可能にします。
各々について簡潔に記述し、結果の views.py
ファイルを後で示します。
Note
views.py
というファイル名に特別な意味はありません。プロジェクト
はそのコードベース全体で任意の名前のファイル中に多くのビューを持つこ
とができます。ビューを実装するファイルは多くの場合 view
というファ
イル名を持っています (もしくは views
という名前の アプリケーショ
ンパッケージの中の Python サブパッケージに存在しています) が、これは
単なる慣例です。
ビュー関数 view_wiki
¶
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
¶
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
¶
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
¶
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
拡張子を持たなければなりません。
view.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()
によって使用されます。以下の内容が含まれています:
- ビューによって提供される
content
値で置き換えられるdiv
要素 (45-47行目)。content
には HTML が含まれます。そのためエスケープ (つまり “>” を “>” にするような変更) を防ぐためにstructure
キーワードが使われています。 - 表示されているページに対して
edit_page
ビューを呼び出す “edit” URL を指すリンク (49-51行目)。
edit.pt
テンプレート¶
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()
によって使用されます。これは以下のようなフォームを含む
ページを表示します。
- レンダリングされた時に既存のページのデータで埋められる、
10列60行の “body” という名前の
textarea
フィールド (46-47行目)。 - “form.submitted” という名前の送信ボタン (48行目)。
このフォームは、ビューによって提供される “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
にルートを追加する¶
__init__.py
ファイルにはアプリケーションにルートを追加するための
pyramid.config.Configurator.add_route()
の呼び出しが含まれています。
最初に、テンプレート (訳注: scaffold) によって作成された 'home'
という
名前を使用している既存のルートを取り除きます。これは単なる例で、この
アプリケーションには関係ありません。
その後、 add_route
を4回呼び出す必要があります。なお、これらの宣言
の 順番 は非常に重要です。 ルート
宣言はそれらが __init__.py
ファイルの中で見つかった順番でマッチされます。
/
というパターン (ルート URL の意味) からview_wiki
という名前の ルートにマッピングする宣言を追加します。それはview_wiki
ビュー 関数に付けられたroute_name='view_wiki'
を示す@view_config
に よって、ビュー callableview_wiki
にマッピングします。
/{pagename}
というパターンをview_page
という名前のルートに マッピングする宣言を追加します。これはページに対する通常のビューです。 それはview_page
ビュー関数に付けられたroute_name='view_page'
を示す@view_config
によって、ビュー callableview_page
にマッピングします。
/add_page/{pagename}
というパターンをadd_page
という名前の ルートにマッピングする宣言を追加します。これは新しいページを追加する ためのビューです。それはadd_page
ビュー関数に付けられたroute_name='add_page'
を示す@view_config
によって、ビュー callableadd_page
にマッピングします。
/{pagename}/edit_page
というパターンをedit_page
という名前の ルートにマッピングする宣言を追加します。これはページの編集ビューです。 それはedit_page
ビュー関数に付けられたroute_name='edit_page'
を示す@view_config
によって、ビュー callableedit_page
にマッピングします。
編集の結果として __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 を開き、結果が予想通りであることをチェックしてください:
http://localhost:6543
にブラウザでアクセスするとview_wiki
ビューが呼び出されます。このビューは常に FrontPage page オブジェクトのview_page
ビューにリダイレクトします。
http://localhost:6543/FrontPage
にブラウザでアクセスすると、 フロントページオブジェクトのview_page
ビューが呼び出されます。
http://localhost:6543/FrontPage/edit_page
にブラウザでアクセス すると、フロントページ page オブジェクトの edit ビューが呼び出されます。
http://localhost:6543/add_page/SomePageName
にブラウザでアクセス すると、ページの add ビューが呼び出されます。
- エラーを発生させるために
http://localhost:6543/foobars/edit_page
に アクセスしてください。それはNoResultFound: No row was found for one()
エラーを発生させます。 pyramid_debugtoolbar によって提供された インタラクティブトレースバック機能が見られるでしょう。