RSS

Grok tutorial #1: viste e modelli

Tutti i web framework moderni introducono la nozione di viste e modelli, e forniscono quanto necessario per gestire queste strutture e le loro funzionalità.

Un modello, rappresenta uno o più dati logicamente interconnessi fra loro, e fornisce anche tutte le operazioni di manipolazione di questi. Ad esempio, la scheda di anagrafica di un cliente può essere vista come un modello dell'applicazione. Un'applicazione è tendenzialmente composta di una serie di modelli, che spesso non svolgono necessariamente il ruolo di contenitori di dati, ma possono svolgere anche altri compiti di servizio per il funzionamento dell'intera applicazione.

Le viste, come il nome suggerisce, sono una rappresentazione dei modelli. Possono esistere viste di diversa natura per gli stessi dati: nei framework web in genere esiste almeno la vista che permette di interagire via HTTP con il modello attraverso la classica pagina web, ma possono esistere anche altre viste non orientate alla fruizione dell'utente (XML, REST, ecc).

Questo modello di progettazione è una versione più semplificata del più generale pattern Model View Controller, che in aggiunta introduce un ulteriore strato intermedio (il controller) che fissa le regole di interazione tra il modello e le viste associate. Grok spinge sulla versione più semplificata del pattern MVC ma, come sarà più chiaro in avanti, è perfettamente adottabile una libera progettazione dell'applicazione.

 

Creazione di un progetto base

Il primo passo per la costruzione di un'applicazione Grok è installare le componenti di base del framework. Da oltre un anno la comunità Zope ha lavorato pesantemente sulla struttura fisica di Zope 3: molte funzionalità sono state spacchettate in moduli self-contained, che spesso possono essere adoperati singolarmente e in totale isolamento dalla restante parte del framework. Grok fa uso di circa un centinaio di questi moduli, ma la gestione della loro installazione è demandata automaticamente al gestore che configura l'ambiente di esecuzione di un progetto basato su Grok, a sua volta un altro componente sviluppato dalla comunità: zc.buildout.
Per prima cosa, è necessario installare il pacchetto grokproject, che è sostanzialmente composto dai template Paste necessari alla generazione dello scheletro del progetto. Tale operazione può essere effettuata in automatico con l'uso del comando easy_install (con i necessari privilegi di amministrazione):

 

ymir:~ cnoviello$ easy_install grokproject
...

Terminata questa breve frase, è possibile generare lo scheletro del progetto ed installare tutti i moduli di dipendenza tramite il comando buildout:

 

ymir:~ cnoviello$ grokproject testproject
Enter user (Name of an initial administrator user): admin
Enter passwd (Password for the initial administrator user): 
Downloading info about versions...
Creating directory ./testproject
Downloading zc.buildout...
Invoking zc.buildout...
Develop: '/Users/cnoviello/testproject/.'
Installing eggbasket.
ymir:~ cnoviello$ cd testproject/
ymir:~ cnoviello$ ./bin/buildout -v
...

 

Questa fase può avere una durata temporale più o meno ampia, a seconda della connessione di rete, se si hanno già una serie di pacchetti di base installati, la potenza dell'elaboratore sottostante. Ma cosa avviene esattamente in questa fase? Il modulo zc.buildout, a partire dalle direttive contenute all'interno del file buildout.cfg nella root del progetto generato, verifica tutte le dipendenze con i moduli necessari al framework e, se non soddisfatte, provvede a scaricare gli .egg dai repository ufficiali tramite pypi. Tutti questi moduli, tuttavia, non sono resi disponibili a livello globale del sistema, ma sono scaricati per default all'interno della directory .buildout della cartella dell'utente che genera il progetto. Questo comportamento di default di Grok, che ha generato diverse discussioni nella comunità che ancora non hanno portato ad una posizione comune, implica che progetti diversi, ma in esecuzione sotto lo stesso utente, condividono la stessa istallazione del framework di base di Grok. L'alternativa è la configurazione di un ambiente di esecuzione isolato per ogni progetto Grok. Esistono due alternative: quella di avere utenti di sistema separati per ogni installazione, o di adoperare virtualenv. Alla fine di questo post troverete una sezione specifica che illustra brevemente quali sono i passi per configurare virtualenv, dato che ho trovato la documentazione ufficiale un po' datata e contraddittoria sull'argomento.
Infine c'è un altro aspetto. Esistono sistemi operativi che forniscono supporto diretto all'esecuzione di servizi basati su Zope. Nello specifico mi riferisco ad alcune distribuzioni Linux come Debian e Ubuntu. Per adoperare l'infrastruttura di base messa a disposizione dal sistema (come script di runlevel ed altro) è preferibile configurare grokproject in modo da generare un layout del progetto simile alla classica distribuzione Zope 2. Per fare ciò, al momento della creazione del progetto basta adoperare il comando:

ymir:~ cnoviello$ grokproject --zopectl testproject

Il layout del progetto generato

Al termine della procedura di buildout si ottiene un progetto Grok minimale pronto sia per essere eseguito sia esteso in base alle esigenze. Come è possibile notare, grokproject ha creato diverse sottodirectory. Le principali sono:

  • bin: contiene tutti gli script e gli eseguibili necessari all'esecuzione dell'application server Zope, alla base di Grok;
  • parts: formato da diverse sottodirectory che contengono file di configurazione, percorsi di log e dati dell'applicazione;
  • src: la sottodirectory contenente l'egg della nostra applicazione. Tutti i file sorgenti saranno posizionali al suo interno, e resi disponibili in automatico nei percorsi dell'interprete all'atto del caricamento dell'application server.

Per mandare in esecuzione l'application server, è sufficiente digitare il comando:

ymir:~ cnoviello$ ./bin/testproject-ctl start

Per default, l'application server girerà sulla porta 8080: nel caso di esigenze particolari è possibile specificare una porta diversa modificando il file parts/etc/deploy.ini nella sezione [server:main].Posizionandosi con il browser sull'indirizzo http://localhost:8080, una volta digitate le credenziali scelte alla creazione del progetto, è possibile accedere alla consolle di gestione di Grok, e creare un'istanza dell'applicazione di prova creata da grokproject. Per chi già conosce Zope tutte queste operazioni risulteranno molto familiari. In alternativa, ho creato un piccolo screencast per mostrare questa operazione.

 

 

L'applicazione generata

L'applicazione generata da grokproject si trova all'interno della directory src/testproject/, e contiene i seguenti file e sottodirectory:

  • app.py: è il file principale contenente lo scheletro dell'applicazione;
  • app_templates: è la directory contenente tutti i template associati alle viste definite all'interno del file app.py; per convenzione, Grok associa a tutte le viste definite in file nomefile.py i template presenti nella sottodirectory nomefile_templates;
  • configure.zcml: contiene le direttive ZCML per configurare i componenti di Grok;
  • static: contiene tutti i file "statici" dell'applicazione, quali immagini, fogli di stile, script, ecc.

All'interno del file app.py si trova lo scheletro dell'applicazione generata:

 

1
2
3
4
5
6
7
import grok
 
class Testproject(grok.Application, grok.Container):
    pass
 
class Index(grok.View):
    pass # see app_templates/index.pt

Sembra quasi incredibile che questo frammento di codice così piccolo sia sufficiente a generare un'applicazione web in piena regola. Vediamo nel dettaglio. Alla riga 3 viene definita la classe Testproject, che rappresenta la classe principale della nostra applicazione: non a caso eredita da grok.Application. Testproject è anche una classe contenitore (eredita da grok.Container): questo significa che è in grado di ospitare al suo interno istanze di altri modelli. Già, perché il fatto che Testproject erediti da grok.Container, implica che siamo alla presenza di una classe che è un modello della nostra applicazione. Infine, alla riga 6 viene definita la vista per la classe Testproject: sarà in automatico Grok, che all'atto della partenza dell'application server "grokkerà" la classe Index associandone l'istanza alla istanza della classe Testproject, che è creata da noi attraverso l'interfaccia di amministrazione di Grok (come riportato nello screencast di esempio). Grok, inoltre, si preoccupa anche di associare in automatico il template presente nella sottodirectory app_templates, che per convenzione deve avere lo stesso nome della classe vista.

Proviamo a rendere l'applicazione di test più sofisticata, realizzando un'applicazione che permette l'inserimento di brevi note. Innanzitutto, abbiamo bisogno di una classe che modella il concetto di nota. Questo può essere ottenuto con un codice molto elementare simile al seguente:

 

1
2
3
4
5
6
class Note(grok.Model):
    def __init__(self, note):
        super(Note, self).__init__()
 
        self.note = note
        self.when = time.time()

Si parte con la definizione di una classe Note, che eredita da grok.Model: questo implica definire un nuovo modello dell'applicazione. Nel costruttore della classe viene da prima invocato il costruttore della superclasse (operazione sempre necessaria quando si eredita da classi di base di Grok), dopo di che si imposta l'attributo note, che contiene la nota, e l'attributo when, che indica la data e l'ora del momento di inserimento della nota.
Definito il modello, è necessario creare una semplice vista per mostrare all'utente il contenuto della nota: 

1
2
3
4
5
6
class NoteView(grok.View):
    grok.context(Note)
    grok.name('index')
 
    def time(self):
        return dt.fromtimestamp(self.context.when)

Essendo NoteView una vista deve ereditare da grok.View, la superclasse delle viste Grok. Con la direttiva grok.context() eseguiamo l'associazione tra la vista e la classe modello: in questo modo, sarà sempre possibile accedere nel codice della vista al modello tramite l'attributo self.context (chi conosce Zope 3 ha già capito che grok.View è una comune BrowserView di Zope 3 e la direttiva grok.context permette di specificare la classe dell'oggetto adattato). Con la direttiva grok.name() si specifica il nome della vista, ossia l'url da specificare per visualizzare la vista specifica. Per default, se grok.name() non viene specificato, il nome della vista corrisponde al nome della classe vista in lowercase, ossia nota/noteview; specificando grok.name('index') non stiamo facendo altro che specificare la vista di default per un oggetto di tipo Note. Infine, il metodo time() restituisce un oggetto datetime a partire dal timestamp della funzione time.time().
All'oggetto vista è associato un piccolo template ZPT (il linguaggio di templating di Zope), che ha lo stesso nome della classe NoteView ed è posizionato all'interno della directory app_templates.

1
...

Nota inserita il:

1
 

 

1
...

Nella riga 2 di questo frammento di codice non si fa altro che invocare il metodo time() della classe NoteView: in un template ZPT con view si fa sempre automaticamente riferimento alla vista. Nella riga successiva, tramite il riferimento context al modello associato alla vista, ricaviamo direttamente il valore del campo note, che contiene la nota inserita dall'utente.
Per quanto riguarda la parte di inserimento della nota, partiamo con il commentare il codice ZPT associato alla classe Testproject:

 

1
...
1
 

Aggiungi una nota!

1
 
1
2
            <input id="note" name="note" type="text" />
            <input id="add" name="add" type="submit" value="Aggiungi" />

Come è possibile vedere, il codice scritto non è niente altro che il codice HTML necessario alla gestione di una form di inserimento, con un campo contenente il testo della nota ed un pulsante aggiunti. Tutto il lavoro è svolto dalla vista Index, associata al modello principale Testproject:

 

 

 

1
2
3
4
5
6
7
8
9
class Index(grok.View):
    grok.context(Testproject)
 
    def update(self, note=None, add=None):
        if add == &#39;Aggiungi&#39;:
            self.context.notecounter += 1
            noteid = &#39;note%d&#39; % self.context.notecounter
            self.context[noteid] = Note(note)
            self.redirect(self.url(self.context[noteid]))

Si comincia con l'associare la vista alle istanze della classe Testproject con la direttiva grok.context(). Alla riga 4 viene definito il metodo update(), che è invocato ogniqualvolta la vista viene renderizzata. I parametri note e add corrispondono ai campi della form definita nel codice ZPT mostrato in precedenza. Il corpo del metodo non fa altro che generare un nuovo identificativo per la nota (vi ricordo che self.context fa riferimento al modello, e quindi a Testproject), e attraverso il codice della linea 8, la nuova nota viene inserita all'interno di Testproject, che ereditando da grok.Container può memorizzare al suo interno oggetti che sono modelli (in realtà non solo, ma questo sarà oggetto di un altro tutorial): grok.Container è una classe che si comporta come un dizionario, permettendo la memorizzazione di oggetti nella forma chiave/valore. Inoltre, gli oggetti contenitori supportano il cosiddetto traversing, ossia la capacità di esporre in automatico la loro struttura di annidamento tramite URL HTTP. Ad esempio, una volta inserita una nota, è possibile visualizzarla all'URL http://localhost:8080/testproject/note1.

Tutto il codice di esempio può essere scaricato direttamente da qui. Il pacchetto scaricato va però risottoposto al buildout (è un operazione da fare ogni qualvolta si ridistribuisce il codice dell'applicazione), con i comandi:

 

ymir:~ cnoviello$ python bootstrap.py
ymir:~ cnoviello$ ./bin/buildout

Prossimamente andremo più nel dettaglio della direttiva grok.context(), illustrando i concetti fondativi alla base di Zope 3: la Zope Component Architecture. Nel frattempo vi rimando alla documentazione ufficiale di Grok.

 

*Creare un ambiente di esecuzione isolato con virtualenv

virtualenv è il tool per creare ambienti di esecuzione Python isolati dalla configurazione generale di sistema. Questo significa nei fatti creare una configurazione che sovrascriva i path generali della distribuzione di Python, configurando un ambiente minimale all'interno del quale effettuare tutte le necessarie configurazioni, con l'aggiunta dei tool diventati standard nella toolchain di Python, come easy_install ed altri. Per creare un progetto Grok isolato è necessario effettuare una serie di passi preliminari prima della creazione del progetto. Innanzitutto, è necessario installare virtualenv e creare l'ambiente virtuale all'interno del quale sarà creato il progetto:

ymir:~ cnoviello$ easy_install virtualenv
...
ymir:~ cnoviello$ virtualenv virtualgrok
...

Una volta creato l'ambiente è necessario attivarlo (operazione che implica la sovrascrittura dei PATH generali di python) ogni qualvolta si effettuano operazioni con esso come, ad esempio, creare il progetto Grok che vogliamo isolare o mandarlo in esecuzione. Per attivare o disattivare un ambiente virtuale è sufficiente digitare i comandi:

ymir:~ cnoviello$ source ./virtualgrok/bin activate
...
ymir:~ cnoviello$ deactivate

Prima di procedere con la configurazione di grokproject, è necessario però installare Paste:

ymir:~ cnoviello$ source ./virtualgrok/bin activate
(virtualgrok)ymir:~ cnoviello$ easy_install Paste PasteScript PasteDeploy
...
(virtualgrok)ymir:~ cnoviello$ easy_install grokproject

Una volta costruito l'ambiente all'interno di esso sarà possibile creare il progetto Grok desiderato come descritto in precedenza. A questo punto è necessaria una precisazione. Questa operazione non implica adoperare una directory separata per la cache dei singoli .egg scaricati dal buildout: il buildout continuerà ad accedere alla sottodirectory .buildout contenuta nella home dell'utente. Per isolare anche quest'ultimo aspetto, è necessario modificare il file buildout.cfg, nello specifico nella sezione [buildout] aggiungendo la direttiva eggs-directory:

[buildout]
...
eggs-directory = /Users/cnoviello/virtualgrok/.buildout/eggs
Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay

Commenti