pyramidでpyramid-deformを使ってみる

前回の記事で簡単なdeform利用はできるようになりました。
pyramidにはpyramid-deformと呼ばれるdeform連携パッケージが存在するので今回はpyramid-deformを利用してのdeform利用を試します。

1. リソースの定義

自分はpython歴が浅いのでpython界で常道なのかは知らないですが、参考にしたサイトの書き方を見る限りpyramidはリソースという関数を定義してそこでリクエストパラメータをrequestオブジェクトに割り当てる事ができるようです。
add_routeの第三引数で定義したform_indexの戻り値がrequestオブジェクトのcontextに代入されます。

// 「/form」へリクエスト発生すると動作する処理
$ vi myform/resources.py
def form_index(request):
    values = { 'name' : '', 'shoe_size' : 0 }
    if 'name' in request.POST:
        values['name'] = request.POST['name']

    if 'shoe_size' in request.POST:
        values['shoe_size'] = request.POST['shoe_size']
    return values

// 上記で定義した「form_index」をadd_routeで関連付
$ vi myform/__init__.py
from pyramid.config import Configurator
from myform.resources import form_index

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('form_index', '/form', factory=form_index)
    config.scan()

    return config.make_wsgi_app()

2 viewの変更

前回作成したviewを以下のように修正します。
・クラスにルーティング設定のデコレータを移動
・save_successにPOSTが成功した場合の処理を定義
・appstructにリクエストデータをスキーマクラスに紐づけする処理を定義

$ vi myform/views.py
from pyramid.view   import view_config
from pyramid_deform import FormView
from myform.schema import Person

@view_config(route_name='form_index', renderer="templates/form.jinja2")
class PageEditView(FormView):
    schema = Person()
    buttons = ('save',)
    form_options = (('formid', 'pyramid-deform'),
                    ('method', 'POST'))

    def save_success(self, appstruct):
        context = self.request.context
        context['name'] = appstruct['name']
        context['shoe_size'] = appstruct['shoe_size']
        return None


    def appstruct(self):
        context = self.request.context
        return {'name': context['name'],
                'shoe_size': context['shoe_size']}

3 templateの変更

contextでデータにアクセスするように修正します。

<html>
  <head>
    <title>Projector</title>
  </head> 
  <body>
    <h2>Hello Form!</h2>
    <div >{{ form| safe }}</div>
    <p>Valid form values: {{context.name}} and {{context.shoe_size}}.</p>
  </body>
</html>

あとはdeformの使い方を調べればある程度のpyramidでのdeformマスターにはなれると思われます。

pyramidでdeformを使ってみる

以前の記事でmongoengineの使い方が解ってきました。今度はdeformを使ってのフォームの作成を試してみました。
最終的にはpyramid-deformを利用したフォーム連携を目指しますが、まずはdeformを素で使ったシンプルなフォームを作成してからpyramid-deformを利用に進みます。

1. deformのインストール

pyramid-deformをインストールします。deform、colanderも一緒にインストールされます。

$ pip install pyramid-deform

2. schemaの作成

フォームの構造はスキーマと呼ばれるクラスで定義します。
今回の例ではnameという文字列型の項目とshoe_sizeという数値型の項目を定義してます。

$ vi myform/schema.py
import colander

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    shoe_size = colander.SchemaNode(
        colander.Integer(),
        missing = 0,
    )

3. viewの作成

「/form」でアクセス可能なviewを用意します。

$ vi myform/__init__.py
from pyramid.config import Configurator

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('form_index', '/form')
    config.scan()

    return config.make_wsgi_app()

$ vi myform/views.py
from pyramid.view   import view_config
from deform import Form
from deform import ValidationFailure
from myform.schema import Person

class ProjectorViews(object):
    def __init__(self, request):
        self.request = request

    @view_config(route_name='form_index', renderer="templates/form.jinja2")
    def site_view(self):
        schema = Person()
        myform = Form(schema, buttons=('submit',))

        if 'submit' in self.request.POST:
            controls = self.request.POST.items()
            try:
                appstruct = myform.validate(controls)
            except ValidationFailure, e:
                return {'form':e.render(), 'values': False}

            values = {
                "name": appstruct['name'],
                "shoe_size": appstruct['shoe_size'],
                }
            return {"form": myform.render(), "values": values}

        return {"form": myform.render(), "values": None}

4. templateの作成

テンプレートではPOSTされたら、その値をフォームの下に表示してます。

$ vi myform/templates/form.jinja2
<html>
<head>
  <title>Projector</title>
</head>
<body>
<h2>Hello Form!</h2>
<div >{{ form| safe }}</div>
<p>Valid form values: {{values.name}} and {{values.shoe_size}}.</p>
</body>
</html>

5. 確認

サーバを起動してこんな画面が表示されたらまずはdeform理解の第一歩に成功です。

f:id:taka512:20130928194035p:plain

PyCon APAC 2013に行ってきた(2日目)

前日朝までFF14をやってましたが、エオルゼアのご加護のおかげでなんとか遅刻せずに2日目に参加できました。

PofEAA in SQLAlchemy

資料はなかったので動画が以下です。
http://www.youtube.com/watch?v=nClPtRhlDxs

SQLAlchemyと関連してPofEAAに記載されているパターン紹介とデータマッピング周りの発表者さんの考える良い設計的な内容でした。
ユニットオブワーク、一意キーマッピング・・と主にデータ設計周りのパターンの紹介でしたが、現在の自分の興味領域と被っていたので理解は出来た方だと思います!後半の発表者さんの設計についての話は、なんとなくですがドメイン駆動設計の本を読むとサービス/レポジトリ/エンティティみたいな概念がフィットするかもしれないなと思いました。
途中アクティブレコードモデルの肥大化の問題点みたいな話をされていたので、だったらデータマッパーの方が良い書き方ができるのでは的な意図でSQLAlchemyでデータマッパーなアーキテクチャは使えるのかと質問しましたが、最初の例のプログラムがデータマッパー的な書き方をしてたのでそもそもSQLAlchemyはデータマッパーである事が自明であり愚問でした、すいません!

Webセキュアコーディングの基本

資料は以下です。
http://www.slideshare.net/gjo/pycon-apac-2013-web-secure-coding

pythonのセキュリティ話というよりもセキュリティを保つためにはどんな感じにやってけば良いのかみたいな内容でした。
個々の処理にescape処理をいれるよりもフレームワークなど仕組みとしてエスケープすべしなど意識せずに実施しているような基礎を思い出させてくれました。

pythonで、ハードウェアをWebAPIにする話

資料はなかったので動画が以下です。
http://www.youtube.com/watch?v=KwmU3dRZagY

会社ではなく趣味的?な開発チームをやってる人がarduinoとRaspberry Pi(Linux)を連携をweb apiで実装したよって内容でした。
arduinoの温度センサからシリアル接続で温度を取得してウエブで表示するデモ等ありました。pythonだとCのラッパーが書きやすいという事なので、シリアル接続などC言語がはびこる領域との連携はpythonだと実装しやすいんだろうなという印象をうけました。

Django 1.5 における効果的な MTV 設計 & ネイティブApp

資料は以下です。
http://www.slideshare.net/luyikei

前半は設計の話ではなくDjangoでのmodel、template、session、cache等の機能のtips、後半はPyQtを利用したネイティブアプリの実装例の紹介という内容でした。紹介文で「日本 Qt ユーザー会を設立。日本における Qt の情報の発信を促進する傍ら、 Django でWEBアプリケーションを作成することをメインとして活動する。」のように書いてあったのでギークなおっさん的な人を想像したのですが普通に高校生でした。自分が高校生の時はwindows95で自作ブームだったなぁとノスタルジックな気持ちで生暖かい気持ちで聞いたセッションです。

Django最速デバッグ指南

資料は以下です。
http://www.slideshare.net/hirokiky/django-yconapac2013

デバッグツール(django-debug-toolbar/django-pdb/django-devserver)の紹介とロギングの説明な内容でした。
発表者の人が発表慣れをしている感じで説明がわかりやすかったです。実際の画面を操作しながら説明してくれたのでpython弱者の自分がこのセッションを聞いただけであたかもデバッグ力が上がったかのような感覚を与えてくれましたw
後半はログの話でerror,warning,info,debugの扱いでprintデバッグするならdebugログだそうよって話では自分は適当に出しがちなので意識の低さに自省しました。

LT

英語でのLTだったのであまり理解できませんでした(汗

感想

個人的にはじめてのPyCon参加だったのですが、発表内容に関していうと他言語のカンファレンスとそんなに違いは無い印象をうけました。ただ、pythonは海外の方が盛んなので英語のセッションが半分を占めるのは他言語ではない所かなと思いました。

pyramidでmongoengineを使ってみる

pythonのmongodb用ODM(ORM)であるmongoengineのお話です。
mongoengineでmongoのデータを更新するフォームの勉強をしたかったので、mongoengineを軽く触ってみました。

1. 環境構築

今回のテスト用にmyform環境を作成し「/form」をルーティングに追加します。
ちなみにSymfony2でtwigを使ってたのでテンプレートエンジンには書式が同じjinja2を設定してます。

// 必要モジュールのインストール
$ pip install pyramid==1.3.4
$ pip install waitress
$ pip install pyramid_jinja2
$ pip install mongoengine
// プロジェクト作成
$ pcreate -s starter myform
$ cd myform
$ python setup.py develop
// 「/form」をルーティングにしてテンプレートエンジンにjinja2を設定
$ vi myform/__init__.py
from pyramid.config import Configurator
from mongoengine    import connect

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.include('pyramid_jinja2')
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('form_index', '/form')
    config.scan()

    connect('pyramid_form')
    return config.make_wsgi_app()

2. コレクション用のモデルを作成

「email」「first_name」「last_name」の文字列型のカラムを持った「user」コレクションのモデルを定義します。

$ mkdir myform/models
$ touch myform/models/__init__.py
$ vi myform/models/user.py
from mongoengine import Document
from mongoengine import StringField

class User(Document):
    email      = StringField(required=True)
    first_name = StringField(max_length=50)
    last_name  = StringField(max_length=50)

3. データを挿入するviewを作成

Userモデルのインスタンスを生成して保存するビューとテンプレートを作成

// データを挿入するview
$ vi myform/views.py
from pyramid.view import view_config
from models.user import User

@view_config(route_name='form_index', renderer='templates/form/index.jinja2')
def my_view(request):
    test = User(email='test1@example.com')
    test.first_name  = 'test1_fname'
    test.last_name   = 'test1_lname'
    test.save()

    return {'value':'test!'}

// テンプレート作成 
$ mkdir  myform/templates/form/
$ vi myform/templates/form/index.jinja2
hello {{ value }}

4. サーバを起動して確認

サーバを起動後「/form」にアクセスすると「hello test!」と表示されます。
mongodbにデータが保存されている事が確認できます。

$ mongo
MongoDB shell version: 2.4.5
connecting to: test
> show dbs;
local   0.078125GB
pyramid_form    0.203125GB
> show collections
system.indexes
user
> var x = db.user.findOne();
> printjson(x)
{
        "_id" : ObjectId("52345cfe2e5229637bcaa389"),
        "email" : "test1@example.com",
        "first_name" : "test1_fname",
        "last_name" : "test1_lname"
}

5. EmbeddedDocumentの使用

mongodbの特性の1つにEmbeddedDocument機能があるので使ってみました。
「post」コレクションにCommentという埋め込みドキュメントを設定します。

$ vi myform/models/posts.py
# --* encoding:utf-8 *--

from mongoengine import Document
from mongoengine import EmbeddedDocument
from mongoengine import StringField
from mongoengine import ReferenceField
from mongoengine import ListField
from mongoengine import EmbeddedDocumentField
from .user       import User

class Comment(EmbeddedDocument):
    content = StringField()
    name    = StringField(max_length=120)

class Post(Document):
    title    = StringField(max_length=120, required=True)
    author   = ReferenceField(User)
    tags     = ListField(StringField(max_length=30))
    comments = ListField(EmbeddedDocumentField(Comment))

    meta     = {'allow_inheritance': True}

class TextPost(Post):
    content = StringField()

class ImagePost(Post):
    image_path = StringField()

class LinkPost(Post):
    link_url = StringField()

// TextPostとLinkPostの2つのインスタンスを作成して保存
$ vi myform/views.py 
from pyramid.view import view_config
from models.user  import User
from models.posts import TextPost
from models.posts import LinkPost
from models.posts import Comment

@view_config(route_name='form_index', renderer='templates/form/index.jinja2')
def my_view(request):
    user = User(email='test2@example.com', first_name='test2_fname', last_name='test2_lname').save()

    comment = Comment(content='comment_content', name='comment_name')

    post1 = TextPost(title='Fun with MongoEngine', author=user)
    post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
    post1.tags = ['mongodb', 'mongoengine']
    post1.comments = [comment]
    post1.save()

    post2 = LinkPost(title='MongoEngine Documentation', author=user)
    post2.link_url = 'http://docs.mongoengine.com/'
    post2.tags = ['mongoengine']
    post2.save()


    return {'value':'test!'}

6. EmbeddedDocumentの確認

mongoで確認するとauthorでUserとの関連が設定されて、commentsにコメントが埋め込まれている事が確認できます。

> db.post.find().forEach(printjson);
{
        "_id" : ObjectId("52345fda2e52296395f90bdf"),
        "_cls" : "Post.TextPost",
        "title" : "Fun with MongoEngine",
        "author" : ObjectId("52345fda2e52296395f90bde"),
        "tags" : [
                "mongodb",
                "mongoengine"
        ],
        "comments" : [
                {
                        "content" : "comment_content",
                        "name" : "comment_name"
                }
        ],
        "content" : "Took a look at MongoEngine today, looks pretty cool."
}
{
        "_id" : ObjectId("52345fda2e52296395f90be0"),
        "_cls" : "Post.LinkPost",
        "title" : "MongoEngine Documentation",
        "author" : ObjectId("52345fda2e52296395f90bde"),
        "tags" : [
                "mongoengine"
        ],
        "comments" : [ ],
        "link_url" : "http://docs.mongoengine.com/"
}

さすがODMって感じです。

参考

http://docs.mongoengine.org/en/latest/tutorial.html

PyCon APAC 2013に行ってきた(1日目)

最近、仕事でpythonに触れる事が多いのでPyCon APAC 2013にいってきました。1日目に聞いたセッションの感想です。

パッケージングの今と未来

資料は以下です。
http://www.slideshare.net/aodag/ss-26183017

自分は7月から本格的にpythonを触り始めたのでpythonの歴史とか常識など抜けてる部分が結構あってその辺の知識の補完を期待して聞きました。
内容としてはわりと期待通りでパッケージングの基礎〜今〜未来とどんなツールが使われてきて、モダンと言われる構成の紹介と共に今後はこんなツールがあるよ的な内容でした。pythonに深く関わっている人の生の声的な事を聞けたのはよかったです。

sphinxと僕

資料は以下です
https://dl.dropboxusercontent.com/u/540566/pyconapac2013/_build/html/index.html

発表者はtell-kさん・・尊敬する大先輩ですね。昔はこのような大舞台で発表する事はなかったので大きくなったなと目頭が熱くなりました(上から目線)
前半はsphinxの使い方、後半はsphinxで出来る事を語っておられました。始める前からスライドの時間を気にしていましたがきっちり終わらすのはさすがです。ただ、最初から気にしてるなら内容を削っても良かったのではと思いました!

Python ユーザのための構成管理入門

資料は以下です。
http://www.slideshare.net/TakeshiKomiya/python-26185953

fabricやcapistrano的なアプリの構成管理を期待して聞いたのですが、chefやpuppetのようなサーバ構成管理のお話でしたw
冪等性などサーバの構成管理の概念とchefの使い方のお話で、中身も勉強になりましたが発表者さんの話が淀みなかった上、スライド内容につながりがあって発表スキルが高さが個人的に一番気になりました!

PythonとDataDogを使って簡単システムモニターリング

スライドは見つからなかったですが、動画が下記にありました。
http://www.youtube.com/watch?v=ow5kreNQaHA&feature=youtu.be&a

zabbix的な監視ツールのDataDogの紹介でした。個人的には便利そうで使ってみたいと思ったけど、仕事で使う時にはデータを取る時にアプリケーションに入れる処理が大量アクセス時にも大丈夫かなとか少し現実的な事を考えてしまいました。モニタリングでのfacebookって言葉が一番印象に残りました。

Programming AWS with Python

資料は以下です。
http://www.slideshare.net/understeer/20130914-py-conapacawspublic

今日一番自分に刺さったセッションだと思います。awsのpython SDK(boto)を使ったawsツール開発のお話で、元々非公式sdkだったのが開発者がawsの中の人になってそのまま公式化したらしく、対応機能の種類や新機能への対応速度はjavaと並んで早いとの事。有用な話が聞けたのでJAWS FESTA Kansai 2013の期待値も高まりました。行こうかな・・

about mock

資料は以下です。
http://podhmo.github.io/pycon/slide/index.html?transition=none#/

mockで必要とされるような事を自分で実装してみた!みたいなお話だったと思うのですが、理解が追いつけなくて自分のpython力の低さを実感しました。

明日も楽しみです!

pyramidで開発環境と本番環境でタイトルの表示を切り分ける方法

pyramidで作成した管理画面で本番と開発環境でタイトルを切り分けたいとの要望があったのでその対応した時の手順を記します。
プロジェクトの構成は下記となります。

$ pcreate -s starter taka512
$ tree
taka512
|- development.ini
|- production.ini
|- setup.cfg
|- setup.py
|- taka512
    |- __init__.py
    |- static
    |- templates
    |  |- mytemplate.pt
    |- views.py

1.設定ファイルに環境切替用の変数を記述

development.iniとproduction.iniに「taka512.env」と1行追加してそれぞれ「development」と「production」を指定します。

$ cd taka512
$ vi development.ini
[app:main]
use = egg:taka512

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
    pyramid_debugtoolbar

taka512.env = development

2.環境変数で開発と本番を判定するutilを作成
「is_dev」メソッドを作成します。

$ mkdir taka512/utils
$ touch taka512/utils/__init__.py
$ vi taka512/utils/common.py
from pyramid.threadlocal import get_current_registry

def is_dev():
    settings = get_current_registry().settings
    if(settings.get('taka512.env') == 'development'):
        return True
    else:
        return False

3.環境変数によって開発と本番用の文字列を取得するヘルパーを作成
先ほど作成したis_devを使用して「get_env_display」を作成します。

$ mkdir taka512/helpers
$ touch taka512/helpers/__init__.py
$ vi taka512/helpers/helper.php
#--* encoding: utf-8 *-- 
from taka512.utils.common import is_dev

def get_env_display():
    if(is_dev()):
        return u'開発'
    else:
        return u'本番'

4.ヘルパーを設定
テンプレートからヘルパーメソッドを呼び出すための設定を追加します。
「add_renderer_globals」の設定で「h.~」でヘルパーメソッドがテンプレートから呼べるようになります。

$ vi taka512/__init__.py
from pyramid.config import Configurator
from pyramid.events import BeforeRender
from taka512.helpers import helper

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.add_subscriber(add_renderer_globals, BeforeRender)

    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.scan()
    return config.make_wsgi_app()

def add_renderer_globals(event):
    event['h'] = helper

テンプレートからヘルパーを呼び出し
「${h.get_env_display()}」でヘルパーを呼び出します。jinjaであれば「{{ h.get_env_display() }}」となります。

$ vi taka512/templates/mytemplate.pt
~略~
    <div id="middle">
      <div class="middle align-center">
        <p class="app-welcome">
          Welcome to <span class="app-name">${project}${h.get_env_display()}</span>, an application generated by<br/>
          the Pyramid web application development framework.
        </p>
      </div>
    </div>
~略~

下記のように「開発」と表示されれば成功です!

f:id:taka512:20130905001351p:plain

djangoのチュートリアルをさくっとやってみた(フロントサイト構築)

前回の記事でdjangoチュートリアルの2までの内容(管理サイトの構築)まで行ったので、チュートリアルの3以降の内容(フロントサイトの構築)を行います。

3.2 フロントサイトの作成
投票一覧(index)・詳細(detail)・投票(vote)・投票結果(result)のページを作成します。

ルーティングの追加
各ページへのurlの設定を行います。

$ vi urls.py
  6 urlpatterns = patterns('',
  7     url(r'^polls/$', 'polls.views.index'),
  8     url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
  9     url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
 10     url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
 11     url(r'^admin/', include(admin.site.urls)),
 12 )

ビューの追加
一旦、各ページのダミーのビューを追加します。
これで「/polls」にアクセスすると表示はされるようになります。

vi polls/views.py
  1 from django.http import HttpResponse
  2
  3 def index(request):
  4     return HttpResponse("Hello, world. You're at the poll index.")
  5
  6 def detail(request, poll_id):
  7     return HttpResponse("You're looking at poll %s." % poll_id)
  8
  9 def results(request, poll_id):
 10     return HttpResponse("You're looking at the results of poll %s." % poll_id)
 11
 12 def vote(request, poll_id):
 13     return HttpResponse("You're voting on poll %s." % poll_id)

投票一覧画面(index)の作成1
テンプレートを利用しない一覧の実装例

$ vi polls/views.py
  1 from polls.models import Poll
  2 from django.http import HttpResponse
  3
  4 def index(request):
  5     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
  6     output = ', '.join([p.question for p in latest_poll_list])
  7     return HttpResponse(output)

投票一覧画面(index)の作成2
テンプレートを利用した一覧の実装例

$ vi polls/views.py
  1 from django.template import Context, loader
  2 from polls.models import Poll
  3 from django.http import HttpResponse
  4
  5 def index(request):
  6     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
  7     t = loader.get_template('polls/index.html')
  8     c = Context({
  9         'latest_poll_list': latest_poll_list,
 10     })
 11     return HttpResponse(t.render(c))

$ mkdir templates/polls
$ vi templates/polls/index.html
  1 {% if latest_poll_list %}
  2     <ul>
  3     {% for poll in latest_poll_list %}
  4         <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
  5     {% endfor %}
  6     </ul>
  7 {% else %}
  8     <p>No polls are available.</p>
  9 {% endif %}

投票一覧画面(index)の作成3
ショートカット(render_to_response)を利用した実装例

$ vi polls/views.py
  1 from django.shortcuts import render_to_response
  2 from polls.models import Poll
  3 from django.http import HttpResponse
  4
  5 def index(request):
  6     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
  7     return render_to_response('polls/index.html',
  8                               {'latest_poll_list': latest_poll_list})

投票詳細画面(detail)の作成1
リクエストパラメータのpoll_idが存在しない番号の場合、HTTP STATUS 404を応答するように実装

$ vi polls/views.py
  4 from django.http import Http404
~(略)~
 10 def detail(request, poll_id):
 11     try:
 12         p = Poll.objects.get(pk=poll_id)
 13     except Poll.DoesNotExist:
 14         raise Http404
 15     return render_to_response('polls/detail.html', {'poll': p})

$ vi templates/polls/detail.html
  1 <h1>{{ poll.question }}</h1>
  2 <ul>
  3 {% for choice in poll.choice_set.all %}
  4     <li>{{ choice.choice }}</li>
  5 {% endfor %}
  6 </ul>

投票詳細画面(detail)の作成2
HTTP STATUS 404の応答をショートカット(get_object_or_404)で実装

$ vi polls/views.py
  1 from django.shortcuts import render_to_response, get_object_or_404
~(略)~
 10 def detail(request, poll_id):
 11     p = get_object_or_404(Poll, pk=poll_id)
 12     return render_to_response('polls/detail.html', {'poll': p})

URLconfsの記述の簡略化と別ファイル化
urls.pyの記述をpolls/urls.pyに分離して記述も簡略化
(patternsの第一引数にパッケージ名を指定し簡略化)

// 「/polls」のルーティングは「polls/urls.py」に移動
$ vi urls.py
  1 from django.conf.urls.defaults import patterns, include, url
  2
  3 from django.contrib import admin
  4 admin.autodiscover()
  5
  6 urlpatterns = patterns('',
  7     url(r'^polls/', include('polls.urls')),
  8     url(r'^admin/', include(admin.site.urls)),
  9 )

$ vi polls/urls.py 
  1 from django.conf.urls.defaults import patterns, include, url
  2
  3 urlpatterns = patterns('polls.views',
  4     url(r'^$', 'index'),
  5     url(r'^(?P<poll_id>\d+)/$', 'detail'),
  6     url(r'^(?P<poll_id>\d+)/results/$', 'results'),
  7     url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
  8 )

投票フォーム(vote)と結果(result)の作成
一覧→投票→投票結果表示までの一連の流れを実装します。

// 投票詳細画面(detail)にフォームを組込
$ vi templates/polls/detail.html
  1 <h1>{{ poll.question }}</h1>
  2
  3 {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
  4
  5 <form action="/polls/{{ poll.id }}/vote/" method="post">
  6 {% csrf_token %}
  7 {% for choice in poll.choice_set.all %}
  8     <input type="radio" name="choice" id="choice{{ forloop.counter }}"
  9      value="{{ choice.id }}" />
 10     <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
 11 {% endfor %}
 12 <input type="submit" value="投票する" />
 13 </form>

// 結果画面(result)のテンプレートを作成
  1 <h1>{{ poll.question }}</h1>
  2
  3 <ul>
  4 {% for choice in poll.choice_set.all %}
  5     <li>{{ choice.choice }} -- {{ choice.votes }} 票</li>
  6 {% endfor %}
  7 </ul>
  8
  9 <a href="/polls/{{ poll.id }}/">Vote again?</a>

// 各ページのビューの実装
// detailのcontext_instanceはテンプレートの「csrf_token」のために必要
$ vi polls/views.py
  1 from django.shortcuts import render_to_response, get_object_or_404
  2 from django.template import RequestContext
  3 from polls.models import Choice, Poll
  4 from django.http import HttpResponseRedirect, HttpResponse
  5 from django.core.urlresolvers import reverse
  6
  7 def index(request):
  8     latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
  9     return render_to_response('polls/index.html',
 10                               {'latest_poll_list': latest_poll_list})
 11 def detail(request, poll_id):
 12     p = get_object_or_404(Poll, pk=poll_id)
 13     return render_to_response('polls/detail.html', {'poll': p},
 14                                context_instance=RequestContext(request))
 15
 16 def results(request, poll_id):
 17     p = get_object_or_404(Poll, pk=poll_id)
 18     return render_to_response('polls/results.html', {'poll': p})
 19
 20 def vote(request, poll_id):
 21     p = get_object_or_404(Poll, pk=poll_id)
 22     try:
 23         selected_choice = p.choice_set.get(pk=request.POST['choice'])
 24     except (KeyError, Choice.DoesNotExist):
 25         return render_to_response('polls/detail.html', {
 26             'poll': p,
 27             'error_message': "You didn't select a choice.",
 28         }, context_instance=RequestContext(request))
 29     else:
 30         selected_choice.votes += 1
 31         selected_choice.save()
 32         return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))

汎用ビューの利用

$ vi polls/urls.py
  1 from django.conf.urls.defaults import *
  2 from django.views.generic import DetailView, ListView
  3 from polls.models import Poll
  4
  5 urlpatterns = patterns('',
  6     url(r'^$',
  7         ListView.as_view(
  8             queryset=Poll.objects.order_by('-pub_date')[:5],
  9             context_object_name='latest_poll_list',
 10             template_name='polls/index.html')),
 11     url(r'^(?P<pk>\d+)/$',
 12         DetailView.as_view(
 13             model=Poll,
 14             template_name='polls/detail.html')),
 15     url(r'^(?P<pk>\d+)/results/$',
 16         DetailView.as_view(
 17             model=Poll,
 18             template_name='polls/results.html'),
 19         name='poll_results'),
 20     url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
 21 )

// 各ビュー(index,detail,result)の削除
$ vi polls/views.py
  1 from django.shortcuts import get_object_or_404, render_to_response
  2 from django.http import HttpResponseRedirect
  3 from django.core.urlresolvers import reverse
  4 from django.template import RequestContext
  5 from polls.models import Choice, Poll
  6
  7 def vote(request, poll_id):
  8     p = get_object_or_404(Poll, pk=poll_id)
  9     try:
 10         selected_choice = p.choice_set.get(pk=request.POST['choice'])
 11     except (KeyError, Choice.DoesNotExist):
 12         return render_to_response('polls/detail.html', {
 13             'poll': p,
 14             'error_message': "You didn't select a choice.",
 15         }, context_instance=RequestContext(request))
 16     else:
 17         selected_choice.votes += 1
 18         selected_choice.save()
 19         return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

これでチュートリアルは一通り完了です。少しdjangoを解った気分にはなれました。