Componentes y Plugins

Los componentes y plugins son características relativamente nuevas de web2py, y hay algo de desacuerdo entre los desarrolladores acerca de que son y que deberían ser. La mayor parte de la confusión se debe a los diferentes usos que tienen estos términos en otros proyectos de software y por el hecho de que los desarrolladores están aún trabajando en terminar las especificaciones.

Sin embargo, el soporte de plugins es una característica importante y necesitamos proveer algunas definiciones. Estas definiciones no están destinadas a ser definitivas, sólo consistentes con los patrones de programación que queremos discutir en este capítulo.

Aquí trataremos de abordar las siguientes dos cuestiones:

  • ¿ Cómo podemos construir aplicaciones modulares que minimicen la carga del servidor y maximicen la reutilización del código ?
  • ¿ Cómo podemos distribuir piezas de código en un estilo más o menos plugin-and-play ?

Los componentes se ocupan de la primera cuestión; los plugins abordan la segunda.

Componentes

Un componente es una parte funcionalmente autónoma de una página web.

Un componente puede estar compuesto de módulos, controladores y vistar, pero no hay ningún otro requisito estricto más que, cuando es incrustado en una página web, deba estar localizado dentro de una etiqueta html (por ejemplo un DIV, SPAN o IFRAME) y deba realizar su tarea independiente del resto de la página. Estamos especialmente interesados en componente que son cargados en la página y que se comunican con el controlador del componente por medio de Ajax.

Un ejemplo de un componente es un “componente de comentarios” que está contenido en un DIV y que muestra los comentarios de los usuarios y un formulario envía-nuevo-comentario. Cuando el formulario es entregado, se envía al servidor por medio de Ajax, la lista es actualizada, y el comentario es almacenado del lado del servidor en la base de datos. El contenido del DIV es actualizado sin recargar el resto de la página.

La función LOAD de web2py hace que realizar ésto sea una tarea sencilla sin necesidad de conocimiento explícito de JavaScript/Ajax o de su programación.

Nuestra meta es ser capaz de desarrollar aplicaciones ensamblando componentes en los diseños de páginas.

Considere una simple aplicación de web2py “test” que extiende la aplicación de andamiaje por defecto con un modelo personalizado en el archivo “models/db_comments.py”:

1
2
3
4
5
6
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

una acción en “controllers/comments.py”

1
2
3
4
@auth.requires_login()
def post():
    return dict(form=crud.create(db.comment),
                comments=db(db.comment.id>0).select())

y el “views/comments/post.html” correspondiente

1
2
3
4
5
6
7
8
{{extend 'layout.html'}}
{{for comment in comments:}}
<div class="comment">
  on {{=comment.posted_on}} {{=comment.posted_by.first_name}}
  says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}

Puede acceder de la manera usual en:

http://127.0.0.1:8000/test/comments/post

comments_html.png

Hasta ahora no hay nada especial en esta acción, pero podemos convertirla en un componente definiendo una nueva vista con extensión ”.load” que no extiende el diseño.

Por lo tanto creamos un “views/comments/post.load”:

{{#extend 'layout.html' <- notice this is commented out!}}
{{for comment in comments:}}
<div class="comment">
  on {{=comment.posted_on}} {{=comment.posted_by.first_name}}
  says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}

Podemos accesar desde el URL

http://127.0.0.1:8000/test/comments/post.load

y se verá así:

comments_load.png

Éste es un componente que podemos incrustar en cualquier otra página simplemente haciendo

1
{{=LOAD('comments','post.load',ajax=True)}}

Por ejemplo podemos editar en “controllers/default.py”

1
2
def index():
    return dict()

y agregar en la vista correspondiente:

1
2
3
{{extend 'layout.html'}}
<p>{{='bla '*100}}</p>
{{=LOAD('comments','post.load',ajax=True)}}

Visitando la página

http://127.0.0.1:8000/test/default/index

veremos el contenido normal y el componente de comentarios:

embedded_comments.png

El componente {{=LOAD(...)}} es hecho como sigue:

<script type="text/javascript"><!--
web2py_component("/test/comment/post.load","c282718984176")
//--></script><div id="c282718984176">loading...</div>

(el código generado real depende de las opciones pasadas a la función LOAD).

La función web2py_component(url, id) es definida en “web2py_ajax.html” y realiza toda la magia: llama al url por medio de Ajax e inscrusta la respuesta en el DIV con el id correspondiente; atrapa todo envío de formulario en el DIV y los envía por medio de Ajax. El objetivo de Ajax es siempre el mismo DIV.

La firma completa del ayudante LOAD es la siguiente:

LOAD(c=None, f='index', args=[], vars={},
     extension=None, target=None,
     ajax=False, ajax_trap=False,
     url=None):

Aquí:

  • los primeros dos argumentos c y f son respectivamente el controlador y la función que queremos llamar.
  • args y vars son los argumentos y variables que queremos pasar a la función. El primero es una lista, mientras que el segundo es un diccionario.
  • extension es una extensión opcional. Note que la extensión también puede ser pasada como parte de la función como en f=’index.load’.
  • target es el id del DIV de destino. Si no se especifica, un id aleatorio de objetivo es generado.
  • ajax debe ser establecido en True si el DIV debe ser llenado por medio de Ajax y en False si el DIV tiene que ser llenado antes de que sea devuelta la página actual (por lo tanto evitando la llamada Ajax).
  • ajax_trap=True significa que cualquier envío de formulario en el DIV debe ser capturado y envíado por medio de Ajax, y la respuesta debe ser mostrada dentro del DIV. ajax_trap=False indica que el formulario debe ser envíado de manera normal, por lo tanto recargando toda la página. ajax_trap es ignorado y se asume True si se tiene ajax=True.
  • url, si se especifica, reemplaza los valores de c, f, args, vars, y extension y carga el componente en el url. Es usado para cargar como páginas de componentes servidas por otras aplicaciones (las cuales pueden o no ser creadas con web2py).

Si no se especifica una vista .load, hay un generic.load que muestra el diccionario devuelto por la acción sin el diseño. Trabaja mejor si el diccionario contiene un sólo ítem.

Si usted CARGA un componente teniendo la extensión .load y si el controlador de función correspondiente redirige a otra acción, la extensión .load se propaga y el nuevo url (al cual también se redirige) también es cargado con una extensión .load.

Comunicaciones de Componentes Cliente-Servidor

Cuando la acción de un componente es llamada por medio de Ajax, web2py pasa dos encabezados HTTP junto con la solicitud:

1
2
web2py-component-location
web2py-component-element

los cuales pueden ser accesados por la acción a través de las variables:

1
2
request.env.http_web2py_component_location
request.env.http_web2py_component_element

la última también es accesible por medio de:

1
request.cid

La primera contiene el URL de la pagina que llamó a la acción del componente. La última contiene el id del DIV que contendrá la respuesta.

La acción del componente puede también almacenar datos en dos encabezados de respuesta HTTP especiales que serán interpretados por toda la página al producirse la respuesta. Ellos son:

1
2
web2py-component-flash
web2py-component-command

y pueden configurarse por medio de:

1
2
response.headers['web2py-component-flash']='....'
response.headers['web2py-component-command']='...'

o de manera automática (si la acción es llamada por un componente) por medio de:

1
2
response.flash='...'
response.js='...'

La primera contiene texto que usted desea que sea mostrado como flash en la respuesta. El último contiene código JavaScript que usted desea sea ejecutado en la respuesta. No puede contener saltos de líneas.

Como ejemplo, definamos un componente de formulario de contacto en “controller/contact/ask.py” que permite al usuario realizar una pregunta. El componente enviará por correo la pregunta al administrador del sistema, presentará un mensaje flash “thank you” (gracias), y elimina el componente de la página:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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='admin@example.com',
                  subject='from %s' % form.vars.your_email,
                  message = form.vars.question):
            response.flash = 'Thank you'
            response.js = "jQuery('#%s').hide()" % request.cid
        else:
            form.errors.your_email = "Unable to send the email"
    return dict(form=form)

Las primeras cuatro líneas definen el formulario y lo aceptan. El objeto mail usado para el envío es definido en la aplicación de andamiaje por defecto. Las últimas cuatro líneas implementa la lógica específica del componente obteniendo los datos desde los encabezados de solicitud HTTP y enviando los encabezados de respuesta HTTP.

Ahora usted puede incrustar este formulario de contacto en cualquier página por medio de

1
{{=LOAD('contact','ask.load',ajax=True)}}

Note que no definimos una vista .load para nuestro componente ask. No tenemos que hacerlos por retorna un único objeto (formulario) y por lo tanto “generic.load” no tendrá problemas.

Enlances Ajax atrapados

Normalmente un enlace no es atrapado, y al darle click a un enlace dentro de un componente toda la página enlazada es cargada. Algunas veces usted quiere que la página enlazada sea cargada dentro del componente. Ésto puede hacerse usando el ayudante A:

1
{{=A('linked page',_href='http://example.com',cid=request.cid)}}

Si se especifica cid, la página enlazada es cargada a través de Ajax. El cid es el id de elemento html donde se ubica el contenido de la página cargada. En este caso lo establecemos a request.cid, es decir, el id del componente que genera el enlace. La página enlazada puede ser y usualmente es un URL generado usando el comando URL.

Usted puede bloquear el acceso a una función excepto desde un componente de llamada ajax usando un decorador que busque el cid:

@auth.requires(request.cid)

Plugins

Un plugin es cualquier subconjunto de los archivos de una aplicación.

y de verdad queremos decir cualquier:

  • Un plugin no es un módulo, ni un modelo, ni un controlador, ni una vista, sin embargo puede contener módulos, modelos, controladores y/o vistas.
  • Un plugin no necesita ser autónomo funcionalmente y puede depender en otros plugins o código de usuario específico.
  • Un plugin no es un sistema de plugins y por lo tanto no tiene concepto de registro o aislamiento, aunque daremos algunas reglas para tratar de lograr cierto aislamiento.
  • Estamos hablando de plugins para su aplicación, no para web2py.

Así que ¿ por qué es llamado plugin ? Porque proporciona un mecanismo para empacar un subconjunto de una aplicación y desempacarlo encima de otra aplicación (es decir, plug-in (conectar)). Bajo esta definición, cualquier archivo en su aplicación puede ser tratado como un plugin.

Cuando es distribuida la aplicación, sus plugins son empacados y distribuidos con ella.

En la práctica, admin proporciona una interfaz para empacar y desempacar plugins de manera separada de su aplicación. Los archivos y carpetas de su aplicación que tengan nombres con el prefijo plugin_name pueden ser empacados juntos en otro archivo llamado:

web2py.plugin.name.w2p

y distribuidos juntos.

plugins_list.png

Los archivos que componen un plugin no son tratados por web2py de una manera diferente a los demás archivos excepto porque el admin entiende de sus nombres que deben distribuirse juntos, y los muestra en una página separada:

plugin_page.png

Sin embargo de hecho, por la definición de arriba, estos plugins son más generales que aquellos reconocidos como plugins por admin.

En la práctica sólo nos interesaremos en dos tipos de plugins:

  • Plugins de Componentes. Éstos son plugins que contienen componentes como se definió en la sección anterior. Un plugin de componente puede contener uno o más componentes. Podemos pensar por ejemplo en un plugin_comments que contiene el componente de comentarios propuesto arriba. Otro ejemplo podría ser plugin_tagging que contiene un componente de etiquetado y un componente de nube de etiquetas que comparten algunas tablas de base de datos definidas por el plugin.
  • Plugins de diseño. Éstos son plugins que contienen una vista de diseño que los archivos estáticos requeridos por dicho diseño. Cuando el plugin es aplicado le da a la aplicación una nueva apariencia y estilo.

Por las definiciones de arriba, los componentes creados en la anterior sección, por ejemplo “controllers/contact.py”, ya son plugins. Podemos moverlos de una aplicación a otra y usar los componentes que ellos definen. Sin embargo no son reconocidos como plugins por admin debido a que no hay nada que los etiquete como tales. Por lo tanto hay dos problemas que debemos solucionar:

  • Darle nombre a los archivos de los plugins usando una convención, de manera que admin pueda reconocerlos como pertenecientes al mismo plugin.
  • Si el plugin tiene archivos modelos, establecer una convención de manera que el objeto que define no contaminen el espacio de nombres y no entren en conflicto entre ellos.

Asumamos un plugin llamado name. Aquí están las reglas que deben seguirse:

Regla 1: Los modelos y controladores de Plugins deben ser llamados, respectivamente

  • models/plugins_name.py
  • controllers/plugins_name.py

y las vistas, módulos, archivos estáticos y privados de los plugins deben estar en las carpetas llamadas, respectivamente:

  • views/plugin_name/
  • modules/plugin_name/
  • static/plugin_name/
  • private/plugin_name/

Regla 2: Los modelos de Plugins sólo pueden definir objetos con nombres que comienzan con

  • plugin_name
  • PluginName
  • _

Regla 3: Los modelos de Plugins sólo pueden definir variables de sesiones con nombres que comienzan con

  • session.plugin_name
  • session.PluginName

Regla 4: Los Plugins deben incluir licencia y documentación. Éstos deben colocarse en:

  • static/plugin_name/license.html
  • static/plugin_name/about.html

Regla 5: El Plugin sólo puede depender en la existencia de los objetos globales definidos en el “db.py” de andamiaje, es decir

  • una conexión de base de datos llamada db
  • una instancia Auth llamada auth
  • una instancia Crud llamada crud
  • una instancia Service llamada service

Algunos plugins pueden ser más sofisticados y tiene un parámetro de configuración en caso de que más de una instancia de base de datos exista.

Regla 6: Si un plugin necesita parámetros de configuración, éstos deben ser establecidos por medio de un gestor de plugins como se describe abajo.

Siguiendo las reglas anteriores podemos asegurar que:

  • admin reconoce todos los archivos y carpetas plugin_name como parte de una única entidad.
  • los plugins no interfieren entre ellos.

Las anteriores reglas no resuelven el problema de las versiones y dependencias de plugins. Eso está más alla de nuestro alcance.

Plugins de los Componentes

Los plugins de los componentes son plugins que definen componentes. Los componentes usualmente acceden a la base de datos y definen sus propios modelos.

Aquí transformamos el anterior componente comments a un comments_plugin usando el mismo código que escribimos antes, pero siguiendo todas las reglas anteriores.

Primero, creamos un modelo llamado “models/plugin_comments.py”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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.load',ajax=True) # Nota: En la versión original en inglés está omitida la extensión .load, pero debe colocarse para que la función web2py_component() llame a la vista .load y no a la .html.

(note que las últimas dos líneas definen una función que simplificará la incrustación del plugin)

Segundo, definimos un “controllers/plugin_comments.py”

1
2
3
4
5
@auth.requires_login()
def post():
    comment = db.plugin_comments_comment
    return dict(form=crud.create(comment),
                comments=db(comment.id>0).select())

Tercero, creamos una vista llamada “views/plugin_comments/post.load”:

{{for comment in comments:}}
<div class="comment">
  on {{=comment.posted_on}} {{=comment.posted_by.first_name}}
  says <span class="comment_body">{{=comment.body}}</span>
</div>
{{pass}}
{{=form}}

Ahora podemos usar admin para empacar el plugin para distribución. Admin almacenará este plugin como:

1
web2py.plugin.comments.w2p

Podemos usar el plugin en cualquier vista simplemente instalando el plugin por medio la página edit en admin y agregando los siguiente en sus propias vistas

{{=plugin_comments()}}

Por supuesto podemos hacer el plugon más sofisticado teniendo componentes que toman parámetros y opciones de configuración. Mientras más complejos son los componentes, más difícil resulta evitar colisiones de nombres. El Gestor de Plugins descrito abajo es diseñado para evitar este problema.

Gestor de Plugins

El PluginManager es una clase definida en gluon.tools. Antes que expliquemos como funciona, explicaremos como usarlo.

Aquí consideraremos el anterior comments_plugin y lo mejoraremos. Queremos poder personalizar:

1
db.plugin_comments_comment.body.label

sin tener que editar el propio plugin del código.

Aquí está como podemos hacerlo:

Primero, reescribamos el plugin “models/plugin_comments.py” de la siguiente manera:

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.load',ajax=True)

Note como todo el código excepto la definición de la tabla es encapsulada en una única función. Tambien note como la función crea una instancia de un PluginManager.

Ahora en cualquier otro modelo en su aplicación, por ejemplo en “models/db.py”, usted puede configurar este plugin como sigue:

1
2
3
from gluon.tools import PluginManager
plugins = PluginManager()
plugins.comments.body_label = T('Post a comment')

El objeto plugins ya tiene una instancia en la aplicación de andamiaje por defecto en “models/dbpy”

El objeto PluginManager es un único objeto de Almacenamiento a nivel de la tarea de objetos de Almacenamiento. Eso significa que usted puede crear instancias de tantos como quiera dentro de la misma aplicación pero (sea que tienen o no el mismo nombre) ellos actúan como si hubiese una única instancia PluginManager.

En particular, cada archivo de plugin puede hacer su propio objeto PluginManager y registrarse a sí mismo y sus parámetros por defecto con el:

1
plugins = PluginManager('name', param1='value', param2='value')

Usted puede reemplazar estos parámetros en cualquier lado (por ejemplo en “models/db.py”) con el código:

1
2
plugins = PluginManager()
plugins.name.param1 = 'other value'

Usted puede configurar múltiples plugins en un lugar.

1
2
3
4
5
6
plugins = PluginManager()
plugins.name.param1 = '...'
plugins.name.param2 = '...'
plugins.name1.param3 = '...'
plugins.name2.param4 = '...'
plugins.name3.param5 = '...'

Cuando se define el plugin, el Gestor de Plugins debe tomar argumentos: el nombre del plugin y argumentos opcionales con nombre que son parámetros por defecto. Sin embargo, cuando son configurados los plugins, el constructor del Gestor de Plugins no debe tomar argumentos. La configuración debe preceder a la definición del plugin (es decir, debe estar en un archivo modelo que venga primero alfabéticamente).

Plugins de Diseño

Los plugins de diseño son más simples que los plugins de componentes debido a que usualmente no contienen código, sino sólo vistas y archivos estáticos. Sin embargo usted debe seguir una buena práctica:

Primero, cree una carpeta llamada “static/plugins_layout_name/” (donde name es el nombre de su diseño) y coloque allí todos los archivos estáticos.

Segundo, cree un archivo de diseño llamado “views/plugins_layout_name/layout.html” que contenga su diseño y enlace los archivos de imágenes, CSS y JavaScript en “static/plugin_layout_name/

Tercero, modifique “views/layout.html” de manera que simplemente tenga:

{{include 'plugin_layout_name/layout.html'}}

El beneficio de este diseño es que los usuarios de este plugin pueden instalar múltiples diseños y elegir cual aplicar simplemente editando “views/layout.html”. Aún más, “views/layout.html” no será empacado junto con el plugin por admin, así que no hay riesgo de que el plugin reemplaze el código del usuario en el diseño previamente instalado.

plugin_wiki

Renuncia de Responsabilidad: plugin_wiki todavía está en desarrollo y por lo tanto no prometemos compatibilidad con versiones anteriores al mismo nivel como las funciones básicas de web2py.

plugin_wiki es un plugin en esteroides. Lo que queremos decir es que define múltiples componentes útiles y puede cambiar el modo en que usted desarrolla sus aplicaciones:

Puede descargarlo desde

http://web2py.com/examples/static/web2py.plugin.wiki.w2p

La idea detrás de plugin_wiki es que la mayoría de las aplicaciones incluyan páginas que son semi-estáticas. Estas son páginas que no incluyen lógica personalizada compleja. Contienen texto estructurado (piense en una página de ayuda), imágenes, audio, video, formularios crud, o un conjunto de componentes estándar (comentarios, etiquetas, gráficos, mapas), etc. Estas páginas pueden ser públicas, requerir inicio de sesión o tener otras restricciones de autorización. Estas páginas pueden estar enlazadas por un menú o sólo ser alcanzables por medio de un formulario asistente. plugin_wiki proporciona una manera fácil de agregar páginas que encajan en esta categoría a su aplicación web2py.

En particular plugin_wiki proporciona:

  • Una interfaz tipo wiki que permite agregar páginas a su aplicación y referenciarlas por un slug. Estas páginas (a las cuales nos referiremos como páginas wiki) tienen versiones y son almacenadas en una base de datos.

  • Páginas públicas y privadas (requiere inicio de sesión). Si una página requiere inicio de sesión, puede requerir también que el usuario tenga una membresía de grupo particular.

  • Tres niveles: 1,2,3. En el nivel 1, las páginas sólo pueden incluir texto, imágenes, audio y video. En el nivel 2, las páginas también pueden incluir widgets (éstos son componentes como se definió en la sección previa que pueden ser incrustados en páginas wiki). En el nivel 3, las páginas también pueden incluir código de plantillas web2py.

  • La posibilidad de editar las páginas con la sintaxis markmin o en HTML usando un editor WYSIWYG.

  • Una colección de widgets: implementados como componentes, son auto-documentados y pueden ser incrustados como componentes regulares en vistas normales web2py o, usando una sintaxis simplificada, en páginas wiki.

  • Un conjunto de páginas especiales (meta-code, meta-menu, etc) que pueden ser usadas para personalizar el plugin (por ejemplo definir el código que el plugin debe ejecutar, personalizar el menú, etc.)

    La aplicación welcome junto con el plugin_wiki puede ser vista como un entorno de desarrollo en sí mismo que es apropiada para construir simples aplicaciones web como un blog.

De aquí en adelante asumiremos que el plugin_wiki ha sido aplicado a una copia de la aplicación de andamiaje welcome.

La primera cosa que usted notará luego de instalar el plugin es que agrega un nuevo ítem del menú llamado pages.

De click en el ítem del menú pages y será redirigido a la acción del plugin:

http://127.0.0.1:8000/myapp/plugin_wiki/index

plugin_wiki_index.png

La página índice del plugin lista las páginas creadas usando el plugin en sí mismo y le permite crear nuevas páginas usando un slug. Intente creando una página home. Será redirigido a

http://127.0.0.1:8000/myapp/plugin_wiki/page/home

Haga click en create page para editar su contenido.

plugin_wiki_edit.png

De manera predeterminada, el plugin está en nivel 3, lo que significa que usted puede insertar widgets así como código en las páginas. por defecto usa la sintaxis markmin para describir el contenido de la página.

Sintaxis Markmin

He aquí una cartilla para la sintaxis markmin:

markmin html
# title <h1>title</h1>
## subtitle <h2>subtitle</h2>
### subsubtitle <h3>subsubtitle</h3>
**bold** <b>bold</b>
‘’italic’‘ <i>italic</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” />

Usted puede agregar enlaces a otras páginas con

[[mylink name page:slug]]

Si la página no existe, se le pedirá que cree una.

La página de edición le permite agregar adjuntos a las páginas (es decir, archivos estáticos)

plugin_wiki_attachments.png

y puede hacer enlaces a ellos con

[[myimage attachment:3.png center 200px]]

El tamaño (200px) es opcional. center no es opcional pero puede ser reemplazado por left o right.

Usted puede incrustar texto en bloque citado con

-----
this is blockquoted
-----

así como tablas

-----
0 | 0 | X
0 | X | 0
X | 0 | 0
-----

y texto literal

``
verbatim text
``

Usted también puede anteponer un :class opcional al `` final. Para texto en bloque citado y tablas, será traducido en la clase de la etiqueta, por ejemplo:

-----
test
-----:abc

se muestra como

<blockquote class="abc">test</blockquote>

Para texto litera, la clase puede ser usada para incrustar contenido de tipos diferentes.

Usted puede, por ejemplo, incrustar código con resaltado de sintaxis especificando el idioma con :code_language

``
def index(): return 'hello world'
``:code_python

Usted puede incrustar widgets:

``
name: widget_name
attribute1: value1
attribute2: value2
``:widget

Desde la página de edición usted puede hacer click en “widget builder” para insertar widgets desde una lista, de manera interactiva:

plugin_wiki_builder.png

(para una lista de widgets, revise la siguiente subsección).

Usted también puede incrustar código de plantillas de idioma de web2py:

``
{{for i in range(10):}}<h1>{{=i}}</h1>{{pass}}
``:template

Permisos de las Páginas

Al editar la página usted encontrará los siguientes campos:

  • active (por defecto True). Si una página no está activa, no será accesible a los visitantes (incluso si es publica).
  • public (por defecto True). Si una página es pública, puede ser accesada por los visitantes sin necesidad de que inicien sesión.
  • Role (por defecto None). Si una página tiene un rol, la página puede ser accesada sólo por los visitantes que iniciaron sesión y son miembros del grupo con el rol correspondiente.

Páginas Especiales

meta-menu contiene el menú. Si esta página no existe, web2py usa el response.menu definido en “models/menu.py”. El contenido de la página meta-menu reemplaza el menú. La sintaxis es la siguiente:

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

donde la tabulación determina la estructura del submenú. Cada ítem esta compuesto por el texto del ítem del menú seguido de un enlace. Un enlace puede ser page:slug. Un enlace None no enlaza a ninguna página. Los espacios extra son ignorados.

Aquí tenemos otro ejemplo:

Home             page:home
Search Engines   None
   Yahoo         http://yahoo.com
   Google        http://google.com
   Bing          http://bing.com
Help             page:help

Ésto se muestra de la siguiente manera:

plugin_wiki_menu.png

meta-code es otra página especial y debe contener código web2py. Ésta es una extensión de sus modelos, y de hecho usted puede poner código de modelos aquí. Es ejecutado cuando el código “models/plugin_wiki.py” es ejecutado.

Usted puede definir tablas en meta-code.

Por ejemplo, usted puede crear una simple tabla “friends” colocando los siguiente en meta-code:

db.define_table(‘friend’,Field(‘name’,requires=IS_NOT_EMPTY()))

y usted puede crear con ella una interfaz de administración amigable incrustando en la página de su elección el siguiente código:

# List of friends
``
name: jqgrid
table: friend
``:widget

# New friend
``
name: create
table: friend
``:widget

La página tiene dos encabezados (comenzando con #): “List of Friends” y “New Friend”. La página contiene dos widgets (bajo los encabezados correspondientes): un widget jqgrid que lista los amigos y un widget de creación crud para agregar un nuevo amigo.

plugin_wiki_crud.png

meta-header, meta-footer, meta-sidebar no son usadas por el diseño por defecto en “welcome/views/layout.html”. Si usted quiere usarlos, edite “layout.html” usando admin (o la cónsola) y coloque las siguientes etiquetas en los lugares apropiados:

{{=plugin_wiki.embed_page('meta-header') or ''}}
{{=plugin_wiki.embed_page('meta-sidebar') or ''}}
{{=plugin_wiki.embed_page('meta-footer') or ''}}

De esta manera, el contenido en esas páginas se mostrará en el encabezado, la barra lateral y el pie de página del diseño.

Configurando plugin_wiki

Como con cualquier otro plugin en “models/db.py” usted puede hacer

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’

donde:

  • editor es true si el usuario que ha iniciado sesión actualmente está autorizado para editar páginas plugin_wiki.
  • level es el permiso: 1 para editar páginas regulares, 2 para incrustar widgets en las páginas, 3 para incrustar código.
  • mode determina si se usa un editor “markmin” o un editor “html” WYSIWYG.
  • theme es el nombre del tema JQuery UI requerido. Por defecto sólo “ui-darkness” de color neutral está instalado.

Usted puede agregar temas aquí:

static/plugin_wiki/ui/%(theme)s/jquery-ui-1.8.1.custom.css

Widgets actuales

Cada widget puede ser incrustado tanto en las páginas plugin_wiki como en plantillas web2py normales.

Por ejemplo, para incrustar un video YouTube en una página plugin_wiki, usted puede hacer

``

name: youtube

code: 17AWnfFRc7g

``:widget

o para incrustar el mismo widget en una vista web2py, usted puede hacer:

{{=plugin_wiki.widget('youtube',code='l7AWnfFRc7g')}}

En cualquier caso, ésta es la salida:

plugin_wiki_youtube.png

Los argumentos del Widget que no tienen un valor por defecto son requeridos.

He aquí una lista de todos los widgets existentes actualmente:

1
read(table,record_id=None)

Lee y muestra un registro

  • table es el nombre de una tabla
  • record_id es el número del registro
1
2
create(table,message='',next='',readonly_fields='',
       hidden_fields='',default_fields='')

Muestra un formulario de creación de registro

  • table es el nombre de la tabla
  • message es el mensaje que será mostrado después de que el registro ha sido creado
  • next es a donde redirigir, ejemplo “page/index/[id]”
  • readonly_fields es una lista de campos separados por coma
  • hidden_fields es una lista de campos separados por coma
  • defauld_fields es una lista de “fieldname=value” separados por coma
1
2
update(table,record_id='',message='',next='',
       readonly_fields='',hidden_fields='',default_fields='')

Muestra un formulario de actualización de un registro

  • table es el nombre de la tabla
  • record_id es el registro a ser actualizado o {{=request.args(-1)}}
  • message es el mensaje a ser mostrado después de que el registro ha sido creado
  • next es a donde redirigir, por ejemplo “page/index/[id]”
  • readonly_fields es una lista de campos separados por coma
  • hidden_fields es una lista de campos separados por coma
  • defauld_fields es una lista de “fieldname=value” separados por coma
1
select(table,query_field='',query_value='',fields='')

Lista todos los registros de la tabla

  • table es el nombre de la tabla
  • query_field y query_value de estar presentes filtrarán los registros de acuerdo a la consulta query_field == query_value
  • fields es una lista de campos ha ser mostrados, separados por coma
1
search(table,fields='')

Widgets para registros seleccionados

  • table es el nombre de la tabla
  • fields es una lista de campos ha ser mostrados, separados por coma
1
2
jqgrid(table,fieldname=None,fieldvalue=None,col_widths='',
       _id=None,fields='',col_width=80,width=700,height=300)

Incrusta el plugin jqGrid

  • table es el nombre de la tabla
  • fieldname, fieldvalue son filtros opcionales (fieldname==fieldvalue)
  • _id es el “id” del DIV que contiene el jqGrid
  • columns es una lista de nombres de columnas ha ser mostradas
  • col_width es el ancho de cada columna (por defecto)
  • heigh es el alto del jqGrid
  • width es el ancho del jqGrid
1
latex(expression)

Usa el API de gráficos de Google para incrustar LaTeX

1
pie_chart(data='1,2,3',names='a,b,c',width=300,height=150,align='center')

Incrusta un gráfico tipo torta

  • data es una lista de valores separados por coma
  • names es una lista de etiquetas separadas por coma (una por ítem de datos)
  • width es el ancho de la imágen
  • height es el alto de la imágen
  • align determina el alineamiento de la imágen
1
bar_chart(data='1,2,3',names='a,b,c',width=300,height=150,align='center')

Usa el API de gráficos de Google para incrustar un gráfico de barras

  • data es una lista de valores separados por coma
  • names es una lista de etiquetas separadas por coma (una por cada ítem de datos)
  • width es el ancho de la imágen
  • height es el alto de la imágen
  • align determina el alineamiento de la imágen
1
slideshow(table, field='image', transition='fade', width=200, height=200)

Incrusta una presentación de diapositivas. Obtiene las imágenes desde una tabla.

  • table es el nombre de la tabla
  • field es el campo cargado en la tabla que contiene las imágenes
  • transition determina el tipo de transmisión, por ejemplo, desvanecimiento, etc.
  • width es el ancho de la imágen
  • height es el alto de la imágen
1
youtube(code, width=400, height=250)

Incrusta un video YouTube (por código)

  • code es el código del video
  • width es el ancho de la imágen
  • height es el alto de la imágen
1
vimeo(code, width=400, height=250)

Incrusta un video Vimeo (por código)

  • code es el código del video
  • width es el ancho de la imágen
  • height es el alto de la imágen
1
mediaplayer(src, width=400, height=250)

Incrusta un archivo multimedia (como un video Flash o un archivo mp3)

  • src es el src del video
  • width es el ancho de la imágen
  • height es el alto de la imágen
1
comments(table='None', record_id=None)

Incrusta comentarios en la página. Los comentarios pueden ser enlazados a una tabla y/o un record

  • tabla es el nombre de la tabla
  • record_id es el id del registro
1
tags(table='None', record_id=None)

Incrusta etiquetas en la página. Las etiquetas pueden ser enlazadas a una tabla y/o un registro

  • table es el nombre de la tabla
  • record_id es el id del registro
1
tag_cloud()

Incrusta una nube de etiquetas

1
map(key='....', table='auth_user', width=400, height=200)

Incrusta mapa Google. Obtiene los puntos del mapa desde una tabla

  • key es la clave del api del mapa de google (por defecto funciona para 127.0.0.1)
  • table es el nombre de la tabla
  • width es el ancho del mapa
  • height es el alto del mapa
The table must have columns: latitude, longitude and map_popup.
When clicking on a dot, the map_popup message will appear.
1
iframe(src, width=400, height=300)

Incrusta una página en un <iframe></iframe>

1
load_url(src)

Carga el contenido del url usando la función LOAD

1
load_action(action, controller='', ajax=True)

Carga el contenido del URL (request.application, controller, action) usando la función LOAD

Extendiendo los Widgets

Widgets a plugin_wiki pueden ser agregados creando un nuevo archivo modelo llamado “models/plugin_wikiname donde name es arbitrario y el archivo contiene algo así:

1
2
3
4
5
6
7
class PluginWikiWidgets(PluginWikiWidgets):
    @staticmethod
    def my_new_widget(arg1, arg2='value', arg3='value'):
        """
        document the widget
    """
        return "body of the widget"

La primera línea dice que usted está extendiendo la lista de widgets. Dentro de la clase, usted puede definir tantas funciones como necesite. Cada función estática es un nuevo widget, excepto por funciones que inicien con subrayado. La función puede tomar un número arbitrario de argumentos que puede o no tener valores por defectos. La cadena de documentación de la función debe documentar a la función usando la sintaxis markmin en sí misma.

Cuando los widgets son incrustados en las páginas plugin_wiki, los argumentos serán pasados al widget como cadenas. Esto significa que la función widget debe ser capaz de aceptar cadenas para cada argumentos y eventualmente convertirlas en la representación requerida. usted puede decidir cual debe ser la representación de la cadena - sólo asegúrese que esté documentado en la cadena de documentación.

El widget puede devolver una cadena de ayudantes web2py. En el último caso serán serializados usando .xml().

Note como el nuevo widget puede acceder a cualquier variable declarado en el espacio de nombres global.

Como un ejemplo, vamos a crear un nuevo widget que muestre el formulario “contact/ask” creado al comienzo de este capítulo. Ésto puede lograrse creando un archivo “models/plugin_wiki_contact” que contiene:

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='admin@example.com',
                        subject='from %s' % form.vars.your_email,
                        message = form.vars.question):
                       command="jQuery('#%s').hide()" % div_id
               response.flash = 'Thank you'
               response.js = "jQuery('#%s').hide()" % request.cid
        else:
            form.errors.your_email="Unable to send the email"
        return form.xml()

Los widgets plugin_wiki no son mostrados por una vista a menos que la función response.render(...) sea llamada explícitamente por el widget.