Subsections

6. Views, functions and more

So far, we've only used one kind of method: masks. We are going to learn how to use views and functions.

6.1 Different architectures for a web site source code

We'll take two examples to show you two different ways to design the architecture of your code.

6.1.1 First example: straightforward architecture

Let's assume that you want to build a very simple web site where people can look for books and see the details about one perticular book. The web site is made of two kinds of pages: To implement this web site, we'll just use 2 functions and 2 masks: The code of the web site is:
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 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>
As you can see, the code for this "mini" web site is pretty straightforward: each mask corresponds to a page type. Since we have 2 types of pages, we use 2 masks.

Let's take a slightly more complicated example ...

6.1.2 Second example: more elegant architecture for more complex web sites

In this example, we'll add a few more features to our web site:

This now means that we have six types of pages:

If we were to keep the same architecture as the first example, we would have to write 6 masks (plus the functions). Let's try to do better than that ...

There isn't much we can do about the last 2 types of pages (5 and 6). But for the first four, we can in fact use 2 functions and 2 masks. By combining each function with each mask, we have our 4 combinations (2 times 2). We'll use the following:

In order to "link" a mask with a function, we'll use a view. This means that we have 4 views, one for each combination. Each view will have a very simple code: apply this mask to the result of that function

The code for our web site looks like this:

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()

Alternatively, we could save even more lines of code by passing the language (French or English) and the type of list (title or author) as parameters. This way, we wouldn't need to use views, and the masks could be called directly...

6.2 More examples on using functions, masks and views together

In this section, we'll build a small website thats prompts the user for an integer N between 20 and 50, and for a number of columns C between 2 and 10. Then it will display integers from 1 to N in a table of C columns.

Edit Hello.cpy and enter the following code:

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="">
                Integer between 20 and 50: <input type=text name=N><br>
                Number of columns between 2 and 10: <input type=text name=C><br>
                <input type=submit>
            </form>
        </body></html>

How does it work ?

The index mask is easy to understand and is only used to input N and C.

The prepareTableData function is used to process N and C and to compute a list of list that will be ready to render. The renderTableData mask takes as an input the return value of prepareTableData and renders it. The viewResult view is the link between the two. It basically says to compute the result of a function and to apply a mask to it.

Now, what if we want to display the integers by column instead of displaying them by line ?

Well, we just need to create a new mask, and to update the view in order to apply the new mask to the data.

Modify Hello.cpy as follows:

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>

We've rename the renderTableData mask into renderTableDataByLine, and we've added a new mask called renderTableDataByColumn. viewResult now has a displayBy parameter, that's entered by the user. Based on that, viewResult selects which mask to apply and applies it to the result of the prepareTableData function (which hasn't changed).

Now, try one more test: In your browser, enter the URL: http://localhost:8000/prepareTableData?N=30&C=5

You should see the following error:

CherryError: CherryClass "root" doesn't have any view or mask function called "prepareTableData"
This means that a function cannot be "called" directly from a browser. Only views and masks, which return some rendered data, can be "called" directly.

What we've learned:

Note: inside a CherryClass declaration, the different sections (function, mask or view) can appear in any order, as many times as you want.

In the next chapter, we'll see how CherryPy determines which method to call based on the URL...

See About this document... for information on suggesting changes.