Chapter 13: Componenti e plugin
Componenti e plugin
I componenti ed i plugin sono funzionalità relativamente nuove di web2py e c'è ancora disaccordo tra gli sviluppatori su cosa siano e cosa dovrebbero essere. La maggior parte della confusione è generata dall'uso che si fa di questi termini in altri progetti software e dal fatto che gli sviluppatori stiano ancora lavorando per definirne le specifiche.
Queste comunque sono funzionalità importanti di web2py ed è quindi necessario fornire una definizione che sebbene non definitiva sia consistente con gli schemi di programmazione discussi in questo capitolo e che affrontano le seguenti due problematiche:
- Come costruire applicazioni modulari che riducano il carico del server e massimizzino il riutilizzo del codice.
- Come distribuire parti di codice in un modo il più possibile immediato (plugin-and-play).
I componenti affrontano il primo problema; i plugin affrontano il secondo.
Componenti
Un componente è una parte funzionalmente autonoma di una pagina web.
Un componente può essere composto di moduli, controller e viste. Un componente non ha nessun prerequisito particolare, se non quello di poter essere inserito in un singolo tag di una pagina web (per esempio un DIV, uno SPAN o un IFRAME) dove esegue il suo compito indipendentemente dal resto della pagina. I componenti sono caricati nella pagina e comunicano con il controller tramite Ajax.
Un esempio di componente è un "componente per i commenti" che è contenuto in un DIV e mostra i commenti degli utenti insieme ad un form per l'invio di un nuovo commento. Quando il form è compilato viene inviato al server tramite una chiamata Ajax, la lista viene aggiornata e il commento è memorizzato dal server nel database. Alla fine il contenuto del DIV è aggiornato senza ricaricare il resto della pagina. La funzione LOAD di web2py rende questo compito estremamente semplice da eseguire senza nessuna conoscenza della programmazione Javascript o Ajax.
L'obiettivo è quello di riuscire a sviluppare un'applicazione web assemblando i componenti nelle pagine.
Con una semplice applicazione "test" che estende l'applicazione di default con il seguente modello "models/db_comments.py":
db.define_table('comment',
Field('body','text',label='Your comment'),
Field('posted_on','datetime',default=request.now),
Field('posted_by',db.auth_user,default=auth.user_id))
db.comment.posted_on.writable=db.comment.posted_on.readable=False
db.comment.posted_by.writable=db.comment.posted_by.readable=False
e con un'azione nel "controllers/comments.py" definita come:
@auth.requires_login()
def post():
return dict(form=crud.create(db.comment),
comments=db(db.comment.id>0).select())
e con la corrispondente vista in "views/comments/post.html":
{{extend 'layout.html'}}
{{for comment in comments:}}
<div class="comment">
on {{=comment.posted_on}} {{=comment.first_name}}
says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}
si può accedere all'azione all'indirizzo:
http://127.0.0.1:8000/test/comments/post
[image]
Per ora non c'è nulla di speciale in questa azione ma è possibile trasformarla in un componente semplicemente definendo una nuova vista con estensione ".load" che non estende il layout. Ecco il contenuto di "views/comments/post.load":
{{#extend 'layout.html' <- notice this is commented out!}}
{{for comment in comments:}}
<div class="comment">
on {{=comment.posted_on}} {{=comment.first_name}}
says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}
E' possibile accedere a questa vista con l'URL:
http://127.0.0.1:8000/test/comments/post.load
e sarà visualizzata come:
[image]
Questo è un componente che può essere inserito in ogni altra pagina semplicemente con:
{{=LOAD('comments','post.load',ajax=True)}}
Per esempio si può aggiungere nel controller "controllers/default.py":
def index():
return dict()
e nella vista corrispondente si può aggiungere il componente:
{{extend 'layout.html'}}
<h1>{{='bla '*100}}</h1>
{{=LOAD('comments','post.load',ajax=True)}}
Accedendo alla pagina
http://127.0.0.1:8000/test/default/index
sarà mostrato il contenuto normale e il componente dei commenti:
[add image]
Il componente {{=LOAD( ... )}}
è visualizzato tramite il seguente HTML:
<script type="text/javascript"><!--
web2py_component("/test/comment/post.load","c282718984176")
//--></script><div id="c282718984176">loading...</div>
(il codice effettivamente generato dipende dalle opzioni passate alla funzione LOAD). La funzione web2py_component(url,id)
è definita in "views/web2py_ajax.html" ed esegue tutte le operazioni necessarie: chiama la url
tramite Ajax e inserisce la risposta nel DIV con l'id
corrispondente; intercetta ogni invio del form nel DIV e lo invia tramite Ajax. Il target Ajax è sempre lo stesso DIV.
La sintassi completa dell'helper LOAD è il seguente:
LOAD(c=None, f='index', args=[], vars={},
extension=None, target=None,
ajax=False, ajax_trap=False,
url=None):
Dove:
- i primi due argomenti
c
ef
sono il controller e la funzione che si vuole chiamare. args
evars
sono gli argomenti che si vuol passare alla funzione. Il primo è una lista, il secondo è un dizionario.extension
è una estensione opzionale. Notare che l'estensione può anche essere passata come parte della funzione (come, per esempiof='index.load'
).target
è l'id
del DIV. Se non è specificato verrà generato unid
casuale.ajax
deve essere impostato aTrue
se il DIV deve essere riempito tramite una chiamata Ajax e deve essere impostato aFalse
se il DIV deve essere riempito prima che la pagina sia ritornata (evitando così la chiamata Ajax).ajax_trap=True
significa che ogni invio del form nel DIV deve essere catturato ed inviato tramite Ajax e che la risposta deve essere inserita nel DIV.ajax_trap=False
significa che il form deve essere inviato normalmente, ricaricando l'intera pagina.ajax_trap
è ignorato e consideratoTrue
nel caso cheajax=True
.url
, se specificato, ignora i valori dic
,f
,args
,vars
edextension
e carica il componente allaurl
indicata. Questo parametro è utilizzato per caricare un componente da un'altra applicazione (non necessariamente creata con web2py).
Se non è specificata nessuna vista .load
viene utilizzata la vista generic.load
che visualizza senza layout il dizionario ritornato dall'azione. Questa vista generica funziona meglio se il dizionario contiene un singolo elemento.
Se si carica un componente con estensione .load
e la relativa azione reindirizza ad un'altra azione (per esempio per il login), l'estensione .load
viene propagata alla nuova URL (quella a cui si viene reindirizzati) che viene caricata con l'estensione .load
.
Quando un'azione di un componente è chiamata tramite Ajax web2py passa due header HTTP aggiuntivi con la richiesta:
web2py-component-location
web2py-component-element
che possono essere acceduti nell'azione tramite le variabili:
request.env.http_web2py_component_location
request.env.http_web2py_component_element
Il primo contiene la URL della pagina che ha chiamato l'azione del componente. Il secondo contiene l'id
del DIV che conterrà la risposta.
L'azione del componente può anche memorizzare informazioni in due header speciali che saranno interpretati dalla pagina completa durante la risposta:
web2py-component-flash
web2py-component-command
e possono essere acceduti tramite:
response.headers['web2py-component-flash']='....'
response.headers['web2py-component-command']='...'
Il primo contiene del test che deve apparire nel messaggio alla risposta. Il secondo contiene codice Javascript che si vuole far eseguire alla risposta. Non può contenere il carattere di ritorno a capo.
Come esempio ecco un componente per la richiesta di un form di contatti. L'azione in "controllers/contact/ask.py" consente all'utente di porre una domanda che il componente invierà all'amministratore tramite email. Infine visualizza il messaggio "thank you" e rimuove il componente dalla pagina:
def ask():
form=SQLFORM.factory(
Field('your_email',requires=IS_EMAIL()),
Field('question',requires=IS_NOT_EMPTY()))
if form.accepts(request.vars,session):
if mail.send(to='[email protected]',
subject='from %s' % form.vars.your_email,
message = form.vars.question):
div_id = request.env.http_web2py_component_element
command="jQuery('#%s').hide()" % div_id
response.headers['web2py-component-command']=command
response.headers['web2py-component-flash']='thanks you'
else:
form.errors.your_email="Unable to send the email"
return dict(form=form)
Le prime quattro linee definiscono il form e lo validano. L'oggetto mail
utilizzato per inviare la domanda è definito nell'applicazione di default. Le ultime quattro linee implementano tutta la logica specifica del componente leggendo i dati dagli header della richiesta HTTP ed impostando gli header HTTP nella risposta.
Ora è possibile inserire questo form in qualsiasi pagina con:
{{=LOAD('contact','ask.load',ajax=True)}}
Notare che non è stata definita nessuna vista .load
per il componente ask
. Questo non è necessario perchè il componente ritorna un solo oggetto (form) e per questo è sufficiente la vista "generic.load".
Quando è usata la vista "generic.load" si può utilizzare la sintassi:
response.flash='...'
che è equivalente a:
response.headers['web2py-component-flash']='...'
Plugin
Un plugin è un sotto-insieme di qualsiasi file di un'applicazione.
e si intende realmente "qualsiasi":
- Un plugin non è un modulo, non è un modello, non è un controller e nemmeno una vista, ma può contenere moduli, modelli, controllers e viste.
- Un plugin non deve essere funzionalmente autonomo e può dipendere da altri plugin o codice specifico.
- Un plugin non è un sistema di plugin pertanto i plugin non sono nè "registrati" nè "isolati" sebbene esistano delle regole per mantenere un certo isolamento.
- In questo capitolo si parla di plugin per l'applicazione non plugin per web2py.
Perchè questa funzionalità è chiamata plugin? Perchè rende disponibile un meccanismo per includere un sotto-insieme di un'applicazione in un'altra applicazione. Da questa prospettiva qualsiasi file di un'applicazione può essere considerato un plugin.
Quando un'applicazione viene distribuita i suoi plugin sono "impacchettati" e distribuiti con essa.
In pratica l'applicazione admin fornisce un'interfaccia per impacchettare e spacchettare i plugin separatamente dall'applicazione. I file e le cartelle dell'applicazione che hanno nomi che iniziano con plugin_
name possono essere impacchettati insieme in un file chiamato:
web2py.plugin.
name.w2p
e distribuiti insieme.
[ADD IMAGE]
I file che compongono un plugin non sono trattati da web2py in modo differente da ogni altro file tranne per il fatto che admin comprende (dai loro nomi) che devono essere distribuiti insieme e li visualizza in una pagina separata:
[ADD IMAGE]
Per la definizione di plugin data all'inizio di questo capitolo i plugin hanno un utilizzo più ampio oltre al fatto di essere riconosciuti dall'applicazione admin.
In pratica in web2py si possono utilizzare due tipi di plugin:
- plugin di componenti. Sono plugin che contengono componenti, come definiti nella sezione precedente. Un plugin di componenti può contenerne uno o più di uno. Per esempio potrebbe esistere un
plugin_comments
che contiene il componente comments della sezione precedente. Un altro esempio potrebbe essereplugin_tagging
che contiene un componente tagging ed un componente tag-cloud che condividono alcune tabelle di database definite dal plugin stesso. - plugin di layout. Sono plugin che contengono le viste per un layout e i file statici necessari al layout stesso. Quando il plugin è applicato all'applicazione le dà un look differente.
In base alle definizioni precedenti i componenti creati nella precedente sezione, come per esempio "controllers/contact.py", sono già considerati plugin. Possono essere spostati da un'applicazione ad un'altra per utilizzare i componenti che definiscono. Non sono però riconosciuti come plugin dall'applicazione admin perchè non c'è nulla che li etichetta come plugin. Ci sono quindi ancora due problemi da risolvere:
- Utilizzare una convenzione per i nomi dei file che compongono il plugin in modo che l'applicazione admin possa riconoscerli come appartenenti allo stesso plugin.
- Se il plugin ha file di modello stabilire una convenzione per impedire agli oggetti che sono definiti nel modello di mischiarsi nello stesso namespace dell'applicazione.
Queste sono le regole che dovrebbe seguire un plugin chiamato name:
Regola 1: I plugin di tipo modello e di tipo controller dovrebbero essere chiamati rispettivamente:
models/plugin_
name.py
controllers/plugin_
name.py
e quelli di tipo vista, modulo, statici e file privati dovrebbero essere chiamati:
views/plugin_
name/
modules/plugin_
name/
static/plugin_
name/
private/plugin_
name/
Regola 2: I plugin di tipo modello possono definire solamente oggetti i cui nomi iniziano con:
plugin_
namePlugin
Name_
Regola 3: I plugin di tipo modello possono definire solamente variabili i cui nomi iniziano con:
session.plugin_
namesession.Plugin
Name
Regola 4: I plugin dovrebbero includere la licenza e la documentazione che dovrebbero essere presenti in:
static/plugin_
name/license.html
static/plugin_
name/about.html
Regola 5: Un plugin può fare affidamento solo sull'esistenza degli oggetti globali definiti nell'applicazione di base, come per esempio:
- una connessione al database chiamata
db
- una istanza di
Auth
chiamataauth
- una istanza di
Crud
chiamatacrud
- una istanza di
Service
chiamataservice
Alcuni plugin possono essere più complessi ed avere parametri di configurazione per il caso in cui esista più di una istanza di db
.
Regola 6: Se un plugin necessita di parametri di configurazione questo dovrebbero essere aggiunti tramite PluginManager, come descritto in seguito.
Seguendo le regole sopra indicate si può essere sicuri che:
- admin riconosce tutti i file e le cartelle
plugin_
name come facenti parte di una singola entità. - I plugin non interferiscono l'uno con l'altro.
Queste regole non risolvono però il problema delle diverse versioni del plugin e delle sue dipendenze.
Plugin di componenti
I plugin di componenti sono plugin che definiscono dei componenti. I componenti solitamente accedono al database con i loro modelli.
Ecco come trasformare il precedente componente comments
in un plugin comments_plugin
mantenendo lo stesso identico codice scritto precedentemente ma seguento tutte le regole dei plugin illustrate in precedenza.
Creare, per prima cosa, un modello chiamato "models/plugin_comments.py":
db.define_table('plugin_comments_comment',
Field('body','text',label='Your comment'),
Field('posted_on','datetime',default=request.now),
Field('posted_by',db.auth_user,default=auth.user_id))
db.plugin_comments_comment.posted_on.writable=False
db.plugin_comments_comment.posted_on.readable=False
db.plugin_comments_comment.posted_by.writable=False
db.plugin_comments_comment.posted_by.readable=False
def plugin_comments():
return LOAD('plugin_comments','post',ajax=True)
(le ultime due linee di codice definiscono una funzione che semplifica l'inserimento del plugin in un'applicazione).
Definre quindi un controller "controllers/plugin_comments.py"
@auth.requires_login()
def post():
comments = db.plugin_comments_comment
return dict(form=crud.create(comment),
comments=db(comment.id>0).select())
Creare, come terza cosa, una vista chiamata "views/plugin_comments/post.load":
{{for comment in comments:}}
<div class="comment">
on {{=comment.posted_on}} {{=comment.first_name}}
says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}
E' ora possibile utilizzare admin per prepare il plugin per la distribuzione. Il plugin sarà salvato da admin come:
web2py.plugin.comments.w2p
Il plugin può essere ora usato in qualsiasi vista semplicmente installandolo in admin tramite la pagina edit ed aggiungendolo ad una vista con:
{{=plugin_comments()}}
Ovviamente è possibile rendere il plugin più complesso con componenti che richiedono parametri e opzioni di configurazione. Più i plugin sono complessi più si deve far attenzione ad evitare collisioni di nomi. Plugin Manager è progettato per evitare questi problemi.
Plugin Manager
La classe PluginManager
è definita in gluon.tools
. Prima di spiegare come funziona ecco come si utilizza.
E' possibile migliorare il precedente plugin comments_plugin
con la possibilità di aggiungere la personalizzazione di:
db.plugin_comments_comment.body.label
senza doverne modificare il codice.
Prima di tutto, riscrivere il plugin "models/plugin_comments.py" nel seguente modo:
db.define_table('plugin_comments_comment',
Field('body','text',label=plugin_comments.comments.body_label),
Field('posted_on','datetime',default=request.now),
Field('posted_by',db.auth_user,default=auth.user_id))
def plugin_comments()
from gluon.tools import PluginManager
plugins = PluginManager('comments',body_label='Your comment')
comment = db.plugin_comments_comment
comment.label=plugins.comments.body_label
comment.posted_on.writable=False
comment.posted_on.readable=False
comment.posted_by.writable=False
comment.posted_by.readable=False
return LOAD('plugin_comments','post',ajax=True)
Tutto il codice (tranne la definizione della tabella) è incapsulato in una singola funzione che crea un'istanza di PluginManager
.
E' ora possibile configurare in ogni modello dell'applicazione (per esempio in "models/db.py") il plugin nel seguente modo:
from gluon.tools import PluginManager
plugins = PluginManager()
plugins.comments.body_label=T('Post a comment')
L'oggetto
plugins
è istanziato nell'applicazione di base in "models/db.py"
L'oggetto PluginManager è un oggetto singleton di tipo Storage che contiene altri oggetti di tipo Storage con il blocco a livello del thread. Questo significa che si può istanziare tante volte quante è necessario all'interno di una applicazione, anche con nomi diversi, ma agisce come se fosse un'unica istanza di PluginManager.
In particolare ogni file di plugin può definire il suo oggetto PluginManager e registrarlo, insieme ai suoi parametri di default, con:
plugins = PluginManager('name', param1='value', param2='value')
E' possibile modificare i parametri in altre parti del codice (per esempio in "models/db.py") con:
plugins = PluginManager()
plugins.name.param1 = 'other value'
E' possibile inoltre configurare plugin diversi nello stesso codice:
plugins = PluginManager()
plugins.name.param1 = '...'
plugins.name.param2 = '...'
plugins.name1.param3 = '...'
plugins.name2.param4 = '...'
plugins.name3.param5 = '...'
Quando il plugin è definito l'oggetto PluginManager deve avere due argomenti: il nome del plugin e degli argomenti opzionali con nome che sono i suoi parametri di default. Invece quando i plugin sono configurati il costruttore di PluginManager non deve avere argomenti. La configurazione deve precedere la definizione del plugin (per esempio deve essere in un modello che abbia un nome che alfabeticamente sia prima degli altri modelli).
Plugin di layout
I plugin di layout sono più semplici dei plugin di componenti perchè solitamente non contengono codice ma solamente viste e file statici. Ci sono comunque delle regole da seguire: Primo, creare una cartella chiamata "static/plugin_layout_name/" (dove name è il nome del layout) e posizionare in essa tutti i file statici del plugin.
Secondo, creare un file di layout chiamato "views/plugin_layout_name/layout.html" che contiene il layout e i link alle immagini, ai file CSS e ai file JS in "static/plugin_layout_name/"
Terzo, modificare la vista "views/layout.html" aggiungendo:
{{include 'plugin_layout_name/layout.html'}}
Il beneficio di questo design è che gli utenti di questo plugin possono installre più layout e scegliere quello da applicare all'applicazione semplicemente modificando il file "views/layout.html". Inoltre "views/layout.html" non verrà incluso da admin insieme al plugin in modo da non rischiare che un altro plugin sovrascriva il codice scritto dall'utente.
plugin_wiki
AVVISO: plugin_wiki è ancora in una fase iniziale di sviluppo e pertanto non ne è garantita la retro-compatibilità allo stesso livello delle funzioni centrali di web2py.
plugin_wiki è un plugin "con gli steroidi". Infatti definisce diversi componenti e può cambiare il modo di sviluppare le applicazioni:
E' possibile scaricarlo da:
http://web2py.com/examples/static/web2py.plugin.wiki.w2p
L'idea alla base di plugin_wiki è che la maggior parte delle applicazioni includono pagine semi-statiche. Si tratta di pagine che non includono una logica complessa e che contengono testo strutturato (come per esempio una pagina di help), immagini, audio, video, form CRUD o un insieme di componenti standard (commenti, tag, grafici, mappe, ecc.). Queste pagine possono essere pubbliche oppure richiedere un login o avere altre limitazione di autorizzazione. Possono essere raggiunte tramite un menu o da un modulo di autocomposizione. plugin_wiki mette a disposizione un modo semplice per aggiungere ad una applicazione web2py pagine che rientrano in questa categoria.
In particolare plugin_wiki mette a disposizione:
- Un'interfaccia di tipo wiki che consente di aggiungere pagine alla propria applicazione e referenziarle con uno slug. Queste pagine, chiamate pagine wiki, hanno una versione e sono memorizzate in un database.
- Pagine pubbliche e pagine private (che richiedono l'autenticazione). Se una pagina richiede l'autenticazione può anche richiedere una particolare autorizzazione per un utente.
- Tre livelli: 1, 2, 3. Al livello 1 le pagine possono includere solamente testo, immagini, audio e video. Al livello 2 le pagine possono anche includere dei widget (questi sono componenti, definiti nella sezione precedente che possono essere inclusi nelle pagine wiki). Al livello 3 le pagine possono anche includere codice di template di web2py.
- La possibilità di modificare le pagine con la sintassi markmin o con tag HTML utilizzando un editor WYSIWYG (What you see is what you get).
- Una raccolta di widget: implementati come componenti sono auto-documentati e possono essere inseriti nelle normali viste di web2py come componenti, o possono esser inclusi in una pagina wiki utilizzando una sintassi semplificata.
- Un gruppo di pagine speciali (
meta-code
,meta-menu
, ecc.) che possono essere utilizzate per personalizzare il plugin (per esempio per definire codice che il plugin dovrebbe eseguire, per personalizzare il menu, ecc.).
L'applicazione welcome insieme al plugin plugin_wiki possono essere considerati come un ambiente di sviluppo utile per costruire semplici applicazioni web come, per esempio, un blog.
Nel resto del capitolo si presuppone che il plugin plugin_wiki sia stato applicato ad una copia dell'applicazione di base welcome.
La prima cosa che si nota dopo aver installato il plugin è che è disponibile un nuovo menu chiamato pages.
[ADD IMAGE]
Cliccando sul menu pages si accede alle azioni del plugin:
http://127.0.0.1:8000/myapp/plugin_wiki/index
La pagina index del plugin elenca le pagine create con il plugin stesso e consente di creare nuove pagine scegliento uno slug. Provare a creare una pagina home
. Si verrà rediretti a:
http://127.0.0.1:8000/myapp/plugin_wiki/page/home
Cliccare su create page per modificare il contenuto della pagina.
[ADD IMAGE]
Per default il plugin è a livello 3, che significa che è possibile inserire widget e codice nelle pagine. Per default è utilizzata la sintassi markmin per descrivere il contenuto della pagina.
Sintassi di Markmin
Ecco un tutorial per la sintassi markmin:
markmin | html |
# titolo | <h1>titolo</h1> |
## sottotitolo | <h2>sottotitolo</h2> |
### sotto-sottotitolo | <h3>sotto-sottotitolo</h3> |
**grassetto** | <b>grassetto</b> |
''corsivo'' | <i>corsivo</i> |
http://...com | <a href="http://...com">http:...com</a> |
[[name http://example.com]] | <a href="http://example.com">name</a> |
[[name http://...png left 200px]] | <img src="http://...png" alt="name" |
| align="left" width="200px" /> |
E' possibile aggiungere collegamenti ad altre pagine
[[mylink name page:slug]]
Se la pagina non esiste verrà chiesto se la si vuole creare:
[ADD IMAGE]
La pagina di edit consente di aggiungere allegati alla pagina (per esempio file statici)
[ADD IMAGE]
che possono essere collegati con:
[[mylink name attachment:3.png]]
o inseriti direttamente nella pagina con:
[[myimage attachment:3.png center 200px]]
La dimensione (200px
) è opzionale. center
non è opzionale ma può essere sostituito con left
or right
.
SI può inserire testo quotato con:
-----
this is blockquoted
-----
è anche possibile inserire tabelle con:
-----
0 | 0 | X
0 | X | 0
X | 0 | 0
-----
e testo letterale con:
``
verbatim text
``
E' anche possibile aggiungere :class
all'ultima stringa -----
o ``
. Per il testo quotato e per le tabelle :class
verrà trasformato nella classe del tag, per esempio:
-----
test
-----:abc
viene visualizzato come:
<blockquote class="abc">test</blockquote>
Per il testo letterale la classe può essere utilizzata per includere contenuto di tipo differente.
E' possibile, per esempio, includere codice con la sintassi evidenziata specificando il linguaggio con :code_
language
``
def index(): return 'hello world'
``:code_python
E' anche possibile incorporare dei widget:
``
name: widget_name
attribute1: value1
attribute2: value2
``:widget
(vedere la prossima sezione per la lista dei widget)
E' anche possibile incorporare codice nel linguaggio di template di web2py:
``
{{for i in range(10):}}<h1>{{=i}}</h1>{{pass}}
``:template
Permessi di pagina
Quando si modifica una pagina sono presenti i seguenti campi:
- active (default a
True
). Se una pagina non è attiva non sarà accessibile agli utenti (anche se è una pagina pubblica). - public (default a
True
). Se una pagina è definita come pubblica potrà essere acceduta dagli utenti senza la necessità di autenticarsi. - Role (default a
None
). Se una pagina ha un ruolo potrà essere acceduta solamente dagli utenti che si sono autenticati e che sono membri del gruppo con il ruolo corrispondente.
Pagine speciali
meta-menu contiene il menu. Se questa pagina non esiste web2py utilizza l'oggetto response.menu
definito in "models/menu.py". Il contenuto della pagina meta-menu sovrascrive l'oggetto menu. La sintassi è la seguente:
Item 1 Name http://link1.com
Submenu Item 11 Name http://link11.com
Submenu Item 12 Name http://link12.com
Submenu Item 13 Name http://link13.com
Item 2 Name http://link1.com
Submenu Item 21 Name http://link21.com
Submenu Item 211 Name http://link211.com
Submenu Item 212 Name http://link212.com
Submenu Item 22 Name http://link22.com
Submenu Item 23 Name http://link23.com
dove l'indentazione determina la struttura del sotto-menu. Ogni riga è composta del testo del menu seguito da un link. Un link può essere page:
slug. Un link può essere None
se quel menu non punta a nessuna pagina. Gli spazi in più sono ignorati.
Ecco un altro esempio:
Home page:home
Search Engines None
Yahoo http://yahoo.com
Google http://google.com
Bing http://bing.com
Help page:help
Che viene visualizzato come:
[ADD IMAGE]
meta-code
inxx friends
inserendo il seguente codice in meta-code:db.define_table('friend',Field('name',requires=IS_NOT_EMPTY()))
e si può creare una pagina di gestione della tabella friend inserendo in una qualsiasi pagina il seguente codice:
## List of friends
``
name: jqgrid
table: friend
``:widget
## New friend
``
name: create
table: friend
``:widget
Questa pagina ha due intestazioni (che iniziano con #): "List of friends" e "New friend". Contiene inoltre due widget (nelle rispettive intestazioni): un widget jqgrid
che elenca tutti i record e un widget CRUD
per aggiungere un nuovo record.
[ADD IMAGE]
meta-header
, meta-footer
, meta-sidebar
non sono utilizzate nel layout di default "welcome/views/layout.html". Se si vogliono utilizzare si deve modificare la pagina "layout.html" tramite admin (o da linea di comando) ed inserire i seguenti tag nella posizione appropriata:
{{=plugin_wiki.embed_page('meta-header') or ''}}
{{=plugin_wiki.embed_page('meta-sidebar') or ''}}
{{=plugin_wiki.embed_page('meta-footer') or ''}}
In questo modo il contenuto di quelle pagine sarà mostrato nell'intestazione, nella barra laterale e nel piè di pagina del layout.
Configurare plugin_wiki
Come con qualsiasi altro plugin è possibile inserire il codice seguente in "models/db.py":
from gluon.tools import PluginManager
plugins = PluginManager
plugins.wiki.editor = auth.user.email==mail.settings.sender
plugins.wiki.level = 3
plugins.wiki.mode = 'markmin' or 'html'
plugins.wiki.theme = 'ui-darkness'
dove:
- editor è True se l'utente collegato è autorizzato a modificare le pagine del plugin
- level è il permesso: 1 - per modificare pagine standard, 2 - per includere widget, 3 - per inserire codice
- mode determina se utilizzare l'editor "markmin" oppure l'editory "html" WYSIWYG.WYSIWYG
- theme è il nome del tema UI di jQuery richiesto. Per default è installato solo il tema senza colori "ui-darkness".
E' possibile aggiungere temi in:
static/plugin_wiki/ui/%(theme)s/jquery-ui-1.8.1.custom.css
Widget
Ciascun widget può essere incorporato sia nelle pagine di plugin che nei normali template di web2py.
Per esempio il seguente codice server ad incorporare un video di YouTube in una pagina di plugin:
``
name: youtube
code: l7AWnfFRc7g
``:widget
o per incorporare lo stesso widget in una vista web2py si può utilizzare il seguente codice:
{{=plugin_wiki.widget('youtube',code='l7AWnfFRc7g')}}
Gli argomenti del widget che non hanno un valore di default sono obbligatori.
Ecco la lista dei widget attualmente disponibili:
read(table,record_id=None)
Legge e visualizza un record
table
è il nome della tabellarecord_id
è il numero del record
create(table,message='',next='',readonly_fields='',
hidden_fields='',default_fields='')
Visualizza un form di creazione di un record
table
è il nome della tabellamessage
è il messaggio da visualizzare dopo la creazione del recordnext
è dove reindirizzare dopo la creazione, per esempio a "page/index/[id]"readonly_fields
è una lista di campi in sola lettura, separati dalla virgolahidden_fields
è una lista di campi nascosti, separati dalla virgoladefault_fields
è una lista dei campi con i valori di default ("fieldname=value")
update(table,record_id='',message='',next='',
readonly_fields='',hidden_fields='',default_fields='')
Visualizza un form di aggiornamento di un record
table
è il nome della tabellarecord_id
è il record da aggiornare oppure {{=request.args(-1)}}message
è il messaggio da visualizzare dopo l'aggiornamento del recordnext
è dove reindirizzare dopo l'aggiornamento, per esempio a "page/index/[id]"readonly_fields
è una lista di campi in sola lettura, separati dalla virgolahidden_fields
è una lista di campi nascosti, separati dalla virgoladefault_fields
è una lista dei campi con i valori di default ("fieldname=value")
select(table,query_field='',query_value='',fields='')
Elenca i record nella tabella
table
è il nome della tabellaquery_field
equery_value
se presenti filtreranno la tabella con query_field==query_valuefields
è una lista dei campi da visualizzare, separati dalla virgola
search(table,fields='')
E' un widget per selezionare i record
table
è il nome della tabellafields
è una lista dei campi da visualizzare, separati dalla virgola
jqgrid(table,fieldname=None,fieldvalue=None,col_widths='',
_id=None,fields='',col_width=80,width=700,height=300)
Incorpora un plugin di tipo jqGrid
table
è il nome della tabellafieldname
,fieldvalue
sono campi di filtro opzionali (dove fieldname==fieldvalue)_id
è l'id del DIV che contiene l'oggetto jqGridcolumns
è la lista dei nomi delle colonne da visualizzarecol_width
è la larghezza di ciascuna colonna (default)height
è l'altezza dell'oggetto jqGridwidth
è la larghezza dell'oggetto jqGrid
latex(expression)
Usa le API dei grafici di Google per incorporare un oggetto LaTeX
pie_chart(data='1,2,3',names='a,b,c',width=300,height=150,align='center')
Incorpora un grafico a torta
data
è una lista di valori, separati da virgolanames
è una lista di etichette, separate da virgola (una per ogni elemento)width
è la larghezza dell'immagineheight
è l'altezza dell'immaginealign
determina l'allineamento dell'immagine
bar_chart(data='1,2,3',names='a,b,c',width=300,height=150,align='center')
Usa le API dei grafici di Google per incorporare un grafico a barre
data
è una lista di valori, separati da virgolanames
è una lista di etichette, separate da virgola (una per ogni elemento)width
è la larghezza dell'immagineheight
è l'altezza dell'immaginealign
determina l'allineamento dell'immagine
slideshow(table,field='image',transition='fade',width=200,height=200)
Incorpora uno slideshow. Le immagini sono recuperate da una tabella Embeds a slideshow. It gets the images from a table.
table
è il nome della tabellafield
è il campo di upload della tabella che contiene le immaginitransition
determina il tipo di transizionewidth
è la larghezza dell'immagineheight
è l'altezza dell'immagine
youtube(code,width=400,height=250)
Incorpora un video di YouTube (tramite codice)
code
è il codice del videowidth
è la larghezza dell'immagineheight
è l'altezza dell'immagine
vimeo(code,width=400,height=250)
Incorpora un video di Vimeo (tramite codice)
code
è il codice del videowidth
è la larghezza dell'immagineheight
è l'altezza dell'immagine
mediaplayer(src,width=400,height=250)
Incorpora un file multimediale (come un video Flash o un file audio mp3)
src
è il collegamento al videowidth
è la larghezza dell'immagineheight
è l'altezza dell'immagine
comments(table='None',record_id=None)
Incorpora i commenti in una pagina. I commenti possono essere collegati ad una tabella e/o ad un record
table
è il nome della tabellarecord_id
è l'id del record
tags(table='None',record_id=None)
Incorpora dei tag in una pagina. I tag possono essere collegati ad una tabella e/o ad un record
table
è il nome della tabellarecord_id
è l'id del record
tag_cloud()
Inserisce una nuvola di tag (tag cloud)
map(key='....', table='auth_user', width=400, height=200)
Incorpora una mappa di Google Prende i punti di una mappa da una tabella
key
è la chiave delle API di Google Map (default 127.0.0.1)table
è il nome della tabellawidth
è la larghezza della mappaheight
è l'altezza della mappa
La tabella deve avere le seguenti colonne: latidude, longitude e map_popup.
Quando si seleziona un punto appare il messaggio memorizzato in map_popup.
iframe(src, width=400, height=300)
Incorpora una pagina nei tag <iframe> ... </iframe>
load_url(src)
Carica il contenuto della URL utilizzando la funzione LOAD
load_action(action, controller='', ajax=True)
Carica il contenuto della URL(request.application, controller, action) utilizzando la funzione LOAD
Estendere i widget
Per aggiungere un widget a plugin_wiki si deve creare un nuovo modello chiamato "models/plugin_wiki_"name dove name è un nome arbitrario. Il file deve contenere:
class PluginWikiWidgets(PluginWikiWidgets):
@staticmethod
def my_new_widget(arg1, arg2='value', arg3='value'):
"""
document the widget
"""
return "body of the widget"
La prima linea dichiare che si sta estendendo la lista dei widget. ALl'interno della classe si possono definire quante funzioni si vuole. Ogni funzione statica che non inizia con il carattere di underscore (_) è un nuovo widget. La funzione può avere un numero qualsiasi di argomenti che possono avere un valore di default. La docstring della funzione deve documentare l'utilizzo della funzione con la sintassi markmin.
Quando i widget sono incorporati nelle pagine di plugin gli argomenti saranno passati al widget come stringhe. Per questo il widget deve essere in grado di ricevere stringhe per ogni argomento ed eventualmente convertirlo nella rappresentazione appropriata che deve essere adeguatamente documentata nella docstring.
Il widget può restituire una stringa o helper di web2py che verranno serializzati con .xml()
.
Il nuovo widget può accedere a qualsiasi variabile dichiarata nel namespace globale.
Ecco, come esempio, un widget che visualizza il form "contact/ask" creato all'inizio di questo capitolo. Il widget è contenuto in "models/plugin_wiki_contact":
class PluginWikiWidgets(PluginWikiWidgets):
@staticmethod
def ask(email_label='Your email', question_label='question'):
"""
This plugin will display a contact us form that allows
the visitor to ask a question.
The question will be emailed to you and the widget will
disappear from the page.
The arguments are
- email_label: the label of the visitor email field
- question_label: the label of the question field
"""
form=SQLFORM.factory(
Field('your_email',requires=IS_EMAIL(),label=email_label),
Field('question',requires=IS_NOT_EMPTY()),label=question_label)
if form.accepts(request.vars,session):
if mail.send(to='[email protected]',
subject='from %s' % form.vars.your_email,
message = form.vars.question):
div_id = request.env.http_web2py_component_element
command="jQuery('#%s').hide()" % div_id
response.headers['web2py-component-command']=command
response.headers['web2py-component-flash']='thanks you'
else:
form.errors.your_email="Unable to send the email"
return form.xml()
I widget di plugin non sono visualizzati dalla vista a meno che la funzione
response.render(...)
sia esplicitamente chiamata dal widget stesso.