Subsections

6. Views, function ed altro

Fino ad ora abbiamo usato solo metodi di tipo mask: Impareremo ora come usare view e function.

6.1 Differenti architetture per il codice sorgente di un sito web

Prenderemo in esame due esempi per mostrare i due differenti modi di disegnare l'architettura del vostro codice.

6.1.1 Primo esempio: architettura semplice

Assumiamo vogliate costruire un sito web veramente semplice dove persone possono ricercare tra una serie di libri e vedere i dettagli di uno in particolare. Il sito web è costituito da un paio di pagine: Per implementare questo sito web, abbiamo bisogno di usare 2 funzioni e 2 maschere. Il codice per il sito web è il seguente:
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>
Come avete potuto vedere, il codice per questo "mini" sito è veramente semplice: ogni maschera corrisponde ad un tipo di pagina. Dato che avevamo 2 tipi di pagine abbiamo usato 2 maschere.

Andiamo a vedere un esempio leggermente più complicato...

6.1.2 Secondo esempio: un'architettura più elegante per siti più complessi

In questo esempio aggiungeremo alcune caratteristiche al nostro sito web:

Questo significa che avremo bisogno di 6 tipi di pagine:

Se volessimo mantenere la stessa architettura del primo esempio, dovremmo scrivere 6 maschere (più le funzioni). Proviamo a fare qualcosa di meglio ...

Non c'è molto da fare per le ultime 2 pagine (la 5 e la 6). Ma per le prime quattro, possiamo in effetti usare 2 funzioni e 2 maschere. Combinando ogni funzione con ogni maschera, abbiamo le nostre 4 combinazioni (2 per 2). Useremo ciò che segue:

Per permettere l'unione tra maschera e funzione, useremo una view. Questo significa che avremo 4 view, una per ogni combinazione. Ogni view avrà un codice veramente semplice: applica questa maschera al risultato di questa funzione

Il codice per il nostro sito web è il seguente:

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

Alternativamente, avremmo potuto risparmiare alcune lnee di codice passando la lingua (Francese o Inglese) e il tipo di lista (titolo o autore) come parametri. In questo modo, non avremmo avuto bisogno di usare view, e le maschere avrebbero potuto essere chiamata direttamente...

6.2 Altri esempi usando function, mask e view insieme

In questa sezione, costruiremo un piccolo sito web che richiede all'utente un numero intero tra 20 e 50 e un numero di colonne C tra 2 e 20. Quindi visualizzerà i numeri interi da 1 a N in una tabella di C colonne.

Editate Hello.cpy ed inserite le seguenti righe di codice:

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>

Come funziona?

La maschera index è facile da capire ed è usata solo per inserire N e C.

La funzione prepareTableData è usata per processare N e C e per calcolare una lista di liste che sarà pronta per la visualizzazione. La maschera renderTableData prende in ingresso il ritorno di prepareTableData e lo visualizza. La view viewResult è un collegamento tra i due. Basilarmente diciamo di calcolare il risulato di una funzione e applicarvi una maschera.

Ora cosa possiamo fare se volessimo visualizzare gli interi per colonna invece che per riga?

Dobbiamo creare una nuova maschera e aggiornare la view in modo da applicare la nuova maschera ai dati.

Modifichiamo Hello.cpy come segue:

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>

Abbiamo rinominato la maschera renderTableData in renderTableDataByLine, abbiamo quindi chiamato renderTableDataByColumn. viewResult ha adesso il parametro displayBy che è immesso dall'utente. Basandosi su questo, viewResult seleziona la maschera da applicare al risultato della funzione prepareTableData (che non è cambiata).

Facciamo ora alcuni test: nel nostro browser digitiamo l'URL: http://localhost:8000/prepareTableData?N=30&C=5

Dovreste ricevere il seguente errore:

CherryError: CherryClass "root" doesn't have any view or
            mask function called "prepareTableData"
Questo significa che una funzione non può essere "chiamata" direttamente da un browser. Solo funzioni view e mask, che ritornano dati visualizzabili, possono essere "chiamate" direttamente.

Cosa abbiamo imparato:

Nota: all'interno di una dichiarazione di una CherryClass, le sezioni differenti (function, mask o view) possono apparire in qualsiasi ordine, il numero di volte che vogliamo.

Nel prossimo capitolo vedremo come CherryPy determina che metodo chiamare in base all'URL...

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