Subsections

View, function, その他

今までは、私達は1種類の方法、すなわちmaskだけを使ってきました。viewとfunctionの使い方を学ぶ事にしましょう。

ウェブサイトのソースコードの異なる構造

コードの構造をデザインするための2つのやり方を示すために、2つの例を取り上げてみます。

第1の例: 簡単な構造

サイトに訪れた人が本を探し、特定の本についての詳細を見る事ができる、とてもシンプルなウェブサイトを構築したいとあなたは思ったとします。このウェブサイトは以下の2種類のページで作成されています: このウェブサイトに実装するにあたり、2つのfunctionと2つのmaskを使う事にします: このウェブサイトのコードは以下の通りです:
herryClass Root:
variable:
    # Sample book list data. In real life, this would probably come from a database
    # (title, author, price)
    bookListData=[
        ('Harry Potter and the Goblet of Fire', 'J. K. Rowling', '9$'),
        ('The flying cherry', 'Remi Delon', '5$'),
        ('I love cherry pie', 'Eric Williams', '6$'),
        ('CherryPy rules', 'David stults', '7$')
    ]

function:
    def getBookListData(self):
        return self.bookListData
    def getBookData(self, id):
        return self.bookListData[id]
mask:
    def index(self):
        <html><body>
            Hi, choose a book from the list below:<br>
            <py-for="title, dummy, dummy in self.getBookListData()">
                <a py-attr="'displayBook?id=%s'%_index" href="" py-eval="title"></a><br>
            </py-for>
        </body></html>
    def displayBook(self, id):
        <html><body>
            <py-exec="title, author, price=self.getBookData(int(id))">
            Details about the book:<br>
            Title: <py-eval="title"><br>
            Author: <py-eval="author"><br>
            Price: <py-eval="price"><br>
        </body></html>
みての通り、この「小さな」ウェブサイトのコードはかなり簡単です: それぞれのmaskがページの種類に対応します。ページの種類が2種類ですから、2つのmaskを使うわけです。

もうちょっと複雑は例を取り上げてみましょう。。。

第2の例: より複雑なウェブサイトのための、よりエレガントな構造

この例では、ウェブサイトにさらにいくつかの機能を追加することにします:

これは6種類のタイプのページになるということを意味します:

もし、第1の例と同じ構造を保持してしまうと、6つのmask(とそれぞれのfunction)を記述するはめになります。もっとうまくやってみましょう。。。

ページの中の最後の2種類(5と6)は一致しませんが、最初の4つについては、実際は2つのfunctionと2つのmaskで済ませる事ができます。各functionとmaskを組み合わせる事により、(2*2で)4通りの組み合わせができます。以下のような使い方になります:

maskをfunctionに「リンク」するためにviewを使う事にします。これは各組み合わせにつき一つ、つまり4つのviewを持つということを意味します。各viewはとてもシンプルなコードです:このmaskをfunctionの結果に適用します

ウェブサイトのコードは以下のようなものです:

CherryClass Root:
variable:
    # Sample book list data. In real life, this would probably come from a database
    # (title, author, price)
    bookListData=[
        ('Harry Potter and the Goblet of Fire', 'J. K. Rowling', '9$'),
        ('The flying cherry', 'Remi Delon', '5$'),
        ('I love cherry pie', 'Eric Williams', '6$'),
        ('CherryPy rules', 'David Stults', '7$')
    ]

function:
    def getBookListByTitleData(self):
        titleList=[]
        for title, dummy, dummy in self.bookListData: titleList.append(title)
        return titleList
    def getBookListByAuthorData(self):
        authorList=[]
        for dummy, author, dummy in self.bookListData: authorList.append(author)
        return authorList
    def getBookData(self, id):
        return self.bookListData[id]
mask:
    def bookListInEnglishMask(self, myBookListData):
            Hi, choose a book from the list below:<br>
            <py-for="data in myBookListData">
                <a py-attr="'displayBookInEnglish?id=%s'%_index" href="" py-eval="data"></a><br>
            </py-for>
            <br>
    def bookListInFrenchMask(self, myBookListData):
            Bonjour, choisissez un livre de la liste:<br>
            <py-for="data in myBookListData">
                <a py-attr="'displayBookInFrench?id=%s'%_index" href="" py-eval="data"></a><br>
            </py-for>
            <br>
    def displayBookInEnglish(self, id):
        <html><body>
            <py-exec="title, author, price=self.getBookData(int(id))">
            Details about the book:<br>
            Title: <py-eval="title"><br>
            Author: <py-eval="author"><br>
            Price: <py-eval="price"><br>
            <br>
            <a py-attr="'displayBookInFrench?id=%s'%id" href="">Version francaise</a>
        </body></html>
    def displayBookInFrench(self, id):
        <html><body>
            <py-exec="title, author, price=self.getBookData(int(id))">
            Details du livre:<br>
            Titre: <py-eval="title"><br>
            Auteur: <py-eval="author"><br>
            Prix: <py-eval="price"><br>
            <br>
            <a py-attr="'displayBookInEnglish?id=%s'%id" href="">English version</a>
        </body></html>
view:
    def englishByTitle(self):
        page="<html><body>"
        byTitleData=self.getBookListByTitleData()
        page+=self.bookListInEnglishMask(byTitleData)
        page+='<a href="englishByAuthor">View books by author</a><br>'
        page+='<a href="frenchByTitle">Version francaise</a>'
        page+="</body></html>"
        return page
    def frenchByTitle(self):
        page="<html><body>"
        byTitleData=self.getBookListByTitleData()
        page+=self.bookListInFrenchMask(byTitleData)
        page+='<a href="frenchByAuthor">Voir les livres par auteur</a><br>'
        page+='<a href="englishByTitle">English version</a>'
        page+="</body></html>"
        return page
    def englishByAuthor(self):
        page="<html><body>"
        byTitleData=self.getBookListByAuthorData()
        page+=self.bookListInEnglishMask(byTitleData)
        page+='<a href="englishByTitle">View books by title</a><br>'
        page+='<a href="frenchByAuthor">Version francaise</a>'
        page+="</body></html>"
        return page
    def frenchByAuthor(self):
        page="<html><body>"
        byTitleData=self.getBookListByAuthorData()
        page+=self.bookListInFrenchMask(byTitleData)
        page+='<a href="frenchByTitle">Voir les livres par titre</a><br>'
        page+='<a href="englishByAuthor">English version</a>'
        page+="</body></html>"
        return page
    Def index(self):
        # By default, display books by title in English
        return self.englishByTitle()
あるいは、言語(フランス語か英語か)とリストの種類(タイトル別か著者別か)をパラメータとして受け渡すことによって、コードの何行かをさらに節約することができるかもしれません。このやり方だと、viewを使う必要がないでしょうし、maskが直接呼び出されるでしょう。。。

function, mask, viewを一緒に使う、もっと他の例

この節では、ユーザーに20から50の間の整数Nと、結果表示の表の例の数Cを2から10の間で指定するようにユーザーに促すような小さなウェブサイトを構築することにします。

Hello.cpyを編集し、以下のコードを入力してください:

CherryClass Root:
function:
    def prepareTableData(self, N, C):
        # Prepare data that will be rendered in the table
        # Example, for N=10 and C=3, it will return:
        # [[1,2,3],
        #  [4,5,6],
        #  [7,8,9],
        #  [10]]
        N=int(N)
        C=int(C)
        tableData=[]
        i=1
        while 1:
            rowData=[]
            for c in range(C):
                rowData.append(i)
                i+=1
                if i>N: break
            tableData.append(rowData)
            if i>N: break
        return tableData

view:
    def viewResult(self, N, C):
        tableData=self.prepareTableData(N,C)
        return self.renderTableData(tableData)
mask:
    def renderTableData(self, tableData):
        # Renders tableData in a table
        <html><body>
        <table border=1>
            <div py-for="rowData in tableData">
                <tr>
                    <div py-for="columnValue in rowData">
                        <td py-eval="columnValue"></td>
                    </div>
                </tr>
            </div>
        </table>
        </body></html>

    def index(self):
        <html><body>
            <form py-attr="request.base+'/viewResult'" action="">
            for c in range(C):
                rowData.append(i)
                i+=1
                if i>N: break
            tableData.append(rowData)
            if i>N: break
        return tableData

これはどのように動くのでしょうか?

\textbf{index} は理解しやすく、NとCを入力するために使われているだけです。

\textbf{prepareTableData} functionは、NとCを処理し、画面に表示する表のリストを計算するために使われます。\textbf{renderTableData} maskは\textbf{prepareTableData}の返り値の入力し画面にそれを表示するために扱われます。\textbf{viewResult} viewはこの2つのリンクです。これは基本的にfunctionの結果を計算し、maskに適用するのです。

ここで、もし行ごとに表示する代わりに列ごとに表示しようとするならばどうしましょうか?

新しいmaskを作成し、その新しいmaskにデータを適用するためにviewを更新する必要がありそうですね。

\textbf{Hello.cpy}を以下のように変更します:
\begin{verbatim}
CherryClass Root:
function:
    def prepareTableData(self, N, C):
        N=int(N)
        C=int(C)
        tableData=[]
        i=1
        while 1:
            rowData=[]
            for c in range(C):
                rowData.append(i)
                i+=1
                if i>N: break
            tableData.append(rowData)
            if i>N: break
        return tableData

view:
    def viewResult(self, N, C, displayBy):
        tableData=self.prepareTableData(N,C)
        if displayBy=="line": mask=self.renderTableDataByLine
        else: mask=self.renderTableDataByColumn
        return mask(tableData)
mask:
    def renderTableDataByLine(self, tableData):
        <html><body>
        <table border=1>
            <div py-for="rowData in tableData">
                <tr>
                    <div py-for="columnValue in rowData">
                        <td py-eval="columnValue"></td>
                    </div>
                </tr>
            </div>
        </table>
        </body></html>
    def renderTableDataByColumn(self, tableData):
        <html><body>
        <table border=1>
            <tr>
                <div py-for="rowData in tableData">
                    <td valign=top>
                        <div py-for="columnValue in rowData">
                            <div py-eval="columnValue"></div><br>
                        </div>
                    </td>
                </div>
            </tr>
        </table>
        </body></html>

    def index(self):
        <html><body>
            <form py-attr="request.base+'/viewResult'" action="">
                Integer between 20 and 50: <input type=text name=N><br>
                Number of columns (or lines) between 2 and 10: <input type=text name=C><br>
                Display result by: <select name=displayBy>
                    <option>line</option>
                    <option>column</option>
                </select><br>
                <input type=submit>
            </form>
        </body></html>

renderTableData maskをrenderTableDataByLineにリネームし、renderTableDataByColumnと呼ばれる新しいmaskを追加したわけです。viewResultdisplayByパラメータを持ち、これはユーザーが入力したものです。これにもとづいて、viewResultはどのmaskを適用するかを選択し、そのmaskにprepareTableData function(これは変更されていません)の結果を適用します。

さて、もう一つ試してみましょう。ブラウザで、URL: http://localhost:8000/prepareTableData?N=30&C=5 を入力します。

以下のエラーが出たはずです:

CherryError: CherryClass "root" doesn't have any view or mask function called "prepareTableData"
これはfunctionがブラウザから直接呼び出されることは不可能だということを意味します。画面に表示するためのなんらかのデータを返すviewsとmaskが直接「呼び出される」ことが可能なのです。

ここで学んだ事は:

注意: CherryClassの宣言内に、異なるセクション(function, maskもしくはview)はどの順番でも表示できます。しかし各々一回づつだけ可能です。これはいっぺんに全てのmask,全てのview...を宣言する必要があるということです。

次の章では、URLにもとづいた呼び出しをするためにどのメソッドにするかの決め方をみていくことにします。。。

訳者:Webアプリケーションが始めての人にはこの章のviewの使い方がわかりにくかったかもしれません。Webの開発をする時には「プレゼンテーション部分とロジック部分とを分ける」という考え方があります。特に最近のWebアプリケーションでははMVCモデルというのを採用していて、これはModel(そのアプリケーションで表現したい実体), View(表示), Controller(アプリケーションを制御する)の3つに役割分担する考え方です。CherryPyに当てはめると、Modelがfunction部分、ViewがMask(変数が少ない場合)とView(変数が多い場合)、ControllerがViewということになります。

ユーザーがウェブブラウザでサイトを見た時に、コントローラーであるviewがindex()などになって呼び出され、このviewがヘッダやフッタなどの表示部分などのmaskを同時にいくつも呼び出してページを表示します。そのページのフォームにユーザーが入力したら、その入力データはviewが受け取って値の検証などを行い、ロジック部分すなわちモデル部分であるfunctionにデータを送る。。。という感じになると思います。functionがModelそのものか、っていう疑問もありますけどまあこんな感じです。

くわしくはMVCモデルを調べた方が理解が早いでしょう。



Debian User 2003-10-13