Chapter 3: 概要

概要

はじめよう

Linux
Mac
Windows

web2pyにはWindowsとMac OS X用のバイナリパッケージが提供されています。Pythonインタプリタが含まれているので、事前のインストールは不要です。また、Windows、Mac、Linux、その他のUnixシステムで動作するソースコードもあります。ソースコードパッケージは、Pythonが既にコンピュータにインストールされていることを前提としています。

web2pyはインストールする必要がありません。始めるには、利用するオペーティングシステム用にダウンロードしたzipファイルを解凍して、適切な web2py ファイルを実行します。

UnixおよびLinux(ソースコード配布)では、次のように実行します:

python web2py.py

OS X (バイナリ配布)では、次のように実行します:

open web2py.app

Windows (バイナリweb2py配布)では、次のように実行します:

web2py.exe

Windows (ソースweb2py配布)では、次のように実行します:

c:/Python27/python.exe web2py.py

注意、Windwos上でソースからweb2pyを実行するには、まず http://sourceforge.net/projects/pywin32/ からMark Hammondのwin32拡張インストールする必要があります。

web2pyプログラムはさまざまなコマンドラインオプションを受け付けます。これは後ほど説明します。

デフォルトでは、起動時に、起動ウィンドウが表示されます。その後、画面にはGUIウィジェットが表示され、一度限りの管理パスワード、Webサーバーに利用されるネットワークインターフェイスのIPアドレス、リクエストを受けるポート番号を選択するように求められます。デフォルトでは、web2pyは127.0.0.1:8000(ローカルホストのポート8000番)上で動作しますが、任意のIPアドレスとポートでも動作させることができます。ネットワークインターフェイスのIPアドレスはコマンドラインを開いた後、Windowsの場合は ipconfig コマンドを、OS XやLinuxの場合は ifconfig コマンドを入力する事で確認する事ができます。これ以降、web2pyはローカルホスト(127.0.0.1:8000)上で実行しているものとします。任意のネットワークインターフェイス上に、公開したweb2pyを動作させる時は、0.0.0.0:80を使用してください。

image

管理者のパスワードを指定しない場合は、管理インターフェイスは無効になります。これは管理インターフェイスが公然と公開されてしまう事を防ぐ、セキュリティ上の対策です。

管理者インターフェイス admin は、web2pyをApacheとmod_proxyを組み合わせた環境で実行させない限り、ローカルホストからしかアクセスできません。もし管理インターフェイスがプロキシを検出した場合は、セッションクッキーは保護されることとなり、管理インターフェイスのログインは、クライアントとプロキシがHTTPS上で通信しない限り、機能しません。これはセキュリティ対策のためです。クライアントと管理者の間の全ての通信は、常にローカルまたは暗号化されている必要があります。そうしないと、攻撃者は中間者攻撃やリプレイ攻撃を行うことができ、サーバ上で任意のコードを実行することが可能になるからです。

管理者用のパスワードが設定されたら、web2pyは次のページからWebブラウザを立ち上げます:

http://127.0.0.1:8000/

もしデフォルトのブラウザがない場合、Webブラウザを開いて、URLを入力してください。

image

"administrative interface" をクリックすると、管理インターフェイス用のログインページが表示されます。

image

管理者パスワードは、起動時に指定したパスワードと同じです。 なお、管理者は1人だけで、したがって、一つの管理パスワードしかありません。セキュリティ上の理由により、web2pyが起動するたびに開発者は毎回新しいパスワードを尋ねられます。ただし、<recycle>オプションを指定するとその限りではありません。これは、web2pyの認証機構とは区別されます。

管理者がweb2pyにログインすると、ブラウザは "site" ページへとリダイレクトされます。

image

このページでは全てのインストールされているweb2pyアプリケーションがリスト表示され、管理者はそれらを管理することができます。

web2pyには3つのアプリケーションが付属しています:

admin
examples
welcome
scaffolding

  • admin アプリケーション。これは現在あなたが利用しているものです。
  • examples アプリケーション。これはオンラインの対話的なドキュメントと、web2pyの公式サイトのレプリカ(複製)を持っています。
  • welcomeアプリケーション。他のweb2pyアプリケーションのための基本的なテンプレートです。これは、ひな形となるアプリケーション として参照されます。起動時にユーザをウェルカムするアプリケーションです。
appliances

すぐに利用できるweb2pyアプリケーションは、web2pyの アプライアンス として参照されます。[appliances] から、多くのフリーで利用可能なアプリケーションをダウンロードすることができます。 web2pyのユーザは、オープンソースかクローズドソース(コンパイルされてパックされたもの)のいずれの形式でも、新しいアプライアンスを投稿することが推奨されています。

admin アプリケーションの site ページから、次の操作を行うことができます:

  • install アプリケーションのインストールは、ページの右下にあるフォームを完成させることで行います。アプリケーションの名前を入力し、パッケージ化されたアプリケーションを含むファイルを選択、または、アプリケーションが用意されているURLを指定して、"submit" ボタンをクリックします。
  • uninstall アプリケーションのアンインストールは、対応するボタンをクリックします。確認ページがあります。
  • create 新しいアプリケーションの作成は、名前を入力し、"create" ボタンをクリックします。
  • package 配布用のアプリケーションのパッケージングは、対応するボタンをクリックします。ダウンロードされたアプリケーションは、データベースを含む全てを保持するtarファイルです。このファイルは untar してはいけません。adminでインストールした時に、web2pyによって自動的にアンパッケージングされます。
  • clean up セッションや、エラー、キャッシュファイルなどのアプリケーションの一時ファイルをクリーンアップします。
  • enable/disable 個々のアプリケションを有効/無効化します。アプリケーションが無効にされている場合、リモートでの呼び出しはできません。しかしローカルホストからは無効にはなっていません。これは無効化アプリケーションが、プロキシを使用してのアクセスは、まだ可能だということを意味しています。アプリケーションはアプリケーションフォルダに、"DISABLED" と呼ばれるファイルを作成することで無効になります。無効化されたアプリケーションにアクセスを試みるユーザは、503 HTTP エラーを受け取ることになります。エラーページをカスタマイズするために、routes_onerror の使用が可能です。
  • EDIT アプリケーションを編集します。

admin を用いて新規のアプリケーションを作成する時は、"welcome" ひな形アプリのクローンとして開始します。ひな形アプリの "models/db.py" は、SQLiteデータベースを作成及び接続し、Auth・Crud・Serviceをインスタンス化し設定します。"controller/default.py" も提供され、"index" 及び "download"、ユーザ管理用の "user"、サービスのための "call" というアクション公開しています。ここから先の説明では、これらのファイルが削除されていることを前提とします。つまり、アプリをスクラッチから作成していきます。

web2pyには wizard も付属しています。これについては後の章で説明します。wizardは、web用に準備されたレイアウトやプラグインと高いレベルのモデルの記述に基づいた、ベースとなるコードを生成することができる、もう一つの仕組みです。

簡単な例

挨拶しよう

index

ここでは例として、ユーザに "Hello from MyApp" というメッセージを表示する簡単なWebアプリケーションを作成します。このアプリケーションを "myapp" と呼びます。また、同じユーザがページを何回訪問したかをカウントするカウンタを追加します。

新しいアプリケーションは、admin の中の site ページの右上にあるフォームに、アプリケーション名を入れることで簡単に作成できます。

image

[create] ボタンを押すと、アプリケーションは組み込みのwelcomeアプリケーションのコピーとして作成されます。

image

新しいアプリケーションを実行するには、次のURLを訪れてください:

http://127.0.0.1:8000/myapp

これで、welcomeアプリケーションのコピーが作成できました。

アプリケーションを編集するには、新しく作成されたアプリケーションの edit ボタンをクリックしてください。

edit ページは、アプリケーションの内部がどのようなものかを示しています。 全てのweb2pyアプリケーションは特定のファイルから構成され、そのほとんどは次の6つのカテゴリに分類されます:

  • models: データ表現を記述します。
  • controllers: アプリケーションのロジックとワークフローを記述します。
  • views: データの表示方法を記述します。
  • languages: アプリケーションで表示される内容を、他の言語に翻訳するための方法を記述します。
  • modules: アプリケーションに属するPythonモジュールです。
  • static files: 静的イメージファイル、CSSファイル [css-w,css-o,css-school]、JavaScriptファイル [js-w,js-b]、などです。
  • plugins: 一緒に動作するよう設計されたファイルの集合です。

全てのファイルは、モデル - ビュー - コントローラのデザインパターンに従ってきちんと構成されます。edit ページの各セクションは、アプリケーションフォルダ内のサブフォルダに対応します。

なお、セクションの見出をクリックすると、その中身の表示/非表示を切り替えることができます。同様にstatic filesの下のフォルダ名も、折りたたむことができます。

セクション内の各ファイルは、サブフォルダにある物理的なファイルに対応しています。管理 インターフェイスからファイルに対して行える全ての操作(create、edit、delete)は、好みのエディタを使用してシェルから実行することもできます。

アプリケーションは上記以外にも、データベース、セッションファイル、エラーファイルを含みますが、edit ページには掲載されません。これらのファイルは管理者ではなく、アプリケーション自身によって作成・編集されるためです。

コントローラは、アプリケーションのロジックやワークフローを含みます。全てのURLは、コントローラの関数(アクション)のいずれか1つに呼び出しにマッピングされます。"appadmin.py" と "default.py" という2つのデフォルトコントローラが用意されています。appadmin は、データベース管理用のインターフェイスを提供しますが、ここでは必要ありません。"default.py" は、編集するべきファイルであり、URLに対応するコントローラが存在しない時にデフォルトで呼び出されます。次のように "index" 関数を編集してみましょう:

def index():
    return "Hello from MyApp"

オンラインのエディタは次のような表示になります:

image

それを保存し、edit ページに戻ってください。そして、indexのリンクをクリックし、新しく作成されたページを表示してください。

次のURLを開くと、

http://127.0.0.1:8000/myapp/default/index

myappアプリケーションのdefaultコントローラにある、indexアクションが呼び出されます。このメソッドは、ブラウザに表示される文字列を返します。下記のように表示されます:

image

それでは、"index" 関数を次のように編集しましょう:

def index():
    return dict(message="Hello from MyApp")

また、edit ページから、"default/index.html" ビュー(アクションに関連付けられたビュー用のファイル)を編集し、すでに存在しているファイルの内容を、全て以下のものに置き換えてください:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
   </body>
</html>

すると、アクションは message が定義された辞書を返すようになります。アクションが辞書を返すとき、web2pyは下記の名前を持つビューを探します。

[controller]/[function].[extension]

そして実行します。ここでは [extension] は、リクエストされた拡張子です。拡張子が指定されていない場合は "html" になり、ここでもそのデフォルトで仮定しています。この仮定下でビューは、特別な {{ }}タグを用いて、Pythonのコードを埋め込むHTMLファイルになります。例では特に、{{=message}} の部分のタグ付きのコードを、アクションによって返される message の値に置き換えるために、web2pyを指示します。ただしここで、message はweb2pyのキーワードではなく、アクションで定義されたものです。ここまで、web2pyのキーワードは使用されていません。

もしweb2pyがリクエストされたビューを見つけられなかった場合、どのアプリケーションでも用意されている "generic.html" が使われます。

Mac Mail
Google Maps
jsonp
もし拡張子が "html" 以外のもの(たとえば "json")が指定されて、かつ "[controller]/[function].json" というビューファイルが見つからなかった場合、web2pyは "generic.json" というビューを探します。web2pyでは、generic.html、generic.json、generic.jsonp、generic.xml、generic.rss、generic.ics (MACメールカレンダ用)、generic.map (埋め込みGoogleマップ用)、そして generic.pdf (fpdfが基)、といったファイルが用意されています。これらの汎用的なビューは、アプリケーションごとに個別に変更することができます。そして新しいビューも簡単に追加できます。

汎用的なビューは、開発用のツールです。開発する全てのアクションは、固有のビューを持つべきです。実際、初期設定では汎用的なビューは、localhostからのアクセス時のみ有効となっています。

次のようにビューを指定することもできます。 response.view = 'default/something.html'

このトピックに関する詳細は、第10章を読んでください。

"EDIT"ページに戻りindexをクリックすると、次のHTMLページが表示されます:

image

デバッグ・ツールバー

toolbar

デバッグ目的のために、次のコードを挿入することができます。

{{=response.toolbar()}}

ビューの中でこのコードは、役に立つ情報を表示します。リクエスト、レスポンス、セッションオブジェクト、そして同じタイミングで発行された全てのDBクエリのリストなどを含みます。

数えよう

session

今度は、同じ訪問者がこのページを何回表示したかを数える、カウンタを追加しましょう。

web2pyはセッションとクッキーを使って、自動的かつ透過的に訪問者を追跡します。新しい訪問者が来るたびに、セッションが作成されユニークな "session_id" が割り当てます。セッションはサーバサイドに保存される、変数のためのコンテナです。一意のIDがクッキーを介してブラウザに送信されます。訪問者が同じアプリケーションから別のページをリクエストするとき、ブラウザはクッキーを送り戻し、そのクッキーはweb2pyによって取得され、対応するセッションが復元されます。

セッションを使用するには、デフォルトのコントローラを次のように変更します:

def index():
    if not session.counter:
        session.counter = 1
    else:
        session.counter += 1
    return dict(message="Hello from MyApp", counter=session.counter)

counter はweb2pyのキーワードではなく、session に保存される変数であることに注意してください。ここではsessionの中にcounter変数が存在するか、チェックするようにweb2pyに求めます。存在しない場合は作成し、1に設定します。存在すれば、counterを1増加させるようにweb2pyに求めます。最後にビューに、counterの値を渡します。

同じ機能をコーディングするための、よりコンパクトな方法を以下に示します:

def index():
    session.counter = (session.counter or 0) + 1
    return dict(message="Hello from MyApp", counter=session.counter)

そしてビューを変更し、counterの値を表示するための行を追加します:

<html>
   <head></head>
   <body>
      <h1>{{=message}}</h1>
      <h2>Number of visits: {{=counter}}</h2>
   </body>
</html>

このページを再び(そして何回も)訪れると、次のようなHTMLのページが表示されます。

image

このcounterは各訪問者と関連づけられ、訪問者がこのページをリロードするたびに増えていきます。異なる訪問者は異なるカウンタを見ることになります。

名前を名乗ろう

form
request.vars

ここでは、2つのページ(firstとsecond)を作成します。firstページはフォームを作成し、訪問者の名前を尋ね、secondページへリダイレクトします。secondページは訪問者に名前で挨拶します。

yUML diagram

デフォルトのコントローラに、対応するアクションを書きます:

def first():
    return dict()

def second():
    return dict()

次に、firstアクションに対する "default/first.html" ビューを作成し、 以下を入力します:

{{extend 'layout.html'}}
<h1>What is your name?</h1>
<form action="second">
  <input name="visitor_name" />
  <input type="submit" />
</form>

最後に、secondアクションに対する "default/second.html" ビューを作成します:

{{extend 'layout.html'}}
<h1>Hello {{=request.vars.visitor_name}}</h1>
layout

両方のビューにおいて、web2pyに用意されている基本的な "layout.html" ビューが拡張されています。このレイアウト・ビューは、2つのページのルック&フィールの一貫性を保ちます。レイアウト・ファイルは主にHTMLコードにより構成されているので、簡単に編集や置き換えができます。

firstページを開いて、あなたの名前を入力してください:

image

そして、フォームをサブミットしてください。すると挨拶が表示されます:

image

ポストバック(Postbacks)

redirect
URL
postback

先に使用したフォームのサブミットに関するメカニズムは、とても一般的なものです。しかし、これはあまり良いプログラミング練習ではありません。全てのインプットは検証されるべきですが、上記の例では、検証の責任はsecondアクションが負っています。つまり、検証を行うアクションはフォームを生成したアクションと異なります。これは、コードの冗長性を引き起こしがちになります。

フォームサブミッションのためのより良いパターンは、フォームを生成したのと同じアクションに、今回の例では "first" にフォームをサブミットすることです。"first" アクションは、変数を受け取り、処理し、サーバーサイドに保存し、訪問者を "second" ページにリダイレクトします。リダイレクト先でその変数を取得します。 このメカニズムは、ポストバック(postback) と呼ばれます。

yUML diagram

デフォルトのコントローラを自己サブミット(self-submission)するように変更してみましょう:

def first():
    if request.vars.visitor_name:
        session.visitor_name = request.vars.visitor_name
        redirect(URL('second'))
    return dict()

def second():
    return dict()

"default/first.html" ビューは次のように変更します:

{{extend 'layout.html'}}
What is your name?
<form>
  <input name="visitor_name" />
  <input type="submit" />
</form>

"default/second.html" ビューは、request.vars の代わりに session からデータを取得する必要があります:

{{extend 'layout.html'}}
<h1>Hello {{=session.visitor_name or "anonymous"}}</h1>

訪問者から見ると、この自己サブミットは、前の実装と全く同じ挙動をしています。バリデーションはまだ加えていませんが、バリデーションがfirstアクションで行われるようになることは明白です。

このアプローチはより優れています。なぜなら、訪問者の名前はセッション内に留まるようになり、明示的に渡されなくてもアプリケーションの全てのアクションとビューからアクセスできるようになるからです。

訪問者の名前が設定される前に "second" アクションが呼び出された場合、画面上には "Hello anonymous" と表示されることに注意してください。これは、session.visitor_nameNone を返すからです。もう一つの方法は、コントローラ(second 関数内)に次のコードを追加することです:

if not request.function=='first' and not session.visitor_name:
    redirect(URL('first'))

これはコントローラに認証を強制するために使用できる 臨機応変な メカニズムです。ただし、より強力な方法のためには第9章を参照してください。

FORM
INPUT
requires
IS_NOT_EMPTY
accepts

web2pyではもう一歩先に進むことができ、検証を含むフォームをweb2pyに生成させることができます。web2pyはHTMLタグと同じ名前を持つヘルパー(FORM, INPUT, TEXTAREA, SELECT/OPTION)を提供します。これらを利用して、コントローラとビューのどちらにおいても、フォームを生成させることができます。

例として、firstアクションの書き換えが可能な方法を示します:

def first():
    form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()),
              INPUT(_type='submit'))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

ここで、FORMタグには2つのINPUTタグが含まれているのが分かります。inputタグの属性は、アンダースコアで始まる名前付きの引数で指定されます。requires 引数はタグの属性ではありません(アンダースコアで始まってないからです)。これはvisitor_nameの値のためのバリデータを設定します。

同じフォームを作成する、もっと別の良い方法があります。

def first():
    form = SQLFORM.factory(Field('visitor_name',
                                 label='what is your name?',
                                 requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        session.visitor_name = form.vars.visitor_name
        redirect(URL('second'))
    return dict(form=form)

form オブジェクトは、"default/first.html" ビューに埋め込むによって、簡単にHTMLとしてシリアライズすることができます。

{{extend 'layout.html'}}
{{=form}}

form.process() メソッドはバリデータを適用し、フォーム自体を返します。フォームが処理されバリデーションをパスした場合には、form.accepts 変数は True に設定されます。自己サブミットしたフォームがバリデーションをパスすれば、変数をセッションに保存し、以前と同様にリダイレクトします。フォームがバリデーションをパスしなかった場合、エラーメッセージがフォームに挿入され、次のようにユーザに表示されます:

image

次のセクションでは、どのようにフォームがモデルから自動生成されるかを示します。

この全ての例では、firstアクションからsecondアクションにユーザ名を渡すために、セッションを使用しています。違うメカニズムを使用し次のように、リダイレクトURLの一部としてデータを渡すことができます:

def first():
    form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY()))
    if form.process().accepted:
        name = form.vars.visitor_name
        redirect(URL('second',vars=dict(name=name)))
    return dict(form=form)

def second():
    name = request.vars.visitor_name or redirect(URL('first'))
    return dict(name=name)

一般的にはURLを使用し、あるアクションから別のアクションにデータを渡すのは、良いアイデアでないのを留意してください。もっと強くアプリケーションを保護することができる、セッションにデータを格納した方が安全です。

国際化

今までのコードは、"What is your name?" のようなハードコードを含んでいます。コードを編集せずに文字列をカスタマイズするのと、特に別の言語による文字列の翻訳文の挿入を行うべきです。訪問者がブラウザの言語を "Italian" に選択している場合、web2pyは可能ならイタリア語の翻訳文字列を使用する、というように行われます。web2pyのこの機能は、"国際化" と呼ばれ、次の章で詳細に説明します。

ここで少し、翻訳に必要なマークアップ文字列の使い方に触れます。次のように、引用符で囲まれた文字列をラップすることによって行います。

"What is your name?"

T 演算子で:

T("What is your name?")

ビューのハードコード文字列の翻訳のマークも可能です。例えば、

<h1>What is your name?</h1>

次のようになります。

<h1>{{=T("What is your name?")}}</h1>

テーブルやフィールド名を除いて、コード内の全ての文字列(フィールドラベル、flashメッセージなど)に、これを行うことをお勧めします。

一度、文字列が特定されマークアップされたら、web2pyは他のほとんど全ての管理をします。またサポートを希望する言語に対して、管理インターフェースは各文字列を翻訳できるページを用意します。

web2pyには次の章で説明する、パワフルな複数形エンジンも含まれています。これは、国際化エンジンとmarkminレンダラーの両方に統合されています。

画像ブログ

upload

ここでは別の例として、管理者が画像を投稿し名前を付けることができ、Webサイトの訪問者が画像を表示しコメントを投稿できる、ようなWebアプリケーションを作成します。

前と同様に admin にある site ページで、images という名前の新しいアプリケーションを作成し、edit ページに移動してください:

image

まずモデルを作成するところから始めます。モデルは、アプリケーション内の永続的なデータ(アップロードする画像、その名前、コメント)の表現です。初めに、モデルを作成/編集するためのファイルを作成します。余り深く考えず、このファイルは "db.py" とします。以下に示すコードは、db.py内の全ての既存のコードを置き換えることを想定します。モデルとコントローラは、Pythonコードなので .py 拡張子を持つ必要があります。もし拡張子が指定されていない場合、web2pyによって追加されます。また主にHTMLコードで構成されるため、ビューは代わりに .html 拡張子を持っています。

"db.py" ファイルの編集は、対応している "edit" ボタンをクリックします:

image

そして、次のように入力してください:

IS_EMAIL
IS_NOT_EMPTY
IS_IN_DB

db = DAL("sqlite://storage.sqlite")

db.define_table('image',
   Field('title', unique=True),
   Field('file', 'upload'),
   format = '%(title)s')

db.define_table('post',
   Field('image_id', 'reference image'),
   Field('author'),
   Field('email'),
   Field('body', 'text'))

db.image.title.requires = IS_NOT_IN_DB(db, db.image.title)
db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')
db.post.author.requires = IS_NOT_EMPTY()
db.post.email.requires = IS_EMAIL()
db.post.body.requires = IS_NOT_EMPTY()

db.post.image_id.writable = db.post.image_id.readable = False

一行ずつ分析してみましょう。

1行目はデータベース接続を表す、db というグローバル変数を定義します。このケースでは、"applications/images/databases/storage.db" ファイルに保存される、SQLiteデータベースへの接続です。SQLiteを使った時、もしデータベースファイルが存在しない場合は新たに作成されます。このファイルの名前は、グローバル変数 db の名前と同じように変更可能です。しかし覚えやすいように、同じ名前にしておいた方が便利です。

3~6行目は "image" テーブルを定義しています。define_tabledb オブジェクトのメソッドです。最初の引数 "image" は定義したテーブルの名前です。他の引数はテーブルに属するフィールドです。このテーブルは、"title" フィールド、"file" フィールド、主キーとして機能する "id" フィールドを持ちます("id" は明示的に宣言されません。全てのテーブルはidフィールドをデフォルトで持つためです)。"title" フィールドは文字列であり、"file" フィールドはupload型です。uploadは、web2pyのデータ抽象化レイヤ(DAL)によって使用される特殊な型で、アップロードされたファイルの名前を保持します。web2pyは、ファイルのアップロード(サイズが大きいとストリーミングを介します)、ファイルの安全なリネーム、ファイルの保存を上手に行うことができます。

テーブルが定義される時、web2pyは以下に示す、幾つか可能なアクションのどれか一つを取ります:

  • テーブルが存在しない場合、テーブルが作成されます。
  • テーブルが存在するが、その定義に対応していない場合、テーブルは定義に従って変更されます。フィールドが異なる型を持つ場合、web2pyはその内容を変更しようと試みます。
  • テーブルが存在し、その定義に対応する場合、web2pyは何もしません。

この振る舞いは、"マイグレーション(migration)" と呼ばれます。web2pyではマイグレーションは自動的で行われます。しかし migrate=Falsedefine_table の最後の引数に渡すことによって、テーブル毎に無効にすることができます。

6行目はテーブルに対して文字列フォーマットを定義しています。レコードが文字列としてどのように表現されるのかを決定します。format 引数には、レコードを受け取り文字列を返す関数を指定することもできます。次に例を示します。

format=lambda row: row.title

8~12行目では、"post"というテーブルを定義しています。 postは、"author" フィールド、"email" フィールド(postの作者のメールアドレスを保存します)、"text" 型の "body" フィールド(その作者によって投稿された実際のコメントを保存するために使用します)、idフィールドを介して db.image を指す参照型の "image_id" フィールドを持ちます。

14行目で db.image.title は、 "image" テーブルの "title" フィールドであることを表します。requires 属性は、web2pyフォームによって強制されることになる、要求/制約を設定することを可能にします。ここでは、"title" はユニークであることを要求します:

IS_NOT_IN_DB(db, db.image.title)

Field('title', unique=True) により自動的でセットされるため、オプションであることに注意してください。

これらの制約を表現するオブジェクトは、バリデータと呼ばれます。複数のバリデータは、リストでグループ化できます。バリデータは出現する順序で実行されます。 IS_NOT_IN_DB(a, b) は特殊なバリデータです。これは、新規レコードの b フィールドの値が、a にすでにないかをチェックします。

15行目は、"post" テーブルの "image_id" フィールドが db.image.id に存在することを要求します。データベースに関する限り "post" テーブルを定義した時点で、これはすでに宣言されています。 Line 15 requires that the field "image_id" of table "post" is in db.image.id. As far as the database is concerned, we had already declared this when we defined the table "post". ここではさらに、明示的に、この制約がweb2pyによって強制されることをモデルに知らせています。この制約は、新規のコメントが投稿された時、フォーム処理のレベルで強制されます。その結果、不正な値は入力フォームからデータベースへ伝搬しません。ここではまた、"image_id" が対応するレコードの "title"、'%(title)s' によって表現されるように要求しています。

20行目は、writable=False によって、"post" テーブルの "image_id" フィールドがフォームに表示されないように指示しています。さらに readable=False で、読み取り専用フォームでも表示されないようにしています。

17~18行目のバリデータの意味は明らかです。

format

なお、次のバリデータは、

db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s')

次のように、テーブル参照のformatを指定した場合、(自動的に)無視されます:

db.define_table('image', ..., format='%(title)s')

formatは文字列、またはレコードを受け取り文字列を返す関数にすることができます。

appadmin

一旦モデルが定義され、エラーがない場合、web2pyはデータベースを管理するためのアプリケーションの管理インターフェイスを作成します。このインターフェイスには、edit ページの "データベース管理"(database administration) リンクから、もしくは直接次のURLからアクセスします。

http://127.0.0.1:8000/images/appadmin

これは、appadmin インターフェイスのスクリーンショットです:

image

このインターフェイスは、"appadmin.py" というコントローラと対応する "appadmin.html" ビューで実装されています。以降、このインターフェイスを単に appadmin と呼びます。これにより、管理者は新規のデータベースレコードを挿入し、既存のレコードを編集及び削除し、テーブルを閲覧し、データベースの結合(join)を行えます。

appadmin に最初にアクセスした時、モデルが実行されテーブルが作成されます。web2pyのDALは、選択したデータベース・バックエンド(この例ではSQLite)固有のSQL文にPythonコードを変換します。生成されたSQLは、edit ページから "models" の下にある "sql.log" リンクをクリックして、見ることができます。ただし、テーブルが作成されるまでリンクは現れません。

image

モデルを編集し、再び appadmin にアクセスする場合、web2pyは既存のテーブル修正するSQLを生成します。生成されたSQLは "sql.log" にログとして記録されます。

さて、appadmin に戻って、新しい画像レコードを挿入してみましょう:

image

web2pyは、db.image.file の "upload" フィールドを、ファイルをアップロードするためのフォームに変換します。フォームがサブミットされ画像がアップロードされる時、拡張子はそのままで、安全な方法でファイルはリネームされ、アプリケーションの "uploads" フォルダの下に新しい名前で保存されます。新しい名前は db.image.file フィールドに保存されます。この処理は、ディレクトリトラバーサル攻撃を防ぐために設計されています。

なお、各フィールドの型はウィジェット(widget)によってレンダリングされています。デフォルトのウィジェットはオーバーライドすることができます。

appdamin においてテーブル名をクリックすると、web2pyは現在のテーブルの全てのレコードに対するselectを実行します。これは次のDALクエリと同一です。

db.image.id > 0

そして、結果は次のようにレンダリングされます。

image

DALクエリを編集し[Submit]ボタンを押して、異なるレコードセットをselectすることができます。

単一のレコードを編集、または削除するには、レコードのid番号をクリックします。

IS_IN_DB バリデータの影響で、"image_id" 参照フィールドはドロップダウンメニューでレンダリングされます。ドロップダウンの項目はキー(db.image.id)として格納されますが、バリデータで指定したように、db.image.title で表示されます。

バリデータは強力なオブジェクトです。これはどのようにフィールドを表示し、フィールドの値をフィルタし、エラーを生成し、フィールドから取り出した値をフォーマットするかを知っています。

次の図は、検証を通らないフォームをサブミットしたときに何が起こるかを示しています:

image

appadmin によって自動生成されたものと同じフォームは、SQLFORM ヘルパーを介してプログラム的に生成し、ユーザのアプリケーションに埋め込むことができます。これらのフォームは、CSSフレンドリで、カスタマイズすることができます。

全てのアプリケーションには appadmin が存在します。したがって、appadmin 自体、他のアプリケーションに影響を与えずに変更することができます。

ここまで、アプリケーションがデータを保存する方法、appadmin を介してどのようにデータベースにアクセスするかを見てきました。appadmin へのアクセスは管理者に対して制約されていて、アプリケーションのための本番用のwebインターフェイスとして意図されたものではありません。したがって、このウォークスルーの次のパートがあります。具体的には、次のものを作成します:

  • "index" ページ。これは、全ての利用可能な画像をtitleでソートして一覧表示します。そして、それらの画像に詳細ページへのリンクを張ります。
  • "show/[id]" ページ。これは、リクエストされた画像を訪問者に提示します。そして、コメントを見たり投稿したりできるようにします。
  • "download/[name]" アクション。アップロードした画像をダウンロードするために用いられます。

これはその図式です:

yUML diagram

edit ページに戻り、"default.py" コントローラを編集し、その内容を次のものと入れ替えてください:

select
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

このアクションは、辞書を返します。辞書の項目のキーは、アクションに関連付けられたビューに渡される変数として解釈されます。開発中にビューが存在しない場合、アクションは "generic.html" ビューにより表示されます。これは、全てのweb2pyアプリケーションで用意されています。

indexアクションは、db.image.title でソートされた、imageテーブルの全フィールド(db.image.ALL)のselectを実行します。selectの結果は、レコードが含まれた Rows オブジェクトです。これをアクションによってビューへ返される、images と呼ばれるローカル変数に割り当てます。images は反復可能(iterable)で、その要素はselectされた行になります。行ごとにカラムは辞書としてアクセスすることができます:

ビューを記述しない場合、辞書は "views/generic.html" によってレンダリングされます。indexアクションの呼び出しは次のように表示されます:

image

まだこのアクションのビューを作成していないので、web2pyはレコードをシンプルな表形式で表示しています。

では、indexアクション用のビューを作成します。adminに戻り、"default/index.html" を編集し、内容を次のように置き換えます:

{{extend 'layout.html'}}
<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

最初に注目する点は、ビューが特別な {{...}} タグを持つ純粋なHTMLということです。{{...}} に埋め込まれたコードは純粋なPythonのコードです。ただしインデントが無意味になります。コードのブロックは、行末にコロン(:)がついた行で始まり、pass というキーワードで始まる行で終わります。ブロックの終わりが明らかな場合には、pass は不要です。

5~7行目は、各行の画像のために、image行をループで回します:

LI(A(image.title, _href=URL('show', args=image.id))

これは、image.title を含む <a href="...">...</a> タグを含んだ <li>...</li> タグになります。ハイパーテキスト参照(href属性)の値は次のようになります:

URL('show', args=image.id)

つまりこれは、現在のリクエストと同じアプリケーションとコントローラ内にある、"show" という関数を呼び出しており、さらに、その関数に単一の引数 args=images.id を渡すURLです。 LIA などはweb2pyのヘルパーで、対応するHTMLタグをマッピングします。無名引数はシリアライズされるオブジェクトとして解釈され、タグのinnerHTMLに挿入されます。アンダースコアで始まる名前付き引数(例えば _href)はタグ属性として解釈されますが、ただし、アンダースコアは付きません。例えば、_hrefhref 属性、_classclass 属性、などになります。

例えば次の文は:

{{=LI(A('something', _href=URL('show', args=123))}}

次のようにレンダリングされます:

<li><a href="/images/default/show/123">something</a></li>

少数のヘルパ(INPUT, TEXTAREA, OPTION and SELECT)はまた、アンダースコアで始まらない特別な名前付き引数(valuerequires)をサポートしています。これらはカスタムフォームを構築するために重要です。後ほど説明します。

edit ページに戻ります。すると、"default.py exposes index" というものが示されます。"index" をクリックして、新しく作成したページを訪れることができます:

http://127.0.0.1:8000/images/default/index

これは次のように表示されます:

image

画像名のリンクをクリックすると、次に遷移します:

http://127.0.0.1:8000/images/default/show/1

これはエラーになります。なぜなら、"default.py" コントローラには、"show" というアクションがまだ作成されていないからです。

"default.py" コントローラを編集して、その内容を次のものと置き換えてみましょう:

SQLFORM
accepts
response.flash
request.args

response.download
def index():
    images = db().select(db.image.ALL, orderby=db.image.title)
    return dict(images=images)

def show():
    image = db.image(request.args(0,cast=int)) or redirect(URL('index'))
    db.post.image_id.default = image.id
    form = SQLFORM(db.post)
    if form.process().accepted:
        response.flash = 'your comment is posted'
    comments = db(db.post.image_id==image.id).select()
    return dict(image=image, comments=comments, form=form)

def download():
    return response.download(request, db)

このコントローラは、"show" と "download" の2つのアクションを含んでいます。 "show" アクションは、request.argsから解析された id の画像と、その画像に関連する全てのコメントをselectします。そして全てを "default/show.html" ビューに渡します。

画像のidは、"default/index.html" ビューでは、次によって参照されます:

URL('show', args=image.id)

これは、"show" アクションから、次のようにアクセスすることができます:

request.args(0,cast=int)

cast=int 引数はオプションですが、とても重要です。これは、PATH_INFOで渡された文字列の値をint型にキャスト(変換)しようとします。失敗した場合、チケット発行の代わりに適切な例外が発生します。次のようにキャストが失敗した場合に、リダイレクトを指定することもできます:

request.args(0,cast=int,otherwise=URL('error'))

また、db.image(...) は次のショートカットです。

db(db.image.id==...).select().first()

"download" アクションは、request.args(0) にファイル名がセットされていることを予期しています。そして、ファイルがある予定の場所へのパスを構築し、それをクライアントに返します。もしファイルが大きすぎる場合、いかなるメモリのオーバーヘッドも発生しないように、ファイルをストリーミングします。

以下の文(statement)について注意してください:

  • 6行目は、参照フィールドの値をセットします。これは入力フォームの一部ではありません。なぜなら、上で指定したフィールドのリストにないからです。
  • 7行目は、指定されたフィールドだけを使用し、db.post テーブルの挿入フォーム、SQLFORMを作成します。
  • 8行目は、現在のセッション内で(セッションは二重サブミッションの防止とナビゲーションの実施のために使われます)、サブミットされたフォーム(サブミットされたフォームの変数は request.vars にあります)を処理します。もしサブミットされたフォームの変数がバリデートされた場合、db.post テーブルに新規のコメントが挿入されます。そうでない場合、フォームはエラーメッセージを含むように変更されます(例えば、作者のメールアドレスが不適当である)。これは、全ての9行目で行われます!
  • 9行目は、フォームが受理されデータベース・テーブルにレコードを挿入した後のみ、実行されます。response.flash は、web2pyの変数でビューに表示され、訪問者に何が発生したかを通知するために使われます。
  • 10行目は、現在の画像を参照する、全てのコメントをselectします。

"download" アクションは、ひな形アプリケーションの "default.py" コントローラに、既に定義されています。

"download" アクションは辞書を返さないので、ビューは必要ありません。一方、"show" アクションはビューを持つべきです。このため、admin に戻って "default/show.html" という新規のビューを作成してください。

この新規のファイルを編集し、その内容を次のものと置き換えてください:

{{extend 'layout.html'}}
<h1>Image: {{=image.title}}</h1>
<center>
<img width="200px"
     src="{{=URL('download', args=image.file)}}" />
</center>
{{if len(comments):}}
  <h2>Comments</h2><br /><p>
  {{for post in comments:}}
    <p>{{=post.author}} says <i>{{=post.body}}</i></p>
  {{pass}}</p>
{{else:}}
  <h2>No comments posted yet</h2>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

このビューは、<img ... /> タグ内において、"download" アクションを呼び出すことによって image.file を表示します。 コメントがある場合は、それらに対してループを回し、一つずつ表示します。

どのように全てが表示されるかを以下に示します:

image

訪問者がこのページでコメントをサブミットすると、コメントがデータベースに保存され、ページの下部に追加されます。

認証の追加

web2pyのロールベースアクセス制御用のAPIは非常に洗練されています。しかし今のところ、認証されたユーザにshowアクションのアクセスを制限するのに、自分自身をも制限してしまいます。もっと詳細な説明は第9章で行います。

認証されたユーザのアクセスを制限するには、3つのステップを完了する必要があります。モデル、例えば "db.py" では、次の追加が必要です:

from gluon.tools import Auth
auth = Auth(db)
auth.define_tables(username=True)

コントローラに、次のアクションを追加する必要があります:

def user():
    return dict(form=auth())

これで、ログイン、ユーザ登録、ログアウトなどのページを有効にするには十分です。デフォルトのレイアウトでは、対応するページの右上のコーナーにオプションが表示されます。

image

制限したい関数をデコレートすることが、できるようになりました。例えば:

@auth.requires_login()
def show():
    ...

アクセスしようとすると、

http://127.0.0.1:8000/images/default/show/[image_id]

ログインが要求されます。ユーザがログインしていない場合は、次のアドレスにリダイレクトされます。

http://127.0.0.1:8000/images/default/user/login

image

user 関数は他にも、次のアクションを公開します:

http://127.0.0.1:8000/images/default/user/logout
http://127.0.0.1:8000/images/default/user/register
http://127.0.0.1:8000/images/default/user/profile
http://127.0.0.1:8000/images/default/user/change_password
http://127.0.0.1:8000/images/default/user/request_reset_password
http://127.0.0.1:8000/images/default/user/retrieve_username
http://127.0.0.1:8000/images/default/user/retrieve_password
http://127.0.0.1:8000/images/default/user/verify_email
http://127.0.0.1:8000/images/default/user/impersonate
http://127.0.0.1:8000/images/default/user/not_authorized

この時、ログインし、コメントを読み、投稿できるようにするために、最初にユーザを登録する必要があります。

ひな形アプリケーションでは、auth オブジェクトと user 関数は両方とも既に定義されています。auth オブジェクトは高度にカスタマイズ可能で、emailによる照合、登録の承認、CAPTHCA、プラグインを介したログインメソッドの変更を行うことができます。

グリッドの追加

管理インターフェイスを作成するための SQLFORM.gridSQLFORM.smartgrid の二つのガジェットを使用すれば、アプリケーションはさらに向上します:

@auth.requires_membership('manager')
def manage():
    grid = SQLFORM.smartgrid(db.image,linked_tables=['post'])
    return dict(grid=grid)

関連する "views/default/manage.html" です。

{{extend 'layout.html'}}
<h2>Management Interface</h2>
{{=grid}}

appadminを使用して "manager" グループを作成し、さらにそのグループに幾つかのユーザメンバーを作成します。このメンバーは以下のURLにアクセス可能になります。

http://127.0.0.1:8000/images/default/manage

ブラウジングや検索もできます。

image

画像の追加、更新、削除、そして画像へのコメントが行なえます。

image

レイアウトの設定

"views/layout.html" を編集することにより、デフォルトのレイアウトを、HTMLの編集なしで設定することが可能です。実際、第5章で説明する、 "static/base.css" のスタイルシートはよく文書化されています。HTMLを編集せずに、色、カラム数、サイズ、枠線、背景を変更することができます。メニュー、タイトル、サブタイトルを編集したい場合は、任意のモデルファイルで行うことができます。ひな形のアプリは "models/menu.py" ファイルで、以下のパラメータのデフォルト値を設定します:

response.title = request.application
response.subtitle = 'customize me!'
response.meta.author = 'you'
response.meta.description = 'describe your app'
response.meta.keywords = 'bla bla bla'
response.menu = [ [ 'Index', False, URL('index') ] ]

シンプル wiki

wiki
RSS
Ajax
XMLRPC

このセクションではシンプルwikiを、低レベルAPIのみを使用しスクラッチから構築します(次のセクションでデモを行う、web2pyのビルトインwikiの機能を使用するのではありません)。訪問者はページの作成、(タイトルによる)検索、編集を行うことができます。訪問者はまた、(前のアプリケーションと全く同様に)コメント投稿を行うことができ、そして(ページ添付のによう)ドキュメント投稿すること、ページからそれにリンクを張ることができるようになります。約束事として、ここではWiki構文のためにMarkmin構文を採用します。ここではまた、Ajaxを用いた検索ページ、そのページに対するRSSフィード、XML-RPC[xmlrpc] を介したページ検索用のハンドラ、を実装します。次の図は、実装が必要なアクションと、それらの間で構築すべきリンクを列挙しています。

yUML diagram

"mywiki" という名の、新規のひな形アプリを作成し始めましょう。

モデルはページ(page)、コメント(comment)、ドキュメント(document)という3つのテーブルを持つ必要があります。commentとdocumentの両者はpageを参照します。それらはpageに属しているからです。documentは、前回の画像アプリケーションのように、upload型のファイル・フィールドを持ちます。

以下に全てのモデルを示します:

db = DAL('sqlite://storage.sqlite')

from gluon.tools import *
auth = Auth(db)
auth.define_tables()
crud = Crud(db)

db.define_table('page',
    Field('title'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(title)s')

db.define_table('post',
    Field('page_id', 'reference page'),
    Field('body', 'text'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id))

db.define_table('document',
    Field('page_id', 'reference page'),
    Field('name'),
    Field('file', 'upload'),
    Field('created_on', 'datetime', default=request.now),
    Field('created_by', 'reference auth_user', default=auth.user_id),
    format='%(name)s')

db.page.title.requires = IS_NOT_IN_DB(db, 'page.title')
db.page.body.requires = IS_NOT_EMPTY()
db.page.created_by.readable = db.page.created_by.writable = False
db.page.created_on.readable = db.page.created_on.writable = False

db.post.body.requires = IS_NOT_EMPTY()
db.post.page_id.readable = db.post.page_id.writable = False
db.post.created_by.readable = db.post.created_by.writable = False
db.post.created_on.readable = db.post.created_on.writable = False

db.document.name.requires = IS_NOT_IN_DB(db, 'document.name')
db.document.page_id.readable = db.document.page_id.writable = False
db.document.created_by.readable = db.document.created_by.writable = False
db.document.created_on.readable = db.document.created_on.writable = False

"default.py" コントローラを編集し、以下のアクションを作成してください:

  • index: 全てのwikiページを列挙する
  • create: 新しいwikiページを追加する
  • show: wikiページとそのコメントを表示し、新しいコメントを追加する
  • edit: 既存のページを編集する
  • documents: ページに添付された文書を管理する
  • download: (images(訳注:この章で説明されたサンプル・アプリ)の例のように)文章をダウンロードする
  • search: 検索用のボックスを表示し、Ajaxコールバックを介して、訪問者が入力したタイトルに該当するもの全てを返す
  • callback: Ajax用のコールバック関数。訪問者の入力に合わせて、検索ページに埋め込まれるHTMLを返す

以下、"default.py" コントローラです:

def index():
     """ this controller returns a dictionary rendered by the view
         it lists all wiki pages
     >>> index().has_key('pages')
     True
     """
     pages = db().select(db.page.id,db.page.title,orderby=db.page.title)
     return dict(pages=pages)

@auth.requires_login()
def create():
     """creates a new empty wiki page"""
     form = SQLFORM(db.page).process(next=URL('index'))
     return dict(form=form)

def show():
     """shows a wiki page"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.post.page_id.default = this_page.id
     form = SQLFORM(db.post).process() if auth.user else None
     pagecomments = db(db.post.page_id==this_page.id).select()
     return dict(page=this_page, comments=pagecomments, form=form)

@auth.requires_login()
def edit():
     """edit an existing wiki page"""
     this_page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     form = SQLFORM(db.page, this_page).process(
         next = URL('show',args=request.args))
     return dict(form=form)

@auth.requires_login()
def documents():
     """browser, edit all documents attached to a certain page"""
     page = db.page(request.args(0,cast=int)) or redirect(URL('index'))
     db.document.page_id.default = page.id
     db.document.page_id.writable = False
     grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id])
     return dict(page=page, grid=grid)

def user():
     return dict(form=auth())

def download():
     """allows downloading of documents"""
     return response.download(request, db)

def search():
     """an ajax wiki search page"""
     return dict(form=FORM(INPUT(_id='keyword',_name='keyword',
              _onkeyup="ajax('callback', ['keyword'], 'target');")),
              target_div=DIV(_id='target'))

def callback():
     """an ajax callback that returns a <ul> of links to wiki pages"""
     query = db.page.title.contains(request.vars.keyword)
     pages = db(query).select(orderby=db.page.title)
     links = [A(p.title, _href=URL('show',args=p.id)) for p in pages]
     return UL(*links)

2~6行目は、indexアクションのコメントを構成します。コメント内にある4~5行目は、テストコード(doctest)としてpythonによって解釈されます。テストは管理インターフェイスから実行できます。この場合、テストはindexアクションがエラーなしで実行されることを検証します。

18、27、35行目は、request.args(0) のidを持つ、page レコードを取り出そうと試みます。

13、20行目は、それぞれ新規ページ、新規コメントのための作成フォームを定義し処理します。

28行目は、wikiページのための更新フォームを定義し処理します。

38行目は、ページにリンクしているコメントの表示、追加及び更新することができる、grid オブジェクトを作成をします。

51行目は、幾つかの魔法が起こっています。"keyword" (訳注:という名前)のINPUTタグの onkeyup 属性が設定されます。訪問者がキーを放すたびに、onkeyup 属性内におけるJavaScriptコードが、クライアント・サイドで実行されます。そのJavaScriptコードは次の通りです:

ajax('callback', ['keyword'], 'target');

ajax は、デフォルトの "layout.html" により組み込まれる "web2py.js" ファイルに、定義されているJavaScript関数です。これは3つのパラメタをとります: 同期コールバックを実行するアクションのURL、コールバックに送る変数のIDリスト(["keyword"])、そして、レスポンスが挿入される場所のID("target")です。

検索ボックスに何かをタイプしキーを放すとすぐに、クライアントはサーバーを呼び出し、'keyword' フィールドの内容を送信します。そして、サーバーが応答したら、そのレスポンスは 'target' タグのinnerHTMLとして、ページ自身に埋め込まれます。

'target' タグは52行目で定義されるDIVです。これはビューにおいても定義することができます。

これは "default/create.html" ビューのコードです:

{{extend 'layout.html'}}
<h1>Create new wiki page</h1>
{{=form}}

ユーザ登録及びログインしていると仮定すると、もし create ページを訪問した場合、次のように表示されます:

image

"default/index.html" ビューのコードです:

{{extend 'layout.html'}}
<h1>Available wiki pages</h1>
[ {{=A('search', _href=URL('search'))}} ]<br />
<ul>{{for page in pages:}}
     {{=LI(A(page.title, _href=URL('show', args=page.id)))}}
{{pass}}</ul>
[ {{=A('create page', _href=URL('create'))}} ]

これは、次のページを生成します:

image

"default/show.html" ビューのコードです:

markdown
MARKMIN

{{extend 'layout.html'}}
<h1>{{=page.title}}</h1>
[ {{=A('edit', _href=URL('edit', args=request.args))}}
| {{=A('documents', _href=URL('documents', args=request.args))}} ]<br />
{{=MARKMIN(page.body)}}
<h2>Comments</h2>
{{for post in comments:}}
  <p>{{=db.auth_user[post.created_by].first_name}} on {{=post.created_on}}
     says <i>{{=post.body}}</i></p>
{{pass}}
<h2>Post a comment</h2>
{{=form}}

markimin構文の代わりにmarkdown構文を使用する場合、次のようにします:

from gluon.contrib.markdown import WIKI as MARKDOWN

そして、MARKMIN ヘルパの代わりに MARKDOWN を使用してください。 また、markminの構文の代わりに、未処理のHTMLを受け入れることを選択することができます。この場合、次を:

{{=MARKMIN(page.body)}}

次に置き換えます:

{{=XML(page.body)}}
sanitize

(XMLはエスケープしません。しかしweb2pyはセキュリティを理由に、通常はデフォルトでエスケープします)。

これは次のようにした方が良いです:

{{=XML(page.body, sanitize=True)}}

sanitize=True と設定すると、 "<script>" タグのような安全でないXMLタグをエスケープするようにし、XSSの脆弱性を防ぎます。

これで、indexページからページタイトルをクリックすると、作成したページを見ることができます:

image

"default/edit.html" ビューのコードです:

{{extend 'layout.html'}}
<h1>Edit wiki page</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
{{=form}}

これは作成ページと、ほぼ同じように見えるページを生成します。

次は "default/documents.html" ビューのコードです:

{{extend 'layout.html'}}
<h1>Documents for page: {{=page.title}}</h1>
[ {{=A('show', _href=URL('show', args=request.args))}} ]<br />
<h2>Documents</h2>
{{=grid}}

"show" ページでdocuments(訳注:ページ上部にあるリンクのこと)をクリックすると、ページに添付されたドキュメントを管理することができます。

image

最後は "default/search.html" ビューのコードです:

{{extend 'layout.html'}}
<h1>Search wiki pages</h1>
[ {{=A('listall', _href=URL('index'))}}]<br />
{{=form}}<br />{{=target_div}}

これは、次のようなAjax検索フォームを生成します:

image

例えば次のURLに訪れることで、コールバック・アクションを直接呼び出すことも可能です:

http://127.0.0.1:8000/mywiki/default/callback?keyword=wiki

このページのソースを見ると、コールバックによって返されるHTMLが次のようになります:

<ul><li><a href="/mywiki/default/show/4">I made a Wiki</a></li></ul>
rss

web2pyを用いてwikiページからRSSフィードを生成することは簡単です。なぜならweb2pyには、gluon.contrib.rss2 が含まれているからです。単に、次のアクションをdefaultコントローラに追加してください:

def news():
    """generates rss feed from the wiki pages"""
    response.generic_patterns = ['.rss']
    pages = db().select(db.page.ALL, orderby=db.page.title)
    return dict(
       title = 'mywiki rss feed',
       link = 'http://127.0.0.1:8000/mywiki/default/index',
       description = 'mywiki news',
       created_on = request.now,
       items = [
          dict(title = row.title,
               link = URL('show', args=show.id, scheme=True, 
	                  host=True, extension=False)
               description = MARKMIN(row.body).xml(),
               created_on = row.created_on
               ) for row in pages])

そして、次のページを訪問すると

http://127.0.0.1:8000/mywiki/default/news.rss

フィードが表示されます(フィードリーダによって見た目は異なります)。なお、URLの拡張子が.rssなので、dictは自動的にRSSに変換されています。

image

web2pyはまた、サードパーティのフィードを読むためのフィードパーサ(フィード解析)も含んでいます。

次の行に注意してください:

response.generic_patterns = ['.rss']

URLのglobパターンの最後が ".rss" の時、汎用ビュー(このケースでは "views/generic.rss")を使用するようweb2pyに指示します。デフォルトでは、開発目的のためのローカルホストからのみ、汎用ビューの利用が許可されています。

XMLRPC

最後に、プログラムでwikiを検索可能にするXML-RPCハンドラを追加しましょう:

service = Service()

@service.xmlrpc
def find_by(keyword):
     """finds pages that contain keyword for XML-RPC"""
     return db(db.page.title.contains(keyword)).select().as_list()

def call():
    """exposes all registered services, including XML-RPC"""
    return service()

ハンドラのアクションは、リストに指定された関数を(XML-RPCを介して)単純に公開しています。このケースでは、find_by です。find_by はアクションではありません(引数があるためです)。この関数は、.select() でデータベースに問い合わせ、.response でレコードをリストとして取り出し、そのリストを返します。

ここに、どのように外部のPythonプラグラムからXML-RPCハンドラにアクセスするかの例を示します。

>>> import xmlrpclib
>>> server = xmlrpclib.ServerProxy(
    'http://127.0.0.1:8000/mywiki/default/call/xmlrpc')
>>> for item in server.find_by('wiki'):
        print item['created_on'], item['title']

ハンドラは、XML-RPCを理解する多くのプログラミング言語(C、C++、C#、Javaなど)からアクセスすることができます。 The handler can be accessed from many other programming languages that understand XML-RPC, including C, C++, C# and Java.

datedatetime そして time の書式

datedatetime そして time のフィールドの型のそれぞれに、3つの異なる表現があります。

  • データベースでの表現
  • web2py内部での表現
  • フォームやテーブルで使われる文字列表現

データベースの表現は、内部の問題であり、コードには影響しません。web2pyの内部では、それぞれ datetime.datedatetime.datetime そして datetime.time というオブジェクトの表現で保存されています。そして次のように操作することができます。

for page in db(db.page).select():
    print page.title, page.day, page.month, page.year

フォームの中で日付が文字列に変換される場合、次のISOの表現に従って変換されます。

%Y-%m-%d %H:%M:%S

この表現は国際化が行なわれていますが、この表現を別のものに変更するためにadminの翻訳ページを使用することもできます。例えば:

%m/%b/%Y %H:%M:%S

デフォルトではweb2pyは、アプリケーションが英語で書かれていると想定しているため、英語では翻訳されません。英語用の国際化の機能を有効にしたい場合には、(adminを使用して)翻訳ファイルを作成する必要があります。そしてアプリケーションの現在の言語が英語以外であることを、次のように宣言する必要があります。

T.current_languages = ['null']

ビルトイン web2py wiki

ビルトイン web2py wiki の使用例を説明していきますが、これによって、前セクションでビルトインしたコードを忘れることができます(web2py API を学んだことを忘れるということではなく、コード例のことです)。

実際web2pyは、メディア添付、タグ、タグクラウド、ページアクセス制御、oembedのサポート [oembed]、そしてコンポーネント(14章)、を含むwiki機能が付属しています。このwikiは、web2pyのどのアプリケーションでも使用することが可能です。

ビルトインwikiのAPIは、まだ実験的であり、小さな変更の可能性があると考えられます。

ここで "wikidemo" は、"welcome" アプリケーションの単純な複製から、スクラッチを始めるということを仮定します。コントローラを編集し、"index" アクションを次のように置き換えます。

def index(): return auth.wiki()

これは完全に動作するwikiです。作成されたページがない時にページを作成するためには、ログインしている必要があり、さらに "wiki_editor" もしくは "wiki_author" グループのメンバーである必要があります。もし管理者としてログインしている場合は、"wiki_editor" グループが自動作成され、そのグループのメンバーになります。editorとauthorの違いは、editorはページの作成と任意のページの編集及び削除が可能です。authorはページの作成(幾つかのオプション制限付き)は可能ですが、修正/削除は自分が作成したページにのみ可能になります。

auth.wiki() 関数は、ひな形の "views/default/index.html" が理解可能な、content キーを持つ辞書を返します。このアクションのために、次のように独自のビューを作成することが可能です:

{{extend 'layout.html'}}
{{=content}}

そして必要に応じて、特別なHTMLやコードを追加します。wikiを公開する "index" アクションは、使用する必要はありません。異なる名前のアクションの使用が可能です。

wikiを試すには、単に管理者でログインし、次のページを訪問してください。

http://127.0.0.1:8000/wikidemo/default/index

その後にslug(出版業界では、slugは作成中の記事に対する短い名前です)を選択し、MARKMIN wiki 記法を使っったコンテンツの編集が可能な空のページにリダイレクトします。"[wiki]" という新しいメニュー項目が、作成、検索、そしてページの編集を許可します。wikiページは、次のようなURLになります:

http://127.0.0.1:8000/wikidemo/default/index/[slug]

サービスページは、アンダースコアで始まる名前になります:

http://127.0.0.1:8000/wikidemo/default/index/_create
http://127.0.0.1:8000/wikidemo/default/index/_search
http://127.0.0.1:8000/wikidemo/default/index/_could
http://127.0.0.1:8000/wikidemo/default/index/_recent
http://127.0.0.1:8000/wikidemo/default/index/_edit/...
http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
http://127.0.0.1:8000/wikidemo/default/index/_preview/...

"index"、"aboutus"、"contactus" などのページを、さらに作成してみましょう。 それらの編集をしてみます。

wiki メソッドのシグネチャは次の通りです:

def wiki(self, slug=None, env=None, render='markmin',
         manage_permissions=False, force_prefix='',
         restrict_search=False, resolve=True,
         extra=None, menugroups=None)

これは次の引数を取ります:

  • render デフォルトは 'markmin' です。但し、'html' も同じようにセット可能です。これは wiki 構文を決定します。後で、markmin wiki マークアップについて説明します。もし HTML に変更した場合は、TinyMCE もしくは NicEdit のような、WYSIWYG javascriptエディタの使用が可能になります。
  • manage_permissions デフォルトは False がセットされており、"wiki_editor" 及び "wiki_author" のみを権限として認めています。もし True に変更した場合、ページの読み込みと編集権限を持つメンバーのグループ名(複数)を指定するオプションを、作成/編集ページにて提供します。"everybody" グループは、全てのユーザを含みます。
  • force_prefix もし '%(id)s-' のようにセットする場合、"[user id]-[page name]" のようなプリフィックスを持つページを作成するauthor(editorではない)が制限されます。プレフィックスは、id ("%(id)s") ユーザ名("%(username)s")、もしくは、URLバリデーションをパスする有効な文字列を含む列に対応しているのに限り、auth_userテーブルの他の任意のフィールドを含めることができます。
  • restrict_search デフォルトは False で、どのログインしているユーザも全てのwikiページを検索することができます(但し、それらを読み込んだり、編集する必要はありません)。もし True をセットした場合、authorは自分のページのみ検索が可能、editorは全ての検索が可能、他のユーザは検索することが不可能になります。
  • menu_groups デフォルトは None で、これはwiki管理メニュー(検索、作成、編集など)を常に表示することを表します。例えば ['wiki_editor','wiki_author'] のように、このメニューを表示可能なメンバーのみの、グループのリストをセットすることが可能です。注意、例え誰に対してもメニューが公開されていたとしても、アクセス制御により制限されているため、メニューにリストされたアクションの実行が誰に対しても許可されるという意味ではありません。

wiki メソッドは後で説明しますが、次のような追加のパラメータを幾つか持っています: slugenv、そして extra。 The wiki method has some additional parameters which will be explained later: slug, env, and extra.

MARKMIN 基本

太字 テキストは **太字**イタリック テキストは ''イタリック''、そして コード テキストは二重反転引用符で区切ることにより、MARKMIN構文のマークアップが可能です。タイトルは、#でプレフィックスする必要があり、##でセクション、###でサブセクションになります。順序のないプレフィックスはマイナス(-)を、順序のあるプレフィックスはプラス(+)を、使用してください(訳注:これはリストで使用します)。URLは自動的にリンクに変換されます。次はmarkminテキスト例です:

# This is a title
## this is a section title
### this is a subsection title

Text can be **bold**, ''italic'', ``code`` etc.
Learn more at:

http://web2py.com

MARKMINヘルパーに対して特別なレンダリングルールを渡すために、auth.wikiextra パラメータの使用が可能です。

MARKMIN構文についてのもっと詳しい情報は、5章を参照してください。

auth.wiki は、ベアボーンMARKMINヘルパーと比べて、より強力です。oembedとコンポーネントをサポートしています。

wikiに関数を公開するために、auth.wikienv パラメータを使用可能です。 例えば:

auth.wiki(env=dict(join=lambda a,b,c:"%s-%s-%s" % (a,b,c)))

次のようにマークアップ構文を使用できます:

@(join:1,2,3)

これは、特別なパラメータとして a,b,c=1,2,3 が渡されるjoin関数を呼び出し、1-2-3 のようにレンダリングします。

Oembed プロトコル

wikiページに任意のURLを入力(もしくはカット&ペースト)することができ、URLはリンクとしてレンダリングされます。しかし次の例外があります:

  • URLに画像の拡張子がある場合、リンクが画像 <img/> として埋め込まれます。
  • URLにオーディオの拡張子がある場合、リンクがHTML5オーディオ <audio/> として埋め込まれます。
  • URLにビデオの拡張子がある場合、リンクがHTML5ビデオ <video/> として埋め込まれます。
  • URLにMSオフィスもしくはPDFの拡張子がある場合、Googleドキュメントビューアが埋め込まれ、ドキュメントの内容を表示します(公開ドキュメントに対してだけ動きます)。
  • URLがYouTubeページ、Vimeoページ、もしくはFlickrページを指している場合、web2pyは対応するwebサービスにコンタクトし、コンテンツを埋め込む適切な方法を照会します。これは、oembed プロトコルを使用し実行されます。

ここにサポートするフォーマットの完全なリストを示します:

Image (.PNG, .GIF, .JPG, .JPEG)
Audio (.WAV, .OGG, .MP3)
Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)

Googleドキュメントビューアでのサポートは次の通りです:

Microsoft Excel (.XLS and .XLSX)
Microsoft PowerPoint 2007 / 2010 (.PPTX)
Apple Pages (.PAGES)
Adobe PDF (.PDF)
Adobe Illustrator (.AI)
Adobe Photoshop (.PSD)
Autodesk AutoCad (.DXF)
Scalable Vector Graphics (.SVG)
PostScript (.EPS, .PS)
TrueType (.TTF)
xml Paper Specification (.XPS)

oembedによるサポートは次の通りです:

flickr.com
youtube.com
hulu.com
vimeo.com
slideshare.net
qik.com
polleverywhere.com
wordpress.com
revision3.com
viddler.com

これはweb2pyの gluon.contrib.autolinks ファイルの、具体的には expand_one 関数に実装されています。より多くのサービスを登録することにより、oembedのサポートを拡張することが可能です。これは EMBED_MAPS リストに、エントリを追加することで行われます:

from gluon.contrib.autolinks import EMBED_MAPS
EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
                   'http://vimeo.com/api/oembed.json'))

wikiコンテンツの参照

もしslugが "contactus" でwikiページを作成した場合、このページは次のように参照可能です。

@////contactus

ここで、@//// は次のコードを表しています。

@/app/controller/function/

しかし "app"、"controller"、及び "function" は、デフォルトを想定しているため省略しています。

同様に、ページにリンクされているメディアファイル(例えば画像)をアップロードするために、wikiメニューを使用することができます。"manage media" ページは、アップロードした全てのファイルを表示し、しかもメディアファイルをリンクするための適切な表記で表示します。例えば、"beach" というタイトルで "test.jpg" ファイルをアップロードした場合、リンクの表記は次のようになります:

@////15/beach.jpg

@//// は、前に説明したプレフィックスと同じです。15 はメディアファイルを格納したレコードのid番号です。beach はタイトルです。.jpg はオリジナルのファイルの拡張子です。

もしwikiページに @////15/beach.jpg をカット&ペーストした場合、画像を埋め込むことになります。

メディアファイルはページにリンクされており、ページのアクセス権を継承することに注意してください。

Wiki メニュー

slugが "wiki-menu" というページを作成した場合、メニューの説明として解釈されます。次はその例です:

- Home > @////index
- Info > @////info
- web2py > http://www.web2py.com
- - About us > @////aboutus
- - Contact us > @////contactus

各行がメニュの項目です。二重のダッシュが、ネストしたメニュ項目として使用しています。> はメニュ項目リンクから、メニュ項目タイトルを分離しています。

メニューは、response.menu に追加されていることに注意してください。それは置き換えではありません。サービス関数の [wiki] メニュー項目は、自動で追加されます。

サービス関数

例えばもし、編集可能なサイドバーの作成にwikiを利用したい場合、slug="sidebar"

{{=auth.wiki(slug='sidebar')}}

"sidebar" という言葉が、特別なものでないことに注意してください。どのwikiページもどの時点のコードでも、取得し埋め込むことができます。標準のweb2pyの機能とwikiの機能を、混在させたり調和させたりすることができます。

auth.wiki('sidebar')
auth.wiki(slug='sidebar')
は同等です。これはslugが、メソッドシグネチャの第一キーワードパラメータのためです。前者は少し簡単な構文を提供します。

タグ検索などの特別なwikiの関数も埋め込むことが可能です:

{{=auth.wiki('_search')}}

もしくはタグクラウド:

{{=auth.wiki('_cloud')}}

auth.wiki 機能の拡張

wiki対応アプリがもっと複雑になった場合、認証インターフェースによるwiki DBレコードの管理をカスタマイズしたり、wiki CRUDタスクにカスタマイズフォームを公開するなどが、たぶん必要になるかもしれません。例えば、wikiテーブルレコードの表示をカスタマイズしたり、新しいフィールドバリデータを追加することもできます。auth.wiki()メソッドでwikiインターフェースをリクエストされた後にwikiモデルを定義しているため、これはデフォルトでは許可されていません。アプリのモデル内にある、wikiの特定のdbセットアップにアクセスするには、次のセンテンスをモデルファイル(すなわちdb.py)に追加することが必要です。

# Make sure this is called after the auth instance is created
# and before any change to the wiki tables
auth.wiki(resolve=False)

モデルで上記の行を使用することにより、カスタムCRUDや他のdbタスクで、wikiテーブルがアクセス可能になります(つまり wiki_page)。

wikiインターフェースを公開する要求では、コントローラやビューでauth.wiki()をまだコールする必要があります。これは resolve=False パラメータが、他のインターフェースのセットアップなしで、wikiモデルだけをビルドするようにauthオブジェクトに指示するためです。

また、メソッド呼び出しで設定を False で解決することによって、wikiレコード管理用の <app>/appadmin のデフォルトのdbインターフェースを介して、wikiテーブルがアクセス可能になります。

この他のカスタマイズとして、標準のwikiテーブルに特別にフィールドを追加することができます(9章で説明する、auth_user テーブルと同じ方法です)。次のようにします:

# Place this after auth object initialization
auth.settings.extra_fields["wiki_page"] = [Field("ablob", "blob"),]

上記の行は、wiki_page テーブルに blob フィールドを追加します。このオプションや、他のカスタマイズのためにwikiモデルへのアクセスを必要としない限り、

auth.wiki(resolve=False)
を呼び出す必要はありません。

コンポーネント

新しいweb2pyの最もパワフルな機能の一つは、他のアクションの内部にアクションを埋め込む能力にあります。これをコンポーネントと呼びます。

次のようなモデルを考えてみます:

db.define_table('thing',Field('name',requires=IS_NOT_EMPTY()))

そして次のアクション:

@auth.requires_login()
def manage_things():
    return SQLFORM.grid(db.thing)

このアクションは特別です。なぜなら辞書オブジェクトでない、ウィジット/ヘルパーを返すためです。今この manage_things アクションを、次を用いて任意のビューに埋め込むことができます。

{{=LOAD('default','manage_things',ajax=True)}}

訪問者が、ウィジットを埋め込んだホストページをリロードなしで、Ajaxを介したコンポーネントと対話することができます。アクションはAjaxを介して呼び出され、ホストページのスタイルを継承し、現在のページ内で処理されるために、全てのフォームのサブミットとflashメッセージをキャプチャします。この上の SQLFORM.grid ウィジットは、アクセスを制限するためにデジタル署名されたURLを使用しています。コンポーネントの詳細については、13章で説明します。

上記のようなコンポーネントを、MARKMIN構文を使用したwikiページに埋め込むことが可能です:

@{component:default/manage_things}

これは単に、Ajax "コンポーネント" のように、"default" コントローラに定義された "manage_things" アクションを含めることを、web2pyに指示します。

ページ及びメニュー、wikiページに埋め込んだカスタムコンポーネントの作成に auth.wiki を使用することにより、ほとんどのユーザは比較的複雑なアプリケーションを簡単に構築できるようになります。wikiはグループのメンバーがページを作成できるようにする、メカニズムと考えることができますが、モジュール方式によるアプリケーション開発とも考えることができます。

adminの追加情報

admin

管理インターフェイスはさらなる機能を提供します。ここではそれを簡単に見ていきます。

サイト

site

このページは、web2pyのメインの管理インターフェースです。左側にインストールされいる全てアプリケーションが一覧表示され、右側には幾つかの特別なアクションのフォームがあります。

それらの最初にはweb2pyのバージョンが表示され、新しいバージョンが利用可能であればアップグレードを薦めます。もちろん、アップグレード前に完全に動作可能なバックアップがあることを確認してください!。

さらに、名前を指定することによって新しいアプリケーションの作成を可能にする、2つの異なったフォーム(シンプル、もしくはオンラインウィザードを使うもの)があります。

Instant Press
Movuca
次のフォームは、ローカルファイルもしくはリモートURLのいずれかから、既存のアプリケーションのアップロードを許可します。アプリケーションのアップロード時に、名前を指定する必要があります(違う名前を使うことによって、同じアプリケーションの複数のコピーのインストールを許可します)。例えば、Bruno Rochaによって作成されたMovuca Social Networking applicationアプリを試すことができます:

https://github.com/rochacbruno/Movuca

もしくはMartin Muloneによって作成されたInstant Press CMS:

http://code.google.com/p/instant-press/

もしくは、利用可能なたくさんのサンプルアプリケーションの一つ:

http://web2py.com/appliances

web2pyのファイルは .w2p ファイルにパッケージングされます。これは tar gzip されたファイルです。web2pyでは、ブラウザにおけるダウンロード時のファイル解凍を防ぐため、拡張子 .tgz の代わりに拡張子 .w2p を使います。これらは、tar zxvf [ファイル名] として、手動で解凍することができます。ただし、その必要性は全くありません。

image

アップロードに成功すると、 web2pyはアップロードしたファイルのMD5チェックサムを表示します。これにより、ファイルがアップロード中に破損していないことを確認することができます。InstantPressの名前がインストールされたアプリケーションのリストに表示されます。

もしweb2pyをソースから動かしており、さらに gitpython をインストール(必要であれば、'easy_install gitpython' でセットアップ)している場合、アップロードフォームに .git URL を使用し、gitリポジトリから直接アプリケーションをインストールできます。このケースでも、リポジトリに変更をプッシュバックするための管理インターフェースの使用が有効になりますが、しかしこれは実験的な機能です。

例えば、web2pyサイト上のこのBookを表示するアプリケーションを、次のURLでローカルにインストールできます:

https://github.com/mdipierro/web2py-book.git

リポジトリは現在の、このBookの更新バージョンを提供します(現在のwebサイトで見ている安定バージョンと異なる可能性があります)。pullリクエストフォームでの、改善と修正及び訂正の提出のために、是非使用してください。

各インストールされたアプリケーションにおいて、site ページは次のことを可能にします:

  • 名前をクリックすることによって、直接アプリケーションに移動。
  • アプリケーションのアンインストール。
  • aboutページ(下で説明します)への遷移。
  • editページ(下で説明します)への遷移。
  • errorsページ(下で説明します)への遷移。
  • 一時ファイル(セッション、エラー、cache.diskファイル)のクリーンアップ。
  • 全てのパック。これは、アプリケーションの完全なコピーを保持するtarファイルを返します。アプリケーションをパックする前に、一時ファイルをクリーンアップするべきです。
  • アプリケーションのコンパイル。エラーがない場合は、このオプションは全てのコントローラ、ビュー、モデルをバイトコードとしてコンパイルします。ビューは木のように他のビューの中で拡張や組み込みができるため、バイトコンパイルする前に、全てのコントローラに対するビューの木構造は1つのファイルに折りたたまれます。この実質的な効果としてバイトコンパイルされたアプリケーションは、テンプレートの解析や文字列の置換が実行時に行われなくなるため、より高速に動作します。
  • コンパイル結果のパック。このオプションは、バイトコードでコンパイルしたアプリケーションにのみ提示されます。これは、クローズドソースとしてソースコードを含まない状態でアプリケーションをパックできるようにします。ただし、Pythonは(他のプログラミング言語と同様に)技術的には逆コンパイルすることができます。そのためコンパイル結果では、ソースコードの完全な保護は行えません。しかし、逆コンパイルは難しく、違法行為です。
  • コンパイル結果の削除。これは単に、アプリケーションからバイトコードでコンパイルされた、モデル、ビュー、コントローラを削除します。アプリケーションがソースコードとともにパックされているか、ローカルで動くようになっている場合、バイトコードでコンパイルされたファイルを削除することはなんら弊害はなく、アプリケーションは動作し続けます。アプリケーションがコンパイル結果をパックしたファイルからインストールされた場合は、安全ではありません。元に戻すためのソースコードがないためで、アプリケーションはもはや動作しなくなります。
admin.py

web2pyのadminサイトのページで利用できる全ての機能はまた、gluon/admin.pyモジュールで定義されているAPIを介してプログラム的にアクセスできます。単にPythonのシェルを開いて、このモジュールをインポートしてください。

もしGoogle App Engine SDKがインストールされている場合、管理 site ページはアプリケーションをGAEにプッシュするボタンを表示します。もし python-git がインストールされている場合、アプリケーションをOpen Shift にプッシュするボタンもあります。Heroku や他のホスティングサービスのインストールのための適切なスクリプトは、"scripts" フォルダを参照ください

About

about
license

about タブから、アプリケーションの説明とライセンス条項を編集することができます。これらはそれぞれに、applicationフォルダにあるABOUTとLICENCEファイルに書き込まれます。

image

これらのファイルに対して MARKMINgluon.contrib.markdown.WIKI の構文を使用することができ、[markdown2] で説明しています。

デザイン

EDIT

本章では、すでに edit ページを使用しています。ここでは edit ページのさらに、幾つかの機能に注目します。

  • 任意のファイル名をクリックすると、構文が強調表示された状態でファイルの内容を見ることができます。
  • editをクリックすると、Webインタフェースを介してファイルを編集することができます。
  • deleteをクリックすると、ファイルを(完全に)削除することができます。
  • testをクリックすると、web2pyはテストを走らせます。テストはPythonのdoctestを使用し書かれます。そして各関数は自分自身のテストがあります。
  • 言語ファイルを追加し、全ての文字列を見つけるためアプリをスキャンし、そしてWebインターフェースを介して翻訳文字列を編集することができます。
  • 静的ファイルがフォルダとサブフォルダで構成されている場合、フォルダ階層はフォルダ名をクリックすることで切り替えることができます。

下の画像は、welcomeアプリケーションのテストページの出力を示しています。

image

下の画像は、welcomeアプリケーションのlanguagesタブを示しています。

image

下の画像は、どのように言語ファイルを編集するかを示しています。このケースでは、welcomeアプリケーションでの "it" (イタリア)語のファイルです。

image

統合デバッガー

(Python2.6 以降が必要です) (requires Python 2.6 or later)

web2py管理インターフェースには、webベースデバッガーが含まれています。提供されているwebベースエディタを使用し、Pythonコードにブレークポイントを追加することができ、関連するデバッガーコンソールからブレークポイントのシステム変数を検査と、実行を再開することができます。これは次のスクリーンショットで説明します:

image

この機能はMariano Reingartにより作成された、Qdbデバッガーをベースにしています。 JSON-RPCのようなストリームプロトコルで、バックエンドとフロントエンド間のコミュニケーションを、multiprocessing.connectionを使って行います。[qdb]

シェル

edit のコントローラタブの下にある "shell" リンクをクリックすると、web2pyはWebベースのPythonのシェルを開き、現在のアプリケーションに対するモデルを実行します。これにより対話的にアプリケーションを実行することができます。

image

webベースシェルの使用には注意が必要です。なぜなら、異なるシェルは異なるスレッドで実行を要求されるためです。これはとりわけ、データベースの作成と接続で利用した場合、容易にエラーになります。このような動作のためには(つまり永続性が必要な場合)、pythonコマンドラインの使用の方が遥かに優れています。

Crontab

edit のコントローラタブの下にはまた、"crontab" のリンクがあります。このリンクをクリックすると、web2pyのcrontabファイルを編集することができます。これはUnixのcrontabと同じ構文に従いますが、Unixには依存していません。実際、この機能はweb2pyだけを必要とし、Windows上でも動作します。これにより、スケジュールされた時間にバックグラウンドで起動する必要のあるアクションを登録することができます。

Errors

errors

web2pyをプログラムする場合、ミスを犯すことやバグを取り込むことは避けられません。web2pyは2つの方法で支援します。1)全ての関数に対して、edit ページからブラウザで実行できるテストを作成することができます。2)エラーが明示されるとき、チケットが訪問者に発行され、エラーがログとして記録されます。

以下のように、故意に画像アプリケーションにエラーを挿入します:

def index():
    images = db().select(db.image.ALL,orderby=db.image.title)
    1/0
    return dict(images=images)

indexアクションにアクセスすると、次のようなチケットが表示されます:

image

管理者のみが、このチケットにアクセスすることができます:

image

チケットは、トレースバック及び、問題の原因となったファイルの内容、そして最終的なシステムの状態(変数、リクエスト、セッションなど)を表示します。エラーがビューの中で発生した場合は、web2pyはビューをHTMLからPythonコードに変換して表示します。これにより、簡単にファイルの論理構造を確認することができます。

デフォルトでは、チケットはトレースバックファイルシステムと表示されたグループに格納されています。管理インターフェイスでは、集計ビュー(発生のトレースと番号のタイプ)と詳細ビュー(全てのチケットは、チケットIDにより一覧表示されます)を提供しています。管理者は、2つのビューを切り替えることができます。

なお admin は、全ての箇所で構文がハイライトされたコードを表示します(たとえば、エラーレポートでは、web2pyのキーワードはオレンジ色で表示されます)。web2pyのキーワードをクリックすると、キーワードに関するドキュメントページにリダイレクトされます。

indexアクションにあるゼロ除算のバグを修正し、さらにそれをindexビューの中に導入してみます:

{{extend 'layout.html'}}

<h1>Current Images</h1>
<ul>
{{for image in images:}}
{{1/0}}
{{=LI(A(image.title, _href=URL("show", args=image.id)))}}
{{pass}}
</ul>

すると、次のようなチケットを得ます:

image

ここでweb2pyは、ビューをHTMLからPythonファイルに変換します。チケットに記述されるエラーは、元のビューファイルではなく、生成されたPythonコードを参照することに注意してください。

image

最初は混乱するかもしれませんが、実践的にはデバッグを容易にします。なぜなら、Pythonのインデントがビューに埋め込まれた論理構造を強調するからです。

コードは、同じページの下部に表示されます。

全てのチケットは、各アプリケーションの error ページのadminの下にリストされています:

image

Mercurial

Mercurial

ソースから実行している場合、管理インターフェースは "Versioning" を呼ばれる、一つ以上のメニュー項目を表示します。

images

結果のページで、コメントを入力し "commit" ボタンを押すと、現在のアプリケーションをコミットします。最初のコミットで、指定したアプリケーションのローカルMercurialリポジトリが作成されます。

裏でMercurialは、コードで行った変更情報をアプリのサブフォルダの ".hg" 隠しフォルダに格納します。全てのアプリは、独自の ".hg" フォルダと独自の ".hgignore" ファイル(無視するファイルをMercurialに指示します)を持ちます。

この機能を使用するには、Mercurialバージョン・コントロール・ライブラリがインストールされている必要があります(バージョン1.9以降)。

easy_install mercurial

Mercurialのウェブインタフェースを使用すると、前回のコミットやdiffファイルを閲覧することができます。 しかし、より強力な機能を持つシェルまたはGUIベースのMercurialのクライアントを使って、直接Mercurialを使用することを推奨します。例えば、リモート・ソース・リポジトリを使用してアプリを同期できるようになります。

Mercurialについては、以下のURLでより詳しい情報を読むことができます:

http://mercurial.selenic.com/

管理ウィザード(実験的)

admin インターフェースでは、新しいアプリケーションを作成できるようにウィザードが含まれています。 下の画像に示すように、"sites" ページからウィザードにアクセスできます。

image

ウィザードは、新しいアプリケーションの作成に必要な一連の手順を示します:

  • アプリケーション名の選択
  • アプリケーションの構成と、必要なプラグインの選択
  • 必要なモデルのビルド(各モデルのCRUDを行なうページが作成される)
  • MARKMIN構文を使用して、ページのビューの編集を許可する

下の画像は、プロセスの2番目のステップを示しています。

image

web2py.com/layouts から)レイアウトプラグインの選択を行なうためのドロップダウン、(web2py.com/plugins から)その他のプラグインを複数選択するためのドロップダウン、そしてJanrain用の "ドメイン:キー(domain:key)" をどこに配置するかを指定する "ログイン設定(login config)" フィールドが用意されています。

他のステップは、ほぼ自明です。

このウィザードはよく機能していますが、二つの理由から 実験的な機能 となっています:

  • ウィザードで作成したアプリケーションを手動で変更した場合、その後はウィザードで編集することができません。
  • ウィザードのインターフェイスは、より多くの機能をサポートし、簡単にビジュアルな開発が行えるように、今後も変更されます。

いずれにしてもウィザードは、高速プロトタイピングのための便利なツールであり、別のレイアウトとオプションのプラグインを使用した新しいアプリケーションを作成するためのブートストラップとして使用することができます。

adminの設定

通常は、admin の構成を行う必要はありませんが、幾つかのカスタマイズが可能です。adminにログインした後、次のURLを介して管理コンフィギュレーションファイルを編集することができます:

http://127.0.0.1:8000/admin/default/edit/admin/models/0.py

admin 自身も編集することができます。実際、admin も他と同じく一つのアプリケーションです。

"0.py" ファイルは、多かれ少なかれ自己文書化されており、とにかく、ここで最も重要なカスタマイズの可能性の一部は次の通りです:

GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py'))

これは、Google App EngineのSDKに付属する "appcfg.py" ファイルの場所を指している必要があります。SDKを使用している場合は、正しい値に設定パラメータを変更する必要があるかもしれません。正しく設定されていると、管理者インタフェースからGAEにデプロイすることができます。

DEMO_MODE

デモモードで、web2py adminをセットすることもできます:

DEMO_MODE = True
FILTER_APPS = ['welcome']

FILTER_APPSにリストされているアプリだけがアクセスできるようになり、それらは読み取り専用モードでのみアクセスできるようになります。

MULTI_USER_MODE
virtual laboratory

もし、あなたが教師で(バーチャルラボのような場所で)学生に対して教師側の管理インターフェイスを公開して共有できるようにする場合は、次のようにセットできます:

MULTI_USER_MODE = True

この場合、学生はログインが必要であり、そして学生の独自のアプリにはadminを介してしかアクセスできません。第一ユーザ/教師は、それら全てにアクセスすることができます。

マルチユーザモードでは、学生の登録にadminの "bulk register" リンクを使用でき、彼らの管理には "manage students" リンクを使用できます。システムは、学生がログインしそしてコードに対し何行を追加/削除したか、履歴を保持することもできます。このデータは、アプリケーションの "about" ページの下のチャートのように、管理者に提示されます。

このメカニズムは、今まで通り全てのユーザーが信頼されていると仮定している点に注意してください。 adminの下に作成されたすべてのアプリケーションは、同じファイルシステム上に同じ資格情報で実行されます。他の学生が作成したデータやソースにアクセスできる、アプリケーションを作成することもできます。サーバをロックするアプリを作成する学生もまた可能です。

モバイル admin

adminアプリケーションはjQueryモバイルのパッケージ、"plugin_jqmobile" を含んでいることに注意してください。adminがモバイルデバイスでアクセスされる時、web2pyにより検出され、モバイル用レイアウトを使用しインターフェースが表示されます:

image

appadmin の追加情報

appadmin

appadmin は一般に公開されることを意図されていません。これはデータベースへのアクセスを簡単に行えるようし、開発者の支援を行うために設計されています。これには2つのファイルしか含まれていません: 1つは "appadmin.py" コントローラで、もう1つはそのコントローラのすべてのアクションから利用される "appadmin.html" ビューです。

appadmin コントローラは比較的小さく、読み取りが可能です: ここでは、データベースのインターフェイス定義の例を提示しています。

appadmin はどのデータベースが利用可能なのか、どのテーブルが各データベースに存在しているかを示します。レコードを挿入することができ、また、各テーブルのレコードを個別にリストすることができます。appadmin は一度に100レコードを出力するようにページングします。

一度レコードセットが選択されると、ページのヘッダが変化し、選択されたレコードを更新または削除することができるようになります。

レコードを更新するには、Query文字列フィールドにSQL文を入力します:

title = 'test'

ここで、文字列の値はシングルクォートで囲む必要があります。複数のフィールドはカンマで区切ることができます。

レコードを削除するには、確かであることを確認するために、対応するチェックボックスをクリックします。

appadmin は、SQLのFILTERが2つ以上のテーブルを含むSQLの条件を持つ場合、結合(join)を実行することもできます。例えば、次を試してください:

db.image.id == db.post.image_id

web2pyは、これをDALを通して渡します。DALはクエリが、2つのテーブルをリンクしていることを理解します。従って、両者のテーブルは内部結合(INNER JOIN)によって選択されます。これはその出力です:

image

idフィールドの番号をクリックすると、対応するidを持つレコードに対する編集ページが得られます。

参照フィールドの数字をクリックすると、その参照されたレコードに対する編集ページが得られます。

結合(join)によって選択された行を更新または削除することはできません。なぜなら、それらは複数のテーブルからのレコードを含み、対象が曖昧になるからです。

そのデータベース管理機能に加え、appadmin は、アプリケーションの キャッシュ (場所は /yourapp/appadmin/ccache)の内容の詳細を表示することができます。同様に、現在の リクエストレスポンス そして セッション オブジェクト(場所は /yourapp/appadmin/state)の内容も表示できます。

appadminresponse.menu を自分自身のメニューに置き換えます。そこでは次のリンクが提供されます。admin の中にあるアプリケーションごとの edit ページ、db(データベース管理)ページ、state ページ、cache ページです。もし、アプリケーションのレイアウトで response.menu を使ったメニューの生成を行なわない場合、appadmin メニューは表示されません。この場合は、appadmin.htmlを修正して、メニューを表示するために {{=MENU(response.menu)}} を追加することができます。

第3版 - 翻訳: 細田謙二 レビュー: 中垣健志
第4版 - 翻訳: 中垣健志 レビュー: Mitsuhiro Tsuda
第5版 - 翻訳: Hitoshi Kato レビュー: Omi Chiba
 top