So far, we've only used one kind of method: masks. We are going to learn how to use views and functions.
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>
Let's take a slightly more complicated example ...
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...
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"
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.