15: View Classesの詳細(15: More With View Classes)¶
ビューをクラスにグループ化し、構成、状態、およびロジックを共有します。
背景(Background)¶
より挑戦的なWebアプリケーションの構築を支援するという使命の一環として、Pyramidではビューやビュークラスにさらに多くの機能を提供しています。
PyramidのドキュメントではビューをPythonの「呼び出し可能」として説明しています。この呼び出し可能関数は、関数、aを持つオブジェクト __call__
、またはPythonクラスです。この最後のケースで``@view_config`` はクラスのメソッドを装飾してして、 configurator をビューとしてクラスメソッドを登録できます。
当初はビューは単純で自由な関数でした。多くの場合、同じデータを表示または操作するさまざまな方法や複数の操作を処理するREST APIなど、さまざまなビューが関連しています。これらをまとめて view class としてまとめると意味があります。
- グループビュー。
- いくつかの繰り返しのデフォルトを一元化する。
- ステートやヘルパーを共有します。
Pyramidビューにはリクエストメソッド、フォームパラメータなどの要素に基づいて、リクエストに一致するビューを決定する view predicates があります。これらのpredicatesは多くの柔軟性の軸を提供します。
次の4つの操作の簡単な例を示します。フォームにつながるホームページを表示して、変更を保存して削除ボタンを押します。
目的(Objectives)¶
- 関連するビューをビュークラスにグループ化します。
- クラスレベル
@view_defaults
で構成を一元化します。 - リクエストデータに基づいて、1つのルート/URLを複数のビューにディスパッチします。
- ビュークラスをかいしてビューとテンプレート間の状態とロジックを共有します。
手順(Steps)¶
最初に前の手順の結果をコピーします:
$ cd ..; cp -r templating more_view_classes; cd more_view_classes $ $VENV/bin/pip install -e .
more_view_classes/tutorial/__init__.py
のルートにはいくつかの置換パターンが必要です:1 2 3 4 5 6 7 8 9 10
from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy/{first}/{last}') config.scan('.views') return config.make_wsgi_app()
more_view_classes/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
from pyramid.view import ( view_config, view_defaults ) @view_defaults(route_name='hello') class TutorialViews(object): def __init__(self, request): self.request = request self.view_name = 'TutorialViews' @property def full_name(self): first = self.request.matchdict['first'] last = self.request.matchdict['last'] return first + ' ' + last @view_config(route_name='home', renderer='home.pt') def home(self): return {'page_title': 'Home View'} # Retrieving /howdy/first/last the first time @view_config(renderer='hello.pt') def hello(self): return {'page_title': 'Hello View'} # Posting to /howdy/first/last via the "Edit" submit button @view_config(request_method='POST', renderer='edit.pt') def edit(self): new_name = self.request.params['new_name'] return {'page_title': 'Edit View', 'new_name': new_name} # Posting to /howdy/first/last via the "Delete" submit button @view_config(request_method='POST', request_param='form.delete', renderer='delete.pt') def delete(self): print ('Deleted') return {'page_title': 'Delete View'}
メインビューにはテンプレート
more_view_classes/tutorial/home.pt
が必要です:<!DOCTYPE html> <html lang="en"> <head> <title>Quick Tutorial: ${view.view_name} - ${page_title}</title> </head> <body> <h1>${view.view_name} - ${page_title}</h1> <p>Go to the <a href="${request.route_url('hello', first='jane', last='doe')}">form</a>.</p> </body> </html>
more_view_classes/tutorial/hello.pt
の前のセクションの他のビューについては:<!DOCTYPE html> <html lang="en"> <head> <title>Quick Tutorial: ${view.view_name} - ${page_title}</title> </head> <body> <h1>${view.view_name} - ${page_title}</h1> <p>Welcome, ${view.full_name}</p> <form method="POST" action="${request.current_route_url()}"> <input name="new_name"/> <input type="submit" name="form.edit" value="Save"/> <input type="submit" name="form.delete" value="Delete"/> </form> </body> </html>
編集ビューもあります。このビューにはテンプレート
more_view_classes/tutorial/edit.pt
が必要です:<!DOCTYPE html> <html lang="en"> <head> <title>Quick Tutorial: ${view.view_name} - ${page_title}</title> </head> <body> <h1>${view.view_name} - ${page_title}</h1> <p>You submitted <code>${new_name}</code></p> </body> </html>
最後に、ビューのテンプレート
more_view_classes/tutorial/delete.pt
を削除します:<!DOCTYPE html> <html lang="en"> <head> <title>Quick Tutorial: ${page_title}</title> </head> <body> <h1>${view.view_name} - ${page_title}</h1> </body> </html>
more_view_classes/tutorial/tests.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
import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['page_title']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'TutorialViews - Home View', res.body)
テストを実行します:
$ $VENV/bin/py.test tutorial/tests.py -q .. 2 passed in 0.40 seconds
Pyramidアプリケーションを以下のように実行します:
$ $VENV/bin/pserve development.ini --reload
ブラウザで http://localhost:6543/howdy/jane/doe を開いてみます。「Save」ボタンと「Delete」ボタンをクリックしてコンソールウィンドウで出力を確認します。
分析(Analysis)¶
ご覧のとおり4つのビューは論理的にグループ化されています。具体的には以下の通りです:
- http://localhost:6543/ にある
home
ビューをhello
ビューへのクリック可能なリンクと共に利用できます。 /howdy/jane/doe
に行くと2番目のビューが返却されます。 このURLは、オプションの@view_defaults
を使用して一元に設定するhello
ルートにマップされます。POST
メソッドを使用してフォームを送信すると3番目のビューが返却されます。 このルールは、そのビューの@view_config
で指定されています。<input type="submit" name="form.delete" value="Delete"/>
のようなボタンをクリックすると、4番目のビューが返却されます
このステップでは以下の情報を基準として、、どのビューを使用するかを決定する方法を説明します。
- HTTPリクエスト(
GET
,POST
,など) の方法 - リクエスト(送信されたフォームフィールド名)内のパラメータ情報
また、 @view_defaults
を使用してビュー構成の一部をクラスレベルに一元化させて、あるビューでそのビューのデフォルトをオーバーライドします。 最後にこの共通性をビュー間で共有することによってビュークラス内で機能させます
TutorialViews.__init__
内に割り当てられたステート- 計算した値
これらは、ビューメソッドとテンプレート(たとえば、${view.view_name}
と ${view.full_name}
)の両方で使用できます 。
テンプレートとしてURLの生成方法を変更しました。以前は以下ののようなURLをハードコードしていました。
<a href="/howdy/jane/doe">Howdy</a>
home.pt
では以下のように切り替えました:
<a href="${request.route_url('hello', first='jane',
last='doe')}">form</a>
Pyramidには、柔軟でエラーのない方法でURLを生成するための豊富な機能があります。
エクストラクレジット(Extra credit)¶
- テンプレートが
${view.full_name}
を行い、${view.full_name()}
を実行する必要がないのはなぜですか? - 「編集」ビューと「削除」ビューはどちらも「POST」リクエストを受信します。 「編集」ビューの設定で「削除」で使用されたPOSTが検出されないのはなぜですか
- 「full_name」にPythonの
@property
を使用しました。テンプレートやビューコードでこれを何度も参照すると毎回これを再計算します。Pyramidはプロパティの初期計算をキャッシュするものを提供していますか? - 同じビューに複数のルートを関連付けできますか?
request.route_path
があります。 これはrequest.route_url
とどう違うのですか?